【问题标题】:Why allocation on ArrayPool is faster then allocation on Stack?为什么 ArrayPool 上的分配比 Stack 上的分配更快?
【发布时间】:2019-03-18 20:25:19
【问题描述】:

我有以下基准,它使用堆栈分配、堆分配和 ArrayPool 分配从文件中读取字符串。

我希望堆栈分配最快,因为它只是堆栈指针增量,但根据基准 ArrayPool 稍快。

这怎么可能?

static void Main(string[] args)
{         
     BenchmarkRunner.Run<BenchmarkRead>();          
}

using BenchmarkDotNet.Attributes;
using System;
using System.Buffers;
using System.IO;
using System.Linq;

namespace RealTime.Benchmark
{
[MemoryDiagnoser]
public class BenchmarkRead
{
    const string TestFile = "TestFiles/animals.txt";

    public BenchmarkRead()
    {
        Directory.CreateDirectory(Path.GetDirectoryName(TestFile));
        // cca 100 KB of text
        string content = string.Concat(Enumerable.Repeat("dog,cat,spider,cat,bird,", 4000));
        File.WriteAllText(TestFile, content);
    }

    [Benchmark]
    public void ReadFileOnPool() => ReadFileOnPool(TestFile);

    [Benchmark]
    public void ReadFileOnHeap() => ReadFileOnHeap(TestFile);

    [Benchmark]
    public void ReadFileOnStack() => ReadFileOnStack(TestFile);

    public void ReadFileOnHeap(string filename)
    {
        string text = File.ReadAllText(filename);
        // ....call parse
    }

    public void ReadFileOnStack(string filename)
    {
        Span<byte> span = stackalloc byte[1024 * 200];
        using (var stream = File.OpenRead(filename))
        {
            int count = stream.Read(span);
            if (count == span.Length)
                throw new Exception($"Buffer size {span.Length} too small, use array pooling.");
            span = span.Slice(0, count);
            // ....call parse
        }
    }

    public void ReadFileOnPool(string filename)
    {
        ArrayPool<byte> pool = ArrayPool<byte>.Shared;
        using (var stream = File.OpenRead(filename))
        {
            long len = stream.Length;
            byte[] buffer = pool.Rent((int)len);
            try
            {
                int count = stream.Read(buffer, 0, (int)len);
                if (count != len)
                    throw new Exception($"{count} != {len}");

                Span<byte> span = new Span<byte>(buffer).Slice(0, count);
                // ....call parse
            }
            finally
            {
                pool.Return(buffer);
            }
        }
    }
}
}

结果:

|          Method |     Mean | Gen 0/1k Op | Gen 2/1k Op |Al. memory/Op|
|---------------- |---------:|------------:|------------:|------------:|
|  ReadFileOnPool | 109.9 us |      0.1221 |           - |       480 B |
|  ReadFileOnHeap | 506.0 us |     87.8906 |     58.5938 |    393440 B |
| ReadFileOnStack | 115.2 us |      0.1221 |           - |       480 B |

【问题讨论】:

  • 这里真的有问题吗?简单地比较事物并不能真正帮助任何未来的用户,因为它只针对您的性能测试。通过一个真实世界的例子,概括性能答案会容易得多。
  • 我不认为 Stack 在这里是最快的,因为你肯定在那里分配内存,而 Pool 方法可能有一个已经分配的块可用。
  • 我想我们必须深入研究生成的字节码
  • because it is just stack pointer increment,我怀疑内存分配和实际文件IO花费的时间比例较大,和你看到的一致。

标签: c# memory-management heap-memory allocation pooling


【解决方案1】:
  • 由于InitLocalsSpan&lt;byte&gt; span = stackalloc byte[1024 * 200] 将被初始化为零。

  • byte[] buffer = pool.Rent((int)len); 根本不会被零初始化。

所以你已经达到了零初始化本地数组的成本比整个 Rent() 例程更昂贵的地步。

几个月前我实际上为此创建了一个 nuget 包https://github.com/josetr/InitLocals,但我们很快也会从 Microsoft 获得类似的东西:https://github.com/dotnet/corefx/issues/29026

【讨论】:

    猜你喜欢
    • 2015-10-15
    • 2012-09-14
    • 2019-12-07
    • 1970-01-01
    • 1970-01-01
    • 2013-05-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多