2012-01-25 3 views
17

32 비트 시스템에서 실행 중이며 매우 빠르게 실행되는 다음 코드 스 니펫을 사용하여 긴 값을 찢을 수 있는지 확인할 수 있습니다. 나는 복식과 비슷한 때C에서 double을 시뮬레이트합니다.

 static void TestTearingLong() 
     { 
      System.Threading.Thread A = new System.Threading.Thread(ThreadA); 
      A.Start(); 

      System.Threading.Thread B = new System.Threading.Thread(ThreadB); 
      B.Start(); 
     } 

     static ulong s_x; 

     static void ThreadA() 
     { 
      int i = 0; 
      while (true) 
      { 
       s_x = (i & 1) == 0 ? 0x0L : 0xaaaabbbbccccddddL; 
       i++; 
      } 
     } 

     static void ThreadB() 
     { 
      while (true) 
      { 
       ulong x = s_x; 
       Debug.Assert(x == 0x0L || x == 0xaaaabbbbccccddddL); 
      } 
     } 

는하지만, 나는 어떤 찢어를 얻을 수 아니에요. 아무도 이유를 아나요? 내가 사양에서 알 수있는 한, float에 대한 할당 만이 원자 적입니다. 두 배로의 할당은 찢어 질 위험이 있습니다.

static double s_x; 

    static void TestTearingDouble() 
    { 
     System.Threading.Thread A = new System.Threading.Thread(ThreadA); 
     A.Start(); 

     System.Threading.Thread B = new System.Threading.Thread(ThreadB); 
     B.Start(); 
    } 

    static void ThreadA() 
    { 
     long i = 0; 

     while (true) 
     { 
      s_x = ((i & 1) == 0) ? 0.0 : double.MaxValue; 
      i++; 

      if (i % 10000000 == 0) 
      { 
       Console.Out.WriteLine("i = " + i); 
      } 
     } 
    } 

    static void ThreadB() 
    { 
     while (true) 
     { 
      double x = s_x; 

      System.Diagnostics.Debug.Assert(x == 0.0 || x == double.MaxValue); 
     } 
    } 
+4

어리석은 질문 - 무엇이 찢어지고 있습니까? – Oded

+0

int에 대한 연산은 다중 스레드에 의한 액세스와 관련하여 원자 적이어야합니다. 그럴 수는 없습니다. 찢어지고 두 개의 중간 값 (나쁜)이 혼합됩니다. 그는 복식이 원자 조작을 보장하지 않기 때문에 복식에서 왜 같은 것이 보이지 않는지 궁금해합니다. – hatchet

+13

@Oded : 32 비트 컴퓨터에서는 한 번에 32 비트 만 씁니다. 32 비트 컴퓨터에서 64 비트 값을 쓰고 동시에 두 개의 다른 스레드에서 같은 주소에 쓰는 경우 쓰기는 32 비트에서 수행되기 때문에 * 2 *가 아닌 * 4 개의 쓰기가 실제로 있습니다. 시간. 따라서 스레드가 경쟁 할 수 있으며 연기가 없어지면 변수는 한 스레드에서 쓰여진 상위 32 비트와 다른 스레드에서 쓰여진 하위 32 비트를 포함합니다. 따라서 한 스레드에는 0xDEADBEEF00000000을 쓰고 다른 스레드에는 0x00000000BAADF00D를 쓰고 메모리에는 0x0000000000000000으로 끝낼 수 있습니다. –

답변

10
static double s_x; 

당신이 배를 사용할 때 효과를 입증하기 훨씬 어렵습니다. CPU는 전용 명령을 사용하여 각각 FLD와 FSTP의 두 배를로드하고 저장합니다. 이 길면 일 때 훨씬 편리합니다. 32 비트 모드에서 64 비트 정수를로드/저장하는 단일 명령이 없기 때문입니다. 이것을 관찰하기 위해서는 변수의 주소가 잘못 정렬되어 cpu 캐시 라인 경계에 걸쳐 있어야합니다.

JIT 컴파일러는 사용 된 선언에서 결코 일어나지 않을 것입니다. JIT 컴파일러는 double이 적절하게 정렬되고 8의 배수 인 주소에 저장되도록합니다. 클래스의 필드 인 GC 할당 자만 저장할 수 있습니다 32 비트 모드에서 4로 정렬됩니다. 그러나 그건 헛소리입니다.

가장 좋은 방법은 의도적으로 포인터를 사용하여 이중을 잘못 정렬하는 것입니다.프로그램 클래스의 앞에 안전하지 않은을 넣고는 다음과 유사합니다 AllocCoTaskMem()가 정렬됩니다 위치를 정확하게 제어 할 수있는 방법이 없기 때문에

static double* s_x; 

    static void Main(string[] args) { 
     var mem = Marshal.AllocCoTaskMem(100); 
     s_x = (double*)((long)(mem) + 28); 
     TestTearingDouble(); 
    } 
ThreadA: 
      *s_x = ((i & 1) == 0) ? 0.0 : double.MaxValue; 
ThreadB: 
      double x = *s_x; 

