[英]Is this kind of optimization a compiler bug or not?
Declarations: I use vs 2010/vs 2013, and clang 3.4 prebuilt binary. 声明:我使用vs 2010 / vs 2013和clang 3.4预先构建的二进制文件。
I've found a bug in our production code. 我在生产代码中发现了一个错误。 I minimize the reproduce code to the following: 我将重现代码最小化为以下内容:
#include <windows.h>
#include <process.h>
#include <stdio.h>
using namespace std;
bool s_begin_init = false;
bool s_init_done = false;
void thread_proc(void * arg)
{
DWORD tid = GetCurrentThreadId();
printf("Begin Thread %2d, TID=%u\n", reinterpret_cast<int>(arg), tid);
if (!s_begin_init)
{
s_begin_init = true;
Sleep(20);
s_init_done = true;
}
else
{
while(!s_init_done) { ; }
}
printf("End Thread %2d, TID=%u\n", reinterpret_cast<int>(arg), tid);
}
int main(int argc, char *argv[])
{
argc = argc ; argv = argv ;
for(int i = 0; i < 30; ++i)
{
_beginthread(thread_proc, 0, reinterpret_cast<void*>(i));
}
getchar();
return 0;
}
To compile and run the code: cl /O2 /Zi /Favc.asm vc_O2_bug.cpp && vc_O2_bug.exe 编译并运行代码:cl / O2 / Zi /Favc.asm vc_O2_bug.cpp && vc_O2_bug.exe
Some of the threads are busying in the while loop. 一些线程正在while循环中忙碌。 By checking the produced assembly code, I found the assembly code of 通过检查产生的汇编代码,我找到了
while(!s_init_done) {; while(!s_init_done){; } }
is: 是:
; Line 19
mov al, BYTE PTR ?s_init_done@@3_NA ; s_init_done
$LL2@thread_pro:
; Line 21
test al, al
je SHORT $LL2@thread_pro
; Line 23
It's obvious that when use -O2 optimization flag, VC copy the s_init_done to al register, and repeatedly test the al register. 显然,当使用-O2优化标志时,VC将s_init_done复制到al寄存器,并反复测试al寄存器。
I then use the clang-cl.exe compiler driver to test the code. 然后,我使用clang-cl.exe编译器驱动程序来测试代码。 The result is same, and the assembly code are 结果是一样的,并且汇编代码是
equivalent. 当量。
It looks that the compiler think that variable s_init_done will never be changed because the only statement which change it's value is in the "if" block, which is exclusive with the current "else" branch. 看来编译器认为永远不会更改变量s_init_done,因为唯一更改其值的语句是在“ if”块中,该块与当前“ else”分支互斥。
I tried the same code with VS2013, The result is also same. 我用VS2013尝试了相同的代码,结果也相同。
What I doubt is: In C++98/C++03 standard, there's no concept of thread. 我怀疑的是:在C ++ 98 / C ++ 03标准中,没有线程的概念。 So the compiler can perform such an optimization for a single-thread-machine. 因此,编译器可以对单线程计算机执行这种优化。 But since c++11 has thread, and both clang 3.4 and VC2013 have support C++11 well, do my question is: 但是由于c ++ 11具有线程,并且clang 3.4和VC2013都很好地支持C ++ 11,所以我的问题是:
Is think kind of optimization a compiler bug for C++98/C++03, and for C++11 separately? 认为优化是针对C ++ 98 / C ++ 03和C ++ 11的编译器错误吗?
BTW: When I use -O1 instead, or add volatile qualifier to s_init_done, the bug disappeared. 顺便说一句:当我改用-O1或向s_init_done添加volatile限定符时,该错误消失了。
Your program contains data races on s_begin_init
and s_init_done
, and therefore has undefined behavior. 您的程序包含s_begin_init
和s_init_done
上的数据s_init_done
,因此具有未定义的行为。 Per C++11 §1.10/21: 根据C ++ 11§1.10/ 21:
The execution of a program contains a data race if it contains two conflicting actions in different threads, at least one of which is not atomic, and neither happens before the other. 如果程序的执行在不同线程中包含两个冲突的动作,则其中至少有一个不是原子的,并且在两个线程之前都没有发生,因此该程序的执行将引起数据争用 。 Any such data race results in undefined behavior. 任何此类数据争用都会导致未定义的行为。
The fix is to declare both boolean variables to be atomic: 解决方法是将两个布尔变量都声明为原子的:
std::atomic<bool> s_begin_init{false};
std::atomic<bool> s_init_done{false};
or to synchronize accesses to them with a mutex
(I'll throw in a condition variable to avoid busy-waiting): 或使用mutex
同步对它们的访问(我将抛出一个条件变量以避免繁忙等待):
std::mutex mtx;
std::condition_variable cvar;
bool s_begin_init = false;
bool s_init_done = false;
void thread_proc(void * arg)
{
DWORD tid = GetCurrentThreadId();
printf("Begin Thread %2d, TID=%u\n", reinterpret_cast<int>(arg), tid);
std::unique_lock<std::mutex> lock(mtx);
if (!s_begin_init)
{
s_begin_init = true;
lock.unlock();
Sleep(20);
lock.lock();
s_init_done = true;
cvar.notify_all();
}
else
{
while(!s_init_done) { cvar.wait(lock); }
}
printf("End Thread %2d, TID=%u\n", reinterpret_cast<int>(arg), tid);
}
EDIT: I just noticed the mention of VS2010 in the OP. 编辑:我只是注意到在OP中提到VS2010。 VS2010 does not support C++11 atomics , so you will have to use the mutex
solution or take advantage of MSVC's non-standard extension that gives volatile
variables acquire-release semantics : VS2010不支持C ++ 11原子 ,因此您将不得不使用mutex
解决方案或利用MSVC的非标准扩展,该扩展提供了volatile
变量的获取-释放语义 :
volatile bool s_begin_init = false;
volatile bool s_init_done = false;
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.