我正在使用英特尔SSE/AVX/FMA内部函数来实现一些数学函数的完美内联SSE/AVX指令。
给定以下代码
#include <cmath>
#include <immintrin.h>
auto std_fma(float x, float y, float z)
{
return std::fma(x, y, z);
}
float _fma(float x, float y, float z)
{
_mm_store_ss(&x,
_mm_fmadd_ss(_mm_load_ss(&x), _mm_load_ss(&y), _mm_load_ss(&z))
);
return x;
}
float _sqrt(float x)
{
_mm_store_ss(&x,
_mm_sqrt_ss(_mm_load_ss(&x))
);
return x;
}
clang 3.9生成的程序集-march=x86-64-mfma-O3
std_fma(float, float, float): # @std_fma(float, float, float)
vfmadd213ss xmm0, xmm1, xmm2
ret
_fma(float, float, float): # @_fma(float, float, float)
vxorps xmm3, xmm3, xmm3
vmovss xmm0, xmm3, xmm0 # xmm0 = xmm0[0],xmm3[1,2,3]
vmovss xmm1, xmm3, xmm1 # xmm1 = xmm1[0],xmm3[1,2,3]
vmovss xmm2, xmm3, xmm2 # xmm2 = xmm2[0],xmm3[1,2,3]
vfmadd213ss xmm0, xmm1, xmm2
ret
_sqrt(float): # @_sqrt(float)
vsqrtss xmm0, xmm0, xmm0
ret
虽然为sqrt生成的代码很好,但与std fma(依赖于编译器内部std::fma)相比,fma中存在不必要的VxORP(将绝对未使用的xmm3寄存器设置为零)和MOVS指令
GCC 6.2生成的程序集-march=x86-64-mfma-O3
std_fma(float, float, float):
vfmadd132ss xmm0, xmm2, xmm1
ret
_fma(float, float, float):
vinsertps xmm1, xmm1, xmm1, 0xe
vinsertps xmm2, xmm2, xmm2, 0xe
vinsertps xmm0, xmm0, xmm0, 0xe
vfmadd132ss xmm0, xmm2, xmm1
ret
_sqrt(float):
vinsertps xmm0, xmm0, xmm0, 0xe
vsqrtss xmm0, xmm0, xmm0
ret
这里有很多不必要的vinsertps
指令
工作示例:https://godbolt.org/g/q1BQym
默认的x64调用约定在XMM寄存器中传递浮点函数参数,因此应该消除那些vmovs和vinsertps指令。为什么提到的编译器仍然发出它们?有没有可能在没有内联装配的情况下摆脱它们?
我还尝试使用mm\u cvtss\u f32来代替mm\u store\u ss和多个调用约定,但没有改变。
我根据评论、一些讨论和我自己的经验写下了这个答案。
正如Ross Ridge在评论中指出的那样,编译器不够聪明,无法识别只使用了XMM寄存器的最低浮点元素,因此它使用那些vxorps
vinsertps
指令将其他三个元素归零。这是绝对不必要的,但你能做什么呢?
需要注意的是,clang 3.9在为Intel Intrinsic生成程序集方面比GCC 6.2(或当前7.0的快照)做得好得多,因为在我的示例中,它只在mm\fmadd\u ss处失败。我还测试了更多的内部函数,在大多数情况下,clang在发出单个指令方面做得很好。
你能做什么
您可以使用标准
这还不够
编译器(如GCC)通过对NaN和无穷大的特殊处理来实现这些函数。因此,除了内部函数之外,它们还可以进行一些比较、分支和可能的errno标记处理。
编译器标记
-fno-math-errno
-fno-traping-ma
确实有助于GCC和clang消除额外的浮点特例和errno
处理,因此如果可能,它们可以发出单个指令:https://godbolt.org/g/LZJyaB.
您可以使用
-ffast-数学
实现相同的效果,因为它还包括上述标志,但它包括的远不止这些,而且那些(如不安全的数学优化)可能是不需要的。
不幸的是,这不是一个可移植的解决方案。它在大多数情况下都有效(请参阅天箭链接),但您仍然依赖于实现。
还有什么
您仍然可以使用内联汇编,它也是不可移植的,更为复杂,还有很多事情需要考虑。尽管如此,对于这样简单的单行指令来说,这是可以接受的。
需要考虑的事情:
第一个GCC/clang和Visual Studio对内联程序集使用不同的语法,Visual Studio不允许在x64模式下使用它。
第二,您需要为AVX目标发出VEX编码指令(3个操作变量,例如vsqrtss xmm0 xmm1 xmm2),为AVX之前的CPU发出非VEX编码指令(2个操作变量,例如sqrtss xmm0 xmm1)。VEX编码指令是3个操作数指令,因此它们为编译器优化提供了更多的自由。为了发挥其优势,必须正确设置寄存器输入/输出参数。因此,下面这样的方法可以完成这项工作。
# if __AVX__
asm("vsqrtss %1, %1, %0" :"=x"(x) : "x"(x));
# else
asm("sqrtss %1, %0" :"=x"(x) : "x"(x));
# endif
但以下是VEX的一个糟糕技术:
asm("vsqrtss %1, %1, %0" :"+x"(x));
它可能会产生不必要的移动指令,请检查https://godbolt.org/g/VtNMLL.
第三,正如PeterCordes指出的那样,内联汇编函数可能会丢失公共子表达式消除(CSE)和常量折叠(常量传播)。但是,如果内联asm未声明为volatile,编译器可以将其视为仅依赖于其输入的纯函数,并执行公共子表达式消除,这非常好。
正如彼得所说:
“不要使用内联asm”并不是一条绝对的规则,它只是您在使用之前应该注意并仔细考虑的一点。如果备选方案不符合您的要求,并且最终没有将此内联到无法优化的地方,那么请继续。
我遵循这里的说明,并试图通过调用lein jar从clj源生成一个Java类。 但是,当我稍微编辑代码以添加自己的测试函数时: .. 然后用lein jar生成一个Java类文件(我在文章末尾追加了project.clj),我发现生成的jar包含作为内部类的方法: 那是一些。示例类只包含main方法,不包含foo: 所以问题是:我们如何指定一个clj Clojure文件来生成一个Java类,该类包
英特尔C编译器和/或GCC是否像2012/2013年以来的MSVC一样支持以下英特尔内部函数 如果支持这些内部函数,那么支持哪个版本(请使用编译时常量)?
为了更好地理解编译器,特别是汇编语言,我一直在实验一段简单的代码,其中计算第一个数字的总和,这应该导致或. 如代码所示,有两个功能: 在第一个函数中,I从O循环到N,即
问题内容: 当我写课时 编译器生成的构造函数是 public 还是 default ? 公众会喜欢 而默认类似于 问题答案: 这取决于您的 类可见性* 。编译器使用类可见性并生成具有 相同可见性 的无参数默认构造函数 *
问题内容: 在Linux中,如果将设备驱动程序构建为可加载的内核模块,则在插入设备驱动程序内核模块后,内核会调用宏所指出的设备驱动程序的init函数。 这对于静态编译到内核中的设备驱动程序如何起作用?他们的init函数如何调用? 问题答案: 内置驱动程序的 init 例程仍可以使用宏声明该入口点。或者,当驱动程序永远不会被编译为可加载模块时,驱动程序可以使用。或者要在启动顺序的早期阶段移动其初始化
考虑以下类来演示Java中的内部类行为。主代码在方法中。其余的只是管道代码。 我只是打印下面的javap输出隐藏的构造函数: 通常情况下,编译器确保子类构造函数在初始化自己的字段之前首先调用超类构造函数。这有助于正确构造对象,但我发现编译器为内部类生成的构造函数与规范行为不符。为什么会这样?是否由JLS指定? 附言:我知道内部类包含对外部类的隐藏引用,该引用在上面的javap输出中设置。但问题是为