C#,C和OCaml中的模运算
我想确认模运算是一个昂贵的运算,所以我测试了这段代码,检查给定的数字是否为偶数:C#,C和OCaml中的模运算,c#,c,ocaml,complexity-theory,modulo,C#,C,Ocaml,Complexity Theory,Modulo,我想确认模运算是一个昂贵的运算,所以我测试了这段代码,检查给定的数字是否为偶数: bool is_even(int n) { return (n & 1) == 0; } 那么这个, bool is_even_bis(int n) { return (n % 2) == 0; } 我一开始使用C#,事实上,使用逻辑&的代码比另一个更快,有时甚至快三倍。使用ILSpy,我发现编译到MSIL时没有进行优化,代码完全相同 然而,正如我的一位朋友在C中发现的,使用gcc-O3
bool is_even(int n) {
return (n & 1) == 0;
}
那么这个,
bool is_even_bis(int n) {
return (n % 2) == 0;
}
我一开始使用C#,事实上,使用逻辑&
的代码比另一个更快,有时甚至快三倍。使用ILSpy,我发现编译到MSIL时没有进行优化,代码完全相同
然而,正如我的一位朋友在C中发现的,使用gcc-O3
将代码编译为:
is_even:
mov eax, DWORD PTR [esp+4] # tmp63, n
and eax, 1 # tmp63,
xor eax, 1 # tmp63,
ret
以及:
所以基本上是一样的。即使使用-O0
优化,操作也不会出现:
is_even:
push ebp #
mov ebp, esp #,
mov eax, DWORD PTR [ebp+8] # tmp63, n
and eax, 1 # D.1837,
test eax, eax # D.1837
sete al #, D.1838
movzx eax, al # D.1836, D.1838
pop ebp #
ret
不用说,编译后的代码在-O0
中的是偶数
和是偶数
之间也是相同的
更有趣的是,我的另一位朋友使用OCaml尝试了同样的方法:
let is_even x = ((x land 1) == 0)
let _ =
let i = ref 100000000 in
while !i > 0 do
ignore (is_even !i);
decr i
done
以及:
而且,在运行字节码时,模版本的速度似乎更快,但在本机代码中则更慢!也许有人能解释这个谜
然后我开始思考为什么它的行为与C#中的行为不同(这两个函数之间的性能存在明显差距),以及为什么JIT编译器没有应用与gcc
相同的优化。我不知道是否有办法截取JIT编译器的输出,也许这有助于理解
附加问题:我猜模是基于除法的,因为除法是在O(n²)时间内完成的(n是位数),我们能说模具有二次时间复杂度吗?首先,从可移植的意义上讲,这些运算没有速度的概念。您的断言对于您的系统可能是正确的,但对于所有系统都是无效的。出于这个原因,对微观优化的推测是毫无意义的。通过生成一个解决有意义问题的程序,对其进行分析以找到占用执行时间最多的代码部分,并在这些时间引入更快的算法,您可以找到更重要的优化。所谓更快的算法,我指的是更好的数据结构(或更少的操作),而不是不同的运算符。停止关注微观优化
你的
的C版本甚至没有很好的定义。它可能会产生负零或陷阱表示,尤其是负数。使用陷阱表示是未定义的行为
似乎您可能看到的差异可能是由您的系统上的错误引起的。考虑如果-1用补语<代码> 11111111…1111111×0 < /代码>来表示。您希望-1%2
的结果是-1,而不是0,不是吗?(编辑:…但是如果将-1表示为11111111…11111110
,您希望-1&1
会产生什么结果?)对于使用一个补码作为有符号整数表示的实现,需要一些开销来处理此问题
也许您的C编译器已经注意到您使用的%
表达式和您使用的&
表达式在您的系统上是等效的,因此进行了优化,但无论出于何种原因,C或OCaml编译器都没有执行优化
附加问题:我猜模是基于除法的,因为
除法是在O(n²)时间内完成的(n为位数),我们可以吗
假设模具有二次时间复杂度
考虑这两个基本操作的时间复杂性是没有意义的,因为它们会因系统而异。我在第一段中谈到了这一点。我们可以说模具有二次时间复杂度吗?不,你肯定是在安排发布时间,是吗?@MatthewWatson:我不是,我只是好奇:)@HenkHolterman你能详细说明一下吗?“的C版本甚至没有很好的定义。它可能会产生负零或陷阱表示,”不,一的补码机上的负值是错误的,但仅此而已。@DanielFischer“如果实现不支持负零,则&、|、^、~、运算符与操作数的行为将产生这样一个值是未定义的。”是的,但x&1
不会产生负零x&1
只能产生(无符号/非负)零或1。@DanielFischer标准状态是否在&
运算符对符号位进行操作的任何位置?“二进制和运算符的结果是按位和操作数的结果(也就是说,当且仅当转换后的操作数中的每个对应位都已设置时,才会设置结果中的每个位)。”不是说“值位”;我猜它如何处理填充位取决于实现,与算术运算相反,它不能明确保证逐位运算无法生成陷阱表示(或者我还没有找到保证)。但是如果除了算术运算之外的所有东西都被允许随意生成陷阱表示,那就太糟糕了。
let is_even x = ((x land 1) == 0)
let _ =
let i = ref 100000000 in
while !i > 0 do
ignore (is_even !i);
decr i
done
let is_even_bis x = ((x mod 2) == 0)
let _ =
let i = ref 100000000 in
while !i > 0 do
ignore (is_even_bis !i);
decr i
done