2011-12-13 12 views
12

我一直在試圖確定在C#中對包含固定數組的管理不安全結構使用固定語句的真實成本。請注意我不是指非託管結構。包含固定數組的管理的不安全結構上的C#固定語句的開銷是多少?

具體來說,是否有任何理由避免以下'MultipleFixed'類顯示的模式?是簡單地將數據固定爲非零,接近於零的成本(==成本類似於設置&在進入/退出固定範圍時清除單個標誌),還是足夠重要以避免可能的情況?

很明顯,這些類是爲了解釋這個問題而設計的。這對於XNA遊戲中的高使用率數據結構來說非常重要,因爲如果我需要修復陣列並將它傳遞到任何地方,我都會這樣做,但如果完全沒有區別,更喜歡保留方法的固定(),以幫助保持函數簽名稍微更容易移植到不支持不安全代碼的平臺。 (是的,它的一些額外的咕嚕代碼,但不惜一切代價。)

 

    unsafe struct ByteArray 
    { 
     public fixed byte Data[1024]; 
    } 

    class MultipleFixed 
    { 
     unsafe void SetValue(ref ByteArray bytes, int index, byte value) 
     { 
      fixed(byte* data = bytes.Data) 
      { 
       data[index] = value; 
      } 
     } 

     unsafe bool Validate(ref ByteArray bytes, int index, byte expectedValue) 
     { 
      fixed(byte* data = bytes.Data) 
      { 
       return data[index] == expectedValue; 
      } 
     } 

     void Test(ref ByteArray bytes) 
     { 
      SetValue(ref bytes, 0, 1); 
      Validate(ref bytes, 0, 1); 
     } 
    } 

    class SingleFixed 
    { 
     unsafe void SetValue(byte* data, int index, byte value) 
     { 
      data[index] = value; 
     } 

     unsafe bool Validate(byte* data, int index, byte expectedValue) 
     { 
      return data[index] == expectedValue; 
     } 

     unsafe void Test(ref ByteArray bytes) 
     { 
      fixed(byte* data = bytes.Data) 
      { 
       SetValue(data, 0, 1); 
       Validate(data, 0, 1); 
      } 
     } 
    } 

而且,我找了類似的問題,我發現最接近的是this,但它與純粹的只關注這個問題是不同的託管代碼以及在該上下文中使用固定的特定成本。

感謝您的任何信息!

回答

8

從經驗上看,在最好的情況下,開銷似乎在32位JIT上約爲270%,在64位上約爲200%(並且越多的時間開銷就越「致電」fixed)。所以我會盡量減少你的fixed塊如果性能是非常關鍵的。

對不起,我不是固定/不安全的代碼非常熟悉,知道爲什麼這是


詳細

我還添加了一些TestMore方法,它調用你的兩種測試方法10的情況下而不是2 ,以便在您的fixed結構中調用多個方法的更真實的場景。

我使用的代碼:

class Program 
{ 
    static void Main(string[] args) 
    { 
     var someData = new ByteArray(); 
     int iterations = 1000000000; 
     var multiple = new MultipleFixed(); 
     var single = new SingleFixed(); 

     // Warmup. 
     for (int i = 0; i < 100; i++) 
     { 
      multiple.Test(ref someData); 
      single.Test(ref someData); 
      multiple.TestMore(ref someData); 
      single.TestMore(ref someData); 
     } 

     // Environment. 
     if (Debugger.IsAttached) 
      Console.WriteLine("Debugger is attached!!!!!!!!!! This run is invalid!"); 
     Console.WriteLine("CLR Version: " + Environment.Version); 
     Console.WriteLine("Pointer size: {0} bytes", IntPtr.Size); 
     Console.WriteLine("Iterations: " + iterations); 

     Console.Write("Starting run for Single... "); 
     var sw = Stopwatch.StartNew(); 
     for (int i = 0; i < iterations; i++) 
     { 
      single.Test(ref someData); 
     } 
     sw.Stop(); 
     Console.WriteLine("Completed in {0:N3}ms - {1:N2}/sec", sw.Elapsed.TotalMilliseconds, iterations/sw.Elapsed.TotalSeconds); 

     Console.Write("Starting run for More Single... "); 
     sw = Stopwatch.StartNew(); 
     for (int i = 0; i < iterations; i++) 
     { 
      single.Test(ref someData); 
     } 
     sw.Stop(); 
     Console.WriteLine("Completed in {0:N3}ms - {1:N2}/sec", sw.Elapsed.TotalMilliseconds, iterations/sw.Elapsed.TotalSeconds); 


     Console.Write("Starting run for Multiple... "); 
     sw = Stopwatch.StartNew(); 
     for (int i = 0; i < iterations; i++) 
     { 
      multiple.Test(ref someData); 
     } 
     sw.Stop(); 
     Console.WriteLine("Completed in {0:N3}ms - {1:N2}/sec", sw.Elapsed.TotalMilliseconds, iterations/sw.Elapsed.TotalSeconds); 

     Console.Write("Starting run for More Multiple... "); 
     sw = Stopwatch.StartNew(); 
     for (int i = 0; i < iterations; i++) 
     { 
      multiple.TestMore(ref someData); 
     } 
     sw.Stop(); 
     Console.WriteLine("Completed in {0:N3}ms - {1:N2}/sec", sw.Elapsed.TotalMilliseconds, iterations/sw.Elapsed.TotalSeconds); 


     Console.ReadLine(); 
    } 
} 

