在.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的优势并不在这种操作中能够体现的,若有见解,还请在评论区中指出,笔者可以及时修改。

posted @ 2022-05-09 19:37  .NetDomainer  阅读(280)  评论(0编辑  收藏  举报