C++与Java基准测试,结果不切实际

3

我做了一个简单的测试,我知道C++更快,但我的测试结果不太现实。

C++代码如下:

#include <stdio.h>
#include <windows.h>

unsigned long long s(unsigned long long n)
{
    unsigned long long s = 0;

    for (unsigned long long i = 0; i < n; i++)
        s += i;

    return s;
}

int main()
{
    LARGE_INTEGER freq, start, end;
    QueryPerformanceFrequency(&freq);
    QueryPerformanceCounter(&start);

    printf("%llu\n", s(1000000000));

    QueryPerformanceCounter(&end);
    double d = (double) (end.QuadPart - start.QuadPart) / freq.QuadPart * 1000.0;

    printf("Delta: %f\n", d);

    return 0;
}

Java代码如下:

public class JavaApplication5 {

    public static long s(long n) {
        long s = 0;

        for (long i = 0; i < n; i++) {
            s += i;
        }

        return s;
    }

    public static void main(String[] args) {

        long start = System.nanoTime();

        System.out.println(s(1000000000));

        long end = System.nanoTime();

        System.out.println((end - start)/1000000);
    }
}

C++编译器:gcc 4.4.0和Java:jdk 1.6.0

Java:2795毫秒

C++:0.013517毫秒

它说C ++比Java快206777倍!不可能!我的测试有什么问题?


1
你的比较基于一个不现实的代码片段。不仅如此,将语言进行比较就像是在把苹果和橙子进行对比。或许你可以拿同一种语言的不同实现进行比较,但如果是不同语言的不同实现,那几乎没有什么意义。 - Khaled Alshaya
1
你的基准测试没有问题。当比较C++和Java的性能时,通常会比较一个平均程序,该程序执行一些输入、输出、用户界面和一些计算。实际计算在C/C++中要快得多。此外,如果使用float或double值,则差异将更加明显。 - Aleks G
1
@bdares,您是否建议C++在递增整数方面比Java快206776倍? - Khaled Alshaya
6
你的基准测试有很多问题,但其中一个主要问题是:Java基准测试中包含了一些启动时间。第一次调用 System.out.println() 可能会导致加载许多之前不需要的类,我猜这一个因素就占据了相当大一部分差距。请注意删除这个因素对结果的影响。 - Joachim Sauer
1
@AleksG:基准测试有很多问题。测量区域内包含I/O代码。而且,测量的代码只运行一次,这意味着JIT的成本没有分摊。(最好在计时开始之前先运行一次)。并且没有尝试防止编译器将函数内联并将循环简化为单个赋值。 - Ben Voigt
显示剩余2条评论
3个回答

