【问题标题】:Disassembly view of C# 64-bit Release code is 75% longer than 32-bit Debug code?C# 64 位 Release 代码的反汇编视图比 32 位 Debug 代码长 75%?
【发布时间】:2012-09-12 22:41:15
【问题描述】:

编辑

我测试了 32 位版本,代码很紧凑。因此下面是一个 64 位的问题。


我正在使用 VS 2012 RC。 Debug 是 32 位的,Release 是 64 位的。下面是debug然后release反汇编的一行代码:

         crc = (crc >> 8) ^ crcTable[((val & 0x0000ff00) >> 8) ^ crc & 0xff];
0000006f  mov         eax,dword ptr [ebp-40h] 
00000072  shr         eax,8 
00000075  mov         edx,dword ptr [ebp-3Ch] 
00000078  mov         ecx,0FF00h 
0000007d  and         edx,ecx 
0000007f  shr         edx,8 
00000082  mov         ecx,dword ptr [ebp-40h] 
00000085  mov         ebx,0FFh 
0000008a  and         ecx,ebx 
0000008c  xor         edx,ecx 
0000008e  mov         ecx,dword ptr ds:[03387F38h] 
00000094  cmp         edx,dword ptr [ecx+4] 
00000097  jb          0000009E 
00000099  call        6F54F5EC 
0000009e  xor         eax,dword ptr [ecx+edx*4+8] 
000000a2  mov         dword ptr [ebp-40h],eax
-----------------------------------------------------------------------------
         crc = (crc >> 8) ^ crcTable[((val & 0x0000ff00) >> 8) ^ crc & 0xff];
000000a5  mov         eax,dword ptr [rsp+20h] 
000000a9  shr         eax,8 
000000ac  mov         dword ptr [rsp+38h],eax 
000000b0  mov         rdx,124DEE68h 
000000ba  mov         rdx,qword ptr [rdx] 
000000bd  mov         eax,dword ptr [rsp+00000090h] 
000000c4  and         eax,0FF00h 
000000c9  shr         eax,8 
000000cc  mov         ecx,dword ptr [rsp+20h] 
000000d0  and         ecx,0FFh 
000000d6  xor         eax,ecx 
000000d8  mov         ecx,eax 
000000da  mov         qword ptr [rsp+40h],rdx 
000000df  mov         rax,qword ptr [rsp+40h] 
000000e4  mov         rax,qword ptr [rax+8] 
000000e8  mov         qword ptr [rsp+48h],rcx 
000000ed  cmp         qword ptr [rsp+48h],rax 
000000f2  jae         0000000000000100 
000000f4  mov         rax,qword ptr [rsp+48h] 
000000f9  mov         qword ptr [rsp+48h],rax 
000000fe  jmp         0000000000000105 
00000100  call        000000005FA5D364 
00000105  mov         rax,qword ptr [rsp+40h] 
0000010a  mov         rcx,qword ptr [rsp+48h] 
0000010f  mov         ecx,dword ptr [rax+rcx*4+10h] 
00000113  mov         eax,dword ptr [rsp+38h] 
00000117  xor         eax,ecx 
00000119  mov         dword ptr [rsp+20h],eax

64 位版本中的所有额外代码在做什么?它在测试什么?我没有对此进行基准测试,但是 32 位代码应该执行得更快。

编辑

整个函数:

public static uint CRC32(uint val)
{
    uint crc = 0xffffffff;

    crc = (crc >> 8) ^ crcTable[(val & 0x000000ff) ^ crc & 0xff];
    crc = (crc >> 8) ^ crcTable[((val & 0x0000ff00) >> 8) ^ crc & 0xff];
    crc = (crc >> 8) ^ crcTable[((val & 0x00ff0000) >> 16) ^ crc & 0xff];
    crc = (crc >> 8) ^ crcTable[(val >> 24) ^ crc & 0xff];

    // flip bits
    return (crc ^ 0xffffffff);
}

【问题讨论】:

  • 这将有助于查看整个函数,而不仅仅是它的单行。
  • 当一个版本是为调试而构建而另一个版本无效时,尝试比较差异。至少比较相同的物种(调试/调试或发布/发布)。
  • @KenWhite 我同意。但引起我注意的是调试更紧凑的事实,加上事实发布添加了检查。
  • 检查crcTable[] 的索引是否超过允许的最大值(看起来,这里不需要)。不过,我不太确定为什么 64 位版本中有更多代码。
  • @KirkWoll 这是 32 位代码。没有一个 64 位操作。如果您阅读反汇编,您会看到添加了额外的检查。只说“当然 64 位更大”是不准确的,根本不能解释反汇编。

标签: c# assembly compiler-construction visual-studio-2012 disassembly


【解决方案1】:

我怀疑您在调试发布版本以获取汇编代码时使用“转到反汇编”。

转到工具 -> 选项、调试、常规并禁用“在模块加载时抑制 JIT 优化”后,我得到了一个 x64 程序集列表,没有错误检查。

