Skip to content

ASLR开启导致rapidjson性能波动问题

问题描述

本案例中的rapidjson是一个benchmark中的一个测试用例,是单线程的任务,测试时会启动N个进程来实现对CPU的压测。(可参考speccpu的测试模式)

测试过程中发现,ASLR开启时,偶先个别任务用时大幅增加,导致整体测试分数偏低。

观察后发现,出现问题的任务随机发生,不固定在某个核心上,和超线程无关。

微观特征分析和热点

这种仍然是随机出现的问题,所以还是需要采集多个任务的特征,来提高采集到问题进程的概率。

采集到特征后,使用topdown先大致定位问题,发现问题进程的特征不是很好采集,因为有多个测试用例,很容易对判断产生影响。有时是后端瓶颈,有时又是L1瓶颈。

所以这里直接看热点。采集的热点问题就明显多了,均命中在一行代码上。

cpp
if(RAPIDJSON_UNLIKELY(stackTop_+sizeof(T)*count == stackEnd_))
// ldp x3, x2, [x21, #24]

汇编中ldp是热点,ldp会一次性读取stackTop_和stackEnd_。进一步,可以定位一下寄存器x21的值,结合cacheline的size 64B,用x21+24的地址对64B取模,来判断数据存放的偏移量。

比较之下发现,正常的任务的x21+24的地址对64B取模,结果为8(或其它小于56的值),而异常的任务的x21+24的地址对64B取模,结果为56。ldp会一次性读取16字节的数据,显然如果从56处开始读,就跨cacheline了,这是导致性能变差的原因。


当然定位过程不是这么一帆风顺,还有很多细节的问题,如perf采集热点会有一定的偏移,原始汇编中load后store也会产生误导,我最早以为是数据依赖导致的,但是针对数据依赖,硬件上有预取/load to store forward的优化技术,但是跨cacheline的访问会使这种优化手段失效。

为什么x86架构下不会出现问题?

从汇编来看,x86加载stackTop_和stackEnd_的地址,使用了两个mov指令,是可以并行或者乱序的,也就是被硬件优化掉了。但是arm上ldp没有跨cacheline的访问,会有很好的性能表现,但是一旦跨cacheline,就会性能退化(可能退化成两个ldr?)

解决方案

目前有下面的解决方案:

  1. os层面,关闭ASLR。(但是有一种情况是关闭ASLR下,地址固定在56,反而性能会变差,实际上测试A家的产品就是这么“倒霉”)
  2. 从原始代码入手,手动排布stackTop_和stackEnd_的地址,来避免跨cacheline的访问。