找回密码
 立即注册
首页 业界区 业界 别让”高性能“骗了你

别让”高性能“骗了你

士沌 2026-1-20 22:00:05
别让”高性能“骗了你

博客园或是CSDN上总是会有一大堆诸如“.net X 引入全新方法 点燃性能革命”的标题党帖子。
千万不要被骗了,试试才知道!

为什么写这篇文章

某年某月某日作者我看到一篇盛赞System.Buffers.ArrayPool的教程。碰巧我在制作我的项目CsGrafeq,更巧的是这个绘图项目后端代码的核心之一是区间集合运算,换句话说是对两个数组的数据交叉分别运算,结果放入一个新数组
注:在以下的使用条件中 一般数组大小在4个元素以下 甚至大部分时候只有单个元素
  1. //其中Range代表一个 (Inf,Sup) 元组结构体public static IntervalSet IntervalSetMethod(IntervalSet i1, IntervalSet i2,Func handler){    var Ranges = new Range[i1.Intervals.Length * i2.Intervals.Length];    var loc = 0;    foreach (var i in i1.Intervals)    foreach (var j in i2.Intervals)        Ranges[loc++] = handler(i, j);    return IntervalSet.Create(FormatRanges(Ranges), i1._Def & i2._Def);}
复制代码
于是乎我想都没想,把代码改成了这样
  1. private readonly ArrayPool Shared=ArrayPool.Shared;public static IntervalSet IntervalSetMethod(IntervalSet i1, IntervalSet i2,Func handler){    var Ranges = Shared.Rend(i1.Intervals.Length * i2.Intervals.Length);    var loc = 0;    foreach (var i in i1.Intervals)    foreach (var j in i2.Intervals)        Ranges[loc++] = handler(i, j);    Shared.Return(i1.Intervals);    Shared.Return(i2.Intervals);    return IntervalSet.Create(FormatRanges(Ranges), i1._Def & i2._Def);}
复制代码
WoW 这完美符合了
System.Buffers 命名空间下提供了一个可对 array 进行复用的高性能池化类 ArrayPool,在经常使用 array 的场景下可使用 ArrayPool 来减少内存占用,提升效率
然而令我大跌眼镜的是,看上去高端大气上档次的ArrayPool实战拉跨到了极致。运行效率经过大约估计,慢了不止十倍
我去!怎么会这样???
实际测试

本着Talk is cheap, show me the code的箴言,写一个基准测试
  1. [MemoryDiagnoser][SimpleJob]public class ArrayAllocVsPoolBenchmarks{    // 每次迭代要“分配/租用”的数组个数    [Params(100_000)]    public int Iterations { get; set; }    //超小 小 大 数组    [Params(4,16, 65_536)]    public int RequestedLength { get; set; }    // 是否在归还时清零    [Params(false, true)]    public bool ClearOnReturn { get; set; }    private ArrayPool _pool = default!;    [GlobalSetup]    public void Setup()    {        _pool = ArrayPool.Shared;    }    [Benchmark(Baseline = true)]    public int NewArray()    {        int checksum = 0;        for (int i = 0; i < Iterations; i++)        {            var arr = new int[RequestedLength];            // 写入少量元素,避免 JIT 把分配当成“无用”而过度优化            // 同时不让工作量随数组大小线性暴涨,聚焦“分配/回收”的差异            arr[0] = i;            arr[^1] = i ^ 12345;            checksum += arr[0];            checksum += arr[^1];        }        return checksum;    }    [Benchmark]    public int ArrayPool_RentReturn()    {        int checksum = 0;        for (int i = 0; i < Iterations; i++)        {            var arr = _pool.Rent(RequestedLength);            try            {                arr[0] = i;                arr[RequestedLength - 1] = i ^ 12345;                checksum += arr[0];                checksum += arr[RequestedLength - 1];            }            finally            {                _pool.Return(arr, clearArray: ClearOnReturn);            }        }        return checksum;    }
复制代码
结果如下
MethodIterationsRequestedLengthClearOnReturnMeanErrorStdDevMedianRatioRatioSDGen0Gen1Gen2AllocatedAlloc RatioNewArray1000004False580.3 us11.85 us34.20 us573.2 us1.000.08637.2070--4000000 B1.00ArrayPool_RentReturn1000004False1,106.3 us39.18 us107.92 us1,126.0 us1.910.21----0.00NewArray1000004True545.7 us23.01 us65.64 us551.5 us1.030.25637.2070--4000000 B1.00ArrayPool_RentReturn1000004True1,451.9 us28.64 us62.86 us1,456.8 us2.730.60----0.00NewArray10000016False804.9 us41.15 us114.72 us828.8 us1.040.301402.3438--8800000 B1.00ArrayPool_RentReturn10000016False673.5 us26.67 us76.52 us639.5 us0.870.24----0.00NewArray10000016True428.8 us8.51 us18.86 us429.5 us1.000.061402.8320--8800000 B1.00ArrayPool_RentReturn10000016True756.1 us10.84 us9.05 us755.3 us1.770.08----0.00NewArray10000065536False725,208.8 us14,294.53 us40,551.21 us721,440.9 us1.0030.088333000.00008333000.00008333000.000026219426512 B1.00ArrayPool_RentReturn10000065536False628.4 us12.38 us17.36 us625.4 us0.0010.00----0.00NewArray10000065536True679,028.3 us13,495.60 us29,338.33 us680,642.6 us1.000.068333000.00008333000.00008333000.000026219525632 B1.00ArrayPool_RentReturn10000065536True368,874.7 us1,215.74 us1,137.20 us368,559.3 us0.540.02----0.00分析结果可以得到,对于ArrayPool的Rent操作,不论数组大小基本可以保持在600μs左右,出于未知的原因,当数组长度为4时,竟达到了1000μs
同时Return操作(即ZeroMemory)所需时间随数组大小基本线性增长,在65536长度下,总时长也基本在Array时长的一半。
总体而言,ArrayPool并没有表现出想象中那么牛逼的效果。在极小数组情况下,不论是否清除数据,效率都不及Array,而在大数组下,的确有显著效率优势。
当然,这项测试存在一些问题,比如没有考虑ArrayPool的Return操作带来的GC压力减轻等。不过从我踩坑的血泪教训可以看出,在小数组下,ArrayPool绝非一个好的选择。
就这样我因为盲目追求高性能,浪费了一个下午的宝贵时光,把所有的数组改成了ArrayPool,然后git回档。。。
All in all 搞清楚真实使用场景,以此作适当的测试,再使用!!!


来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册