草庐IT

c++ - 编译器为内部函数生成程序集的问题

coder 2024-02-22 原文

我正在使用英特尔 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 生成的代码很好,但有不必要的 vxorps(它将绝对未使用的 xmm3 寄存器设置为零)和 movss _fma 中的指令与 std_fma(依赖于编译器内在的 std::fma)

相比

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 寄存器中传递浮点函数参数,因此应该删除那些 vmovssvinsertps 指令。为什么提到的编译器仍然发出它们?是否可以在不使用内联汇编的情况下摆脱它们?

我还尝试使用 _mm_cvtss_f32 代替 _mm_store_ss 和多个调用约定,但没有任何改变。

最佳答案

我根据评论、一些讨论和我自己的经验写下这个答案。

正如 Ross Ridge 在评论中指出的那样,编译器不够智能,无法识别仅使用了 XMM 寄存器的最低浮点元素,因此它使用 vxorps vinsertps 指令将其他三个元素归零。这是绝对没有必要的,但是你能做什么呢?

需要注意的是,clang 3.9 在为英特尔内部函数生成程序集方面比 GCC 6.2(或 7.0 的当前快照) 做得更好,因为它只在 _mm_fmadd_ss 处失败在我的例子中。我也测试了更多内在函数,在大多数情况下,clang 都能完美地发出单个指令。

你能做什么

您可以使用标准的 <cmath> 函数,如果有合适的 CPU 指令可用,希望它们被定义为编译器内部函数。

这还不够

GCC 等编译器通过对 NaN 和无穷大的特殊处理来实现这些功能。因此,除了内在函数之外,它们还可以进行一些比较、分支和可能的 errno 标志处理。

编译器标志 -fno-math-errno -fno-trapping-math 确实有助于 GCCclang 消除额外的浮点特殊情况和 errno 处理,因此它们可以在可能的情况下发出单个指令:https://godbolt.org/g/LZJyaB

您可以使用 -ffast-math 实现相同的效果,因为它也包含上述标志,但它 includes much more than that 和那些(如不安全的数学优化)可能是不需要的。

不幸的是,这不是一个可移植的解决方案。 它在大多数情况下都有效(请参阅 Godbolt 链接),但您仍然依赖于实现。

还有什么

您仍然可以使用内联汇编,它也不可移植,更棘手,需要考虑的事情也更多。尽管如此,对于如此简单的一行指令来说还是可以的。

需要考虑的事项:

1st GCC/clangVisual Studio 对内联汇编使用不同的语法,而 Visual Studio 不会在 x64 模式下不允许它。

2nd 您需要为 AVX 目标发出 VEX 编码指令(3 个运算变体,例如 vsqrtss xmm0 xmm1 xmm2 ),为 AVX 之前的 CPU 发出非 VEX 编码指令(2 个运算变体,例如 sqrtss xmm0 xmm1 )变体。 VEX 编码指令是 3 个操作数指令,因此它们为编译器提供了更大的优化自由度。要利用它们,必须正确设置 register input/output parameters。所以像下面这样的东西就可以完成这项工作。

#   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

3rd 正如 Peter Cordes 指出的那样,对于内联汇编函数,您可能会丢失 common subexpression elimination (CSE)constant folding (constant propagation)。但是,如果内联汇编未声明为 volatile ,编译器可以将其视为仅依赖于其输入的纯函数并执行公共(public)子表达式消除,这很棒。

正如彼得所说:

"Don't use inline asm" isn't an absolute rule, it's just something you should be aware of and consider carefully before using. If the alternatives don't meet your requirements, and you don't end up with this inlining into places where it can't optimize, then go right ahead.

关于c++ - 编译器为内部函数生成程序集的问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40416570/

