为什么 C++ 编译器不将此条件布尔赋值优化为无条件赋值?
Why don't C++ compilers optimize this conditional boolean assignment as an unconditional assignment?
考虑以下函数:
void func(bool& flag)
{
if(!flag) flag=true;
}
在我看来,如果 flag 有一个有效的布尔值,这相当于无条件地将它设置为 true
,就像这样:
void func(bool& flag)
{
flag=true;
}
然而,gcc 和 clang 都没有以这种方式优化它 — 都在 -O3
优化级别生成以下内容:
_Z4funcRb:
.LFB0:
.cfi_startproc
cmp BYTE PTR [rdi], 0
jne .L1
mov BYTE PTR [rdi], 1
.L1:
rep ret
我的问题是:是否只是代码太特殊而无法优化,或者是否有任何充分的理由为什么不希望进行这种优化,因为 flag
不是对volatile
?似乎唯一的原因可能是 flag
可能以某种方式具有非 true
-or-false
值,而在读取它时没有未定义的行为,但我不是确定这是否可能。
出于 cache coherence 考虑,这可能会对程序的性能产生负面影响。每次调用 func()
时写入 flag
会弄脏包含的缓存行。无论写入的值是否与写入前在目标地址处找到的位完全匹配,都会发生这种情况。
编辑
hvd has provided another 阻止了这种优化。这是反对提议的优化的更有说服力的论据,因为它可能会导致未定义的行为,而我的(原始)答案仅涉及性能方面。
经过更多的思考,我可以再举一个例子,为什么编译器应该被强烈禁止——除非他们能证明转换对于特定上下文是安全的——引入无条件写入。考虑这段代码:
const bool foo = true;
int main()
{
func(const_cast<bool&>(foo));
}
在 func()
中进行无条件写入肯定会触发未定义的行为(写入只读内存将终止程序,即使写入的结果是无操作)。
除了 Leon 关于性能的回答:
假设 flag
是 true
。假设有两个线程在不断调用func(flag)
。在这种情况下,所编写的函数不会将任何内容存储到 flag
,因此这应该是线程安全的。两个线程访问同一个内存,但只是为了读取它。无条件地将 flag
设置为 true
意味着两个不同的线程将写入同一内存。这不安全,即使正在写入的数据与已经存在的数据相同,这也不安全。
我不确定这里 C++ 的行为,但在 C 中,内存可能会改变,因为如果内存包含 1 以外的非零值,它会在检查时保持不变,但会更改为 1支票。
但是我的C++不是很流利,我不知道这种情况是否可能。
考虑以下函数:
void func(bool& flag)
{
if(!flag) flag=true;
}
在我看来,如果 flag 有一个有效的布尔值,这相当于无条件地将它设置为 true
,就像这样:
void func(bool& flag)
{
flag=true;
}
然而,gcc 和 clang 都没有以这种方式优化它 — 都在 -O3
优化级别生成以下内容:
_Z4funcRb:
.LFB0:
.cfi_startproc
cmp BYTE PTR [rdi], 0
jne .L1
mov BYTE PTR [rdi], 1
.L1:
rep ret
我的问题是:是否只是代码太特殊而无法优化,或者是否有任何充分的理由为什么不希望进行这种优化,因为 flag
不是对volatile
?似乎唯一的原因可能是 flag
可能以某种方式具有非 true
-or-false
值,而在读取它时没有未定义的行为,但我不是确定这是否可能。
出于 cache coherence 考虑,这可能会对程序的性能产生负面影响。每次调用 func()
时写入 flag
会弄脏包含的缓存行。无论写入的值是否与写入前在目标地址处找到的位完全匹配,都会发生这种情况。
编辑
hvd has provided another
经过更多的思考,我可以再举一个例子,为什么编译器应该被强烈禁止——除非他们能证明转换对于特定上下文是安全的——引入无条件写入。考虑这段代码:
const bool foo = true;
int main()
{
func(const_cast<bool&>(foo));
}
在 func()
中进行无条件写入肯定会触发未定义的行为(写入只读内存将终止程序,即使写入的结果是无操作)。
除了 Leon 关于性能的回答:
假设 flag
是 true
。假设有两个线程在不断调用func(flag)
。在这种情况下,所编写的函数不会将任何内容存储到 flag
,因此这应该是线程安全的。两个线程访问同一个内存,但只是为了读取它。无条件地将 flag
设置为 true
意味着两个不同的线程将写入同一内存。这不安全,即使正在写入的数据与已经存在的数据相同,这也不安全。
我不确定这里 C++ 的行为,但在 C 中,内存可能会改变,因为如果内存包含 1 以外的非零值,它会在检查时保持不变,但会更改为 1支票。
但是我的C++不是很流利,我不知道这种情况是否可能。