分析 _mm_setzero_ps 和 0.0f,0.0f,0.0f,0.0f

     2023-03-06     263

关键词:

【中文标题】分析 _mm_setzero_ps 和 0.0f,0.0f,0.0f,0.0f【英文标题】:Profiling _mm_setzero_ps and 0.0f,0.0f,0.0f,0.0f分析 _mm_setzero_ps 和 0.0f,0.0f,0.0f,0.0f 【发布时间】:2016-12-21 17:00:26 【问题描述】:

编辑:正如 Cody Gray 在他的评论中指出的那样,禁用优化的分析完全是浪费时间。那么我应该如何进行这个测试呢?


Microsoft 在其XMVectorZero 中如果定义了_XM_SSE_INTRINSICS_,则使用_mm_setzero_ps,如果没有定义0.0f,0.0f,0.0f,0.0f。我决定检查一下胜利有多大。所以我在 Release x86 中使用了以下程序,并将 Configuration Properties>C/C++>Optimization>Optimization 设置为 Disabled (/Od)

constexpr __int64 loops = 1e9;
inline void fooSSE() 
    for (__int64 i = 0; i < loops; ++i) 
        XMVECTOR zero1 = _mm_setzero_ps();
        //XMVECTOR zero2 = _mm_setzero_ps();
        //XMVECTOR zero3 = _mm_setzero_ps();
        //XMVECTOR zero4 = _mm_setzero_ps();
    

inline void fooNoIntrinsic() 
    for (__int64 i = 0; i < loops; ++i) 
        XMVECTOR zero1 =  0.f,0.f,0.f,0.f ;
        //XMVECTOR zero2 =  0.f,0.f,0.f,0.f ;
        //XMVECTOR zero3 =  0.f,0.f,0.f,0.f ;
        //XMVECTOR zero4 =  0.f,0.f,0.f,0.f ;
    

int main() 
    fooNoIntrinsic();
    fooSSE();

我第一次运行程序两次,只有 zero1 和第二次,所有行都未注释。在第一种情况下,内在输了,在第二种情况下,内在显然是赢家。所以,我的问题是:

为什么内在并不总是赢? 我使用的分析器是否适合进行此类测量?

【问题讨论】:

在禁用优化的情况下分析事物会给您带来毫无意义的结果,完全是浪费时间。 @CodyGray 非常感谢,我更新了问题 我不确定我是否理解您修改后的问题。你应该如何处理它?启用优化!我猜你已经发现优化器通过完全删除你的基准代码来胜过你? @CodyGray 是的,就是这样,所以我没有优化就做到了:) 我的意思是我如何重写这个测试 【参考方案1】:

在禁用优化的情况下分析事物会给您带来毫无意义的结果,而且完全是浪费时间。如果您要禁用优化,否则优化器会注意到您的基准实际上没有任何用处并完全删除它,那么欢迎接受微基准测试的困难!

通常很难编造一个测试用例,它实际上做了足够多的实际工作,不会被足够聪明的优化器删除,但这项工作的成本不会压倒你的结果并使你的结果变得毫无意义。例如,很多人的第一直觉是使用printf 之类的东西打印出增量结果,但这是行不通的,因为printf 非常慢并且绝对会破坏您的基准。将收集中间值的变量设置为volatile 有时会起作用,因为它有效地禁用了对该特定变量的加载/存储优化。尽管这依赖于定义不明确的语义,但这对于基准测试并不重要。另一种选择是对中间结果执行一些无意义但相对便宜的操作,例如将它们加在一起。这依赖于优化器不会超过您,并且为了验证您的基准测试结果是否有意义,您必须检查编译器发出的目标代码并确保代码实际上在做事情。不幸的是,制作微基准没有灵丹妙药。

最好的技巧通常是将代码的相关部分隔离在函数内部,将其参数化为一个或多个不可预测的输入值,安排返回结果,然后将其放入在一个外部模块中运行,这样优化器就无法得到它肮脏的爪子。

因为无论如何您都需要查看反汇编以确认您的微基准测试案例是否合适,所以这通常是一个不错的起点。如果你有足够的能力阅读汇编语言,并且你已经充分提炼了有问题的代码,这甚至可能足以让你对代码的效率做出判断。如果您无法确定代码的正面或反面,那么它可能已经足够复杂,您可以继续进行基准测试。

