在对this回答(这表明使用位移位运算符在整数乘法/除法,出于性能考虑),我问这是否会真正更快。在我的脑海里是一个想法,在部分的水平,一些会聪明地制定出>> 1
和 / 2
是相同的操作。不过,我现在想知道,其实这是真的,如果是,它会发生什么样的水平。
一个测试程序产生以下的比较CIL(与优化
),了解其分别划分和转移他们的说法两种方法:
IL_0000:ldarg.0
IL_0001:ldc.i4.2
IL_0002:DIV
IL_0003:RET
法计划} //结束::分频器
与
IL_0000:ldarg.0
IL_0001:ldc.i4.1
IL_0002:SHR
IL_0003:RET
法计划} //结束::移位
因此,C#编译器发出 DIV
或 SHR
的说明,而不聪明。我现在想看到抖动产生的实际x86汇编,但我不知道如何做到这一点。它甚至有可能?
修改导入
感谢您的回答,已经接受了nobugz的一个,因为它包含有关调试选项的关键信息。是什么最终为我工作是:
切换到发布配置 在工具|选项|调试器
,关闭模块负载燮preSS JIT优化(即我们想要的允许的JIT优化)
在同一个地方,关掉启用仅我的code'(即我们要调试的所有的code)
将一个 Debugger.Break()
语句的地方
在生成组装
运行.exe文件,当它打破,调试使用现有的VS实例
现在,反汇编窗口会显示实际的x86,那将被执行
结果是启发,至少可以说 - 原来抖动实际上可以做算术!这里的编辑样本反汇编窗口。各种 -Shifter
方法除以使用二的幂>>
;各 -Divider
方法除以使用整数 /
Console.WriteLine(的String.Format(
{0}
换挡除以2:{1}
除以除以2:{2},
60,TwoShifter(60),TwoDivider(60)));
00000026 MOV DWORD PTR [EDX + 4],三通遥控
...
0000003b MOV DWORD PTR [EDX + 4],1EH
...
00000057 MOV DWORD PTR [ESI + 4],1EH
无论静态除以2的方法不仅被内联,但实际计算已经完成了抖动
Console.WriteLine(的String.Format(
{0}
除以除以3:{1},
60,ThreeDivider(60)));
00000085 MOV DWORD PTR [ESI + 4],三通遥控
...
000000a0 MOV DWORD PTR [ESI + 4],14H
同样的,静态除以3。
Console.WriteLine(的String.Format(
{0}
换挡除以4:{1}
除以除以4 {2},
60,FourShifter(60),FourDivider(60)));
000000CE MOV DWORD PTR [ESI + 4],三通遥控
...
000000e3 MOV DWORD PTR [EDX + 4]的0Fh
...
000000ff MOV DWORD PTR [ESI + 4]的0Fh
和静态除以4。
最好的:
Console.WriteLine(的String.Format(
{0}
正除以2:{1}
正除以3:{2}
正除以4:{3},
60,分频器(60,2),分频器(60,3),分频器(60,4)));
0000013e MOV DWORD PTR [ESI + 4],三通遥控
...
0000015b MOV DWORD PTR [ESI + 4],1EH
...
0000017b MOV DWORD PTR [ESI + 4],14H
...
0000019b MOV DWORD PTR [EDI + 4]的0Fh
它内联,然后计算出所有这些静态分裂!
但是如果结果不是静态的?我加入到code读取从控制台的一个整数。这就是它产生的分歧上:
Console.WriteLine(的String.Format(
{0}
换挡除以2:{1}
除以除以2:{2},
I,TwoShifter(i)中,TwoDivider(ⅰ)));
00000211特区EAX,1
...
00000230特区EAX,1
所以尽管CIL是不同的,抖动知道,再除以2是右移1。
Console.WriteLine(的String.Format(
{0}
除以除以3:{1},我,ThreeDivider(ⅰ)));
00000283 IDIV EAX,ECX
和它知道你要除以3分。
Console.WriteLine(的String.Format(
{0}
换挡除以4:{1}
除以除以4 {2},
I,FourShifter(i)中,FourDivider(ⅰ)));
000002c5特区EAX,2
...
000002ec特区EAX,2
和它知道,除以4是右移2。
最后(最好的一次!)
Console.WriteLine(的String.Format(
{0}
正除以2:{1}
正除以3:{2}
正除以4:{3},
I,分频器(1,2),分频器(ⅰ,3),分频器(ⅰ,4)));
00000345特区EAX,1
...
00000370 IDIV EAX,ECX
...
00000395特区ESI,2
据内联的方式,制定了做事情的最好方法的基础上,静态可用参数。尼斯。
所以,是的,在C#和x86,东西之间的协议栈的某处的是的聪明来制定出>> 1
和 / 2
是相同的。而所有这一切给了更多的重量在我心中我认为,加在一起的C#编译器,抖动,和CLR做出的一大堆更多克利夫研究比任何小动作,我们可以尝试卑微应用程序的程序员:)
您不会得到有意义的结果,直到你配置调试器。工具+选项,调试,通用,关闭模块负载燮preSS JIT优化。切换到发布模式配置。一个示例代码段:
静态无效的主要(字串[] args){
int值= 4;
INT结果= divideby2(值);
}
您这样做是正确的,如果拆卸看起来是这样的:
00000000 RET
您将不得不愚弄JIT优化,迫使前pression进行评估。使用Console.WriteLine(变量)可以提供帮助。然后,你应该看到这样的内容:
0000000A MOV EDX,2
0000000F MOV EAX,DWORD PTR [ECX]
00000011电话DWORD PTR [EAX + 000000BCh]
是的,它评估的结果在编译时。作品pretty的好,不是么。
In a comment on this answer (which suggests using bit-shift operators over integer multiplication / division, for performance), I queried whether this would actually be faster. In the back of my mind is an idea that at some level, something will be clever enough to work out that >> 1
and / 2
are the same operation. However, I'm now wondering if this is in fact true, and if it is, at what level it occurs.
A test program produces the following comparative CIL (with optimize
on) for two methods that respectively divide and shift their argument:
IL_0000: ldarg.0
IL_0001: ldc.i4.2
IL_0002: div
IL_0003: ret
} // end of method Program::Divider
versus
IL_0000: ldarg.0
IL_0001: ldc.i4.1
IL_0002: shr
IL_0003: ret
} // end of method Program::Shifter
So the C# compiler is emitting div
or shr
instructions, without being clever. I would now like to see the actual x86 assembler that the JITter produces, but I have no idea how to do this. Is it even possible?
edit to add
Thanks for answers, have accepted the one from nobugz because it contained the key information about that debugger option. What eventually worked for me is:
Switch to Release configuration InTools | Options | Debugger
, switch off 'Suppress JIT optimization on module load' (ie we want to allow JIT optimization)
Same place, switch off 'Enable Just My Code' (ie we want to debug all code)
Put a Debugger.Break()
statement somewhere
Build the assembly
Run the .exe, and when it breaks, debug using the existing VS instance
Now the Disassembly window shows you the actual x86 that's going to be executed
The results were enlightening to say the least - it turns out the JITter can actually do arithmetic! Here's edited samples from the Disassembly window. The various -Shifter
methods divide by powers of two using >>
; the various -Divider
methods divide by integers using /
Console.WriteLine(string.Format("
{0}
shift-divided by 2: {1}
divide-divided by 2: {2}",
60, TwoShifter(60), TwoDivider(60)));
00000026 mov dword ptr [edx+4],3Ch
...
0000003b mov dword ptr [edx+4],1Eh
...
00000057 mov dword ptr [esi+4],1Eh
Both statically-divide-by-2 methods have not only been inlined, but the actual computations have been done by the JITter
Console.WriteLine(string.Format("
{0}
divide-divided by 3: {1}",
60, ThreeDivider(60)));
00000085 mov dword ptr [esi+4],3Ch
...
000000a0 mov dword ptr [esi+4],14h
Same with statically-divide-by-3.
Console.WriteLine(string.Format("
{0}
shift-divided by 4: {1}
divide-divided by 4 {2}",
60, FourShifter(60), FourDivider(60)));
000000ce mov dword ptr [esi+4],3Ch
...
000000e3 mov dword ptr [edx+4],0Fh
...
000000ff mov dword ptr [esi+4],0Fh
And statically-divide-by-4.
The best:
Console.WriteLine(string.Format("
{0}
n-divided by 2: {1}
n-divided by 3: {2}
n-divided by 4: {3}",
60, Divider(60, 2), Divider(60, 3), Divider(60, 4)));
0000013e mov dword ptr [esi+4],3Ch
...
0000015b mov dword ptr [esi+4],1Eh
...
0000017b mov dword ptr [esi+4],14h
...
0000019b mov dword ptr [edi+4],0Fh
It's inlined and then computed all these static divisions!
But what if the result isn't static? I added to code to read an integer from the Console. This is what it produces for the divisions on that:
Console.WriteLine(string.Format("
{0}
shift-divided by 2: {1}
divide-divided by 2: {2}",
i, TwoShifter(i), TwoDivider(i)));
00000211 sar eax,1
...
00000230 sar eax,1
So despite the CIL being different, the JITter knows that dividing by 2 is right-shifting by 1.
Console.WriteLine(string.Format("
{0}
divide-divided by 3: {1}", i, ThreeDivider(i)));
00000283 idiv eax,ecx
And it knows you have to divide to divide by 3.
Console.WriteLine(string.Format("
{0}
shift-divided by 4: {1}
divide-divided by 4 {2}",
i, FourShifter(i), FourDivider(i)));
000002c5 sar eax,2
...
000002ec sar eax,2
And it knows that dividing by 4 is right-shifting by 2.
Finally (the best again!)
Console.WriteLine(string.Format("
{0}
n-divided by 2: {1}
n-divided by 3: {2}
n-divided by 4: {3}",
i, Divider(i, 2), Divider(i, 3), Divider(i, 4)));
00000345 sar eax,1
...
00000370 idiv eax,ecx
...
00000395 sar esi,2
It has inlined the method and worked out the best way to do things, based on the statically-available arguments. Nice.
So yes, somewhere in the stack between C# and x86, something is clever enough to work out that >> 1
and / 2
are the same. And all this has given even more weight in my mind to my opinion that adding together the C# compiler, the JITter, and the CLR makes a whole lot more clever than any little tricks we can try as humble applications programmers :)
You won't get meaningful results until you configure the debugger. Tools + Options, Debugging, General, turn off "Suppress JIT optimization on module load". Switch to the Release mode configuration. A sample snippet:
static void Main(string[] args) {
int value = 4;
int result = divideby2(value);
}
You are doing it right if the disassembly looks like this:
00000000 ret
You'll have to fool the JIT optimizer to force the expression to be evaluated. Using Console.WriteLine(variable) can help. Then you ought to see something like this:
0000000a mov edx,2
0000000f mov eax,dword ptr [ecx]
00000011 call dword ptr [eax+000000BCh]
Yup, it evaluated the result at compile time. Works pretty well, doesn't it.