因为某段程序的需要,我需要将一个long数组,不断地填充数据,然后用完了之后又要清空里面的数据,以便再次填充。由于调用及其频繁,所以我很在意清除数据的性能。
测试代码
以下程序都是基于下面的测试代码完成:
using System; using System.Diagnostics; using System.Numerics; namespace FillTest { class Program { static void Main(string[] args) { var array = new long[2515]; var Empty = new long[2515]; Do(() => { Fill(array, 0L); } , "Fill"); Console.ReadLine(); } static void Fill(long[] array,long value) { for (int i = 0; i < array.Length; i++) { array[i] = value; } } static void Do(Action action,string name) { var wathch = Stopwatch.StartNew(); for (int i = 0; i < 1000000; i++) { action(); } wathch.Stop(); Console.WriteLine(name + " 耗时:" + wathch.Elapsed.ToString()); } } }
通常的,最简单的办法就像上面的代码那样,来个循环就可以了。
在我的机器中(i5 6500@3.2GHz 、16GB、 Release、 .net core 2、 windows 10),Fill版本消耗1.3秒。
双指令
我不确认此代码执行时是否使用了SIMD指令,所以我又编写了一个版本:
static void Fill2(long[] array,long value) { var i = 0; var end = array.Length - 1; while (i < end) { array[i] = value; array[i + 1] = value; i += 2; } if((array.Length % 2) == 1){ array[array.Length - 1] = value; } }
我居然发现,仅仅消耗了1.0秒,难道.net默认不会优化这个代码?
Array.Fill
有人会问,你为什么不调用Array自带的Fill方法呢?我尝试调用了,遗憾的是,一样是1.3秒,和我的第一个版本系统。看样子写核心代码的人偷懒了。哈哈
CopyTo
网上其实能够查到一些讨巧的手法,例如使用一个静态的数组,内部都是0,当需要填充某个数据时,将这个静态数组复制到你的数组一样起到赋值的作用。例如:
Empty.CopyTo(array, 0);
这个版本竟然直接达到 0.56秒,几乎翻两倍,的确是个好办法。
SIMD
在我认为CopyTo是最快的方法时,我突然感觉我的第二个实现,是不是没有真正用到SIMD,因为我的印象中,SIMD是非常非常快的,应该不止上升那么一点点。
所以我搬出了Vector对象,我是这么干的。
static void Fill3(long[] array,long value) { Vector<long> v = new Vector<long>(value); var i = 0; var end = array.Length - 3; while (i < end) { v.CopyTo(array, i); i += 4; } if ((array.Length % 4) == 3) { array[array.Length - 3] = value; array[array.Length - 2] = value; array[array.Length - 1] = value; }else if ((array.Length % 4) == 2) { array[array.Length - 2] = value; array[array.Length - 1] = value; } else if ((array.Length % 4) == 1) { array[array.Length - 1] = value; } }
速度提高到 0.36秒。
所以性能无界限,有没有更快的办法呢?
后续
一些其他的尝试,但都不理想。
static unsafe void Fill4(long[] array, long value) { fixed (Int64* destinationBase = array) { for (int g = 0; g < array.Length; g++) { destinationBase[g] = value; } } }//1.2秒 static void Fill5(long[] array, long value) { Vector<long> v = new Vector<long>(value); var i = 0; var end = array.Length - 3; while (i < end) { array[i + 3] = array[i + 2] =array[i + 1] = array[i] = value; //array[i+1] = value; //array[i+2] = value; //array[i+3] = value; i += 4; } if ((array.Length % 4) == 3) { array[array.Length - 3] = value; array[array.Length - 2] = value; array[array.Length - 1] = value; } else if ((array.Length % 4) == 2) { array[array.Length - 2] = value; array[array.Length - 1] = value; } else if ((array.Length % 4) == 1) { array[array.Length - 1] = value; } } //0.8秒