레이블이 volatile인 게시물을 표시합니다. 모든 게시물 표시
레이블이 volatile인 게시물을 표시합니다. 모든 게시물 표시

2017-11-20

[C++] Visual C++의 volatile

 지난번 글에서 말했듯이 C++ 11 이전에는 메모리 접근의 순서를 보장할 수 있는 표준 방법이 없었다. 따라서 플랫폼에 따라 다른 코드를 사용해야 했다. x86은 mfence, ARM이라면 DMB 인스트럭션을 인라인 어셈블리를 사용하여 집어넣거나, gcc의 __sync_synchronize 나 Visual C++의 MemoryBarrier 매크로를 사용하는 방법이 일반적이다. 여기에 Visual C++은 volatile에 추가적인 제약을 거는 방법으로 메모리 접근 순서를 보장하는 방법을 마련하였다.

 Visual C++의 컴파일 옵션에서는 volatile의 동작을 2가지 중 하나로 선택할 수 있다. /volatile:iso로 컴파일하면, iso 표준대로 메모리 접근 순서와 상관없이 as-if rule에 의해 코드가 최적화되어 사라지는 것만을 방지한다. 하지만 /volatile:ms로 컴파일하면 iso 표준에서 규정하는 제약에 추가적으로 volatile object에 접근하는 것이 load-acquire, store-release semantic을 따른다. 즉, 간략히 말하면 store는 뒤에 있는 load가 실행된 다음에 실행될 수 있지만, 다른 메모리 접근의 순서는 순서대로 실행되는 게 보장된다.

 컴파일 때 아무 옵션을 안 주면 x86에서는 /volatile:ms가 기본값이다. 사실 x86 CPU는 load-acquire, store-release semantic을 따르기 때문에 volatile object에 접근하는 코드는 컴파일 타임에 순서를 바꾸지 않겠다는 것이다.
  ARM에서는 /volatile:iso가 기본값이다. 따라서 /volatile:ms를 이용하여 컴파일하면 DMB 인스트럭션을 이용하여 CPU가 순서를 바꿔 실행하는 것을 막기 때문에 성능이 떨어진다. 하지만 x86에서 테스트 된 코드를 그대로 사용할 수 있다.

2017-11-19

[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라고 한다.

2017-11-18

[C++] volatile

 volatile은 C++에서 가장 오해받고 있는 키워드 중 하나일 것이다. 오해받는 이유는 크게 2개라고 생각한다. 첫 번째로 C++의 volatile이 Java나 C# 등 다른 언어에서 말하는 volatile과 다르기 때문이고, 두 번째로 C++의 volatile은 CPU와 컴파일러가 어떻게 동작하는지 알지 않으면 이해하기 어려운 비직관적인 기능이기 때문이라고 생각한다.

 Java나 C# 등 다른 언어에서 volatile은 다른 스레드에서 visibility를 보장해주기 위해 사용된다. 따라서 변수의 접근이 리오더링 되는 것을 막고, 언제나 최신 값을 유지하는 것을 보장해준다. 특히나 멀티 스레드 프로그래밍을 한다면 누구나 한 번쯤 읽어본다는 고전 명작 The Art of Multiprocessor Programming에서 그 예제 코드가 Java로 돼 있기 때문에 C++에서도 volatile 키워드가 같은 의미를 가진다고 생각하는 사람이 많다.

 하지만 C++에서 volatile은 멀티 스레드와 아무런 관련이 없다. 이는 과거 C 표준에 스레드와 관련된 내용이 없던 시절에 추가된 이후로 지금까지 스펙이 변경되지 않았기 때문이다. 그래서 C++의 volatile은 싱글 스레드 코드에서 변수의 접근이 최적화되어 사라지는 것을 막을 뿐이다. 예를 들어 int a가 있을 때 a += 1을 반복하는 코드가 3회 반복될 때, 이 중간에 a에 접근하는 코드가 없으면, 이 코드는 a += 3으로 최적화될 수 있다. 만약 중간에 a에 접근하는 코드가 있어도, 매번 a를 메모리에서 접근하지 않고, a를 레지스터에 집어넣어 최적화할 수 있다. 하지만 a의 타입이 volatile int였다면, 이와 같은 최적화는 할 수 없고 a를 메모리에서 읽어 1을 더하여 메모리에 쓰는 코드가 3번 들어가야 한다.
 하지만 리오더링 되는 것을 막지 않는다. 따라서 의존성이 없는 두 변수가 volatile로 선언돼 있을 때 이 두 변수 사이에 읽기/쓰기는 다른 순서로 실행될 수 있다. 아래의 코드가 volatile이 리오더링되는 것을 막지 않기 때문에 C++이 volatile을 사용했을 때 다른 언어와 다르게 동작하는 대표적인 예제다.
 위의 코드에서 thread1_mainthread2_main이 다른 스레드에서 동시에 실행될 때 코드가 쓰여 있는 순서대로 실행된다면 b가 1이고 a가 0인 순간이 올 수 없기 때문에, 절대로 assert에 걸리지 않는다. 하지만 앞에서 말했듯이 C++의 volatile은 리오더링을 막지 않기 때문에 thread1_mainb = 1a = 1보다 먼저 실행될 수 있어서 b는 1이지만 a는 0인 순간이 올 수 있고 assert에 걸릴 수 있다.