programing

휘발성이 높은가요?

goodjava 2023. 1. 15. 16:55

휘발성이 높은가요?

휘발성, 특히 "원자성 명령과의 상호작용" 섹션의 구현에 대해 컴파일러 라이터용 JSR-133 쿡북을 읽은 후 휘발성 변수를 업데이트하지 않고 읽는 것은 LoadLoad 또는 LoadStore 장벽이 필요하다고 생각합니다.페이지 아래쪽에 Load Load 및 Load Store가 X86 CPU에서 사실상 no-ops임을 알 수 있습니다.즉, 휘발성 읽기 작업은 x86에서 명시적인 캐시 비활성화 없이 수행될 수 있으며 일반 변수 읽기만큼 빠릅니다(휘발성의 순서 변경 제약 조건과는 무관함).

제가 이걸 제대로 이해하지 못한 것 같아요.누가 날 좀 깨우쳐줄래요?

편집: 멀티프로세서 환경에 차이가 있는지 궁금합니다.단일 CPU 시스템에서는 John V.의 설명처럼 CPU가 자체 스레드 캐시를 볼 수 있지만, 다중 CPU 시스템에서는 CPU에 대해 이 구성 옵션이 부족하고 메인 메모리가 히트해야 하므로 다중 CPU 시스템에서 휘발성이 느려집니다.

PS: 자세한 내용을 알아보러 가는 길에 다음과 같은 훌륭한 기사를 접하게 되었습니다.이 질문은 다른 사람에게 흥미로울 수 있기 때문에, 여기서 링크를 공유하겠습니다.

인텔에서는, 경합이 없는 휘발성 판독치는 꽤 싸다.다음과 같은 간단한 경우를 고려한다면:

public static long l;

public static void run() {        
    if (l == -1)
        System.exit(-1);

    if (l == -2)
        System.exit(-1);
}

Java 7의 어셈블리 코드 인쇄 기능을 사용하면 실행 방법은 다음과 같습니다.

# {method} 'run2' '()V' in 'Test2'
#           [sp+0x10]  (sp of caller)
0xb396ce80: mov    %eax,-0x3000(%esp)
0xb396ce87: push   %ebp
0xb396ce88: sub    $0x8,%esp          ;*synchronization entry
                                    ; - Test2::run2@-1 (line 33)
0xb396ce8e: mov    $0xffffffff,%ecx
0xb396ce93: mov    $0xffffffff,%ebx
0xb396ce98: mov    $0x6fa2b2f0,%esi   ;   {oop('Test2')}
0xb396ce9d: mov    0x150(%esi),%ebp
0xb396cea3: mov    0x154(%esi),%edi   ;*getstatic l
                                    ; - Test2::run@0 (line 33)
0xb396cea9: cmp    %ecx,%ebp
0xb396ceab: jne    0xb396ceaf
0xb396cead: cmp    %ebx,%edi
0xb396ceaf: je     0xb396cece         ;*getstatic l
                                    ; - Test2::run@14 (line 37)
0xb396ceb1: mov    $0xfffffffe,%ecx
0xb396ceb6: mov    $0xffffffff,%ebx
0xb396cebb: cmp    %ecx,%ebp
0xb396cebd: jne    0xb396cec1
0xb396cebf: cmp    %ebx,%edi
0xb396cec1: je     0xb396ceeb         ;*return
                                    ; - Test2::run@28 (line 40)
0xb396cec3: add    $0x8,%esp
0xb396cec6: pop    %ebp
0xb396cec7: test   %eax,0xb7732000    ;   {poll_return}
;... lines removed

getstatic에 대한 2가지 참조를 살펴보면 첫 번째 참조는 메모리로부터의 부하를 수반하고 두 번째 참조는 값이 이미 로드되어 있는 레지스터에서 재사용되기 때문에 부하를 건너뜁니다(길이 64비트로 32비트 노트북에서는 2개의 레지스터를 사용합니다).

l변수를 휘발성으로 하면 어셈블리가 달라집니다.

# {method} 'run2' '()V' in 'Test2'
#           [sp+0x10]  (sp of caller)
0xb3ab9340: mov    %eax,-0x3000(%esp)
0xb3ab9347: push   %ebp
0xb3ab9348: sub    $0x8,%esp          ;*synchronization entry
                                    ; - Test2::run2@-1 (line 32)
0xb3ab934e: mov    $0xffffffff,%ecx
0xb3ab9353: mov    $0xffffffff,%ebx
0xb3ab9358: mov    $0x150,%ebp
0xb3ab935d: movsd  0x6fb7b2f0(%ebp),%xmm0  ;   {oop('Test2')}
0xb3ab9365: movd   %xmm0,%eax
0xb3ab9369: psrlq  $0x20,%xmm0
0xb3ab936e: movd   %xmm0,%edx         ;*getstatic l
                                    ; - Test2::run@0 (line 32)
0xb3ab9372: cmp    %ecx,%eax
0xb3ab9374: jne    0xb3ab9378
0xb3ab9376: cmp    %ebx,%edx
0xb3ab9378: je     0xb3ab93ac
0xb3ab937a: mov    $0xfffffffe,%ecx
0xb3ab937f: mov    $0xffffffff,%ebx
0xb3ab9384: movsd  0x6fb7b2f0(%ebp),%xmm0  ;   {oop('Test2')}
0xb3ab938c: movd   %xmm0,%ebp
0xb3ab9390: psrlq  $0x20,%xmm0
0xb3ab9395: movd   %xmm0,%edi         ;*getstatic l
                                    ; - Test2::run@14 (line 36)