这是一个很好的例子,说明粗略检查生成的目标代码足以回答问题,甚至不需要制定基准。

按照我上面的建议,让我们编写一个简单的函数来测试内在函数。在这种情况下,我们没有任何参数化的输入,因为代码实际上只是将寄存器设置为 0。所以让我们返回函数的归零结构:

DirectX::XMVECTOR ZeroTest_Intrinsic()

    return _mm_setzero_ps();

这是另一个以看似天真的方式执行初始化的候选者:

DirectX::XMVECTOR ZeroTest_Naive()

    return  0.0f, 0.0f, 0.0f, 0.0f ;

这里是编译器为这两个函数生成的目标代码(不管是哪个版本,你是为 x86-32 还是 x86-64 编译,或者你是否针对大小或速度进行了优化;结果是同样):

ZeroTest_Intrinsic
    xorps  xmm0, xmm0
    ret
ZeroTest_Naive
    xorps  xmm0, xmm0
    ret

(如果支持 AVX 或 AVX2 指令,则它们都是vxorps xmm0, xmm0, xmm0。)

这很明显,即使对于无法阅读汇编代码的人来说也是如此。他们都是一样的!我会说这非常明确地回答了哪个更快的问题:它们将是相同的,因为优化器识别出看似幼稚的初始化程序并将其转换为单个优化的汇编语言指令以清除寄存器。

现在,确实可能在某些情况下,它被深深嵌入到各种复杂的代码结构中,从而阻止优化器识别它并发挥它的魔力。换句话说,“你的测试功能太简单了!”异议。这很可能是库的实现者选择在可用时显式使用内在函数的原因。它的使用保证代码生成将发出所需的指令,因此代码将尽可能优化。

显式使用内部函数的另一个可能的好处是确保您获得所需的指令,即使在没有 SSE/SSE2 支持的情况下编译代码也是如此。正如我想象的那样,这并不是一个特别引人注目的用例,因为如果使用这些指令是可以接受的,那么您就不会在没有 SSE/SSE2 支持的情况下进行编译。如果您明确尝试禁用 SSE/SSE2 指令的生成以便您可以在遗留系统上运行,那么内在函数会毁了您的一天,因为它会强制发出 xorps 指令,并且遗留系统会抛出一个命中该指令后立即出现无效操作异常。

不过,我确实看到了一个有趣的案例。 xorps 是这条指令的单精度版本,只需要 SSE 支持。但是,如果我只使用 SSE 支持(无 SSE2)编译上面显示的函数,我会得到以下结果:

ZeroTest_Intrinsic
    xorps  xmm0, xmm0
    ret
ZeroTest_Naive
    push   ebp
    mov    ebp, esp
    and    esp, -16
    sub    esp, 16

    mov    DWORD PTR [esp],    0
    mov    DWORD PTR [esp+4],  0
    mov    DWORD PTR [esp+8],  0
    mov    DWORD PTR [esp+12], 0
    movaps xmm0, XMMWORD PTR [esp]

    mov    esp, ebp
    pop    ebp
    ret

显然,由于某种原因,优化器无法在 SSE2 指令支持不可用时将优化应用于初始化器的使用,即使它将使用 xorps 指令不需要SSE2指令支持!这可以说是优化器中的一个错误,但显式使用内部函数可以解决它。

【讨论】:

当人们通过在循环中添加结果来对单个循环体进行微基准测试时,他们通常最终会测试吞吐量,而不是延迟。根据您的实际程序如何使用您正在调整的操作,这可能是错误的优化对象。现代无序执行 CPU 使微基准测试变得困难。一旦你在没有分支错误预测的情况下受 CPU 限制,就需要考虑三个简单的维度:前端瓶颈(主要只是融合域 uop 计数)、执行单元瓶颈(OOO 吞吐量)和延迟瓶颈。 ***.com/a/40879258/224132 如果您使用 MSVC 显式启用 SSE 或 SSE2,那么您必须在 32 位模式下编译。以我的经验,微软在 32 位模式的优化上投入的精力要少得多。我已经回答了关于 SO 的多个问题,其中 MSVC 在 32 位模式下做了一些愚蠢的事情,但在 64 位模式下做出了最佳选择。不幸的是,即使是 64 位模式,MSVC 也默认为 32 位模式,因此您必须显式启用 64 位模式。如果 Microsoft 在 64 位 Windows 上默认为 64 位模式,那么关于 SO 上带有 MSVC 的 SIMD 的问题就会少得多。 是的,我在这里谈论的是 32 位代码。这就是原始问题中讨论的内容。这对于 64 位来说不是问题,因为始终支持 SSE2。 32 位优化器是更老的代码,并且经常做一些今天看起来“愚蠢”但很久以前就有意义的事情。如果不是基于全新的代码,64 位优化器似乎已在很大程度上被重写。我不同意,尽管默认为 64 位目标是有道理的。仍然有 很多 的 32 位处理器。并且将其基于您的开发机器,这违反了最小意外原则。 仅仅因为我在 AMD 处理器上进行开发并不意味着我希望我的所有构建都针对 AMD 等进行调整。而且对于它的价值,我还没有看到从32 位编译器与 64 位编译器相比,我做了很多并排比较。 MSVC 2015 的最后一次更新做了很多工作,进一步优化了 32 位编译器,使其与 VS 2010 附带的 64 位编译器中包含的许多优化增强保持同步。

opengl使用bmp纹理映射画不出东西

... glBegin(GL_QUADS);glTexCoord2f(0.0f,0.0f);glVertex2f(0.0f,0.0f); //纹理和四边形的左下 glTexCoord2f(1.0f,0.0f);glVertex2f(100.0f,0.0f); //纹理和四边形的右下 glTexCoord2f(1.0f,1.0f);glVertex2f(100.0f,100.0f); //纹理和四边形的右上 glTexCoord2f(0.0f,1.0f);glVertex2f(0.0f,100.... 查看详情

从顶点数据中传入顶点位置和顶点颜色(代码片段)

一、目的:不需要在着色器中指定图形的颜色,直接在顶点数据中指定颜色//画三角形的顶点数组 floatvertices[]=   //位置    //颜色   -0.5f,-0.5f,0.0f,1.0f,0.0f,0.0f,   0.5f,-0.5f,0.0f,0.0f,1.0f,0.0f,   0.0f,0.5f,0.0f,0.0f,0.0f,1.0f ;... 查看详情

自然语言0.0_情感分析权威网站_政治经济地理

...加入爬虫模块,直接抓取数据,然后本地可视化 情感分析--数据可视化地理分析,预测各地不同时间段的关键词  政治分析:包括战争,环境,犯罪,石油。。。。   财经预测    查看详情

包和模块

p.p1{margin:0.0px0.0px0.0px0.0px;font:17.0pxHelvetica;color:#29556f}控制模块被全部导入的内容问题:  当使用‘frommoduleimport*‘ 语句时,希望对从模块或包导出的符号进行精确控制 解决方案:  在你的模块中定义一个变量__all__来... 查看详情

为啥 0.0f/0.0f 不会产生任何运行时错误?

...,我期待运行时错误,但它显示“nan”作为输出。为什么和如何?我正在使用g++编译器。#include<iostream>intmain()flo 查看详情

阴影实现(代码片段)

...现的功能:1) 相机跟随,2) 围绕Player旋转(左右和上下调整相机),3) 镜头zoomusingUnityEngine;[RequireComponent(typeof(Camera))]publicclassThirdPersonCamera:MonoBehaviour///摄像机和碰撞体的交点向摄像机的观察点移动的距离privateconstfloat... 查看详情

第二章财务报表分析和财务预测

第二章财务报表分析和财务预测[TOC]财务报表分析方法★★比较分析纵向分析(趋势分析)横向分析(同行业分析)预算差异分析会计要素总量结构百分比财务比率因素分析基数:$F_{0}=A_{0} imesB_{0} imesC_{0}$---(1)置换A因素:$A_{1} im... 查看详情

glvertexattribpointer用法简介(代码片段)

...元素都存入一个缓存对象中staticconstfloatposition[]=-1.0f,-1.0f,0.0f,1.0f,//2顶点位置2纹理坐标1.0f,-1.0f,1.0f,0.0f,-1.0f,1.0f,0.0f,1.0f,1.0f,1.0f,1.0f,1.0f;//-----------基于索引绘制staticconstGLushortplane_indices[]=0,1,2,3;glGenBuffers(1,&_index_buffer);glBindBuffer(GL_... 查看详情

