data|终端卡顿优化的全记录( 二 )


for (;true;){
arr[j][0]++;
}
}
}
可以看到整体程序没有作何变化 , 只是将原来的数组变成了二维数组 , 其中除了第一个数组中除arr[0][0]元素外 , 其余arr[0][1]-a[0][8]元素除完全不起作何与程序运行有关的作用 , 但就这么一个小小的改动 , 却带来了性能有了接近20%的大幅提升 , 如果并发更多的话提升幅度还会更加明显 。
缓存行对齐排查分析过程 首先我们把之前代码的多线程改为单线程串行执行 , 结果发现效率与原始的代码一并没有差很多 , 这就让我基本确定了这是一个由伪共享引发的问题 , 但是我初始代码中并没有变量共享的问题 , 所以这基本可以判断是由于对齐惹的祸 。
现代的CPU一般都不是按位进行内存访问 , 而是按照字长来访问内存 , 当CPU从内存或者磁盘中将读变量载入到寄存器时 , 每次操作的最小单位一般是取决于CPU的字长 。 比如8位字是1字节 , 那么至少由内存载入1字节也就是8位长的数据 , 再比如32位CPU每次就至少载入4字节数据, 64位系统8字节以此类推 。 那么以8位机为例咱们来看一下这个问题 。 假如变量1是个bool类型的变量 , 它占用1位空间 , 而变量2为byte类型占用8位空间 , 假如程序目前要访问变量2那么 , 第一次读取CPU会从开始的0x00位置读取8位 , 也就是将bool型的变量1与byte型变量2的高7位全部读入内存 , 但是byte变量的最低位却没有被读进来 , 还需要第二次的读取才能把完整的变量2读入 。
也就是说变量的存储应该按照CPU的字长进行对齐 , 当访问的变量长度不足CPU字长的整数倍时 , 需要对变量的长度进行补齐 。 这样才能提升CPU与内存间的访问效率 , 避免额外的内存读取操作 。 但在对齐方面绝大多数编译器都做得很好 , 在缺省情况下 , C编译器为每一个变量或是数据单元按其自然对界条件分配空间边界 。 也可以通过pragma pack(n)调用来改变缺省的对界条件指令 , 调用后C编译器将按照pack(n)中指定的n来进行n个字节的对齐 , 这其实也对应着汇编语言中的.align 。 那么为什么还会有伪共享的对齐问题呢?
现代CPU中除了按字长对齐还需要按照缓存行对齐才能避免并发环境的竞争 , 目前主流ARM核移动SOC的缓存行大小是64byte , 因为每个CPU都配备了自己独享的一级高速缓存 , 一级高速缓存基本是寄存器的速度 , 每次内存访问CPU除了将要访问的内存地址读取之外 , 还会将前后处于64byte的数据一同读取到高速缓存中 , 而如果两个变量被放在了同一个缓存行 , 那么即使不同CPU核心在分别操作这两个独立变量 , 而在实际场景中CPU核心实际也是在操作同一缓存行 , 这也是造成这个性能问题的原因 。
Switch的坑 但是处理了这个对齐的问题之后 , 我们的程序虽然在绝大多数情况下的性能都不错 , 但是还是会有卡顿的情况 , 结果发现这是一个由于Switch分支引发的问题 。

推荐阅读