当前位置: 首页 > 知识库问答 >
问题:

有什么方法可以在32位x86中使用MOV移动2个字节而不会导致模式切换或cpu停顿?

翁心思
2023-03-14

如果我想把2个无符号字节从内存移到32位寄存器中,我可以用mov指令而不是模式切换吗?

我注意到,您可以使用movsemovze指令来完成此操作。例如,使用movse,编码0f b7将16位移动到32位寄存器。不过,这是一个3个循环的指令。

或者,我想我可以将4个字节移动到寄存器中,然后以某种方式CMP其中的两个字节。

在32位x86上检索和比较16位数据的最快策略是什么?请注意,我主要是在执行32位操作,所以我不能切换到16位模式并停留在那里。

这里的问题是,32位Intel x86处理器可以MOV8位数据和16位或32位数据,这取决于它们处于何种模式。这种模式被称为“D位”设置。可以使用特殊前缀0x66和0x67来使用非默认模式。例如,如果处于32位模式,并且在指令前缀0x66,这将导致操作数被视为16位。唯一的问题是,这样做会导致性能大受影响。

共有1个答案

符俊材
2023-03-14

使用movzx在现代CPU上加载狭义数据。(或者movsx,如果使用符号扩展而不是零扩展是有用的,但是movzx有时更快,而不是更慢。)

MOVZX只在古老的P5(原始奔腾)微架构上慢,而不是本世纪制造的任何东西。基于最新微架构的奔腾品牌CPU,如奔腾G3258(Haswell,原始奔腾20周年纪念版)是完全不同的野兽,性能类似于同等的i3,但没有AVX、BMI1/2或超线程。

不要根据P5的指导方针/数字来调整现代代码。然而,Knight's Corner(Xeon Phi)基于修改后的P54C微架构,因此它可能也有慢速movzx。Agner Fog和Instlatx64都没有针对KNC的每指令吞吐量/延迟数。

使用16位操作数大小的指令不会将整个流水线切换到16位模式,也不会导致大的perf命中。请参阅Agner Fog的microarch pdf,以了解各种x86 CPU微架构(包括像Intel P5(原始奔腾)这样古老的架构,您似乎出于某种原因正在谈论这些架构)上到底什么是慢的,什么不是慢的。

在某些CPU上,写入16位寄存器,然后读取完整的32/64位寄存器是很慢的(在Intel P6系列上合并时,部分寄存器会停顿)。在其他情况下,写入16位寄存器会合并到旧值中,因此在写入时,即使从未读取完整寄存器,也会对完整寄存器的旧值产生错误依赖。看看哪个CPU做什么。(注意,Haswell/Skylake只单独重命名AH,不像Sandybridge(像Core2/Nehalem一样)也单独重命名AL/AX和RAX,但不会延迟合并。)

除非您特别关心顺序P5(或者可能是骑士角Xeon Phi,基于相同的核心,但是IDK如果movzx在那里也很慢),否则请使用以下命令:

movzx   eax, word [src1]        ; as efficient as a 32-bit MOV load on most CPUs
cmp      ax, word [src2]

来自Agner Fog指令集表的数据:注意,它们可能不包括每个角落的情况,例如mov-加载数可能只适用于32/64位加载。还要注意,Agner Fog的加载延迟数不是L1D缓存中的加载使用延迟;它们只作为存储/重新加载(存储-转发)延迟的一部分才有意义,但是相对数字将告诉我们movzxmov的基础上增加了多少周期(通常没有额外的周期)。

