programing

x86에서 스왑 대 비교 및 스왑 잠금의 상대적 성능

telecom 2023. 10. 7. 09:24
반응형

x86에서 스왑 대 비교 및 스왑 잠금의 상대적 성능

두 가지 일반적인 잠금 관용구는 다음과 같습니다.

if (!atomic_swap(lockaddr, 1)) /* got the lock */

그리고:

if (!atomic_compare_and_swap(lockaddr, 0, val)) /* got the lock */

val단순히 상수이거나 자물쇠의 새로운 잠재적 소유자를 위한 식별자일 수 있습니다.

제가 알고 싶은 것은 x86(및 x86_64) 머신의 두 가지 성능 차이가 현저한지 여부입니다.개별 CPU 모델마다 답이 많이 다를 수 있기 때문에 상당히 광범위한 질문이라는 것을 알고 있지만, 그것이 제가 접근할 수 있는 몇 개의 CPU에 대한 벤치마크를 하는 것보다 SO에게 묻는 이유의 일부입니다.

atomic_swap(lockaddr, 1)이 xchgreg로 번역되고, mem 명령어와 atomic_compare_and_swap(lockaddr, 0, val)이 cmpxchg[8b|16b]로 번역된다고 가정합니다.

일부 리눅스 커널 개발자들은 잠금 접두사가 xchg와 같이 암시되지 않기 때문에 cmpxchgist가 더 빠르다고 생각합니다.따라서 유니프로세서나 멀티 스레드를 사용하거나 잠금이 필요하지 않은지 확인할 수 있는 경우에는 cmpxchg를 사용하는 것이 더 나을 수 있습니다.

하지만 당신의 컴파일러가 그것을 "lock cmpxchg"로 번역할 가능성이 있고, 그 경우에는 그것은 별로 중요하지 않습니다.또한 이 지침에 대한 지연 시간은 낮지만(잠금이 없는 1주기, 잠금이 있는 20주기), 사용하는 경우 두 스레드 간의 공통 동기화 변수(상당히 일반적인 것)를 사용하면 일부 추가 버스 주기가 적용되며, 이는 명령 지연 시간에 비해 영원히 지속됩니다.200 또는 500 cpu 사이클의 긴 캐시 스눕/동기화/멤 액세스/버스 잠금/무엇이든 이러한 것들은 완전히 가려질 가능성이 높습니다.

저는 이 인텔 문서를 찾았는데, 이 문서는 실무에 차이가 없다고 합니다.

http://software.intel.com/en-us/articles/implementing-scalable-atomic-locks-for-multi-core-intel-em64t-and-ia32-architectures/

하나의 일반적인 신화는 xchg 명령을 사용하는 잠금보다 cmpxchg 명령을 사용하는 잠금이 더 저렴하다는 것입니다.이것은 cmp가 먼저 통과하기 때문에 cmpxchg가 독점 모드로 잠금을 시도하지 않기 때문에 사용됩니다.그림 9는 cmpxchg가 xchg 명령어만큼 비싸다는 것을 보여줍니다.

x86에서 LOCK 접두사를 가진 명령어는 읽기-수정-쓰기 사이클로서 모든 메모리 작업을 수행합니다.즉, XCHG(암묵적 LOCK)와 LOCK CMPXCHG(모든 경우 비교에 실패하더라도)는 항상 캐시 라인에서 독점적인 잠금을 갖게 됩니다.결과는 기본적으로 성능에 차이가 없다는 것입니다.

이 모델에서는 동일한 잠금 장치에서 회전하는 많은 CPU가 많은 버스 오버헤드를 유발할 수 있습니다.이것은 스핀-락 루프에 PAUSE 명령이 포함되어야 하는 한 가지 이유입니다.일부 다른 아키텍처는 이를 위한 더 나은 작동 방식을 가지고 있습니다.

