我要投搞

标签云

收藏小站

爱尚经典语录、名言、句子、散文、日志、唯美图片

当前位置:2019跑狗图高清彩图 > 指令寄存器 >

为什么 JVM x86 生成的机器代码有 XMM 寄存器?

归档日期:09-12       文本归类:指令寄存器      文章编辑:爱尚语录

  代码中没有浮点或矢量操作,为什么在 JVM x86 平台生成的机器代码中会看到 XMM 寄存器?

  FPU 和矢量单元在现代 CPU 中无处不在。通常,它们会为 FPU 特定操作提供了备用寄存器。例如,英特尔 x86_64 平台的 SSE 和 AVX 扩展包含了一组丰富的 XMM、YMM 和 ZMM 寄存器供指令操作。

  虽然非矢量指令集与矢量、非矢量寄存器通常不会正交,比如不能在 x86_64 上对 XMM 寄存器执行通用 IMUL,但是这些寄存器仍然提供了一种存储选项。即使不用于矢量计算,也可以在这些寄存器中存储数据。

  寄存器分配器的任务是在一个特定的编译单元(比如方法)中获取程序需要的所有操作数,并为它们分配寄存器——映射到机器实际寄存器。真实程序中,需要的操作数大于机器中可用的寄存器数目。这时寄存器分配器必须把一些操作数放到寄存器以外的某个地方(比如堆栈),也就是说会发生操作数溢出。

  x86_64 上有16个通用寄存器(并非每个寄存器都可用)。目前,大多数机器还有16个 AVX 寄存器。发生溢出时,可以不存储到堆栈而存储到 XMM 寄存器中吗?答案是可以。这么做会带来什么好处?

  看看下面这个简单的 JMH 基准测试,用一种非常特殊的方式构建基准(简单起见,这里假设 Java 具备有预处理能力):

  上面的例子中一次会读写多对字段。实际上,优化器本身并不会与具体程序绑定。事实上,这就是在unordered测试中观察到的结果:

  上面展示了26对 load-store,实际测试中大致有25对,但是这里没有25个通用寄存器!从perfasm结果中可以看到,优化器会把临近的load-store对合并,减小寄存器压力:

  ordered测试会给优化器制造一点混乱,在存储前全部加载。上面的结果也印证了这一点:先全部加载,再全部存储。加载全部完成时寄存器的压力最大,这时还没有开始存储。即便如此,从结果来看与unordered差异不大:

  请注意:这里的确对某些操作数使用了通用寄存器(GPR),但是当所有寄存器被用完时会发生溢出。这里对时机的描述并不确切。看起来先发生了溢出,然后使用 GPR。然而这是一个假象,因为寄存器分配器是在全局进行分配。

  (2) 一些寄存器分配器实际执行的是线性分配,提高了 regalloc 的速度与生成代码的效率。

  (2) 一些寄存器分配器实际执行的是线性分配,提高了 regalloc 的速度与生成代码的效率。

  XMM 溢出延迟似乎是最小的:尽管溢出需要更多指令,但它们的执行效率很高能够有效弥补流水线条溢出指令对,实际只要求4个额外周期。请注意,按照4/34 = ~0.11时钟/指令 计算 CPI 是不对的,计算结果会超出当前 CPU 处理能力。但是实际带来的改进是真实的,因为使用了以前没有用到的执行块。

  上面的结果可以看到 load/store 计数增加,为什么?这些是堆栈溢出。虽然堆栈本身速度很快,但仍然在内存中运行,访问 L1 缓存中的堆栈空间。基本上大约需要额外17个存储对,但现在只需要约11个时钟周期。这里 L1 缓存的吞吐量是主要限制。

  FPU 溢出是缓解寄存器压力的一种好办法。虽然不增加通用寄存器寄存器数量,但确实在溢出时提供了更快的临时存储。在仅需要几个额外的溢出存储时,可以避免转存到 L1 缓存支持的堆栈。

  这为什么有时会出现奇怪的性能差异:如果在一些关键路径上没有用到 FPU 溢出,很可能会看到性能下降。例如,引入一个 slow-path GC 屏障,假定会清除 FPU 寄存器,可能会让编译器回退到堆栈溢出,并不去尝试其它优化。

本文链接:http://f-taiken.net/zhilingjicunqi/718.html