2009-12-13 166 views
69

我需要填寫一個byte[]單個非零值的值。我怎麼能在C#中做到這一點,而不需要遍歷數組中的每個byteC#中memset的等價物是什麼?

更新:的意見似乎這個分成兩個問題 -

  1. 是否有一個框架的方法,以填補一個byte [],可能是類似於memset
  2. 什麼是最有效的當我們處理一個非常大的數組時,如何做到這一點?

我完全同意使用一個簡單的循環工作得很好,正如Eric和其他人所指出的那樣。問題的關鍵是看看我是否可以學習關於C#的新東西:)我認爲Juliet的並行操作方法應該比簡單的循環更快。

基準: 多虧了史雲遜的Mikael:http://techmikael.blogspot.com/2009/12/filling-array-with-default-value.html

原來簡單的for環路去,除非你想使用不安全的代碼的方式。

道歉在我原來的帖子中沒有更清晰。埃裏克和馬克在他們的評論中都是正確的;肯定需要更多重點突出的問題。感謝大家的建議和迴應。

+0

請注意,對於字節,Mark的答案需要稍作修改。 byte [] image = Enumerable.Repeat((byte)255,[....])。ToArray(); 否則它會假定你想要返回int []。 – Jedidja 2009-12-13 20:12:56

+1

如果你必須走性能路線,我懷疑使用unsafe/fixed並且一次設置一個Int32或Int64,並且移動指針會是你在c#中可以達到的最快速度(並且對於左邊的字節使用一個字節)。 – 2009-12-13 21:07:46

+0

測試性能的好處。肯定會這麼做:) – Jedidja 2009-12-13 21:13:07

回答

51

你可以使用Enumerable.Repeat

byte[] a = Enumerable.Repeat((byte)10, 100).ToArray(); 

第一個參數是你想要重複元素,第二個參數是的次數重複。

這對小數組可行,但如果您處理的是非常大的數組並且性能是一個問題,則應該使用循環方法。

+0

投票。我學到了一些東西:-) – 2009-12-13 20:04:48

+1

謝謝:)所以不是我想要完成這項任務。 – Jedidja 2009-12-13 20:10:59

+36

請注意,這比簡單地寫一個循環要慢數十倍。有問題的陣列長達數百萬個項目;性能問題可能相當密切。 – 2009-12-14 15:57:55

4

你可以做到這一點,當你初始化數組,但我不認爲這是你的要求爲:

byte[] myBytes = new byte[5] { 1, 1, 1, 1, 1}; 
+2

這將是正確的:)我正在處理圖像,所以字節[]是幾十萬/百萬項大。 – Jedidja 2009-12-13 20:06:39

11

如果性能是至關重要的,你可以考慮使用不安全的代碼,直接用指針工作到陣列。

另一種選擇可能是從msvcrt.dll導入memset並使用它。然而,調用它的開銷可能比速度的增加更大。

+2

是的,它比Repeat <>或for-loop快約40倍。 – 2009-12-13 20:26:37

+0

導入memset?有趣...將不得不放棄一槍。 – Jedidja 2009-12-13 21:13:52

+0

@jedidja 你可以在這裏找到一個代碼示例:http://www.gamedev.net/community/forums/topic.asp?topic_id=389926 – Jan 2009-12-14 00:12:49

10

如果性能絕對至關重要,那麼Enumerable.Repeat(n, m).ToArray()對您的需求來說太慢了。您可能能夠使用PLINQ或Task Parallel Library炮製出更快的性能:

using System.Threading.Tasks; 

// ... 

byte initialValue = 20; 
byte[] data = new byte[size] 
Parallel.For(0, size, index => data[index] = initialValue); 
+1

是的 - 偉大的觀察,實際上我們正在使用Parallel.For其他圖像處理代碼。 – Jedidja 2009-12-14 16:42:58

+3

是的,但你不覺得骯髒的並行初始化代碼嗎?!? ;) – kenny 2010-03-04 12:30:09

+4

該代碼與提供的不正確。 Parallel.For的第二個參數是toExclusive,意味着數組的最後一個字節不變。將'size - 1'更改爲'size'。 – 2010-12-31 17:20:49

20

