Skip to content

一些我容易不知道的

c/cpp

  1. sizeof,同时应用到类型和表达式中,不管应用到哪个,都是会计算 padding 的大小的。变长数组在运行时候计算。
  2. 类在头文件中定义,在 cc 文件中被包含,不会出现链接问题,这是机制。其他全局变量或者方法就不行,只能放声明,定义放在其他 c 文件里,靠链接。方法默认外部链接,就算不加 extern,一样会去外部找,变量不是这样。
  3. 模板不能分离声明实现,有因为在链接的时候,放在实现中的是未实例化的代码,链接器根据实例化去找非实例化,找不到。
  4. decay,编译器做的隐式退化。比如数组名退化到指针,函数名退化到函数指针,模板实例化的时候 const int 退化到 int 等等。数组名和指针还是有区别的, sizeof 作用的时候, & 运算符作用的时候。
  5. 编译器的 volatile 支持。在生成 llvm ir 的时候, llvm ir 会带 volatile 标识。对于 volatile 编译器要保证可见性,保证每次读写可见,按顺序等等,并且不会做写合并等等的优化,比如两个 8bit 的写操作不会被转换成一个 16 bit 的写。
  6. static 把符号限制在当前链接单元内。初始化的放在 data 段,没初始化放在 bss 段。对于匿名名称空间实现同理。
  7. extern c,保证 c 和 cpp 之间的一些链接不会出问题。
  8. static int a = 10,写在头文件里到处包含,如果改动只会影响每个链接单元内部的。
  9. 虚函数,知道虚函数调用哪个的时候才能进行内联。
  10. 静态变量初始化没顺序,想有顺序把其封装在函数中,通过函数返回。

const in c/cpp

const 在 c 中的分配由编译器决定,如果有访问到 const 的地址,可能会被存储在 read only 段中,如果没有,可能在优化的时候直接被常量传播掉了,这些都是编译器决定的。

cpp 中的 const 还有需要注意的点是 muteable 不会被 const 影响。

inline in c/cpp

static 强调内部链接性,inline 强调共享性。

如果 inline int a = 10; 写在头文件里被到处 include,大家同时改变的还是一个变量。如果函数声明和定义都写在一个头文件里,并被到处包含,这是允许的,同时代码最终只生成一份。要是 static 的话,代码还是会在翻译单元内生成多份,然后靠 LTO 把代码合成一份。

external

global 默认带 external.

empty class

empty class 有 1 字节的大小,这是因为 empty class 可能被放在数组里,有存储空间的分配可能就一定有大小,因此大小为 1.如果有类继承了这个类,那这个 empty 的大小可能会被优化掉。

分区

函数中的本地变量都分配在栈中,不管是不是 const。全局的 const,如果编译器认为是常量的会分到 data section 中。

体系结构

缓存带宽 内存带宽 数据预取

以SPEC benchmark 为例,同时我们考虑三级cache每级 cache 都有预取器的情况。当我们想做预取的时候,我们要先检查实际带宽的利用情况,如果在实际运行的时候,实际带宽已经全部被利用,接近上限带宽了,那说明已经无法进行预取了。

实际带宽使用较满的情况包括:

  1. 预取器使用的非常激进,产生大量的预取请求。
  2. 程序的工作集非常大,经常产生 l3 cache miss。(这种情况可能不多见)。

再考虑每级 cache 的带宽,在实际运行的时候每一级的带宽可能都有高低的可能。以任何一级为例:

  1. 带宽高,程序局部性好,读取能经常命中,这可能是非常有效的预取做到的(检查预取器的 prefetch 发出和 use 的情况,如果 prefetch 的 use / send 非常高说明是有效的预取)。如果预取请求没怎么发出,说明是这个程序本身时空局部性就不错,工作集就在 cache 大小内。如果 prefetch 的 use / send 非常高说明局部性不在这级 cache 之内,但是其特征非常明显,适合预取。
  2. 带宽并没有达到理想这么高,访存的请求可能本身就不多,cache 不怎么工作。(这种情况应该如何发现,算 per cycle 的平均访问内存数吗?)
  3. cache 阻塞,(MSHR 满了,或者 MSHR) 某个条目满了,可能需要抽样检查 MSHR 中的读写成分。等于说这部分实际上是超出了工作集的,导致 cache miss 很明显,带宽上不来。

top-down 中的 cache bound 代表 cpu 因为带宽阻塞的程度:

  1. 如果 cache 带宽高但是 bound 还是很高,说明是请求太多了。
  2. 带宽不高还是 bound 了,可能就是局部性差 miss了。

做预取之前需要做的工作。看实际的 bandwidth,看看到底能不能预取。看了之后分析各级 cache 带宽的情况,如果某一集的带宽都满了,也不适合预取,需要找到一个带宽没满的做预取。找到没满的,根据带宽做预取,看看实际变化情况如何,越靠近 l1 dcache 越好,这样即使 l1 handle 不住预取请求,可以往下 offload,带动l2 l3 来做预取。

编译器

debug 出来的 call 越界,可能是 switch case 里面的 default 优化掉了,就比如说编译器判断switch 的是一个常量,把不可能发生的情况优化掉了。