C++/volatile 키워드에 대해

C++ 에 있는 키워드 중에서 가장 사용 빈도가 낮은 키워드라고 알려져 있다.

의미
volatile로 선언된 변수는 외부에서 언제든지 값이 바뀔 수 있음을 의미.
따라서 컴파일러는 변수에 최적화를 수행하지 않는다. 즉, 레지스터를 사용하지 않고 매번 메모리에서 읽는다
이를 이해하려면 C/C++ 컴파일러가 어떤 최적화를 하는지 알아야 한다.

C/C++의 최적화
영리한 컴파일러라면 코드에서 같은 주소의 메모리를 여러 번 읽고, 마지막에 한번 쓰는 걸 발견하면 속도를 향상 시키기 위해 최종적으로 불필요한 읽기를 제거하고 마지막에 쓰기만 수행할 것이다. 일반적인 코드라면 이런 최적화를 통해 수행 속도 면에서 이득을 보게 된다.

하지만 임베디드 장비처럼 메모리 주소에 연결된 하드웨어 값을 읽어온다면 이야기가 달라진다(Memory-mapped I/O). 항상 값이 변경될 수 있기 때문에 주소가 같다는 이유 만으로 메모리 읽기를 넘기면 안된다. 이런 경우 유용하게 사용될 수 있는 키워드가 volatile이다.

멀티스레드 환경도 마찬가지로 수행 도중 다른 스레드가 전역 변수 값을 임의로 변경할 수 있다. 하지만 컴파일러가 코드를 생성할 때는 다른 스레드의 존재 여부를 모르므로 변수 값을 매번 새로 읽어오지 않는다. 따라서 여러 스레드가 공유하는 전역 변수라면 volatile로 선언해 주거나 명시적으로 락을 잡아야 한다.

volatile이 사용되는 경우 3가지 - 가시성
1) MMIO
2) 인터럽트 서비스 루틴
3) 멀티스레드 환경

세 가지 모두 공통점은 *현재 프로그램의 수행과 상관없이* 외부 요인에 의해 변수 값을 변경할 수 있다는 점이다.
이처럼 레지스터를  재사용하지 않고 매번 메모리를 참조하는 것을 가시성(visibility)이 보장된다고 말한다.

재배치 Reordering
volatile은 표준에서 키워드와 메모리 모델에 대해 명확한 정의를 내리지 않았기 때문에 컴파일러마다 구현에 차이가 있다. MSVC는 volatile에 가시성 뿐만 아니라 재배치 Reordering 문제에 대한 해결책도 추가하였다.

재배치는 컴파일러가 메모리 접근 속도 향상, 파이프라인 활용 등 최적화 목적으로 프로그램 명령의 위치를 바꾸는 것을 말하는데, 코드 상에 a = 1; b = 1; c = 1;라고 작성했다고 해서 반드시 abc 순서로 메모리에 쓰지 않을 수도 있다는 의미다. 캐시 상황 등에 따라 속도 향상을 볼 수 있다면 컴파일러는 a,c를 먼저 쓰고 b를 쓰는 순서가 바꿀 수 있는 것이다.

volatile을 사용하면 컴파일러가 수행하는 재배치에 제약을 준다.

volatile은 단일 CPU 환경에서 재배치 문제는 해결해 주지만 멀티 CPU의 재배치에 대해서는 완전한 대안을 제공해주지 못한다(2006년 자료까지는. 지금은?). 또한 read-modify-write를 멀티스레드에서 원자적으로 수행하는 것을 volatile로 선언하는 것 만으로는 할 수 없다.

유용성과 한계를 충분히 인지하고 volatile을 사용하자.


이 글에는 0 개의 댓글이 있습니다.