0xb3ab9399: cmp    %ecx,%ebp
0xb3ab939b: jne    0xb3ab939f
0xb3ab939d: cmp    %ebx,%edi
0xb3ab939f: je     0xb3ab93ba         ;*return
;... lines removed

이 경우 변수 l에 대한 getstatic 참조는 모두 메모리로부터의 부하를 수반합니다.즉, 값은 여러 개의 휘발성 읽기에 걸쳐 레지스터에 유지할 수 없습니다. 것을 , 이로부터 MMX 레지스터에 .movsd 0x6fb7b2f0(%ebp),%xmm0읽기 조작을 단일 명령으로 합니다(앞의 예에서는 64비트 값은 보통 32비트 시스템에서 32비트 읽기를 2개 필요로 합니다).

따라서 휘발성 읽기의 전체 비용은 메모리 부하와 거의 같으며 L1 캐시 액세스만큼 저렴할 수 있습니다.그러나 다른 코어가 휘발성 변수에 쓰고 있는 경우 캐시 라인은 비활성화되며 메인 메모리 또는 L3 캐시 액세스가 필요할 수 있습니다.실제 비용은 CPU 아키텍처에 따라 크게 달라집니다.Intel과 AMD의 캐시 일관성 프로토콜은 서로 다릅니다.

일반적으로 대부분의 최신 프로세서에서는 휘발성 부하가 일반 부하와 비슷합니다.휘발성 저장소는 montior-enter/monitor-exit 시간의 약 1/3입니다.이는 캐시에 일관성이 있는 시스템에서 볼 수 있습니다.

OP의 질문에 답하자면 휘발성 쓰기는 비용이 많이 들지만 읽기는 그렇지 않습니다.

즉, 휘발성 읽기 작업은 x86에서 명시적인 캐시 비활성화 없이 수행될 수 있으며 일반 변수 읽기만큼 빠릅니다(휘발성의 순서 변경 제약에 관계없이).

네, 경우에 따라서는 필드를 검증할 때 CPU가 메인메모리에 히트하지 않고 다른 스레드캐시를 감시하여 값을 얻을 수 있습니다(매우 일반적인 설명).

단, 여러 스레드에 의해 접근되는 필드가 있는 경우 이를 AtomicReference로 랩해야 한다는 Neil의 제안에 찬성합니다.AtomicReference는 읽기/쓰기 처리량과 거의 동일한 스루풋을 실행하지만 여러 스레드에 의해 필드에 액세스 및 수정되는 것이 보다 명확합니다.

OP의 편집에 응답하려면 편집:

캐시 일관성은 다소 복잡한 프로토콜이지만 간단히 말해서 CPU는 메인 메모리에 연결된 공통 캐시 라인을 공유합니다.CPU가 메모리를 로드하고 다른 CPU가 없는 경우 CPU는 최신 값으로 간주합니다.다른 CPU가 같은 메모리 위치를 로드하려고 하면 이미 로드된 CPU는 이를 인식하고 실제로 캐시된 참조를 요구 CPU에 공유합니다.이때 요구 CPU는 해당 메모리의 복사본을 CPU 캐시에 저장합니다(참조를 위해 메인 메모리를 찾을 필요가 없습니다).

더 많은 프로토콜이 관련되어 있지만 이것은 무슨 일이 일어나고 있는지 알 수 있습니다.또, 복수의 프로세서가 없는 경우, 복수의 프로세서를 사용하는 경우보다, 휘발성의 읽기/기입이 고속이 됩니다.실제로 단일 CPU에서 여러 CPU에서 동시에 더 빠르게 실행되는 응용 프로그램이 있습니다.

Java Memory Model(JSR 133의 Java 5+에 대해 정의된 바와 같이)의 표현에 따르면 읽기 또는 쓰기 동작은volatilevariable은 동일한 변수 상의 다른 모든 연산에 대해 operations-before 관계를 만듭니다.이는 컴파일러와 JIT가 스레드 내의 명령 순서를 변경하거나 로컬 캐시 내에서만 작업을 수행하는 것과 같은 특정 최적화를 피할 수 없다는 것을 의미합니다.

일부 최적화를 이용할 수 없기 때문에, 결과 코드는 아마도 그리 많지 않지만, 반드시 더 느릴 것이다.

그렇다고 변수를 만들어서는 안 된다.volatile외부 여러 스레드로부터 액세스 할 수 있는 경우를 제외하고,synchronized블록.그래도 휘발성이 가장 좋은 선택인지 아닌지를 고려해야 합니다.synchronized,AtomicReference그리고 그 친구들, 명시적인 사람들은Lock클래스 등

휘발성 변수에 액세스하는 것은 동기화된 블록에서 일반 변수에 대한 액세스를 래핑하는 것과 많은 점에서 유사합니다.예를 들어 휘발성 변수에 액세스하면 CPU가 액세스 전후의 명령 순서를 변경할 수 없게 되어 일반적으로 실행 속도가 느려집니다(다만, 어느 정도라고는 말할 수 없습니다.

보다 일반적으로 멀티프로세서 시스템에서는 휘발성 변수에 대한 접근이 패널티 없이 이루어지는지 알 수 없습니다.프로세서 A의 기입이 프로세서 B의 판독에 동기화되는 것을 보증하는 방법이 있을 것입니다.

언급URL : https://stackoverflow.com/questions/4633866/is-volatile-expensive