因為某段程序的需要,我需要將一個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秒