Reordering
- 컴파일러가 성능향상을 위해서 코드의 실행 순서를 변경하는 것.
- 아래 코드에서, b + 1 을 하기 위해서 CPU CACHE에 b를 넣고 값을 증가시킨다.
- 그런데, a에다가 b의 값을 넣기 위해서는 a를 CPU CACHE에 올려야 하는데, 이때 b가 내려가게 된다.
- 이런 상황에서 성능 향상을 하기 위해 컴파일러가 최적화 시에 b를 내리지 않고 b + 1, b = 1을 둘 다 수행한 뒤에 a를 수행하도록 바꾼다.
- thread safe 한 상황에서는 괜찮으나, 그렇지 않은경우 a의 값을 정상 예측하기 힘들다.
- 이러한 최적화는 컴파일 시간, 실행시간 모두 발생할 수 있다.
void foo()
{
a = b + 1;
b = 1;
}
Reordering 해결법
void foo()
{
a = b + 1;
// 메모리에 fence를 치는 어셈 명령어를 준다. (원시적 방법)
// __asm { mfence }
std::atomic_thread_fence( std::memory_order_release );
b = 1;
}
Memory Order
std::atomic<int> x = 0;
std::atomic<int> y = 0;
void foo()
{
int n1 = y.load(std::memory_order_relaxed);
x.store(n1, std::memory_order_relaxed);
}
void goo()
{
int n2 = x.load(std::memory_order_relaxed);
y.store(100, std::memory_order_relaxed);
}
std::memory_order_relaxed
- 오버헤드가 가장 적다.
- atomic operation 만 보장.
- 실행순서는 변경될 수 있다. (아래 두 코드의 실행 순서가 변할 수 있다)
// int n1 = y.load(std::memory_order_relaxed);
// x.store(n1, std::memory_order_relaxed);
void foo()
{
a.store(100, std::memory_order_relaxed);
b.store(1, std::memory_order_release);
**}
void goo()
{
if (flag.load(std::memory_order_acquire) > 0)
{
// Acquire 이후에 아래 값을 읽는것은 보장한다.
assert(a.load(std::memory_order_relaxed) == 100);
}
}
std::memory_order_release, std::memory_order_acquire
- release 이전의 코드는 acquire 이후에 읽을 수 있다는 것을 보장.**
void main()
{
a.store(100, std::memory_order_seq_cst);
b.store(200, std::memory_order_seq_cst);
c.store(300); // 디폴트 옵션이 std::memory_order_seq_cst
}
std::memory_order_seq_cst
- 원자 연산과 순서를 보장한다.
- 해당 옵션이 있는 애들끼리는 순서를 보장.
- 오버헤드가 가장 크다.