有关c++ - 编译器为内部函数生成程序集的问题的更多相关文章

  1. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  2. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  3. ruby - 在 jRuby 中使用 'fork' 生成进程的替代方案? - 2

    在MRIRuby中我可以这样做:deftransferinternal_server=self.init_serverpid=forkdointernal_server.runend#Maketheserverprocessrunindependently.Process.detach(pid)internal_client=self.init_client#Dootherstuffwithconnectingtointernal_server...internal_client.post('somedata')ensure#KillserverProcess.kill('KILL',

  4. ruby - 如何使用 Ruby aws/s3 Gem 生成安全 URL 以从 s3 下载文件 - 2

    我正在编写一个小脚本来定位aws存储桶中的特定文件,并创建一个临时验证的url以发送给同事。(理想情况下,这将创建类似于在控制台上右键单击存储桶中的文件并复制链接地址的结果)。我研究过回形针,它似乎不符合这个标准,但我可能只是不知道它的全部功能。我尝试了以下方法:defauthenticated_url(file_name,bucket)AWS::S3::S3Object.url_for(file_name,bucket,:secure=>true,:expires=>20*60)end产生这种类型的结果:...-1.amazonaws.com/file_path/file.zip.A

  5. ruby - 在没有 sass 引擎的情况下使用 sass 颜色函数 - 2

    我想在一个没有Sass引擎的类中使用Sass颜色函数。我已经在项目中使用了sassgem,所以我认为搭载会像以下一样简单:classRectangleincludeSass::Script::FunctionsdefcolorSass::Script::Color.new([0x82,0x39,0x06])enddefrender#hamlengineexecutedwithcontextofself#sothatwithintemlateicouldcall#%stop{offset:'0%',stop:{color:lighten(color)}}endend更新:参见上面的#re

  6. ruby-on-rails - 在 ruby​​ 中使用 gsub 函数替换单词 - 2

    我正在尝试用ruby​​中的gsub函数替换字符串中的某些单词,但有时效果很好,在某些情况下会出现此错误?这种格式有什么问题吗NoMethodError(undefinedmethod`gsub!'fornil:NilClass):模型.rbclassTest"replacethisID1",WAY=>"replacethisID2andID3",DELTA=>"replacethisID4"}end另一个模型.rbclassCheck 最佳答案 啊,我找到了!gsub!是一个非常奇怪的方法。首先,它替换了字符串,所以它实际上修改了

  7. ruby - 在 Ruby 中有条件地定义函数 - 2

    我有一些代码在几个不同的位置之一运行:作为具有调试输出的命令行工具,作为不接受任何输出的更大程序的一部分,以及在Rails环境中。有时我需要根据代码的位置对代码进行细微的更改,我意识到以下样式似乎可行:print"Testingnestedfunctionsdefined\n"CLI=trueifCLIdeftest_printprint"CommandLineVersion\n"endelsedeftest_printprint"ReleaseVersion\n"endendtest_print()这导致:TestingnestedfunctionsdefinedCommandLin

  8. ruby-on-rails - Ruby on Rails - 为文本区域和图片生成列 - 2

    我是Rails的新手,所以请原谅简单的问题。我正在为一家公司创建一个网站。那家公司想在网站上展示它的客户。我想让客户自己管理这个。我正在为“客户”生成一个表格,我想要的三列是:公司名称、公司描述和Logo。对于名称,我使用的是name:string但不确定如何在脚本/生成脚手架终端命令中最好地创建描述列(因为我打算将其设置为文本区域)和图片。我怀疑描述(我想成为一个文本区域)应该仍然是描述:字符串,然后以实际形式进行调整。不确定如何处理图片字段。那么……说来话长:我在脚手架命令中输入什么来生成描述和图片列? 最佳答案 对于“文本”数

  9. ruby-on-rails - 如何生成传递一些自定义参数的 `link_to` URL? - 2

    我正在使用RubyonRails3.0.9,我想生成一个传递一些自定义参数的link_toURL。也就是说,有一个articles_path(www.my_web_site_name.com/articles)我想生成如下内容:link_to'Samplelinktitle',...#HereIshouldimplementthecode#=>'http://www.my_web_site_name.com/articles?param1=value1¶m2=value2&...我如何编写link_to语句“alàRubyonRailsWay”以实现该目的?如果我想通过传递一些

  10. ruby - Sinatra set cache_control to static files in public folder编译错误 - 2

    我不知道为什么,但是当我设置这个设置时它无法编译设置:static_cache_control,[:public,:max_age=>300]这是我得到的syntaxerror,unexpectedtASSOC,expecting']'(SyntaxError)set:static_cache_control,[:public,:max_age=>300]^我只想将“过期”header设置为css、javaascript和图像文件。谢谢。 最佳答案 我猜您使用的是Ruby1.8.7。Sinatra文档中显示的语法似乎是在Ruby1.

随机推荐