동시성 프로그램에서 프로그래머는 서로 다른 프로세스 또는 스레드 간의 데이터 동기화에 특별한 주의를 기울입니다. 특히 여러 스레드가 동일한 변수를 동시에 수정할 경우 데이터가 올바르게 수정되도록 안정적인 동기화 또는 기타 조치를 취해야 합니다. 여기서 요점은 다음과 같습니다. 명령이 실행되는 순서를 가정하지 마십시오. 서로 다른 스레드 간의 명령이 실행되는 순서를 예측할 수 없습니다.
그러나 단일 스레드 프로그램에서는 일반적으로 명령이 순차적으로 실행된다고 가정하기 쉽습니다. 그렇지 않으면 프로그램에 어떤 끔찍한 변화가 일어날지 상상할 수 있습니다. 이상적인 모델은 다양한 명령이 실행되는 순서가 고유하고 순서가 있다는 것입니다. 이 순서는 프로세서나 기타 요인에 관계없이 코드에 기록되는 순서입니다. von Neumann 시스템을 기반으로 한 모델입니다. 물론 이 가정은 그 자체로 타당하며 실제로는 비정상적으로 거의 발생하지 않지만 실제로는 너무 비효율적이기 때문에 현대의 다중 프로세서 아키텍처에서는 이 모델을 채택하지 않습니다. 컴파일 최적화 및 CPU 파이프라인에서는 거의 모두 명령 재정렬과 관련됩니다.
컴파일 시간 재정렬
일반적인 컴파일 시간 재정렬은 프로그램 의미를 변경하지 않고 레지스터 읽기 및 저장 횟수를 최대한 줄이고 레지스터에 저장된 값을 완전히 재사용하도록 명령 순서를 조정하는 것입니다.
첫 번째 명령어가 값을 계산하여 변수 A에 할당하고 이를 레지스터에 저장한다고 가정합니다. 두 번째 명령어는 A와 관련이 없지만 레지스터를 점유해야 합니다(A가 있는 레지스터를 차지한다고 가정). 명령어는 A 값을 사용하며 두 번째 명령어와는 아무 관련이 없습니다. 그런 다음 순차 일관성 모델에 따르면 첫 번째 명령어가 실행된 후 A가 레지스터에 저장되고 두 번째 명령어가 실행될 때 A가 더 이상 존재하지 않으며 세 번째 명령어가 실행될 때 A가 다시 레지스터로 읽혀집니다. 이 과정에서 A의 값은 변경되지 않았습니다. 일반적으로 컴파일러는 두 번째 명령어와 세 번째 명령어의 위치를 바꿔 A가 첫 번째 명령어 끝의 레지스터에 존재하도록 하고, 그런 다음 A의 값을 레지스터에서 직접 읽을 수 있어 반복 읽기의 오버헤드가 줄어듭니다.
파이프라인 재정렬의 중요성
최신 CPU는 거의 모두 파이프라인 메커니즘을 사용하여 명령 처리 속도를 높입니다. 일반적으로 명령을 처리하려면 여러 CPU 클록 사이클이 필요하며 파이프라인의 병렬 실행을 통해 특정 클록 사이클에서 여러 명령을 실행할 수 있습니다. 방법은 간단하게 설명되어 있습니다. 읽기, 주소 지정, 구문 분석, 실행 및 기타 단계와 같은 실행 주기는 다양한 구성 요소에서 처리됩니다. 동시에 실행 단위 EU에서는 기능 단위가 덧셈 구성 요소, 곱셈 구성 요소와 같은 다양한 구성 요소로 나뉩니다. , 로드 구성 요소, 저장 요소 등은 다양한 계산의 병렬 실행을 더욱 실현할 수 있습니다.
파이프라인 아키텍처는 명령이 순차 모델에서 고려되는 것이 아니라 병렬로 실행되어야 함을 나타냅니다. 재정렬은 파이프라인을 최대한 활용하여 수퍼스칼라 효과를 얻는 데 도움이 됩니다.
질서를 유지하라
명령어가 반드시 작성한 순서대로 실행되는 것은 아니지만 단일 스레드 환경에서 명령어 실행의 최종 효과는 순차적 실행의 효과와 일치해야 한다는 점에는 의심의 여지가 없습니다. 그렇지 않으면 이 최적화가 의미를 잃게 됩니다.
일반적으로 명령어 재정렬이 컴파일 타임에 수행되든 런타임에 수행되든 위의 원칙은 충족됩니다.
Java 스토리지 모델의 재정렬
JMM(Java Memory Model)에서 재정렬은 특히 동시 프로그래밍에서 매우 중요한 섹션입니다. JMM은 작업 B를 수행하는 스레드가 작업 A를 수행하는 결과를 관찰하도록 하려면 사전 발생 규칙을 통해 순차적 실행 의미를 보장합니다. 그렇지 않으면 JVM이 임의 수행을 수행할 수 있습니다. 프로그램 성능을 향상시키기 위해 작업을 정렬합니다.
휘발성 키워드는 휘발성에 대한 작업이 모두 메인 메모리에 있고 메인 메모리가 모든 스레드에 의해 공유되기 때문에 변수의 가시성을 보장할 수 있습니다. 여기서 대가는 성능이 희생되고 레지스터나 캐시가 글로벌이 아니기 때문에 사용할 수 없다는 것입니다. , 가시성을 보장할 수 없으며 더티 읽기가 발생할 수 있습니다.
휘발성의 또 다른 기능은 재정렬을 방지하는 것입니다. 휘발성 변수에 대한 작업 지침은 재정렬되지 않습니다. 재정렬하면 가시성 문제가 발생할 수 있기 때문입니다.
가시성 보장 측면에서 잠금(명시적 잠금, 객체 잠금 포함)과 원자 변수 읽기 및 쓰기는 변수의 가시성을 보장할 수 있습니다. 그러나 구현 방법은 약간 다릅니다. 예를 들어 동기화 잠금은 잠금이 해제될 때 캐시를 새로 고치기 위해 메모리에서 데이터를 다시 읽도록 합니다. 데이터는 표시되는 반면 휘발성 변수는 단순히 메모리를 읽고 씁니다.