Linq下Take使用事项

前言

前一段时间,接到一个需求,就是对接钉钉打卡,获取钉钉打卡数据,要先进行注册钉钉打卡事件,然后对钉钉推送的数据进行解密.然后将数据加密后返回给钉钉.这里忍不住要吐槽一下钉钉给数据加解密的Demo(在Github上开源的)有点不专业的了,这里主要说提供C#示例代码,文档注释是使用Java的,算了,在这里不是重点,说回Linq下Take方法.主要是在加解密用到了Take方法.如果需要性能要求高的话,使用Take还是要稍微慎重一下.

RijndaelManaged rDel = new RijndaelManaged
{
    Mode = CipherMode.CBC,
    Padding = PaddingMode.Zeros,
    Key = aesKey,
    IV = aesKey.ToList().Take(16).ToArray()   //使用Take取16个元素
};

Take是可以进行优化的.

var arr = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var a1 = arr.Take(3); //取前3个元素
            
//⬇⬇ 在.Net Core及之后的版本可以使用下面的代码进行优化

var a2 = arr.AsSpan().Slice(0, 3);
如果不知道Span的话,可以看看 在.Net Core中使用Span
使用Span的话,是不会重新分配一个数组的
可以看到asSpan后Slice没有重新分配一个数组.

性能测试

using System;
using System.Linq;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Order;

namespace net6perf.LinqTest
{
    [DisassemblyDiagnoser(printSource: true, maxDepth: 2)]
    [MemoryDiagnoser]
    [Orderer(SummaryOrderPolicy.FastestToSlowest)]
    public class TakTest
    {
        public int[] bytes = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 42, 48, 98, 10 };

        [Params(1024, 2048)]
        public int Times { get; set; }

        [Benchmark]
        public int Take()
        {
            int sum = 0;
            for (int i = 0; i < Times; i++)
            {
                var array = bytes.Take(5).ToArray();
                sum += array.Length;
            }

            return sum;
        }


        [Benchmark]
        public int AsSpan()
        {
            int sum = 0;
            for (int i = 0; i < Times; i++)
            {
                var array = bytes.AsSpan().Slice(0, 5);
                sum += array.Length;
            }

            return sum;
        }
    }
}

使用BenchmarkDotNet,对Take和Slice性能对比

根据测试结果:发现Take和Slice性能不在一个级别.下面看一下Take源码:

public static IEnumerable<TSource> Take<TSource>(this IEnumerable<TSource> source, int count)
{
    if (source == null)
    {
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
    }

    return count <= 0 ?
        Empty<TSource>() :
        TakeIterator<TSource>(source, count);
}
private static IEnumerable<TSource> TakeIterator<TSource>(IEnumerable<TSource> source, int count)
{
    Debug.Assert(count > 0);

    foreach (TSource element in source)
    {
        yield return element;    //yield简化迭代器,具体可以使用ILSpy工具反编译,查看生成IL代码
        if (--count == 0) break;
    }
}


秋风 2022-06-12