System.IO.Pipelines: .NET高性能IO

本文翻译自dotnet团队博客文章:https://blogs.msdn.microsoft.com/dotnet/2018/07/09/system-io-pipelines-high-performance-io-in-net/ 

  System.IO.Pipelines是一个新的库,旨在简化在.NET中执行高性能IO的过程。它是一个依赖.NET Standard的库,适用于所有.NET实现

Pipelines诞生于.NET Core团队,为使Kestrel成为业界最快的Web服务器之一。最初从作为Kestrel内部的实现细节发展成为可重用的API,它在.Net Core 2.1中作为可用于所有.NET开发人员的最高级BCL API(System.IO.Pipelines)提供。

它解决了什么问题?

为了正确解析Stream或Socket中的数据,代码有固定的样板,并且有许多极端情况,为了处理他们,不得不编写难以维护的复杂代码。
实现高性能和正确性,同时也难以处理这种复杂性。Pipelines旨在解决这种复杂性。

有多复杂?

让我们从一个简单的问题开始吧。我们想编写一个TCP服务器,它接收来自客户端的用行分隔的消息(由\n分隔)。(译者注:即一行为一条消息)

使用NetworkStream的TCP服务器

声明:与所有对性能敏感的工作一样,应在应用程序中测量每个方案的实际情况。根据您的网络应用程序需要处理的规模,可能不需要在乎的各种技术的开销。

在Pipelines之前用.NET编写的典型代码如下所示:

async Task ProcessLinesAsync(NetworkStream stream){ 
   var buffer = new byte[1024];  
    await stream.ReadAsync(buffer, 0, buffer.Length);    // 在buffer中处理一行消息ProcessLine(buffer); }

此代码可能在本地测试时正确工作,但它有几个潜在错误:

  • 一次ReadAsync调用可能没有收到整个消息(行尾)。

  • 它忽略了stream.ReadAsync()返回值中实际填充到buffer中的数据量。(译者注:即不一定将buffer填充满)

  • 一次ReadAsync调用不能处理多条消息。

这些是读取流数据时常见的一些缺陷。为了解决这个问题,我们需要做一些改变:

  • 我们需要缓冲传入的数据,直到找到新的行。

  • 我们需要解析缓冲区中返回的所有行

640?wx_fmt=png

这一次,这可能适用于本地开发,但一行可能大于1KiB(1024字节)。我们需要调整输入缓冲区的大小,直到找到新行。

因此,我们可以在堆上分配缓冲区去处理更长的一行。我们从客户端解析较长的一行时,可以通过使用ArrayPool<byte>避免重复分配缓冲区来改进这一点。

640?wx_fmt=png

这段代码有效,但现在我们正在重新调整缓冲区大小,从而产生更多缓冲区副本。它将使用更多内存,因为根据代码在处理一行行后不会缩缓冲区的大小。为避免这种情况,我们可以存储缓冲区序列,而不是每次超过1KiB大小时调整大小。

此外,我们不会增长1KiB的 缓冲区,直到它完全为空。这意味着我们最终传递给ReadAsync越来越小的缓冲区,这将导致对操作系统的更多调用。

为了缓解这种情况,我们将在现有缓冲区中剩余少于512个字节时分配一个新缓冲区:

译者注:这段代码太复杂了,懒得翻译注释了,大家将就看吧

640?wx_fmt=png

此代码只是得到很多更加复杂。当我们正在寻找分隔符时,我们同时跟踪已填充的缓冲区序列。为此,我们此处使用List<BufferSegment>查找新行分隔符时表示缓冲数据。其结果是,ProcessLineIndexOf现在接受List<BufferSegment>作为参数,而不是一个byte[],offset和count。我们的解析逻辑现在需要处理一个或多个缓冲区序列。

我们的服务器现在处理部分消息,它使用池化内存来减少总体内存消耗,但我们还需要进行更多更改:

  1. 我们使用的byte[]ArrayPool<byte>的只是普通的托管数组。这意味着无论何时我们执行ReadAsyncWriteAsync,这些缓冲区都会在异步操作的生命周期内被固定(以便与操作系统上的本机IO API互操作)。这对GC有性能影响,因为无法移动固定内存,这可能导致堆碎片。根据异步操作挂起的时间长短,池的实现可能需要更改。

  2. 可以通过解耦读取逻辑处理逻辑来优化吞吐量。这会创建一个批处理效果,使解析逻辑可以使用更大的缓冲区块,而不是仅在解析单个行后才读取更多数据。这引入了一些额外的复杂性

  • 我们需要两个彼此独立运行的循环。一个读取Socket和一个解析缓冲区。

  • 当数据可用时,我们需要一种方法来向解析逻辑发出信号。

  • 我们需要决定如果循环读取Socket“太快”会发生什么。如果解析逻辑无法跟上,我们需要一种方法来限制读取循环(逻辑)。这通常被称为“流量控制”或“背压”。

  • 我们需要确保事情是线程安全的。我们现在在读取循环解析循环之间共享多个缓冲区,并且这些缓冲区在不同的线程上独立运行。

  • 内存管理逻辑现在分布在两个不同的代码段中,从填充缓冲区池的代码是从套接字读取的,而从缓冲区池取数据的代码是解析逻辑

  • 我们需要非常小心在解析逻辑完成之后我们如何处理缓冲区序列。如果我们不小心,我们可能会返回一个仍由Socket读取逻辑写入的缓冲区序列。

复杂性已经到了极端(我们甚至没有涵盖所有案例)。高性能网络应用通常意味着编写非常复杂的代码,以便从系统中获得更高的性能。

System.IO.Pipelines的目标是使这种类型的代码更容易编写。

使用System.IO.Pipelines的TCP服务器

让我们来看看这个例子的样子System.IO.Pipelines:

640?wx_fmt=png

我们的行读取器的pipelines版本有2个循环:

  • FillPipeAsync从Socket读取并写入PipeWriter。

  • ReadPipeAsync从PipeReader中读取并解析传入的行。

与原始示例不同,在任何地方都没有分配显式缓冲区。这是管道的核心功能之一。所有缓冲区管理都委托给PipeReader/PipeWriter实现。

这使得使用代码更容易专注于业务逻辑而不是复杂的缓冲区管理。

在第一个循环中,我们首先调用PipeWriter.GetMemory(int)从底层编写器获取一些内存; 然后我们调用PipeWriter.Advance(int)告诉PipeWriter我们实际写入缓冲区的数据量。然后我们调用PipeWriter.FlushAsync()来提供数据给PipeReader。

在第二个循环中,我们正在使用PipeWriter最终来自的缓冲区Socket。当调用PipeReader.ReadAsync()返回时,我们得到一个ReadResult包含2条重要信息,包括以ReadOnlySequence<byte>形式读取的数据和bool IsCompleted,让reader知道writer是否写完(EOF)。在找到行尾(EOL)分隔符并解析该行之后,我们将缓冲区切片以跳过我们已经处理过的内容,然后我们调用PipeReader.AdvanceTo告诉PipeReader我们消耗了多少数据。

在每个循环结束时,我们完成了reader和writer。这允许底层Pipe释放它分配的所有内存。

System.IO.Pipelines

除了处理内存管理之外,其他核心管道功能还包括能够在Pipe不实际消耗数据的情况下查看数据。

PipeReader有两个核心API ReadAsyncAdvanceToReadAsync获取Pipe数据,AdvanceTo告诉PipeReader不再需要这些缓冲区,以便可以丢弃它们(例如返回到底层缓冲池)。


这是一个http解析器的示例,它在接收Pipe到有效起始行之前读取部分数据缓冲区数据。

640?wx_fmt=png

ReadOnlySequence<T>

该Pipe实现存储了在PipeWriter和PipeReader之间传递的缓冲区的链接列表。PipeReader.ReadAsync暴露一个ReadOnlySequence<T>新的BCL类型,它表示一个或多个ReadOnlyMemory<T>段的视图,类似于Span<T>和Memory<T>提供数组和字符串的视图。

640?wx_fmt=png


Pipe内部维护指向reader和writer可以分配或更新它们的数据集合,。SequencePosition表示缓冲区链表中的单个点,可用于有效地对ReadOnlySequence<T>进行切片。

这段实在翻译困难,给出原文
The Pipe internally maintains pointers to where the reader and writer are in the overall set of allocated data and updates them as data is written or read. The SequencePosition represents a single point in the linked list of buffers and can be used to efficiently slice the ReadOnlySequence

由于ReadOnlySequence<T>可以支持一个或多个段,因此高性能处理逻辑通常基于单个或多个段来分割快速和慢速路径(fast and slow paths?)。

例如,这是一个将ASCII ReadOnlySequence<byte>转换为string以下内容的例程:

640?wx_fmt=png

背压和流量控制

在一个完美的世界中,读取和解析工作是一个团队:读取线程消耗来自网络的数据并将其放入缓冲区,而解析线程负责构建适当的数据结构。通常,解析将比仅从网络复制数据块花费更多时间。结果,读取线程可以轻易地压倒解析线程。结果是读取线程必须减慢或分配更多内存来存储解析线程的数据。为获得最佳性能,在频繁暂停和分配更多内存之间存在平衡。

为了解决这个问题,管道有两个设置来控制数据的流量,PauseWriterThreshold和ResumeWriterThreshold。PauseWriterThreshold决定有多少数据应该在调用PipeWriter.FlushAsync之前进行缓冲停顿。ResumeWriterThreshold控制reader消耗多少后写入可以恢复。

640?wx_fmt=png

当Pipe的数据量超过PauseWriterThreshold,PipeWriter.FlushAsync会异步阻塞。数据量变得低于ResumeWriterThreshold,它会解锁时。两个值用于防止在极限附近发生反复阻塞和解锁。

IO调度

通常在使用async / await时,会在线程池线程或当前线程上调用continuation SynchronizationContext。

在执行IO时,对执行IO的位置进行细粒度控制非常重要,这样可以更有效地利用CPU缓存,这对于Web服务器等高性能应用程序至关重要。Pipelines公开了一个PipeScheduler确定异步回调运行位置的方法。这使得调用者可以精确控制用于IO的线程。

实践中的一个示例是在Kestrel Libuv传输中,其中IO回调在专用事件循环线程上运行。

PipeReader模式的其他好处:

  • 一些底层系统支持“无缓冲等待”,即,在底层系统中实际可用数据之前,永远不需要分配缓冲区。例如,在带有epoll的Linux上,可以等到数据准备好之后再实际提供缓冲区来进行读取。这避免了具有大量线程等待数据的问题不会立即需要保留大量内存。

  • 默认情况下Pipe,可以轻松地针对网络代码编写单元测试,因为解析逻辑与网络代码分离,因此单元测试仅针对内存缓冲区运行解析逻辑,而不是直接从网络中消耗。它还可以轻松测试那些难以测试发送部分数据的模式。ASP.NET Core使用它来测试Kestrel的http解析器的各个方面。

  • 允许将底层OS缓冲区(如Windows上的Registered IO API)暴露给用户代码的系统非常适合管道,因为缓冲区始终由PipeReader实现提供。

其他相关类型

作为制作System.IO.Pipelines的一部分,我们还添加了许多新的原始BCL类型:

  • MemoryPool<T>IMemoryOwner<T>MemoryManager<T> - .NET Core 1.0添加了ArrayPool<T>,在.NET Core 2.1中,我们现在有一个更通用的抽象,适用于任何工作的池Memory<T>。这提供了一个可扩展点,允许您插入更高级的分配策略以及控制缓冲区的管理方式(例如,提供预先固定的缓冲区而不是纯托管的阵列)。

  • IBufferWriter<T> - 表示用于写入同步缓冲数据的接收器。(PipeWriter实现这个)

  • IValueTaskSource - ValueTask<T>自.NET Core 1.1以来就已存在,但在.NET Core 2.1中获得了一些超级权限,允许无分配的等待异步操作。有关详细信息,请参阅https://github.com/dotnet/corefx/issues/27445。

我如何使用管道?

API存在于System.IO.Pipelines nuget包中。

以下是使用管道处理基于行的消息的.NET Core 2.1服务器应用程序的示例(上面的示例)https://github.com/davidfowl/TcpEcho。它应该运行`dotnet run`(或通过在Visual Studio中运行)。它侦听端口8087上的套接字并将收到的消息写入控制台。您可以使用netcat或putty等客户端建立与8087的连接,并发送基于行的消息以使其正常工作。

今天Pipelines为Kestrel和SignalR提供支持,我们希望看见它作为.NET社区中许多网络库和组件的核心。

资料:

  1. 转载自System.IO.Pipelines: High performance IO in .NET

  2. Pipelines - a guided tour of the new IO API in .NET, part 1

  3. Pipelines - a guided tour of the new IO API in .NET, part 2

  4. 2号资料的中文翻译 Pipelines - .NET中的新IO API指引(一)

  5. System.IO.Pipelines-Nuget包

PS: 首次翻译英文文章,不足错漏请指出,多谢支持

原文地址:https://www.cnblogs.com/xxfy1/p/9290235.html

.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com

640?wx_fmt=jpeg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/320847.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Full_of_Boys训练1总结

题目来源&#xff1a; 2017-2018 ACM-ICPC Northern Eurasia (Northeastern European Regional) Contest (NEERC 17) A. Archery Tournament 每次查询&#xff0c;找这个位置前面的15个圆&#xff0c;后边15个圆来更新答案。set维护一下圆就行。为什么对&#xff0c;官方题解&a…

CF311B-Cats Transport【斜率优化dp】

正题 题目链接:https://www.luogu.com.cn/problem/CF311B 题目大意 nnn座山在一条线上&#xff0c;有mmm只猫&#xff0c;第iii只从tit_iti​开始在第xix_ixi​座山上游玩结束。 派ppp个人在不同时间从111走到nnn接走所有游玩结束的猫&#xff0c;求所有猫的最小等待时间。 解…

【并查集】黑魔法师之门(codevs 1995/joyoi-codevs 1995)

黑魔法师之门 codevs 1995 joyoi-codevs 1995 题目大意&#xff1a; 有一堆点&#xff0c;每一次操作添加一条边&#xff0c;并要输出每个点的度数都大于1并为偶数的子图的个数 原题&#xff1a; 题目描述 经过了16个工作日的紧张忙碌&#xff0c;未来的人类终于收集到了…

.NET+PostgreSQL实践与避坑指南

简介.NETPostgreSQL(简称PG)这个组合我已经用了蛮长的一段时间&#xff0c;感觉还是挺不错的。不过大多数人说起.NET平台&#xff0c;还是会想起跟它“原汁原味”配套的Microsoft SQL Server(简称MSSQL)&#xff0c;其实没有MSSQL也没有任何问题&#xff0c;甚至没有Windows Se…

Full_of_Boys训练2总结

题目来源:&#xff1a;西安电子科技大学第16届程序设计竞赛网络同步赛 A, B, C: easy problem D. 另一个另一个简单题 做法是加起来&#xff0c;除n。希望会推导的聚聚指点。 #include <bits/stdc.h> const int inf 0x3f3f3f3f; using namespace std; int T; int ans; in…

P2714-四元组统计【数论,容斥】

正题 题目链接:https://www.luogu.com.cn/problem/P2714 题目大意 给出nnn个数&#xff0c;求有多少个(i,j,k,l)(i,j,k,l)(i,j,k,l)使得gcd(ai,aj,ak,al)1gcd(a_i,a_j,a_k,a_l)1gcd(ai​,aj​,ak​,al​)1。 解题思路 我们设fif_ifi​表示gcdgcdgcd和为iii的方案数。FiF_iFi…

【并查集】Supermarket(poj 1456/luogu-UVA1316)

Supermarket poj 1456 luogu-UVA1316 题目大意&#xff1a; 有一堆物品&#xff0c;每一件物品都有自己的价值和保质期&#xff0c;每天只能卖出一件物品&#xff0c;问最大价值是多少 原题&#xff1a; 题目描述 有一个商店有许多批货&#xff0c;每一批货又有N(0<N…

Jimu : .Net Core 分布式微服务框架介绍

一、前言近些年一直浸淫在 .Net 平台做企业应用开发&#xff0c;用过的 .Net 框架不多&#xff08;具体数量不清&#xff0c;印象深刻的有 Asp.Net MVC&#xff0c;WPF&#xff0c;其他很多都是基于微软开发的框架做些封装而形成新的框架&#xff0c;大都是还没起好名就湮灭在历…

组合数学学习笔记

常见组合计数 n球m盒分配问题 球有别&#xff0c;盒子有别&#xff0c;盒子可空&#xff1a;m^n 每个同学都有m种选择 球无别&#xff0c;盒子有别&#xff0c;盒子不可空&#xff1a;C(n-1,m-1) 隔板法 球无别&#xff0c;盒子有别&#xff0c;盒子可空&#xff1a;C(nm-1,m-1…

P3287-[SCOI2014]方伯伯的玉米田【二维树状数组,dp】

正题 题目链接:https://www.luogu.com.cn/problem/P3287 题目大意 nnn个玉米高度不同&#xff0c;可以选择kkk个区间拔高111个高度&#xff0c;求最长不降子序列长度。 解题思路 显然每次拔高都是拔一个后缀&#xff0c;所以我们设fi,jf_{i,j}fi,j​表示到第iii个玉米&#x…

【背包】买装备

买装备 题目大意&#xff1a; 有n件物品&#xff0c;每件物品有它的物抗&#xff0c;魔抗&#xff0c;价格&#xff0c;现在要在物抗魔抗各不小于一个值的前提下&#xff0c;使价格最小&#xff08;每件物品只能买一件&#xff09; 原题&#xff1a; 题目描述 mxy 沉迷于一…

.NetCore2.1 WebAPI 根据swagger.json自动生成客户端代码

前言上一篇博客中我们可以得知通过Swagger插件可以很方便的提供给接口开发者在线调试&#xff0c;但是实际上Swagger附带的功能还有很多&#xff0c;比如使用NSwag生成客户端调用代码&#xff0c;进一步解放接口开发者。NSwag NSwag是一个发布在GitHub上的开源项目&#xff0c;…

BZOJ1485: [HNOI2009]有趣的数列

题解&#xff1a;考虑按顺序从小到大&#xff0c;加入数字&#xff0c;将加入奇数位置看作入栈&#xff0c;加入偶数位置看作出栈。为什么可以&#xff1f;考虑我们要保证相邻奇数小于偶数&#xff0c;所以必须先填上一个奇数的位置才能填偶数的位置&#xff0c;既时刻保证奇数…

jzoj4223-旅游【并查集】

正题 题目大意 nnn个点mmm条边&#xff0c;qqq次询问走边权小于xxx的能联通的点对数。 解题思路 将边权排序&#xff0c;然后并查集预处理答案即可。 时间复杂度O(mlog⁡m)O(m\log m)O(mlogm) codecodecode #include<cstdio> #include<cstring> #include<alg…

【dfs】买门票

买门票 题目大意&#xff1a; 给出一些字母&#xff0c;求出可组合成的组合&#xff08;要按顺序&#xff0c;和一定的规则&#xff09; 原题&#xff1a; 题目描述 mxy 正要经过新世界的大门。 现在有很多人在门口排队&#xff0c;每个人将会被发到一个有效的通行密码作为…

HDU1812 - Count the Tetris

polya自主ac的第一道&#xff0c;讨论方法&#xff1a;先把奇偶分开(1)顺时针0度&#xff0c;90度&#xff0c;180度&#xff0c;270度 (2)镜像竖线&#xff0c;水平线&#xff0c;两条对角线。分别推出公式计算&#xff0c;实在推不出来&#xff0c;写个模拟暴力找循环节&…

使用.NetCore 控制台演示 熔断 降级(polly)

1、熔断降级的概念&#xff1a; 熔断&#xff1a;我这里有一根长度一米的钢铁&#xff0c;钢铁的熔点1000度&#xff08;假设&#xff09;&#xff0c;现在我想用力把这根钢铁折弯&#xff0c;但是人的力有限达不到折弯的点&#xff0c;然后我使用火给钢铁加热&#xff0c;每隔…

I Got a Matrix!

I Got a Matrix! 题目大意&#xff1a; 给一个矩阵&#xff0c;求出边上的数的和 原题&#xff1a; 题目描述 给定一个 n ∗ m 的矩阵 A&#xff0c;询问位于矩阵边缘的元素之和。所谓矩阵边缘的元素&#xff0c;就是第一行和 最后一行的元素以及第一列和最后一列的元素。…

jzoj6803-NOIP2020.9.26模拟tom【构造】

正题 题目大意 nnn个点的一棵树&#xff0c;给每个点一个权值是1∼a1\sim a1∼a或−1∼−b-1\sim -b−1∼−b。每次选择正负中一个绝对值最小的删去使得无论如何选择都不会将树分成两个联通块。 解题思路 因为可以随意选择&#xff0c;所以aaa和−b-b−b的点一定要连在一起&am…

给正在努力的您几条建议(附开源代码)

前言我是一名在广州的某家互联网公司工作&#xff0c;并有6年工作经验&#xff0c;奔着架构师与微软MVP为目标的老鸟程序员。最近回顾了下多年来走的路&#xff0c;有不少的弯路。今天不说技术&#xff0c;而是总结了一些职业生涯上的建议与大家分享。虽说今天不说技术&#xf…