이 여전히 좋은 정렬 불량 (도니는 다르게)을 보장하지 않습니다 cpu 캐시 라인의 시작에 상대적인 할당. 그리고 그것은 CPU 코어 (내 코어는 i5)의 캐시 연관성에 따라 다릅니다. 당신은 오프셋으로 땜질해야 할 것입니다, 나는 실험으로 가치 28을 얻었습니다. 값은 GC 힙 동작을 실제로 시뮬레이트하기 위해 4로 나눌 수 있지만 8로 나눌 수 없어야합니다. 캐시 라인을 넘어서서 어설 션을 트리거 할 때까지 8을 계속해서 값에 추가하십시오.

덜 인위적으로 만들려면 클래스의 필드에 double을 저장하고 가비지 수집기가 메모리에서 주변으로 이동하여 정렬되지 않도록하는 프로그램을 작성해야합니다. 을 보장하는 샘플 프로그램을 찾기가 어렵습니다.

이라는 프로그램에서 문제가되는 프로그램을 보여줍니다. 스레드 B의 Start() 메서드 호출을 주석 처리하고 스레드 A가 얼마나 빨리 실행되는지 확인합니다. cpu 코어간에 캐시 라인을 일관성있게 유지하는 데 드는 CPU 비용을보고 계십니다. 공유는 스레드가 동일한 변수에 액세스하기 때문에 여기에서 의도됩니다. 실수 공유는 스레드가 동일한 캐시 라인에 저장된 다른 변수에 액세스 할 때 발생합니다. 이것은 정렬이 중요한 이유입니다. 일부가 하나의 캐시 라인에 있고 그 일부가 다른 캐시 라인에있는 경우에만 이중에 대한 찢어짐을 관찰 할 수 있습니다.

+0

캐시 라인 경계 교차로 인해 찢어지는 현상을 이해할 수 없습니다. 나는 이것이 등록자의 크기보다 더 많은 공간을 차지하는 가치에 의해서만 발생했다고 생각했습니다. 조금 더 자세히 설명해 주시겠습니까? – Tudor

+0

@ Tudor - 완전히 다른 효과이며 레지스터 크기와 관련이 없습니다. 마지막 단락에 초점을 맞추십시오, cpu 캐시 동기화가 업데이트 단위로 캐시 라인을 갖는 방법을 기록하십시오. 한 줄에 걸치는 잘못 정렬 된 이중은 * 두 번의 갱신이 필요합니다. long은 두 번의 레지스터 쓰기가 필요한 것과 유사합니다. 어느 것이 다른 코어에서 실행되는 코드가 찢어짐을 관찰 할 수 있도록 충분한 시간이 필요합니다. –

11

이상한 소리는 CPU에 따라 다릅니다. 두 배가 인데, 찢어지지 않도록을 보장하지는 않지만 많은 현재 프로세서에서는 그렇지 않습니다. 이 상황에서 찢어지기를 원하면 AMD Sempron을 사용해보십시오.

편집 : 몇 년 전에 어려운 방법을 배웠습니다.

+0

이것은 부동 소수점 레지스터의 크기와 관련이 있습니까? – leppie

+0

TBH 나는 조금이라도 생각하지 못했고 결코 들여다 보지 못했다. 내 데몬 (모든 언어의 Free Pascal)은 같은 이미지에서 설정 한 많은 (어쩌면 100) 중 하나의 유일한 머신에서 터무니없는 결과를 만들어 내기 시작합니다. 주 스레드 및 GTK 생성 보조 스레드. 그렇다면 FPK에서 잠금 프리미티브가 없습니다 ... (간결, 불필요) –

+0

예, CPU의 MMX 또는 SSE 확장이 이와 관련이 있다면 의심하지 않습니다. – antiduh

0

뒷조사를하는, 내가 발견 한 몇 가지 흥미로운의 x86 아키텍처에서 부동 소수점 연산에 관한 읽기 : 80 비트 레지스터에

Wikipedia에 따르면, 86 부동 소수점 유닛 저장 부동 소수점 값 :

는 [...] 후속 x86 프로세서는 x86 명령어 세트의 x87 명령어 사실상 일체형 일부가 칩이 x87 기능 통합. ST (0)에서 ST (7)로 알려진 각 x87 레지스터는 80 비트 폭이며 IEEE 부동 소수점 표준 확장 정밀도 형식의 숫자를 저장합니다.

는 또한이 다른 SO 질문 관련 : 복식 64 비트 있지만, 그들은 원자 적 수술을하는 이유, Some floating point precision and numeric limits question

이 설명 할 수있다.

0

이 주제 및 코드 샘플의 가치는 무엇인지 여기에서 확인할 수 있습니다.

http://msdn.microsoft.com/en-us/magazine/cc817398.aspx

+0

그 기사는 이중에 관한 것이 아니라 오랫동안 이야기합니다. – Tudor

+0

동의. 실제로, 나는 질문에 게시 된 샘플 코드가 그 게시물 (더블 것들을 제외하고)에서 생각합니다. (나는 테스트 프로젝트에서 그것을 가지고 있었고 잠시 동안 잊어 버렸다). –