默认情况下,即使在发布模式下,如果附加了调试器,代码也不会优化。在尝试对代码进行基准测试时请记住这一点。

PS:基准测试显示,对于 10 亿次函数调用,x64 比 x86 略快,分别为 4.3 秒和 4.8 秒。

编辑:断点仍然对我有用,否则取消选中后我将无法看到反汇编。您上面的示例行如下所示(VS 2012 RC):

crc = (crc >> 8) ^ crcTable[((val & 0x0000ff00) >> 8) ^ crc & 0xff];
00000030  mov         r11d,eax 
00000033  shr         r11d,8 
00000037  mov         ecx,edx 
00000039  and         ecx,0FF00h 
0000003f  shr         ecx,8 
00000042  movzx       eax,al 
00000045  xor         ecx,eax 
00000047  mov         eax,ecx 
00000049  cmp         rax,r9 
0000004c  jae         00000000000000A4 
0000004e  mov         eax,dword ptr [r8+rax*4+10h] 
00000053  xor         r11d,eax 

【讨论】:

  • 是的,你是对的。所以似乎在 64 位发布调试模式下,添加了检查,但在 32 位模式下没有。因此,实际的发布将是“干净的”。
  • 自然,禁用该选项后,“转到反汇编”和断点停止工作,所以我无法检查。但我相信你回答了这个问题,并得到了基准的支持。
  • 我不知道你如何让断点工作并且可以看到反汇编。您正在运行 64 位版本吗?哪个VS?
  • @IanC 也许函数调用是内联的,断点开始表现得很奇怪。尝试将其放入调用函数,或调用该函数的函数。
  • 我什至不能在 Form1 代码上设置断点。不过,我的解决方案有点复杂。 1个Win表单项目+4个dll项目,其中有问题的代码在一个dll项目中。
【解决方案2】:

查看代码,这与访问 crcTable 的错误检查有关。在开始挖掘数组之前,它已经完成了你的界限。

在你看到的 32 位代码中

0000008e  mov         ecx,dword ptr ds:[03387F38h] 
....
0000009e  xor         eax,dword ptr [ecx+edx*4+8] 

在这种情况下,它从 03387F38h 加载数组的基地址,然后使用标准指针算法访问正确的条目。

在 64 位代码中,这似乎更复杂。

000000b0  mov         rdx,124DEE68h 
000000ba  mov         rdx,qword ptr [rdx]

这会将地址加载到 rdx 寄存器中

000000da  mov         qword ptr [rsp+40h],rdx 
...
00000105  mov         rax,qword ptr [rsp+40h] 
0000010a  mov         rcx,qword ptr [rsp+48h] 
0000010f  mov         ecx,dword ptr [rax+rcx*4+10h] 

这会将地址移动到堆栈中,然后将其移动到 rax 寄存器并执行相同的指针工作以访问数组。

000000da 和 00000100/00000105 之间的几乎所有内容似乎都是验证码。其余代码在 64 位和 32 位代码之间映射得非常好,在 64 位代码中寄存器使用率较低。

【讨论】:

  • 我做了一个编辑:我在 32 位中测试了发布模式,输出与 32 位 debug:short 相同。这肯定是 64 位问题。
  • @IanC 啊,很高兴知道。我更新了答案以考虑到这一点。
  • 这真是出乎意料。以 32 位模式构建显然会产生更快的代码。现在我想知道 64 位代码是否充满了数组边界检查和复杂的访问代码。
  • @IanC 数组边界检查始终存在,即使在发布模式下也是如此。如果索引超过数组大小,程序需要抛出 ArgumentOutOfRangeException。编译器也许可以(在这种情况下)证明异常永远不会发生,但它似乎没有这样做。我认为我们这里的大多数看似不必要的指令只是为了通过跟踪内存中的所有变量(和临时变量?)而不是将它们保存在寄存器中来简化调试器的使用。
【解决方案3】:

exp ^ crc & 0xff 编译为exp ^ (cr & 0xff):

00000082  mov         ecx,dword ptr [ebp-40h]  
00000085  mov         ebx,0FFh  
0000008a  and         ecx,ebx  
0000008c  xor         edx,ecx  

你应该把表达式写成 ?

(exp ^ crc) & 0xff

64 位版本的优化肯定不如 32 位版本。 CLR 有两个独立的 JIT 编译器实现。

此外,如果性能很关键,请使用不安全代码来删除边界检查。

【讨论】:

  • 显然 64 位的完整版本比 32 位快 12%。答案是附加的调试器阻止了 64 位的优化,但不是 32 位。就像它的设置方式一样。代码是标准 CRC。
猜你喜欢
  • 1970-01-01
  • 2017-10-20
  • 1970-01-01
  • 2011-04-01
  • 1970-01-01
  • 2016-01-02
  • 2022-06-24
  • 1970-01-01
  • 2014-10-10
相关资源
最近更新 更多