有點晚,但下面的方法可能是沒有恢復到不安全的代碼一個很好的妥協。基本上,它使用傳統循環初始化陣列的開始,然後恢復爲Buffer.BlockCopy(),這應該儘可能快地使用託管呼叫。

public static void MemSet(byte[] array, byte value) { 
    if (array == null) { 
    throw new ArgumentNullException("array"); 
    } 
    const int blockSize = 4096; // bigger may be better to a certain extent 
    int index = 0; 
    int length = Math.Min(blockSize, array.Length); 
    while (index < length) { 
    array[index++] = value; 
    } 
    length = array.Length; 
    while (index < length) { 
    Buffer.BlockCopy(array, 0, array, index, Math.Min(blockSize, length-index)); 
    index += blockSize; 
    } 
} 
+2

@downvoter我的回答有什麼問題?任何改進建議? – Lucero 2016-03-06 22:42:23

+0

另外值得注意的是,Buffer.BlockCopy(索引和長度)的輸入參數是字節數組索引和長度,所以爲了將其複製到可以說整數數組中,您需要將Buffer.BlockCopy索引和長度乘以sizeof(int) 。 – 2017-01-10 10:47:24

18

建立在Lucero's answer,這裏是一個更快的版本。它會使每個迭代使用Buffer.BlockCopy複製的字節數加倍。有趣的是,當使用相對較小的數組(1000)時,它的性能優於10倍,但對於較大的數組(1000000)來說差異並不那麼大,但它總是比較快。關於它的好處是,它甚至可以執行到小陣列。在長度= 100左右,它比天真的方法更快。對於一百萬個元素字節的數組,它快了43倍。 (英特爾酷睿i7測試,.NET 2.0)

public static void MemSet(byte[] array, byte value) { 
    if (array == null) { 
     throw new ArgumentNullException("array"); 
    } 

    int block = 32, index = 0; 
    int length = Math.Min(block, array.Length); 

    //Fill the initial array 
    while (index < length) { 
     array[index++] = value; 
    } 

    length = array.Length; 
    while (index < length) { 
     Buffer.BlockCopy(array, 0, array, index, Math.Min(block, length-index)); 
     index += block; 
     block *= 2; 
    } 
} 
10

這種簡單的實現,通過逐次加倍,並且表現相當好(根據我的基準,比天真的版本快約3-4倍):

public static void Memset<T>(T[] array, T elem) 
{ 
    int length = array.Length; 
    if (length == 0) return; 
    array[0] = elem; 
    int count; 
    for (count = 1; count <= length/2; count*=2) 
     Array.Copy(array, 0, array, count, count); 
    Array.Copy(array, 0, array, count, length - count); 
} 

編輯:在閱讀其他答案時,似乎我不是唯一有這個想法的人。儘管如此,我仍然留在這裏,因爲它有點乾淨,並且與其他人相提並論。

+0

我寫了一篇關於如何使用Array.Copy的博客文章。 http://coding.grax.com/2011/11/initialize-array-to-value-in-c-very.html – Grax 2014-04-04 16:01:34

6

Or use P/Invoke way

[DllImport("msvcrt.dll", 
EntryPoint = "memset", 
CallingConvention = CallingConvention.Cdecl, 
SetLastError = false)] 
public static extern IntPtr MemSet(IntPtr dest, int c, int count); 

static void Main(string[] args) 
{ 
    byte[] arr = new byte[3]; 
    GCHandle gch = GCHandle.Alloc(arr, GCHandleType.Pinned); 
    MemSet(gch.AddrOfPinnedObject(), 0x7, arr.Length); 
} 
38

其實,有鮮爲人知稱爲InitblkEnglish version)IL操作這正是這麼做的。因此,讓我們將其用作不需要「不安全」的方法。這裏的幫手類:

public static class Util 
{ 
    static Util() 
    { 
     var dynamicMethod = new DynamicMethod("Memset", MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard, 
      null, new [] { typeof(IntPtr), typeof(byte), typeof(int) }, typeof(Util), true); 

     var generator = dynamicMethod.GetILGenerator(); 
     generator.Emit(OpCodes.Ldarg_0); 
     generator.Emit(OpCodes.Ldarg_1); 
     generator.Emit(OpCodes.Ldarg_2); 
     generator.Emit(OpCodes.Initblk); 
     generator.Emit(OpCodes.Ret); 

     MemsetDelegate = (Action<IntPtr, byte, int>)dynamicMethod.CreateDelegate(typeof(Action<IntPtr, byte, int>)); 
    } 

