[C++] memory barrier - 메모리 접근의 순서 보장하기

지난번 글에서 말했듯이 C++의 volatile object에 대해서 메모리 접근 순서를 보장하지 않지만, 메모리 접근 순서를 보장한다고 잘못 아는 사람이 많이 있다. 이는 지난번에 글에서 말했듯이 Java나 C#으로 멀티 스레드 프로그래밍을 배운 사람들이 잘못 알기 때문이기도 하지만 C와 C++로 멀티 스레드 프로그래밍을 배운 사람들도 잘못 아는 경우가 많다. 그런 경우는 보통 아래와 같은 착각을 하기 때문이다.

Access to volatile objects are evaluated strictly according to the rules of the abstract machine. - C++14 intro.execution 1.9.8.1

C++ 표준에 적혀있는 위의 문장에 따르면 volatile object에 접근이 엄격하게 실행된다고 적혀있다. 이 문장을 잘못 이해해서 엄격한 순서로 실행된다고 받아들이는 사람들이 있다. 하지만 이는 그저 volatile object에 접근하는 코드는 as-if rule에 의해 최적화되지 못한다는 것으로 메모리에 접근해야 할 코드를 최적화해서 없애거나, 레지스터 등을 이용해서 최적화하지 못한다는 것이다. 그보다 중요한 것은 abstract machine의 규칙을 따른다는 것이다.

여기서 말하는 abstract machine은 다른 구현체에서 같은 동작을 보장하기 위해 C++ 표준이 기술한 가상의 기계를 의미한다. 근데 이 abstract machine은 의존성이 없는 다른 메모리 영역에 접근하는 것에 대해 순서를 보장하지 않는다. 이는 컴파일러가 as-if rule에 따라 최적화할 여지를 남겨두기 위해 서기도 하지만, 실제로 CPU가 실행 시간에 메모리 접근을 재배치할 수 있기 때문이다. 그래서 실제로 어셈블리의 순서대로 실행될지 알 수 없다.

물론 모든 인스트럭션이 재배치되는 것은 아니고 CPU마다 자신이 재배치하여 실행할 수 있는 조합이 있다. 예를 들어 가장 많이 사용되는 X86의 경우 메모리에 저장하는 인스트럭션이 메모리에서 값을 읽는 인스트럭션 뒤로 가는 것만 허용하고, 저장-저장, 읽기-읽기의 순서와 메모리를 읽는 인스트럭션이 뒤에 오는 저장 인스트럭션보다 먼저 실행될 것은 보장해준다. 하지만 이 동작에 의존하면 ARM이나 PowerPC 같이 모든 메모리 접근 순서가 보장되지 않는 CPU에서는 의도하지 않은 동작을 할 수 있다.

그렇다면 순서를 보장하기 위해서는 어떻게 해야 할까? C++11이 들어오기 전에는 표준에 스레드와 관련된 내용이 없었다. 이 말은 다른 말로 C++의 abstract machine은 싱글 스레드로 가정하였다는 것이고, 당시에는 의존성 없는 메모리에 접근하는 순서가 중요하지 않았다. 그래서 당시 표준에서는 메모리 접근 순서를 보장하는 방법도 존재하지 않았고, 자신이 실행할 플랫폼에서 지원하는 방법을 사용해야 했다. 하지만 C++11에서는 멀티 스레드를 지원하도록 변하면서 fence를 칠 방법이 표준에 들어왔다.

C++11에서 지원하는 std::atomic_thread_fence는 x86의 경우 mfence 계열의 인스트럭션으로 번역되고, ARM의 경우 DMB 같은 인스트럭션으로 번역된다. 이런 메모리 순서를 보장하는 특별한 instruction을 memory barrier 혹은 memory fence라고 한다.

댓글

이 블로그의 인기 게시물

USB 2.0 케이블의 내부 구조

[C++] enum class - 안전하고 쓰기 쉬운 enum

Log Aggregator 비교 - Scribe, Flume, Fluentd, logstash

[Web] SpeechSynthesis - TTS API

[Python] cache 데코레이터로 최적화하기