2011-12-13 3 views
12

고정 배열을 포함하는 안전하지 않은 관리되는 구조체에서 C# 내에서 고정 된 문을 사용하는 데 실제 비용이 얼마나 듭니다. 내가 관리되지 않는 구조체를 말하는 것이 아닙니다.고정 배열이 포함 된 관리되지 않는 안전하지 않은 구조체에 C# 고정 문이 오버 헤드가 무엇입니까?

특히, 아래의 'MultipleFixed'클래스에 표시된 패턴을 피할 수있는 이유가 있습니까? 단순히 0이 아닌 값으로 데이터를 고정시키는 비용 (= 고정 된 범위를 입력/종료 할 때 & 단일 플래그를 지우는 것과 비슷한 비용) 또는 가능한 경우 피할만큼 충분히 중요합니까?

물론이 수업은 질문을 설명하는 데 도움이되도록 작성되었습니다. 이것은이 데이터의 읽기/쓰기 성능이 중요한 XNA 게임의 높은 사용 데이터 구조를위한 것입니다. 따라서 배열을 수정하고 어디에서나 전달하면됩니다.하지만 차이는 없을 것입니다. 안전하지 않은 코드를 지원하지 않는 플랫폼에 함수 시그니처를 약간 더 이식 가능하게 유지하는 데 도움이되는 메소드에 fixed() 로컬을 유지하는 것을 선호합니다. (그래, 무슨 일이든 그것의 몇 가지 추가 꿀꿀 코드 만 ..) 또한

 

    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을 "호출"하는 횟수가 많을수록 악화됩니다). 그래서 성능이 정말로 중요한 경우 귀하의 블록을 최소화하려고합니다.

미안 해요, 난 그


세부

나는 또한이 시험 방법 (10)를 호출 일부 TestMore 방법을 추가 한 경우의 이유를 알고 고정/안전하지 않은 코드에 충분히 익숙하지 해요 시간 대신 2 대신 fixed 구조체에서 호출되는 여러 메서드에 대한보다 실제적인 시나리오를 제공하십시오.

I가 사용되는 코드 :

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

감사합니다 - 좋은 정보! 오버 헤드의 근본적인 원인이 무엇인지 궁금해하지만 좋은 성과를 얻는 것이 주요 목표입니다. –

+0

그래, 나는 "이유"에 대해 Skeet, Lippart 또는 Gravel에게 연기 할 것이다. 그러나 구조체의 크기를 가지고 놀면 런타임에서 구조체의 복사본을 각각 '고정 된'것으로 만드는 지 알 수 있습니다. 내 짐작은 피닝 작업이 전체 구조체를 복사한다는 것입니다. (참조 : http://www.dotnetperls.com/fixed-buffer) – ligos

+9

이 테스트는 정확하지 않습니다. 완전히 비합리적인 방법으로 고침을 사용하고 있습니다. 올바른 사용법은 한 번 수정하여 여러 번 작성하고 수정하지 않는 것입니다. – JakeSays

9

그건 내가 실제로 가지고있는 흥미로운 질문이었습니다.

내가 얻은 결과는 '고정 된'진술 그 자체보다는 성능 상실에 대한 약간 다른 이유를 제안합니다.

당신은 내가 실행 테스트 및 아래의 결과를 볼 수 있지만 다음과 같은 관측은 내가 그에서 그릴 수있다 :

  • 순수한 포인터로 '고정'사용의 성능 (X *)를 IntPtr하지 않고있다 관리되는 코드만큼이나 좋다. 해제 모드에서 심지어 더 나은 고정 자주 사용되지 않는 경우 - 여러 배열 값을 액세스하는 가장 performent 방법입니다. 디버그 모드에서 '고정'(루프 내부) 사용하면 큰 부정적인 성능 영향을 미치는
  • 릴리스 모드에서는 일반 배열 액세스 (FixedAccess 메서드)와 거의 비슷하게 작동합니다. 대해를 IntPtr 연산 (IntPtrAccess) 다음을 사용할 때 참조 타입 파라미터 값 'REF'를 이용
  • (플로트 [])이었다 일관 이상 또는 동등 확대됨 (양쪽 모드)
  • 디버그 모드 해제 방식 VS 상당한 성능 저하를 가지고 두 모드 모두 정상적인 배열 액세스보다 성능이 좋지 않습니다.
  • 오프셋을 사용하여 배열 값의 오프셋에 정렬되지 않으면 성능에 문제가 있습니다 (실제로 두 모드에서 모두 동일한 시간이 소요됨). 'float'에도 적용되지만 '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)