    public static void Memset(byte[] array, byte what, int length) 
    { 
     var gcHandle = GCHandle.Alloc(array, GCHandleType.Pinned); 
     MemsetDelegate(gcHandle.AddrOfPinnedObject(), what, length); 
     gcHandle.Free(); 
    } 

    public static void ForMemset(byte[] array, byte what, int length) 
    { 
     for(var i = 0; i < length; i++) 
     { 
      array[i] = what; 
     } 
    } 

    private static Action<IntPtr, byte, int> MemsetDelegate; 

} 

什麼是性能?這是我對Windows/.NET和Linux/Mono(不同PC)的結果。

Mono/for:  00:00:01.1356610 
Mono/initblk: 00:00:00.2385835 

.NET/for:  00:00:01.7463579 
.NET/initblk: 00:00:00.5953503 

所以這是值得考慮的。請注意,生成的IL將不可驗證。

+0

非常整齊!感謝分享。 – Jedidja 2014-09-12 13:02:03

+3

我觀察到了一個更大的差異。要初始化10MB陣列1000次,initblk需要0.5s,循環需要22s。這是我的基準:https://gist.github.com/thomaslevesque/6f653d8b3a82b1d038e1 – 2014-11-12 14:02:57

+0

這更加有趣:)用你的基準測試結果是:3.66s vs 0.3s。 – 2014-11-12 15:02:30

5

所有的答案都只寫單字節 - 如果你想用字填充字節數組怎麼辦?還是漂浮?我現在覺得它很有用。因此,在用非泛型方式將類似代碼寫入'memset'幾次併到達此頁面以找到單字節的良好代碼之後,我開始着手編寫下面的方法。

我認爲PInvoke和C++/CLI各有缺點。爲什麼不把你的運行時'PInvoke'換成mscorxxx? Array.Copy和Buffer.BlockCopy肯定是本地代碼。 BlockCopy甚至不是'安全的' - 只要它們在數組中,您可以在另一個上覆制一段時間,或者在DateTime上覆制一段時間。

至少我不會爲這樣的東西去新的C++項目 - 這幾乎可以肯定是浪費時間。

因此,這裏基本上是由Lucero和TowerOfBricks提供的解決方案的擴展版本,可用於memset longs,ints等以及單個字節。

public static class MemsetExtensions 
{ 
    static void MemsetPrivate(this byte[] buffer, byte[] value, int offset, int length) { 
     var shift = 0; 
     for (; shift < 32; shift++) 
      if (value.Length == 1 << shift) 
       break; 
     if (shift == 32 || value.Length != 1 << shift) 
      throw new ArgumentException(
       "The source array must have a length that is a power of two and be shorter than 4GB.", "value"); 

     int remainder; 
     int count = Math.DivRem(length, value.Length, out remainder); 

     var si = 0; 
     var di = offset; 
     int cx; 
     if (count < 1) 
      cx = remainder; 
     else 
      cx = value.Length; 
     Buffer.BlockCopy(value, si, buffer, di, cx); 
     if (cx == remainder) 
      return; 

     var cachetrash = Math.Max(12, shift); // 1 << 12 == 4096 
     si = di; 
     di += cx; 
     var dx = offset + length; 
     // doubling up to 1 << cachetrash bytes i.e. 2^12 or value.Length whichever is larger 
     for (var al = shift; al <= cachetrash && di + (cx = 1 << al) < dx; al++) { 
      Buffer.BlockCopy(buffer, si, buffer, di, cx); 
      di += cx; 
     } 
     // cx bytes as long as it fits 
     for (; di + cx <= dx; di += cx) 
      Buffer.BlockCopy(buffer, si, buffer, di, cx); 
     // tail part if less than cx bytes 
     if (di < dx) 
      Buffer.BlockCopy(buffer, si, buffer, di, dx - di); 
    } 
} 

有了這個,你可以簡單地添加短的方法來把你需要與memset的並調用私有方法,例如值類型只要找到替代ULONG在這個方法:

public static void Memset(this byte[] buffer, ulong value, int offset, int count) { 
     var sourceArray = BitConverter.GetBytes(value); 
     MemsetPrivate(buffer, sourceArray, offset, sizeof(ulong) * count); 
    } 

或者去愚蠢的,與任何類型結構的做到這一點(雖然只有上述MemsetPrivate工程結構元帥的大小爲二的冪):

public static void Memset<T>(this byte[] buffer, T value, int offset, int count) where T : struct { 
     var size = Marshal.SizeOf<T>(); 
     var ptr = Marshal.AllocHGlobal(size); 
     var sourceArray = new byte[size]; 
     try { 
      Marshal.StructureToPtr<T>(value, ptr, false); 
      Marshal.Copy(ptr, sourceArray, 0, size); 
     } finally { 
      Marshal.FreeHGlobal(ptr); 
     } 
     MemsetPrivate(buffer, sourceArray, offset, count * size); 
    } 

我改變了之前提到的initblk,以便使用ulong來比較性能和我的代碼,並且靜靜地失敗 - 代碼運行但結果緩衝區僅包含ulong的最低有效字節。然而,我比較寫作緩衝區大小爲,initblk和我的memset方法的性能。時間以ms爲單位,總共超過100次重複,寫入8個字節的長度,無論多少次都適合緩衝區長度。對於單個ulong的8個字節,for版本是手動循環展開的。

Buffer Len #repeat For millisec Initblk millisec Memset millisec 
0x00000008 100  For 0,0032 Initblk 0,0107 Memset 0,0052 
0x00000010 100  For 0,0037 Initblk 0,0102 Memset 0,0039 
0x00000020 100  For 0,0032 Initblk 0,0106 Memset 0,0050 
0x00000040 100  For 0,0053 Initblk 0,0121 Memset 0,0106 
0x00000080 100  For 0,0097 Initblk 0,0121 Memset 0,0091 
0x00000100 100  For 0,0179 Initblk 0,0122 Memset 0,0102 
0x00000200 100  For 0,0384 Initblk 0,Memset 0,0126 
0x00000400 100  For 0,0789 Initblk 0,0130 Memset 0,0189 
0x00000800 100  For 0,1357 Initblk 0,0153 Memset 0,0170 
0x00001000 100  For 0,2811 Initblk 0,0167 Memset 0,0221 
0x00002000 100  For 0,5519 Initblk 0,0278 Memset 0,0274 
0x00004000 100  For 1,1100 Initblk 0,0329 Memset 0,0383 
0x00008000 100  For 2,2332 Initblk 0,0827 Memset 0,0864 
0x00010000 100  For 4,4407 Initblk 0,1551 Memset 0,1602 
0x00020000 100  For 9,1331 Initblk 0,2768 Memset 0,3044 
0x00040000 100  For 18,2497 Initblk 0,5500 Memset 0,5901 
0x00080000 100  For 35,8650 Initblk 1,1236 Memset 1,5762 
0x00100000 100  For 71,6806 Initblk 2,2836 Memset 3,2323 
0x00200000 100  For 77,8086 Initblk 2,1991 Memset 3,0144 
0x00400000 100  For 131,2923 Initblk 4,7837 Memset 6,8505 
0x00800000 100  For 263,2917 Initblk 16,1354 Memset 33,3719 

我排除在外,每次第一個呼叫,因爲這兩個initblk和memset採取一擊的,我相信它是關於.22ms的第一個電話。稍微令人驚訝的是,我的代碼比initblk填充短緩衝區更快,看到它有半頁的完整安裝代碼。

如果有人覺得這樣優化,那就真的吧。這是可能的。

3

貌似System.Runtime.CompilerServices.Unsafe.InitBlock現在做同樣的事情爲OpCodes.Initblk指令康拉德的回答提到了(他也提到了source link)。

的代碼來填充所述陣列中如下:

byte[] a = new byte[N]; 
byte valueToFill = 255; 

System.Runtime.CompilerServices.Unsafe.InitBlock(ref a[0], valueToFill, (uint) a.Length); 
0

UMM,Array對象有一個稱爲清除方法。我敢打賭,Clear方法比你在c#中編寫的任何代碼都快。

相關問題