【问题标题】:Could allocating a byte array be performance-critical?分配字节数组是否对性能至关重要?
【发布时间】:2014-05-18 02:44:09
【问题描述】:

在我的小型文件传输网站(this one,运行 .NET 4.5.1)中,我按照 Microsoft 知识库文章 812406 将之前上传的文件从服务器发送到浏览器。

做性能优化我惊讶地发现那行

var buffer = new byte[10000];

需要相当多的时间(我使用的是 Red Gate 的ANTS Performance Profiler)。每个完整下载/客户端仅分配一次缓冲区。

我的问题:

  • 以这种方式和这种大小分配缓冲区是一种好习惯吗?
  • 分配 ≈10k 缓冲区的任何替代方案?

更新 1:

感谢您的 cmets,我发现内存也在循环内分配。

不过,ANTS Profiler 仅将分配标记为在循环之外 花费了这么多时间,老实说,我(还)不明白。我已经删除了循环内的(无意义的)分配。

更新 2:

实施了建议的 BufferManager 并将缓冲区大小从 10k 减少到 4096(以防万一...),我的网站从几天以来运行非常流畅。

【问题讨论】:

  • 什么是“相当大的时间百分比”?
  • 您是在重复使用缓冲区,还是为每 10,000 个字节创建一个新缓冲区?实际上对处理器内核征税有多难?您是否受 CPU 限制或 I/O 限制?
  • 你的下载量有多大?
  • 只要你花足够的时间去做,任何事情都可能对性能至关重要。
  • 示例代码循环重新分配它。

标签: c# .net performance generic-handler


【解决方案1】:

是的。其实WCF uses a "buffer manager" to prevent this problem。

我自己一直在开发network service,在分析过程中我发现Byte[] 缓冲区的分配造成了瓶颈。不仅在分配期间,而且处理器在 GC 中浪费的时间也非常高。重用这些缓冲区和避免分配的改进会产生非常大的性能改进。

您可以使用BufferManager 类来避免编写自己的缓冲区管理策略。

【讨论】:

  • 我目前正在实施这个...任何意见是否 10k 就下载块大小而言是一个太大的缓冲区?
  • 我使用 8K(8192 字节),因为我看到这是 TcpListener 在我的计算机中默认使用的。 10k应该也可以。但请确保它是可配置的,并且您可以测试不同的值;)
  • 仅供参考 .NET 5 带有一个 ArrayPool<T> 类。
【解决方案2】:

在 .NET 中创建对象通常非常快,但由于此数组对象的大小很大,因此清除其所有字节需要很长时间。 C# 总是将所有字节设置为0,因此在创建对象时将所有字段设置为其默认值。 (构造函数和字段初始值设定项当然可以在类和结构中分配不同的值。)

在example given by Microsoft 中,缓冲区在循环之前分配,并且永远不会改变其大小。此外,写入输出流只会写入所需的字节。

// Gets the exact number of bytes read
length = iStream.Read(buffer, 0, 10000);

// Writes only 'length' bytes to the output
Response.OutputStream.Write(buffer, 0, length);

因此,无需在每次循环迭代时通过分配一个新缓冲区来“清除”缓冲区。肮脏的额外字节不会受到伤害。

解决方案:将buffer= new Byte[10000]; 放在while 循环中!

【讨论】:

  • 谢谢,我会这样做的。
  • 他说每个下载/客户端都会发生一次。这听起来不像是在循环中发生的。
  • 他的说法是错误的。他提到的example 循环执行此操作。
【解决方案3】:

调用这样的构造函数,尤其是使用如此大的缓冲区时,肯定会占用大量 CPU 时间。当然,您的问题的答案将基于意见,但这是我的:

  1. 分配该大小的缓冲区没有任何问题。 10K 在现代系统上并不是那么多内存。当然,在任何类型的循环中这样做都会很快消耗 CPU 时间。

  2. 如果可以,请尽量避免为每次使用重新构建缓冲区。使用先前定义的缓冲区可以避免每次需要时都重新分配内存。当然,如果这是线程化的(多个连接),每个连接/线程都需要自己的缓冲区,但至少每个连接一个分配,而不是链接示例中的每个流数据“块”的新缓冲区。

【讨论】:

  • 是的,示例代码是一个糟糕的示例。每个客户端只分配一次缓冲区。
  • 清除 10.000 字节并不需要很多时间。确实一直在这样做,但我没有看到证明情况如此的证据。
  • 该示例在循环中分配该数组! cmets 似乎都同意这一点...在绘制/更新循环中使用 small 构造函数运行 XNA 游戏可能会导致大量 CPU 峰值,在快速连接的下载期间,该循环正在运行也很快。
【解决方案4】:

如果你有文件传输任务我建议使用Microsoft.IO.RecyclableMemoryStream。

它有RecyclableMemoryStreamManager类型,可以创建RecyclableMemoryStream(内部复用字节)。

优点:

  • 它在内部重复使用字节数组。例如。如果您需要临时内存流,这些类会直接为您获取,无需额外清理。
  • 它将内部数组按块划分。

缺点:

  • 您应该处置每个流(否则您的应用程序将使用大量内存)

【讨论】:

    猜你喜欢
    • 2013-03-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-29
    • 1970-01-01
    • 1970-01-01
    • 2011-11-13
    相关资源
    最近更新 更多