• 在.net core中使用span/array实现C语言中memset,memcpy功能


           在.net framework时代,C# BCL(basic class library)提供了一些批量操作内存的方法以实现类似于C语言中memset,memcpy等功能。

      Array.Clear()实现了对连续内存的清零/置空,可以实现C语言中memset(void *,0)的功能(遗憾的是,仅能通过该方法填充0/空值,在.net framework中尚未找到能够将连续内存设定为某个非空值的方法,若要使用该方法,多半需要使用p/invoke调用msvcrt.dll的memset方法),该方法示例代码如下:

    var bts = new byte[1000_0000];
    Array.Clear(bts);

     

      Array.Copy(...)。Buffer.BlockCopy(),Buffer.MemoryCopy()(非安全方法)实现了拷贝内存的功能,该方法的示例代码如下:

    var bts = new byte[1000_0000];
    var bts2 = new byte[1000_0000];
    const byte newValue = 5;
    //为了简单起见,此处暂时使用遍历填充数组元素值,之后我们将使用更高效的方法实现;
    for (int i = 0; i < bts2.Length; i++)
    {
           bts2[i] = newValue;
    }
    //将bts2中的数据拷贝到bts中
     Array.Copy(bts2,0,bts, 0, bts.Length);

     

     

    进入.net core时代后,微软进一步加强了Array类,在其中加入了Fill方法以填充任意值,在.net framework中的限制便不存在了,该方法的示例代码如下:

     var bts = new byte[1000_0000];
     const byte newValue = 5;
     Array.Fill<byte>(bts, newValue, 0, bts.Length);

    以上代码将一个长度为1千万的字节数组的所有元素值设置为了5。

     

    另外,从.net standard某个版本开始,微软引入了Span这一数据结构,方便开发者引用,截取(可选)一段已经存在的连续的内存,同时支持连续的内存操作,这就包含了本文所要实现的memcpy,memset.

    通过span实现memset的示例代码如下:

     var bts = new byte[1000_0000];
     var span = new Span<byte>(bts);
     const byte newValue = 5;
     span.Fill(newValue);

    通过span实现memcpy的示例代码如下:

    var bts = new byte[1000_0000];
    var span = new Span<byte>(bts);            
    var bts2 = new byte[1000_0000];
    var span2 = new Span<byte>(bts2);
    const byte newValue = 5;
    Array.Fill(bts2, newValue,0, bts2.Length);
    span2.CopyTo(span);

    那么使用Array和Span的性能比较情况如何呢?下面开始进行测试,我的测试机器的配置是R7-4800H + 16G lpddr4,目标框架是.net 6.0,生成模式为Release.

    首先测试memset:

         /// <summary>
            /// 内存设置测试;
            /// </summary>
            [TestMethod]
            public void MemsetTest()
            {
                var bts = new byte[1_0000_0000];
                var span = new Span<byte>(bts);
                const byte newValue = 5;
                var sw = Stopwatch.StartNew();
                span.Fill(newValue);
                Trace.WriteLine($"Memset with Span.Fill:{sw.ElapsedMilliseconds}ms");
                Assert.IsTrue(bts.All(p => p == newValue));
                
                //清零内存;
                Array.Fill<byte>(bts, 0,0,bts.Length);
    
                sw.Restart(); 
                for (int i = 0; i < bts.Length; i++)
                {
                    bts[i] = newValue;
                }
                Trace.WriteLine($"Memset with for loop:{sw.ElapsedMilliseconds}ms");
                Assert.IsTrue(bts.All(p => p == newValue));
    
                //清零内存;
                Array.Fill<byte>(bts, 0, 0, bts.Length);
                
                sw.Restart();
                Array.Fill<byte>(bts, newValue, 0, bts.Length);
                Trace.WriteLine($"Memset with Array.Fill:{sw.ElapsedMilliseconds}ms");
                Assert.IsTrue(bts.All(p => p == newValue));
            }

     

    输出结果为:

    Memset with Span.Fill:54ms
    Memset with for loop:74ms
    Memset with Array.Fill:3ms

    Span.Fill的效率相对Array.Fill差距还是有点巨大的,仅是比循环设定值快一些。

    接下来看memcpy:

      /// <summary>
            /// 内存拷贝测试;
            /// </summary>
            [TestMethod]
            public void MemcpyTest()
            {
                var bts = new byte[1_0000_0000];
                var span = new Span<byte>(bts);
                
                var bts2 = new byte[1_0000_0000];
                var span2 = new Span<byte>(bts2);
                const byte newValue = 5;
                Array.Fill(bts2, newValue,0, bts2.Length);
                
                var sw = Stopwatch.StartNew();
                span2.CopyTo(span);
                Trace.WriteLine($"Memcpy with Span.CopyTo:{sw.ElapsedMilliseconds}ms");
                Assert.IsTrue(bts.All(p => p == newValue));
    
                Array.Fill(bts, (byte)0, 0, bts.Length);
                sw.Restart();
                for (int i = 0; i < bts.Length; i++)
                {
                    bts[i] = bts2[i];
                }
                Trace.WriteLine($"Memcpy with for loop :{sw.ElapsedMilliseconds}ms");
                Assert.IsTrue(bts.All(p => p == newValue));
    
                Array.Fill(bts, (byte)0, 0, bts.Length);
                sw.Restart();
                Array.Copy(bts2,0,bts, 0, bts.Length);
                Trace.WriteLine($"Memcpy with Array.Copy:{sw.ElapsedMilliseconds}ms");
                Assert.IsTrue(bts.All(p => p == newValue));
            }

    输出结果为:

    Memcpy with Span.CopyTo:62ms
    Memcpy with for loop :76ms
    Memcpy with Array.Copy:9ms

    Array.Copy大幅领先,Span.Copy仅是相对循环赋值快一点,Span.Copy相对Array.Copy的唯一优势便是泛型保证了类型安全。

    此测试可能代码不够客观,或者,Span的优势并不在这种操作中能够体现的,若有见解,还请在评论区中指出,笔者可以及时修改。

  • 相关阅读:
    【Spring学习随笔】1. Spring的体系结构
    MySQL图形化安装相关总结(含软件分享)
    Spring Bean 基于注解的装配
    vue的三种图片引入方式
    wx.navigateTo({ url: '', }) 跳转无效问题
    JDK环境变量配置
    如何实现Vue底部按钮点击加载更多
    Java Web学习路线
    百度地图循环添加标注,并循环为鼠标悬停标注时信息窗口问题解决
    mvc jquery 修改 viewbag
  • 原文地址:https://www.cnblogs.com/ponus/p/16250563.html
Copyright © 2020-2023  润新知