【问题标题】:Why are more x86 instructions faster than less? [duplicate]为什么更多的 x86 指令比更少的指令更快? [复制]
【发布时间】:2021-11-13 05:18:37
【问题描述】:

所以大约半年以来,我一直在阅读有关 x86 处理器内部发生的事情的信息。因此,我决定尝试 x86 汇编以获得乐趣,仅从 80386 指令开始以保持简单。 (我主要是在学习,而不是优化)

我有一个几个月前用 C 编码的游戏,所以我去那里用汇编代码从头开始重写了位图 blitting 函数。我没有得到的是循环的主要像素绘图体使用 C 代码(即 18 条指令)比我的汇编代码(只有 7 条指令)更快,而且我几乎 100% 确定它不会t 跨越缓存行边界)。

所以我的主要问题是为什么 18 条指令比 7 条指令花费的时间更少? 在底部我有 2 个代码 sn-ps。

PS。每种颜色都有 8 位索引。 C代码:

    {
        for (x = 0; x < src.w; x++)
00D35712  mov         dword ptr [x],0                       // Just initial loop setup
00D35719  jmp         Renderer_DrawBitmap+174h (0D35724h)   // Just initial loop setup
00D3571B  mov         eax,dword ptr [x]  
00D3571E  add         eax,1  
00D35721  mov         dword ptr [x],eax  
00D35724  mov         eax,dword ptr [x]  
00D35727  cmp         eax,dword ptr [ebp-28h]  
00D3572A  jge         Renderer_DrawBitmap+1BCh (0D3576Ch)  
        {
                *dest_pixel = renderer_trans[renderer_light[*src_pixel][light]][*dest_pixel][trans];
// Start of what I consider the body
00D3572C  mov         eax,dword ptr [src_pixel]  
00D3572F  movzx       ecx,byte ptr [eax]  
00D35732  mov         edx,dword ptr [light]  
00D35735  movzx       eax,byte ptr renderer_light (0EDA650h)[edx+ecx*8]  
00D3573D  shl         eax,0Bh  
00D35740  mov         ecx,dword ptr [dest_pixel]  
00D35743  movzx       edx,byte ptr [ecx]  
00D35746  lea         eax,renderer_trans (0E5A650h)[eax+edx*8]  
00D3574D  mov         ecx,dword ptr [dest_pixel]  
00D35750  mov         edx,dword ptr [trans]  
00D35753  mov         al,byte ptr [eax+edx]  
00D35756  mov         byte ptr [ecx],al  
            dest_pixel++;
00D35758  mov         eax,dword ptr [dest_pixel]  
00D3575B  add         eax,1  
00D3575E  mov         dword ptr [dest_pixel],eax  
            src_pixel++;
00D35761  mov         eax,dword ptr [src_pixel]  
00D35764  add         eax,1  
00D35767  mov         dword ptr [src_pixel],eax  
// End of what I consider the body
        }
00D3576A  jmp         Renderer_DrawBitmap+16Bh (0D3571Bh)  

还有我写的汇编代码: (esi是源像素,edi是屏幕缓冲区,edx是亮度级别,ebx是透明度级别,ecx是这一行的宽度)

drawing_loop:
00C55682  movzx       ax,byte ptr [esi]  
00C55686  mov         ah,byte ptr renderer_light (0DFA650h)[edx+eax*8]  
00C5568D  mov         al,byte ptr [edi]  
00C5568F  mov         al,byte ptr renderer_trans (0D7A650h)[ebx+eax*8]  
00C55696  mov         byte ptr [edi],al  

00C55698  inc         esi  
00C55699  inc         edi  
00C5569A  loop        drawing_loop (0C55682h)  
// This isn't just the body this is the full row plotting loop just like the code above there

就上下文而言,像素由 LUT 照亮,透明度也由 LUT 完成。 伪C代码:

//transparencyLUT[new][old][transparency level (0 = opaque, 7 = full transparency)]
//lightLUT[color][light level (0 = white, 3 = no change, 7 = full black)]
dest_pixel = transparencyLUT[lightLUT[source_pixel][light]]
                            [screen_pixel]
                            [transparency];

让我感到兴奋的是,我使用的指令与 C 代码几乎相同,但使用的指令更少?

如果您需要更多信息,我很乐意提供更多信息,我只是不希望这是一个大问题。我真的很好奇,因为我是 x86 汇编编程的新手,想了解更多关于我们的 cpu 实际工作的信息。

我唯一的猜测是乱序执行引擎不喜欢我的代码,因为它的所有内存访问都移动到同一个寄存器。

【问题讨论】:

  • 一些事情。 1) 你的版本将受到partial register stalls 的严重影响。 2) 指令只是估计性能的一种间接方式。它们只对它们如何影响其他事物很重要,例如前端/解码器(指令大小/对齐)或后端(微指令、延迟、吞吐量)。如果您要开始认真研究微优化,您可以查看x86 wiki on SO。 PeterCordes 在维护它方面做得非常出色。
  • 几个类似的问题部分解决了这个问题。一个用于较旧的Pentium CPU,一个用于newer CPU。
  • 此外,没有基准/数字很难调试和性能问题。坦率地说,我真的不知道我在第一个代码块中看到的是什么。更清楚地了解您的比较会有所帮助。
  • 另一个注意是loop指令非常慢,见stackoverflow.com/questions/35742570/…
  • movzx ax,byte ptr [esi] 哎哟,您几乎避免了对 movzx eax, byte ptr [esi] 上一次迭代的错误依赖,但您只是将新的低 16 合并到旧的 EAX 中。如果您在现代 Intel CPU 上运行它,另请参阅 How exactly do partial registers on Haswell/Skylake perform? Writing AL seems to have a false dependency on RAX, and AH is inconsistent; AH-merging还是需要一个merging uop,而且好像要自己循环发出来。

标签: c assembly optimization x86


【解决方案1】:

并非所有指令都花费相同的时间,CPU 的现代实现可以并行执行(部分)一些指令(只要一个不读取前一个写入的数据并且所需的单元不冲突) )。最新版本确实将“机器”指令翻译成较低级别的、非常简单的指令,这些指令被调度为在 CPU 中的各个单元上尽可能并行地执行,使用大量的影子寄存器(即,一条指令可以使用%eax(旧值)的一个副本中的值之后另一条指令将新值写入%eax(新值)的另一个副本,从而进一步解耦指令。他们为了表现而跳过的篮球......

【讨论】:

  • 我怀疑这与OOE有关,但直到现在我才确定。所以我做了你所说的,删除和依赖在那里。我的寄存器用完了,但我将 ebp 用于堆栈以外的其他东西。现在inc esi inc edi 之前的最后两行使用 edx 而不是相同的 eax。
  • 请注意,只有很少的指令被翻译成多个 µop(微操作)。最重要的指令实际上恰好对应于一个 µop,即使是有些复杂的指令。而且这些微操作绝非简单,因为它们拥有完整的执行端口配置。
  • 当你说“最新版本”时,这几乎是自 1999 年以来的所有 x86 CPU,当时英特尔停止生产超标量按顺序流水线的 P5 / P5MMX,但没有将复杂的指令拆分为单独的指令uops(所以 P5 最有效地运行 x86 的 RISCier 子集,例如避免添加内存目标。)从字面上看,所有市售的 x86 CPU,包括低功耗/嵌入式,几年来都出现了故障,其中主流 CPU 早在 1995 年的 PPro (P6) 就开始解码为 uop。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-09
  • 1970-01-01
  • 1970-01-01
  • 2018-09-16
  • 1970-01-01
相关资源
最近更新 更多