Strict Alias and Type Punning
最近在看代码的时候老感觉自己的基础不够扎实,加上最近比较摸鱼,于是想灌灌水。事情的起因是早上看到社区的一个优化:https://github.com/apache/arrow/pull/39397
这个优化的修改大概是这样的:
1 | // Read definition and repetition levels. Also return the number of definition levels |
实际上,这个地方 std::count
比循环效率高吗?否,虽然我们可以认为 std::count
和循环大概可以产生等价的代码,那么是什么东西让这样效果好一点呢?答案是编译器无法确定 num_def_levels
, values_to_read
是不是一个东西,在循环中:
1 | if (def_levels[i] == this->max_def_level_) { |
会生成下面的代码:
1 | 0xc0c2f0 <ReadLevels()+96> inc %rdx |
这段很简单的代码在循环中反而显得判断很多余。根据我们之前介绍的内容,编译器可以很好的优化这种场景,甚至做一些向量化的优化。
这里我们会了解到的部分就是 strict aliasing,这部分的 cppreference 可以见:https://en.cppreference.com/w/cpp/language/object#Strict_aliasing 。Strict Aliasing 大概就是,什么之后能够判断变量(想象循环里的 def_levels
, max_def_level_
, values_to_read
)指向不同的区域。
那么,根据我们上面的 cppreference (也可以参考博文 https://zhuanlan.zhihu.com/p/595286568 )。类型上大家都是 int64_t
,并不好判断两边不是同一个 alias!
杂余
告诉编译器好好工作
本 patch 的 solving 用了个很简单的方法:直接把变量拷出来!你两个本地内存你能飞到哪去呢?
除此之外,这里还提到了很多方法
- RESTRICT
- https://en.cppreference.com/w/cpp/language/attributes/assume
gnu::pure
- …
这些方法主要是告诉编译器好好干活不要胡思乱想!具体可以参考这个系列:https://developers.redhat.com/blog/2020/06/02/the-joys-and-perils-of-c-and-c-aliasing-part-1#
Strict Aliasing 和 UB
在 Strict Aliasing 的情况下,这里其实很容易写出一些相关的 ub:
1 | int foo( float *f, int *i ) { |
函数
foo( float *f, int *i )
,编译器根据 strict aliasing rule 会认为*f
和*i
指向不同的内存区域,并以此进行优化,但实际上指向了相同的内存区域,因此产生了错误的结果。
嘛这个虽然 -fno-strict-aliasing
可以阻止一些错误,但是实际也可以参考 Type Punning
Type Punning
最早看 LevelDB 代码的时候,发现它从 buffer 中读取整数走了一个 memcpy。这个我当时还没理解,后来才知道是 Type Punning 的玄机。我们可以介绍一下 Strict Aliasing 之类的允许的类型转化和一些奇怪的问题。
Whenever an attempt is made to read or modify the stored value of an object of type
DynamicType
through a glvalue of typeAliasedType
, the behavior is undefined unless one of the following is true:
AliasedType
andDynamicType
are similar.AliasedType
is the (possibly cv-qualified) signed or unsigned variant ofDynamicType
.AliasedType
isstd::byte
,(since C++17) char, or unsigned char: this permits examination of the object representation of any object as an array of bytes.
此外,还有下面的图:比如说 int32_t 的 object representation 可能很多,但转成同样位数的 float,却可能是 cb,因为它可能有些 value representation 是没有的!
这里具体也可以参考 https://www.youtube.com/watch?v=_qzMpk-22cc&ab_channel=CppCon
Reference
- Type punning in modern C++ - Timur Doumler - CppCon 2019 https://www.youtube.com/watch?v=_qzMpk-22cc&ab_channel=CppCon
- 严格别名(Strict Aliasing)规则是什么,编译器为什么不做我想做的事? - Atled的文章 - 知乎
https://zhuanlan.zhihu.com/p/595286568 - https://developers.redhat.com/blog/2020/06/02/the-joys-and-perils-of-c-and-c-aliasing-part-1#
- https://gist.github.com/shafik/848ae25ee209f698763cffee272a58f8