相机旋转(代码片段)

...向量P是摄像机位置向量glm::LookAt函数需要一个位置、目标和上向量以得到观察矩阵。glm::mat4view;view=glm::lookAt(glm::vec3(0.0f,0.0f,3.0f),glm::vec3(0.0f,0.0f,0.0f),glm::vec3(0.0f,1.0f,0.0f));随时间旋转floatradius=10.0f;floatcamX=sin(glfwGetTime())*radiu... 查看详情

相机旋转(代码片段)

...向量P是摄像机位置向量glm::LookAt函数需要一个位置、目标和上向量以得到观察矩阵。glm::mat4view;view=glm::lookAt(glm::vec3(0.0f,0.0f,3.0f),glm::vec3(0.0f,0.0f,0.0f),glm::vec3(0.0f,1.0f,0.0f));随时间旋转floatradius=10.0f;floatcamX=sin(glfwGetTime())*radiu... 查看详情

OpenGL glRotatef

...么区别glRotatef(angle,1.0f,0.0f,0.0f);glRotatef(angle,0.0f,0.0f,1.0f);和glRotatef(angle,1.0f,0.0f,1.0f);当我将第二个参数1.0f更改为5.0f时,为什么没有任何变化?最后,如何围绕x=5而不是围绕x=0旋转对象?【问题讨论】:【参 查看详情

UIView 属性 hidden = YES vs alpha = 0.0f

...5-04-2812:42:27【问题描述】:我一直在寻找这些案例的答案和一种“最佳实践”,因为对于任何试图处理UI和UIKit的开发人员来说,它们必须经常发生。我已经阅读了thisthread和许多其他网站,但没有找到好的答案我要求的是,在选... 查看详情

多个顶点缓冲区OpenGL c ++

...2个顶点缓冲区,但它只绘制第二个。我正在使用OpenGL4.6和COMPAT配置文件。代码:floatbuffer[]=0.0f,0.0f,1.0f,0.0f,1.0f,1.0f;floatbuffera[]=0.0f,0.0f,1.0f,0.0f,1.0f, 查看详情

dx12纹理贴图(代码片段)

...loatd2=0.5f*depth; //Fillinthefrontfacevertexdata. v[0]=Vertex(-w2,-h2,-d2,0.0f,0.0f,-1.0f,1.0f,0.0f,0.0f,0.0f,1.0f); v[1]=Vertex(-w2,+h2,-d2,0.0f,0.0f,-1.0f,1.0f,0.0f,0.0f,0.0f,0.0f); v[2]=Vertex(+w2,+h2,-d2,0.0f,0.0f,-1.0f,1.0f,0.0f,0.0f,1.0f,0.0f); 查看详情

算法设计与分析「通关指南」

绪论与算法基础:one:定义:如果存在两个正常数$c$和$n_0$,对于所有的$n\\gen_0$,有$|f(n)|\\lec|g(n)|$,则记作$f(n)=O(g(n))$:snake:例题:判断$f(n)=O(g(n))$$f\\left(n\\right) 查看详情

opengl学习笔记-顶点输入

1.构造顶点数据,以三角形为例子,floatvertices[]={-0.5f,-0.5f,0.0f,0.5f,-0.5f,0.0f,0.0f,0.5f,0.0f};2.构造VBO  unsignedintVBO;  glGenBuffers(1,&VBO);//通过传入生成的数量(1)和唯一ID(VBO)生成一个VBO对象  glBindBuffers(GL_ARRAY_BUFFER,V 查看详情

Scikit learn 错误消息“精度和 F 分数定义不明确,在标签中设置为 0.0”[重复]

】Scikitlearn错误消息“精度和F分数定义不明确,在标签中设置为0.0”[重复]【英文标题】:ScikitlearnErrorMessage\'PrecisionandF-scoreareill-definedandbeingsetto0.0inlabels\'[duplicate]【发布时间】:2016-05-1512:03:10【问题描述】:我正在研究二进制... 查看详情

unity3d如何渲染图片?

...个texture变量,渲染的步骤大致如下:1、shader编译2、vertex和fragmentlink出program3、在一次drawcall中,获取texture变量的location;激活纹理单元(单纹理的话不需要专门激活);bind纹理数据到纹理单元;将纹理单元中的数据赋值给locatio... 查看详情