(update:https://uops.info/有更好的测试结果,实际上反映了负载使用延迟,而且这些结果是自动化的,所以更新电子表格时的错别字和文书错误不是问题。但是uops.info只追溯到Intel的Conroe(第一代核心2),而AMD只追溯到Zen。)

>

  • P5 Pentium(按顺序执行):MOVZX-LOAD是一个3周期指令(加上来自0F前缀的解码瓶颈),而MOV-LOAD是单周期吞吐量。(不过,它们仍然有潜伏期)。

    PPro/Pentium II/III:MOVZX/MOVSX只在一个装载端口上运行,吞吐量与普通MOV相同。

    Core2/Nehalem:相同,包括64位movsxd,但在Core2上,movsxd r64、M32加载需要一个加载+ALU uop,不进行微熔断。

    SandyBridge系列(SnB通过Skylake和更高版本):MOVZX/MOVSX加载是单UOP(只是一个加载端口),执行与MOV加载相同的操作。

    Atom(按顺序):Agner的表不清楚内存-sourcemovzx/movsx需要一个ALU,但它们肯定很快。延迟数仅用于reg,reg。

    西尔弗蒙特:和原子一样:速度快,但不清楚需要一个端口。

    KNL(基于Silvermont):Agner将具有内存源的MOVZX/MOVSX列为使用IP0(ALU),但是延迟与MOV r,M相同,因此没有任何惩罚。(执行单元压力不是一个问题,因为KNL的解码器只能勉强维持它的2个ALUs。)

    AMD:

    山猫:movzx/movsx负载为每时钟1,5个周期延迟。mov-load是4C延迟。

    Jaguar:movzx/movsx加载为每时钟1个,4个周期延迟。MOV负载为每时钟1,32/64位时为3C延迟,MOV R8/R16,M时为4C(但仍然只有一个AGU端口,而不是像Haswell/Skylake那样的ALU合并)。

    XOR-在写入16位寄存器之前将完整寄存器归零,可以避免以后在Intel P6-family上部分寄存器合并停顿,并破坏错误的依赖关系。

    如果您也想在P5上运行良好,这可能会更好一些,而在任何现代CPU上都不会更差,除了PPro到PIII,在PIII中XOR-zeroing不破坏DEP,尽管它仍然被认为是一种归零习惯用法,使EAX等同于AX(在写入AL或AX后读取EAX时没有部分寄存器停顿)。

    ;; Probably not a good idea, maybe not faster on anything.
    
    ;mov  eax, 0             ; some code tuned for PIII used *both* this and xor-zeroing.
    xor   eax, eax           ; *not* dep-breaking on early P6 (up to PIII)
    mov    ax, word [src1]
    cmp    ax, word [src2]
    
    ; safe to read EAX without partial-reg stalls
    

    操作数大小的前缀对于P5来说并不理想,因此如果您确信32位加载不会出错、跨越缓存行边界或导致最近16位存储的存储转发失败,则可以考虑使用32位加载。

    实际上,我认为在Pentium上16位MOV加载可能比MOVZX/CMP2指令序列慢。对于处理16位数据来说,似乎真的没有一个像32位数据那样有效的好选择!(当然,除了包装好的MMX材料)。

    有关奔腾的详细信息,请参见Agner Fog的指南,但是操作数大小的前缀需要额外的2个周期才能在P1(原始P5)和PMMX上解码,因此这个序列实际上可能比MOVZX加载更糟糕。在P1上(但不是PMMX),0F转义字节(MOVZX)也作为前缀计算,需要额外的周期进行解码。

    显然movzx是不可配对的。多循环MOVZX将隐藏CMP ax,[src2]的解码延迟,因此MOVZX/CMP可能仍然是最佳选择。或者调度指令,这样movzx就可以更早地完成,cmp就可以与某些东西配对。总之,P1/PMMX的调度规则相当复杂。

    mov     ebp, 100000000
    ALIGN 32
    .loop:
    %rep 4
        xor   eax, eax
    ;    mov   eax, 1234    ; just break dep on the old value, not a zeroing idiom
        mov   ax, cx        ; write AX
        mov   edx, eax      ; read EAX
    %endrep
    
        dec   ebp           ; Core2 can't fuse dec / jcc even in 32-bit mode
        jg   .loop          ; but SnB does
    

    perf stat-r4./testloop在静态二进制文件中输出,该文件在以下情况下进行sys_exit系统调用:

     ;; Core2 (Conroe) with   XOR eax, eax
           469,277,071      cycles                    #    2.396 GHz
         1,400,878,601      instructions              #    2.98  insns per cycle
           100,156,594      branches                  #  511.462 M/sec
                 9,624      branch-misses             #    0.01% of all branches
    
           0.196930345 seconds time elapsed                                          ( +-  0.23% )
    

    每个周期2.98个指令是有意义的:3个ALU端口,所有指令都是ALU,没有宏融合,所以每个指令是1个UOP。所以我们在前端容量的3/4上运行。该循环有3*4+2指令/UOPS。

    在Core2上,XOR-注释为零,并使用MOV eax,IMM32:

     ;; Core2 (Conroe) with   MOV eax, 1234
     1,553,478,677      cycles                    #    2.392 GHz
     1,401,444,906      instructions              #    0.90  insns per cycle
       100,263,580      branches                  #  154.364 M/sec
            15,769      branch-misses             #    0.02% of all branches
    
       0.653634874 seconds time elapsed                                          ( +-  0.19% )
    
     ;;; Skylake (i7-6700k) with xor-zeroing
     Performance counter stats for './testloop' (4 runs):
    
             84.257964      task-clock (msec)         #    0.998 CPUs utilized            ( +-  0.21% )
                     0      context-switches          #    0.006 K/sec                    ( +- 57.74% )
                     0      cpu-migrations            #    0.000 K/sec                  
                     3      page-faults               #    0.036 K/sec                  
           328,337,097      cycles                    #    3.897 GHz                      ( +-  0.21% )
           100,034,686      branches                  # 1187.243 M/sec                    ( +-  0.00% )
         1,400,195,109      instructions              #    4.26  insn per cycle           ( +-  0.00% )  ## dec/jg fuses into 1 uop
         1,300,325,848      uops_issued_any           # 15432.676 M/sec                   ( +-  0.00% )    ###   fused-domain
           500,323,306      uops_executed_thread      # 5937.994 M/sec                    ( +-  0.00% )    ### unfused-domain
                     0      lsd_uops                  #    0.000 K/sec                  
    
           0.084390201 seconds time elapsed                                          ( +-  0.22% )
    

  •  类似资料:
    • 我在Windows上使用Oracle JRE,在Linux上使用OpenJDK6。 我想知道Windows的调度程序是否随机抢占线程而Linux的没有?

    • 问题内容: 我用Java创建了一个简单的程序: 如果我在Linux机器上运行此程序,它会显示100%的CPU使用率,但不会导致操作系统显示缓慢。但是,如果我在Windows上运行完全相同的代码,则仅显示约20%的CPU使用率。 我在Windows上使用Oracle JRE,在Linux上使用OpenJDK 6。 我想知道Windows的调度程序是否会随机抢占线程,而Linux的不是吗? 问题答案:

    • 在使用java程序(带有Eclipse IDE)将leap motion listener从32位windows应用到64位windows后,该程序似乎运行正常。 问题是,现在与控制器的连接已初始化,已连接,然后在我不做任何操作的情况下立即退出。 我试着把手放在控制器上,结果出错了 Java运行时环境检测到一个致命错误: pc=0x000007fee8b4a975,pid=10516时的异常访问(

    • 问题内容: 当我使用CDLL在32位python中调用32位dll时,它运行良好。但是不幸的是,在我的64位win7操作系统中,它只能安装64位python,调用时会变成:这不是有效的win32应用程序! 我可以在64位python中使用32位dll或exe吗?还是我必须安装32位python? 问题答案: 64位EXE无法加载32位DLL。(反之亦然:32位EXE无法加载64位DLL。)毕竟,它

    • 我有一个立方体,我只在x轴上的3个点(浮动位置)之间移动它。所以立方体将从0.00开始,我按下右键,它在x轴上向右移动到2.0f。然后我按下左键,它会回到0.0f。然后我再次按下左键,它会移动到-2.0f。按下右键应该会将其返回到0.0f,但会超出0。误差的大小取决于我移动的速度。 如果我从左键开始,结果也是一样的。 帧时间是

    • 但我会得到另一个例外 java.lang.UnsatisfiedLinkError:dlopen失败:“/data/app/com.example.user.project/lib/x86/libtracker.so”是64位而不是32位 我可以看到我的库已经成功构建,这是它在构建时显示的跟踪消息 这是我的CMakeLists 这是我的母语-lib.cpp 那么,Android studio无法从