识别奇数,偶数 - 二进制与mod

时间:2010-10-11 20:20:01

标签: c# performance

最近,我必须确定一个数字是奇数还是偶数为大数。我想到了一个想法,将一个数字识别为奇数或甚至通过与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();

有没有人见过二进制方法实现?有什么缺点吗?

7 个答案:

答案 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。编译器可能会认识到等价,但它们可能不会。