最近,我必须确定一个数字是奇数还是偶数为大数。我想到了一个想法,将一个数字识别为奇数或甚至通过与1对比并将结果与1进行比较
x & 1 == 1 // even or odd
我从未在实践中看到过这种实现。您经常看到的最常见的方式是:
x % 2 == 0
我决定对这两种方法进行一些性能检查,而我的机器上的二进制方法似乎稍快一点。
int size = 60000000;
List<int> numberList = new List<int>();
Random rnd = new Random();
for (int index = 0; index < size; index++)
{
numberList.Add(rnd.Next(size));
}
DateTime start;
bool even;
// regular mod
start = DateTime.Now;
for (int index = 0; index < size; index++)
{
even = (numberList[index] % 2 == 0);
}
Console.WriteLine("Regualr mod : {0}", DateTime.Now.Subtract(start).Ticks);
// binary
start = DateTime.Now;
for (int index = 0; index < size; index++)
{
even = ((numberList[index] & 1) != 1);
}
Console.WriteLine("Binary operation: {0}", DateTime.Now.Subtract(start).Ticks);
Console.ReadKey();
有没有人见过二进制方法实现?有什么缺点吗?
答案 0 :(得分:20)
嗯,是的,这是一个轻微的优化。此代码段:
uint ix = 3; // uint.Parse(Console.ReadLine());
bool even = ix % 2 == 0;
在发布版本中生成此机器代码:
uint ix = 3;
0000003c mov dword ptr [ebp-40h],3
bool even = ix % 2 == 0;
00000043 mov eax,dword ptr [ebp-40h]
00000046 and eax,1
00000049 test eax,eax
0000004b sete al
0000004e movzx eax,al
00000051 mov dword ptr [ebp-44h],eax
请注意,JIT编译器足够智能,可以使用AND处理器指令。它不会像%运算符通常执行那样进行除法。感谢那里。
但是您的自定义测试会生成以下代码:
uint ix = uint.Parse(Console.ReadLine());
// Bunch of machine code
bool even = (ix & 1) == 0;
00000024 test eax,1
00000029 sete al
0000002c movzx eax,al
0000002f mov esi,eax
我不得不改变赋值语句,因为JIT编译器突然变得聪明并在编译时计算了表达式。代码非常类似,但AND指令被TEST指令取代。在此过程中保存一条指令。相当具有讽刺意味的是,这次选择不使用AND:)
这些是做出假设的陷阱。你原来的本能是正确的,它应该节省大约半纳秒。 非常很难看到它,除非这段代码生活在一个非常紧凑的循环中。当您将变量从uint更改为int时,它会变得非常不同,然后JIT编译器会生成尝试对符号位进行智能化的代码。不必要的。
答案 1 :(得分:4)
对于这样的操作,你应该更喜欢更易读的方法(在我看来是模数方式)而不是被认为更快的方法。
此外,上面的模运算可以由编译器优化为按位和运算。因此,您实际上并不需要关心。
请注意您的示例:要获得更精确的结果,请考虑将要添加的项目数传递到列表的构造函数中。这样可以避免多次重新分配后备阵列引入的差异。对于6000万个整数项(approc.240 MB的内存),不预先分配内存可能代表一个重要的偏见。
答案 2 :(得分:3)
This webpage基准测试至少有六种方法可以确定一个数字是奇数还是偶数。
最快的是(我喜欢它易于阅读):
if (x % 2 == 0)
//even number
else
//odd number
以下是其他测试(code is here)。我实际上对按位和位移操作没有表现出最佳效果感到惊讶:
//bitwise
if ((x & 1) == 0)
//even number
else
//odd number
System.Math.DivRem((long)x, (long)2, out outvalue);
if ( outvalue == 0)
//even number
else
//odd number
if (((x / 2) * 2) == x)
//even number
else
//odd number
//bit shifting
if (((x >> 1) << 1) == x)
//even number
else
//odd number
index = NumberOfNumbers;
while (index > 1)
index -= 2;
if (index == 0)
//even number
else
//odd number
tempstr = x.ToString();
index = tempstr.Length - 1;
//this assumes base 10
if (tempstr[index] == '0' || tempstr[index] == '2' || tempstr[index] == '4' || tempstr[index] == '6' || tempstr[index] == '8')
//even number
else
//odd number
答案 3 :(得分:2)
按位并且将在每周的每一天击败模数除法。按任意数字划分需要很多时钟周期,而按位并且是一个基本的原始操作,几乎总是在1个时钟周期内完成,无论您的CPU架构如何。
您可能会看到,编译器可能正在使用位移位或位掩码指令替换x mod 2
,这些指令将与您自己的位掩码操作具有相同的性能。
要确认编译器正在使用您的代码进行技巧,请将x mod 2的性能与x mod 7或任何其他非基本2整数进行比较。 或模糊了编译器的操作数,使其无法执行优化:
var y = 2;
result = x mod y;
如果您发现执行时间与这些更改存在显着差异,那么这是一个非常强大的指标,表明编译器将x mod 2
视为一种特殊情况,而不使用实际除法来查找余数。
如果您要使用DateTime对单指令操作进行基准测试,请确保您有足够长的循环,测试运行至少5分钟左右,以使您的真实测量值高于本底噪声。
答案 4 :(得分:0)
二进制方法不会更快,因为编译器能够将其优化为位移而不是实际强制cpu执行除法计算吗?
答案 5 :(得分:0)
我同意其他答案,你应该使用模数检查,因为它最能传达意图。
但是,对于您的具体结果;尝试使用even
变量。它会产生显着的差异,因为编译器实际上可能会优化掉一些计算,因为它知道它不需要使用该值。
使用您的程序(修改为使用秒表),常规mod为70 ms,二进制操作为88 ms。如果我使用even
变量,差异要小得多(327 vs 316 ms),模数最快。
答案 6 :(得分:0)
对于无符号数,许多编译器会将'mod'运算符优化为'和'测试。对于带符号的数字,如果数字为奇数且为正数,则(x%2)将为1; -1如果它是奇数和负数;即使+1和-1都不为零,它们也可能无法被识别为等价物。
BTW,当使用“和”运算符时,我会测试!= 0而不是== 1。编译器可能会认识到等价,但它们可能不会。