7
展示您使用的编译器选项和真实代码(#include <stdio>不是您的真实代码)。C++编译器比Java编译器聪明得多(这在平均水平和您的情况下是正确的,但并非每个C++编译器都比每个Java编译器聪明),它预先计算了结果。您测量的唯一事物是printf调用。在大多数Java使用的任务中,它的表现与C++相当。VM语言(Java、C#)有与JIT编译相关的额外成本,但也受益于更高效的内存分配和跨共享库的内联。而C++在访问操作系统系统调用方面要快得多。除此之外,C++内存布局可以被精心调整以获得缓存行为;在托管语言中,您无法获得这种级别的控制。哪些因素具有更大的影响完全取决于应用程序。任何人对“C++通常比Java更快”或“Java通常比C++更快”的断言都是白痴。平均值并不重要。在您的应用程序上的性能才是最重要的。
这是我证明gcc正在预先计算答案的证据。
在这段代码中:
#include <stdio.h>
#include <windows.h>

unsigned long long s(unsigned long long n)
{
    unsigned long long s = 0;

    for (unsigned long long i = 0; i < n; i++)
        s += i;

    return s;
}

int main( int argc, char** argv )
{
    LARGE_INTEGER freq, start, end;
    QueryPerformanceFrequency(&freq);
    QueryPerformanceCounter(&start);

    printf("%llu\n", s(1000000000));

    QueryPerformanceCounter(&end);
    double d = (double) (end.QuadPart - start.QuadPart) / freq.QuadPart * 1000.0;

    printf("Delta: %f\n", d);

    QueryPerformanceCounter(&start);

    printf("%llu\n", s(atol(argv[1])));

    QueryPerformanceCounter(&end);
    d = (double) (end.QuadPart - start.QuadPart) / freq.QuadPart * 1000.0;

    printf("Delta: %f\n", d);
    return 0;
}

使用gcc-4.3.4版本,通过命令行./g++-4 -omasoud-gcc.exe -O3 masoud.cpp编译。

bash-3.2# ./masoud-gcc 1000000000
499999999500000000
Delta: 0.845755
499999999500000000
Delta: 1114.105866

相比之下,MSVC++ 16.00.40219.01 for x64 (2010 SP1) 的命令行 cl /Ox masoud.cpp

> masoud 1000000000
499999999500000000
Delta: 229.684364
499999999500000000
Delta: 354.275606

VC++并没有预先计算答案,但64位代码执行循环的速度比32位快了三倍以上。这就是Java应该达到的速度。


更有趣的事实:gcc预先计算答案的速度比生成的代码计算要快。gcc的编译时间为:

real    0m0.886s
user    0m0.248s
sys     0m0.185s

1
这并不一定是C++编译器更聪明(但也许是)。很多情况下取决于代码的位置---典型的JVM编译器只有在函数被调用多次后才会优化代码。而好的C++编译器可以轻松推断您将恰好执行n次循环,并且算术级数求和的结果可以在没有循环的情况下计算出来。这是一种并不测试任何东西的基准。 - James Kanze

2

我猜gcc编译了你的s方法为:

unsigned long long s(unsigned long long n)
{    
    return n*(n+1)/2;
}

虽然Java JIT没有这样做,但gcc优化过程可能包含比JIT更多的步骤,在合理的时间内JIT无法完成。这也是为什么gcc编译可能需要很长时间,而Java程序可以立即启动(尽管JIT必须在程序运行之前编译、链接和优化所有内容)。

Java编译器(javac)只进行了很少的优化,如常量折叠,但这几乎是全部的。所有主要的优化(内联等)都是由JIT完成的,因此如果它不希望用户在启动之前等待太长时间,它必须赶快。另一方面,由于gcc静态地编译和优化所有内容,它可以花费自己需要的时间。

编辑:

要知道是否有优化差异应该很简单:使用s(1000)运行两个程序,然后再使用s(100000000000000)。如果我说得对,C++程序在两次调用中可能需要相同的时间,而Java程序在第二种情况下需要更长的时间才能完成。


2
我怀疑任何C++编译器都可以做到这一点。在进行优化时,您必须确保程序的语义得到保留,并且据我所知,您所要求的需要智能 - Khaled Alshaya
哇,用上面的函数替换s中的循环,在JDK 1.6.0中返回执行时间为0毫秒。至少在我的机器上是这样。 - home
实际上,System.nanotime() 是使用 QueryPerformance API 实现的,因此结果应该是等效的。然而,规范指出“不保证值的更改频率”,所以这可能是您的情况发生的事情。 - Aurelien Ribon
1
不,gcc没有转换该方法。即使是在运行时并非已知的任何 *n*,它也会非常快,如果它确实这样做了的话。它所做的优化是将 s(1000000000) 替换为 499999999500000000,这依赖于 n 在调用站点处是编译时常量。 - Ben Voigt
答案仍然是一个好的答案。所讨论的代码是一个典型的例子,gcc(以及可能每个其他相当不错的编译器)通常在编译时使用“-O2”或更高版本解决它,用常量替换整个函数调用。这(除了强制异常和垃圾回收)是C++比Java更快的主要原因(尽管Java粉丝说的不同)。它之所以更快,并不是因为C++更好,而是因为a)两种语言做不同的事情,b)其中一种不能轻松地执行另一种可以执行的常量优化类型。 - Damon

-1
首先,您可能不希望相关的打印函数成为您的基准的一部分,除非您真的关心它们的速度。
关于Java或C++哪个更快,没有一个直接的答案...这取决于情况。当编译器可以进行一些C++无法使用的优化时,Java可能会更快。因此,这取决于您具体要做什么以及编译器选项是什么。

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