당신은 당신이 의도한 것이 아닌게 확실합니까?

 if (!atomic_load(lockaddr)) {
       if (!atomic_swap(lockaddr, val)) /* got the lock */

두 번째 거요?

테스트 및 테스트 및 잠금 설정(Wikipedia https://en.wikipedia.org/wiki/Test_and_test-and-set 참조)은 많은 플랫폼에서 매우 일반적인 최적화입니다.

비교 및 교환을 구현하는 방법에 따라 테스트 및 테스트 및 설정보다 빠를 수도 있고 느릴 수도 있습니다.

x86은 상대적으로 강력한 주문형 플랫폼이기 때문에 테스트 및 테스트 및 잠금 설정을 더 빠르게 할 수 있는 HW 최적화가 가능하지 않을 수 있습니다.

Bo Persson이 http://software.intel.com/en-us/articles/implementing-scalable-atomic-locks-for-multi-core-intel-em64t-and-ia32-architectures/ 에서 발견한 문서의 그림 8은 Test and Test and Set lock이 성능 면에서 우수함을 보여줍니다.

xchg 대 cmpxchg를 사용하여 잠금 해제

인텔 프로세서의 성능에 있어서는 동일하지만, 단순성을 위해서는 좀 더 쉽게 계산할 수 있는 방법을 제시해 주신 예에서 첫 번째 방법을 선호합니다.다를 .cmpxchg해로 할 수 수 .xchg.

오캄의 면도기 원리에 따르면 간단한 것이 더 좋습니다.

외에도 것xchg보다 강력합니다. 잠금을 위해 명시적으로 할당되지 않은 메모리 바이트에 액세스하지 않는 소프트웨어의 로직이 정확한지도 확인할 수 있습니다.따라서 올바르게 초기화된 동기화 변수를 사용하고 있는지 확인합니다.그 외에도 잠금 해제가 두 번 되지 않는 것을 확인할 수 있습니다.

일반 메모리 저장소를 사용하여 잠금 해제

시하는 작업을 일반 가)된 의견이 .mov즉 인 ) - (, )lock- - , xchg.

일반 메모리 저장소를 사용하여 잠금을 해제하는 접근 방식은 Peter Cordes가 권장한 것입니다. 자세한 내용은 아래 설명을 참조하십시오.

그러나 이 방법은 간단하고 직관적인 것처럼 보이기 때문에 잠금을 획득하고 해제하는 것 모두 버스 잠금 메모리 저장소에서 수행되는 구현이 있습니다.예를 들어, Windows 10의 LeaveCritical Section은 버스 잠금 저장소를 사용하여 단일 소켓 프로세서에서도 잠금을 해제합니다. NUMA(Non-Uniform-Memory-Access)가 있는 여러 물리적 프로세서에서는 이 문제가 더욱 중요합니다.

단일 소켓 CPU(Kaby Lake)에서 많은 메모리를 할당/재할당/사용 가능한 메모리 관리자에 대해 마이크로벤치마킹을 수행했습니다.경합이 없을 때, 즉 물리적 코어보다 스레드 수가 적을 때, 잠금 해제 시 테스트 완료 속도가 약 10% 느리지만 물리적 코어에서 스레드 수가 많을 때는 잠금 해제 시 테스트 완료 속도가 2% 빠릅니다.따라서 평균적으로 잠금 해제를 위한 일반 메모리 저장소가 잠금 메모리 저장소보다 성능이 뛰어납니다.

동기화 변수의 유효성을 검사하는 잠금 예제

동기화 변수의 데이터의 유효성을 검사하고 획득되지 않은 잠금을 해제하려는 시도를 포착하는 안전한 잠금 기능의 예(Delphi 프로그래밍 언어)를 참조하십시오.

const
  cLockAvailable = 107; // arbitrary constant, use any unique values that you like, I've chosen prime numbers
  cLockLocked    = 109;
  cLockFinished  = 113;

function AcquireLock(var Target: LONG): Boolean; 
var
  R: LONG;
begin
  R := InterlockedExchange(Target, cLockByteLocked);
  case R of
    cLockAvailable: Result := True; // we've got a value that indicates that the lock was available, so return True to the caller indicating that we have acquired the lock
    cLockByteLocked: Result := False; // we've got a value that indicates that the lock was already acquire by someone else, so return False to the caller indicating that we have failed to acquire the lock this time
      else
        begin
          raise Exception.Create('Serious application error - tried to acquire lock using a variable that has not been properly initialized');
        end;
    end;
end;

procedure ReleaseLock(var Target: LONG);
var
  R: LONG;
begin
  // As Peter Cordes pointed out (see comments below), releasing the lock doesn't have to be interlocked, just a normal store. Even for debugging we use normal load. However, Windows 10 uses locked release on LeaveCriticalSection.
  R := Target;
  Target := cLockAvailable;
  if R <> cLockByteLocked  then
  begin
    raise Exception.Create('Serious application error - tried to release a  lock that has not been actually locked');
  end;
end;

주요 애플리케이션은 다음과 같습니다.