unsafe struct ByteArray 
{ 
    public fixed byte Data[1024]; 
} 

class MultipleFixed 
{ 
    unsafe void SetValue(ref ByteArray bytes, int index, byte value) 
    { 
     fixed (byte* data = bytes.Data) 
     { 
      data[index] = value; 
     } 
    } 

    unsafe bool Validate(ref ByteArray bytes, int index, byte expectedValue) 
    { 
     fixed (byte* data = bytes.Data) 
     { 
      return data[index] == expectedValue; 
     } 
    } 

    public void Test(ref ByteArray bytes) 
    { 
     SetValue(ref bytes, 0, 1); 
     Validate(ref bytes, 0, 1); 
    } 
    public void TestMore(ref ByteArray bytes) 
    { 
     SetValue(ref bytes, 0, 1); 
     Validate(ref bytes, 0, 1); 
     SetValue(ref bytes, 0, 2); 
     Validate(ref bytes, 0, 2); 
     SetValue(ref bytes, 0, 3); 
     Validate(ref bytes, 0, 3); 
     SetValue(ref bytes, 0, 4); 
     Validate(ref bytes, 0, 4); 
     SetValue(ref bytes, 0, 5); 
     Validate(ref bytes, 0, 5); 
    } 
} 

class SingleFixed 
{ 
    unsafe void SetValue(byte* data, int index, byte value) 
    { 
     data[index] = value; 
    } 

    unsafe bool Validate(byte* data, int index, byte expectedValue) 
    { 
     return data[index] == expectedValue; 
    } 

    public unsafe void Test(ref ByteArray bytes) 
    { 
     fixed (byte* data = bytes.Data) 
     { 
      SetValue(data, 0, 1); 
      Validate(data, 0, 1); 
     } 
    } 
    public unsafe void TestMore(ref ByteArray bytes) 
    { 
     fixed (byte* data = bytes.Data) 
     { 
      SetValue(data, 0, 1); 
      Validate(data, 0, 1); 
      SetValue(data, 0, 2); 
      Validate(data, 0, 2); 
      SetValue(data, 0, 3); 
      Validate(data, 0, 3); 
      SetValue(data, 0, 4); 
      Validate(data, 0, 4); 
      SetValue(data, 0, 5); 
      Validate(data, 0, 5); 
     } 
    } 
} 

而在.NET 4.0的結果,32位JIT:

CLR Version: 4.0.30319.239 
Pointer size: 4 bytes 
Iterations: 1000000000 
Starting run for Single... Completed in 2,092.350ms - 477,931,580.94/sec 
Starting run for More Single... Completed in 2,236.767ms - 447,073,934.63/sec 
Starting run for Multiple... Completed in 5,775.922ms - 173,132,528.92/sec 
Starting run for More Multiple... Completed in 26,637.862ms - 37,540,550.36/sec 

而在.NET 4.0中,64位JIT:

CLR Version: 4.0.30319.239 
Pointer size: 8 bytes 
Iterations: 1000000000 
Starting run for Single... Completed in 2,907.946ms - 343,885,316.72/sec 
Starting run for More Single... Completed in 2,904.903ms - 344,245,585.63/sec 
Starting run for Multiple... Completed in 5,754.893ms - 173,765,185.93/sec 
Starting run for More Multiple... Completed in 18,679.593ms - 53,534,358.13/sec 
+0

感謝 - 良好的信息!我仍然想知道開銷的根本原因是什麼,但獲得好的表現是主要目標。 – 2011-12-14 17:31:15

9

這實際上是我有我自己有趣的問題。

我設法獲得的結果表明,性能損失的原因與「固定」聲明本身略有不同。

你可以看到我進行的測試,下面的結果,但也有以下意見,我從那些畫:

  • 使用「固定」純指針表現(X *),無IntPtr的,是與託管代碼一樣好;在釋放模式下,如果固定不是太頻繁使用,那麼它甚至更好 - 這是訪問多個數組值的最常見方式
  • 使用'固定'(在循環中)對性能有很大的負面影響但在發佈模式下,它的工作方式幾乎與普通數組訪問一樣(方法FixedAccess);使用「REF」上的基準型的參數值
  • (浮動[])是一致的更多或同等地高性能的(兩種模式中)使用的IntPtr算術(IntPtrAccess),但是,用於當
  • 調試模式具有顯著性能下降VS釋放模式這兩種模式的性能都比正常的數組訪問更差
  • 如果使用偏移量不與數組的值偏移量相匹配,則性能非常糟糕,無論模式如何(兩種模式實際上需要相同的時間)。這適用於'浮動',但對'int'沒有影響。

多次運行測試,給出稍微不同但結果大致一致的結果。也許我應該跑了很多一系列的測試,並取平均時間 - 但沒有時間爲:)

測試類第一:

class Test { 
    public static void NormalAccess (float[] array, int index) { 
     array[index] = array[index] + 2; 
    } 

    public static void NormalRefAccess (ref float[] array, int index) { 
     array[index] = array[index] + 2; 
    } 

    public static void IntPtrAccess (IntPtr arrayPtr, int index) { 
     unsafe { 
      var array = (float*) IntPtr.Add (arrayPtr, index << 2); 
      (*array) = (*array) + 2; 
     } 
    } 

    public static void IntPtrMisalignedAccess (IntPtr arrayPtr, int index) { 
     unsafe { 
      var array = (float*) IntPtr.Add (arrayPtr, index); // getting bits of a float 
      (*array) = (*array) + 2; 
     } 
    } 

    public static void FixedAccess (float[] array, int index) { 
     unsafe { 
      fixed (float* ptr = &array[index]) 
       (*ptr) = (*ptr) + 2; 
     } 
    } 

    public unsafe static void PtrAccess (float* ptr) { 
     (*ptr) = (*ptr) + 2; 
    } 

} 

而測試本身:

static int runs = 1000*1000*100; 
    public static void Print (string name, Stopwatch sw) { 
     Console.WriteLine ("{0}, items/sec = {1:N} \t {2}", sw.Elapsed, (runs/sw.ElapsedMilliseconds) * 1000, name); 
    } 

    static void Main (string[] args) { 
     var buffer = new float[1024*1024*100]; 
     var len = buffer.Length; 

     var sw = new Stopwatch(); 
     for (int i = 0; i < 1000; i++) { 
      Test.FixedAccess (buffer, 55); 
      Test.NormalAccess (buffer, 66); 
     } 

     Console.WriteLine ("Starting {0:N0} items", runs); 


     sw.Restart(); 
     for (int i = 0; i < runs; i++) 
      Test.NormalAccess (buffer, i % len); 
     sw.Stop(); 

     Print ("Normal access", sw); 

     sw.Restart(); 
     for (int i = 0; i < runs; i++) 
      Test.NormalRefAccess (ref buffer, i % len); 
     sw.Stop(); 

     Print ("Normal Ref access", sw); 

     sw.Restart(); 
     unsafe { 
      fixed (float* ptr = &buffer[0]) 
       for (int i = 0; i < runs; i++) { 
        Test.IntPtrAccess ((IntPtr) ptr, i % len); 
       } 
     } 
     sw.Stop(); 

     Print ("IntPtr access (fixed outside loop)", sw); 

     sw.Restart(); 
     unsafe { 
      fixed (float* ptr = &buffer[0]) 
       for (int i = 0; i < runs; i++) { 
        Test.IntPtrMisalignedAccess ((IntPtr) ptr, i % len); 
       } 
     } 
     sw.Stop(); 

     Print ("IntPtr Misaligned access (fixed outside loop)", sw); 

     sw.Restart(); 
     for (int i = 0; i < runs; i++) 
      Test.FixedAccess (buffer, i % len); 
     sw.Stop(); 

     Print ("Fixed access (fixed inside loop)", sw); 

     sw.Restart(); 
     unsafe { 
      fixed (float* ptr = &buffer[0]) { 
       for (int i = 0; i < runs; i++) { 
        Test.PtrAccess (ptr + (i % len)); 
       } 
      } 
     } 
     sw.Stop(); 

     Print ("float* access (fixed outside loop)", sw); 

     sw.Restart(); 
     unsafe { 
      for (int i = 0; i < runs; i++) { 
       fixed (float* ptr = &buffer[i % len]) { 
        Test.PtrAccess (ptr); 
       } 
      } 
     } 
     sw.Stop(); 

     Print ("float* access (fixed in loop)", sw); 

最後的結果:

調試模式

Starting 100,000,000 items 
00:00:01.0373583, items/sec = 96,432,000.00  Normal access 
00:00:00.8582307, items/sec = 116,550,000.00  Normal Ref access 
00:00:01.8822085, items/sec = 53,134,000.00  IntPtr access (fixed outside loop) 
00:00:10.5356369, items/sec = 9,492,000.00  IntPtr Misaligned access (fixed outside loop) 
00:00:01.6860701, items/sec = 59,311,000.00  Fixed access (fixed inside loop) 
00:00:00.7577868, items/sec = 132,100,000.00  float* access (fixed outside loop) 
00:00:01.0387792, items/sec = 96,339,000.00  float* access (fixed in loop) 

發佈模式

Starting 100,000,000 items 
00:00:00.7454832, items/sec = 134,228,000.00  Normal access 
00:00:00.6619090, items/sec = 151,285,000.00  Normal Ref access 
00:00:00.9859089, items/sec = 101,522,000.00  IntPtr access (fixed outside loop) 
00:00:10.1289018, items/sec = 9,873,000.00  IntPtr Misaligned access (fixed outside loop) 
00:00:00.7899355, items/sec = 126,742,000.00  Fixed access (fixed inside loop) 
00:00:00.5718507, items/sec = 175,131,000.00  float* access (fixed outside loop) 
00:00:00.6842333, items/sec = 146,198,000.00  float* access (fixed in loop)