比较运算符性能:<= 与 != 的比较

5

首先,代码可读性胜过微观优化,我们应该将微观优化留给编译器。这只是一个奇怪的情况,具体细节对于一般建议来说似乎很有趣。

所以我在尝试生成质数的函数时遇到了一个奇怪的行为,其中 "!=" 被人们认为是最有效的,但实际上是最低效的,而 "<=" 则是最差的选择,却是最好的选项。

C#

private static void Main(string[] args) {
  long totalTicks = 0;
  for (int i = 0; i < 100; ++i) {
    var stopWatch = Stopwatch.StartNew();
    PrintPrimes(15000);
    totalTicks += stopWatch.ElapsedTicks;
  }
  Console.WriteLine("\n\n\n\nTick Average: {0}", totalTicks / 100);
  Console.Read();
}

private static void PrintPrimes(int numberRequired) {
  if (numberRequired < 1)
    return;
  Console.Write("{0}\t", 2);
  int primeTest = 3;
  /****** UPDATE NEXT TWO LINES TO TEST FOR != *****/
  int numPrimes = 2;  // set numPrimes = 1 for !=
  while (numPrimes <= numberRequired) {  // switch <= to !=
    if (IsPrime(primeTest)) {
      Console.Write("{0}\t", primeTest);
      ++numPrimes;
    }
    primeTest += 2;
  }
}

private static bool IsPrime(int test) {
  for (int i = 3; i * i <= test; i = 2 + i)
    if (test % i == 0)
      return false;
  return true;
}

输出:

<= 1319991
!= 1321251

同样地,在C++中(在不同的机器上)
include <cstddef>
#include <limits>

int main() {
  for(size_t i(0) ; i <= 10000000000 ; ++i);
}

输出:

<=

real        0m16.538s
user        0m16.460s
sys        0m0.000s
~ [master] $ vim d.cc

!=

real        0m16.860s
user        0m16.780s
sys        0m0.000s

这些循环运行相同的次数。是否有针对<=的优化,而不适用于!=或者是一些奇怪的CPU行为?


C#和C++程序并不做相同的事情,你不能将它们进行比较!同时,你还需要考虑计算机在运行你的程序时后台运行了哪些其他任务,如果有一个繁重的进程在运行,那么你的进程也会变慢。 - Some programmer dude
实际上,我曾在C++上运行过一个类似的克隆程序,用于生成质数,其输出行为与此相同。我只是想展示这一点,因为它似乎与质数生成中的某些奇怪现象无关。我也会添加另一个C++代码。 - Viv
1
你需要做的第一件事是确保C#和C++程序执行相同的操作。然后,确保程序在所有运行期间使用相同的运行时环境。最后,在进行任何结论之前多次运行所有程序,并对时间进行平均处理。 - Some programmer dude
2
我没有看到性能上的差异。你了解随机变异显著性检验吗? - Konrad Rudolph
2
@JoachimPileborg:虽然C#和C++程序确实不同,但除了你之外,似乎没有人在比较它们。他正在比较C#中的“!=”和“<”,以及C++中的“!=”和“<”。我没有看到任何关于C#和C++的比较。 - Mooing Duck
1个回答

10

假设循环次数相同,那么存在差异是没有意义的。如果我们假设它是x86处理器,!=会转化为jne(或者根据跳转条件是“它是”还是“它不是”转化为je),<=将会转化为jle或者jgt取决于循环方向。虽然指令不同,但其它处理器也有类似的指令。

我怀疑你可能存在测量误差。在16秒内小于0.2秒的差异并不是很大,你可能只是多收到了一些网络数据包、硬盘中断或后台进程运行等。

[1] 例如,有一个固定迭代次数的for循环通常只有一个“如果不成立,跳转到循环开始”语句,while循环也是如此。

我刚在我的机器上运行了这个代码:

bool IsPrime(int test) {
  for (int i = 3; i * i <= test; i = 2 + i)
    if (test % i == 0)
      return false;
  return true;
}

void PrintPrimes(int numberRequired) {
  if (numberRequired < 1)
    return;
  int primeTest = 3;
  /****** UPDATE NEXT TWO LINES TO TEST FOR != *****/
  int numPrimes = 2;  // set numPrimes = 1 for !=
  while (numPrimes != numberRequired) {  // switch <= to !=
    if (IsPrime(primeTest)) {
      ++numPrimes;
    }
    primeTest += 2;
  }
}

int  main() 
{
  long totalTicks = 0;
  for (int i = 0; i < 100; ++i) {
    PrintPrimes(15000);
  }
}

使用 g++ -O3 primes.cpp 编译。在主循环中使用 !=<= 的区别并不明显。使用 != 最快时间为 3.326 秒,<= 最快时间为 3.329 秒,最慢的 != 时间为 3.332 秒,<= 的时间为 3.335 秒。之前已在我的机器上运行了许多基准测试,我知道毫秒数没有实质性的差别,因此我会说它们都需要 3.33 秒。

并且只是为了确认:

--- primesne.s  2013-04-30 23:52:10.840513380 +0100
+++ primesle.s  2013-04-30 23:52:35.457639603 +0100
@@ -46,7 +46,7 @@
 .L3:
    addl    $2, %esi
    cmpl    $15000, %edi
-   jne .L10
+   jle .L10
    subl    $1, %r9d
    jne .L2
    xorl    %eax, %eax

"不等于"和"小于等于"之间的全部区别在于指令jnejle - 这是使用g ++编译两个代码变体的汇编程序输出 - 这是从diff得到的全部输出。


这就是最清晰的表述了。有道理,两个运算符之间实际上没有任何区别,只是由于机器上的一些随机操作导致时间跨度计算错误的情况。 - Viv
只是为了确认,我也添加了编译器的汇编输出。 - Mats Petersson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接