var
  AreaLocked: LONG;
begin
  AreaLocked := cLockAvailable; // on program initialization, fill the default value

  .... 
 
 if AcquireLock(AreaLocked) then
 try
   // do something critical with the locked area
   ... 

 finally
   ReleaseLock(AreaLocked); 
 end;

....

  AreaLocked := cLockFinished; // on program termination, set the special value to catch probable cases when somebody will try to acquire the lock

end.

효율적인 일시 중지 기반 스핀 대기 루프

테스트, 테스트 및 세트

또한 "일시 중지" 기반 스핀-대기 루프의 작동 예로 다음 어셈블리 코드(아래 "일시 중지 기반 스핀-대기 루프의 어셈블리 코드 예" 섹션 참조)를 사용할 수도 있습니다.

이 코드는 Peter Cordes가 제안한 대로 리소스를 절약하기 위해 회전하는 동안 일반적인 메모리 부하를 사용합니다.이 기술을 "test, test-and-set"이라고 합니다.이 기술에 대한 자세한 내용은 https://stackoverflow.com/a/44916975/6910868 에서 확인할 수 있습니다.

반복 횟수

중지 스핀 하려고 하고,할 수 - 하고, 합니다를 pause5000 5000육 5000 5000에는 Windows API SwitchToThread()합니다를 합니다.5000 사이클의 이 값은 경험적입니다.그것은 제 시험에 근거한 것입니다.500에서 50000 사이의 값도 문제가 없는 것처럼 보이지만 일부 시나리오에서는 낮은 값이 더 나은 반면 다른 시나리오에서는 높은 값이 더 나은 것으로 나타납니다.앞 단락에서 알려드린 URL에서 일시정지 기반 스핀-웨이트 루프에 대해 자세히 읽어보실 수 있습니다.

일시 중지 명령의 사용 가능 여부

SSE2를 지원하는 프로세서에서만 이 코드를 사용할 수 있습니다. 호출하기 전에 해당 CPUID 비트를 확인해야 합니다.pause명령 - 그렇지 않으면 전력 낭비가 발생합니다.프로세서에서 사용 안 함pauseEnterCritical Section/LeaveCritical Section 또는 Sleep(0)과 같은 다른 수단을 사용한 다음 Sleep(1)과 같은 루프를 사용합니다.어떤 사람들은 64비트 프로세서에서는 SSE2를 확인하지 않고 다음을 확인할 수 있다고 말합니다.pause원래 AMD64 아키텍처가 인텔의 SSE와 SSE2를 핵심 명령으로 채택했기 때문에 명령이 구현됩니다. 그리고 실제로, 당신이 64비트 코드를 실행한다면, 당신은 이미 SSE2를 확실히 가지고 있고 따라서pause설명. 인텔은 이 사라질 수 응용 은 항상 통해 .그러나 인텔은 특정 기능을 존재에 의존하는 관행을 방지하고 향후 프로세서에서 특정 기능이 사라질 수 있으며 응용프로그램은 항상 CPUID를 통해 기능을 확인해야 한다고 명시적으로 언급합니다. 명령어를 ) 나 SSE은 64로예: Win64용 델파이)에서는 SSE2다도 없습니다.pause 날씬해요,.

일시정지 기반 스핀-웨이트 루프의 어셈블리 코드 예

// on entry rcx = address of the byte-lock
// on exit: al (eax) = old value of the byte at [rcx]
@Init:
   mov  edx, cLockByteLocked
   mov  r9d, 5000
   mov  eax, edx
   jmp  @FirstCompare
@DidntLock:
@NormalLoadLoop:
   dec  r9
   jz   @SwitchToThread // for static branch prediction, jump forward means "unlikely"
   pause
@FirstCompare:
   cmp  [rcx], al       // we are using faster, normal load to not consume the resources and only after it is ready, do once again interlocked exchange
   je   @NormalLoadLoop // for static branch prediction, jump backwards means "likely"
   lock xchg [rcx], al
   cmp  eax, edx        // 32-bit comparison is faster on newer processors like Xeon Phi or Cannonlake.
   je   @DidntLock
   jmp  @Finish
@SwitchToThread:
   push  rcx
   call  SwitchToThreadIfSupported
   pop   rcx
   jmp  @Init
@Finish:

언급URL : https://stackoverflow.com/questions/5339769/relative-performance-of-swap-vs-compare-and-swap-locks-on-x86

반응형