与Java相比,为什么此C 代码执行速度如此慢

Why is this C++ code execution so slow compared to java?

本文关键字:执行 速度 代码 相比 为什么 Java      更新时间:2023-10-16

我最近在Java中编写了一种计算密集型算法,然后将其转换为C 。令我惊讶的是,C 执行速度较慢。现在,我编写了一个短得多的Java测试程序和一个相应的C 程序 - 见下文。我的原始代码和测试代码都有很多数组访问。C 执行时间需要更长的5.5倍(请参阅每个程序末尾的注释(。

1 st 21评论以下结论...

测试代码:

  1. g++ -o ... Java更快5.5倍
  2. g++ -O3 -o ... Java 2.9倍更快
  3. g++ -fprofile-generate -march=native -O3 -o ...(运行,然后是g++ -fprofile-use等(Java 1.07倍。

我的原始项目(比测试代码复杂得多(:

  1. Java更快1.8倍
  2. C 1.9倍更快
  3. C 2倍
Software environment:
    Ubuntu 16.04 (64 bit).
    Netbeans 8.2 / jdk 8u121 (java code executed inside netbeans)
    g++ (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609
    Compilation: g++ -o cpp_test cpp_test.cpp

Java代码:

public class JavaTest {
    public static void main(String[] args) {
        final int ARRAY_LENGTH = 100;
        final int FINISH_TRIGGER = 100000000;
        int[] intArray = new int[ARRAY_LENGTH];
        for (int i = 0; i < ARRAY_LENGTH; i++) intArray[i] = 1;
        int i = 0;
        boolean finished = false;
        long loopCount = 0;
        System.out.println("Start");
        long startTime = System.nanoTime();
        while (!finished) {
            loopCount++;
            intArray[i]++;
            if (intArray[i] >= FINISH_TRIGGER) finished = true;
            else if (i <(ARRAY_LENGTH - 1)) i++;
            else i = 0;
        }
        System.out.println("Finish: " + loopCount + " loops; " +
            ((System.nanoTime() - startTime)/1e9) + " secs");
        // 5 executions in range 5.98 - 6.17 secs (each 9999999801 loops)
    }
}

C 代码:

//cpp_test.cpp:
#include <iostream>
#include <sys/time.h>
int main() {
    const int ARRAY_LENGTH = 100;
    const int FINISH_TRIGGER = 100000000;
    int *intArray = new int[ARRAY_LENGTH];
    for (int i = 0; i < ARRAY_LENGTH; i++) intArray[i] = 1;
    int i = 0;
    bool finished = false;
    long long loopCount = 0;
    std::cout << "Startn";
    timespec ts;
    clock_gettime(CLOCK_REALTIME, &ts);
    long long startTime = (1000000000*ts.tv_sec) + ts.tv_nsec;
    while (!finished) {
        loopCount++;
        intArray[i]++;
        if (intArray[i] >= FINISH_TRIGGER) finished = true;
        else if (i < (ARRAY_LENGTH - 1)) i++;
        else i = 0;
    }
    clock_gettime(CLOCK_REALTIME, &ts);
    double elapsedTime =
        ((1000000000*ts.tv_sec) + ts.tv_nsec - startTime)/1e9;
    std::cout << "Finish: " << loopCount << " loops; ";
    std::cout << elapsedTime << " secsn";
    // 5 executions in range 33.07 - 33.45 secs (each 9999999801 loops)
}

我唯一可以让C 程序以优于Java的时间是使用分析信息。这表明运行时信息(默认情况下是Java获取(允许更快执行。

除了非平凡的if语句外,您的程序中没有什么发生的。也就是说,在没有分析整个程序的情况下,很难预测最有可能的分支。这使我相信这是一个分支机构错误预测的问题。现代CPU进行管道说明,允许更高的CPU吞吐量。但是,这需要预测下一个执行指令是什么。如果猜测是错误的,则必须清除指令管道,并加载正确的说明(这需要时间(。

在编译时,编译器没有足够的信息来预测最可能的分支。CPU也做了一些分支预测,但这通常沿着循环的线路和IF(而不是(。

。 但是,

java具有能够在运行时使用信息以及编译时间的优势。这使Java可以将中间分支识别为最频繁的分支,因此可以预测该分支。

gcc和clang均无法展开此循环,甚至在-O3和-OS中拔出不变性,但是Java却可以。

java的最终夹具组装代码与此相似(实际上重复两次(:

    while (true) {
        loopCount++;
        if (++intArray[i++] >= FINISH_TRIGGER) break;
        loopCount++;
        if (++intArray[i++] >= FINISH_TRIGGER) break;
        loopCount++;
        if (++intArray[i++] >= FINISH_TRIGGER) break;
        loopCount++;
        if (++intArray[i++] >= FINISH_TRIGGER) { if (i >= ARRAY_LENGTH) i = 0; break; }
        if (i >= ARRAY_LENGTH) i = 0;
    }

使用此循环,我得到了C 和Java之间完全相同的时间(6.4s(。

为什么这是合法的?因为ARRAY_LENGTH为100,这是4个倍数。因此,i只能超过100,并将每4个迭代重置为0。

这似乎是GCC和Clang改进的机会;他们无法展开迭代总数未知的循环,但是即使强迫展开,他们也无法识别仅适用于某些迭代的循环的一部分。

关于您在更复杂的代码中的发现(又称现实生活(:Java的优化器对小循环非常有用,其中已经进行了很多思考,但是Java在虚拟呼叫和GC上浪费了很多时间。<<<<<<<<<<<<<<<<<<<<

最后,它归结为在混凝土架构上运行的机器说明,无论谁提出了最好的套装,胜利。不要以为编译器将"做正确的事情",外观和生成的代码,配置文件,重复。

例如,如果您将循环重组一点:

    while (!finished) {
        for (i=0; i<ARRAY_LENGTH; ++i) {
            loopCount++;
            if (++intArray[i] >= FINISH_TRIGGER) {
                finished=true;
                break;
            }
        }
    }

然后,C 将胜过Java(5.9s vs 6.4s(。(修订的C 组件(

,如果您可以轻微的超支(到达出口条件后增加更多的intArray元素(:

    while (!finished) {
        for (int i=0; i<ARRAY_LENGTH; ++i) {
            ++intArray[i];
        }
        loopCount+=ARRAY_LENGTH;
        for (int i=0; i<ARRAY_LENGTH; ++i) {
            if (intArray[i] >= FINISH_TRIGGER) {
                loopCount-=ARRAY_LENGTH-i-1;
                finished=true;
                break;
            }
        }
    }

现在,Clang能够向量化循环并达到 3.5S vs. Java的 4.8S 的速度(不幸的是,GCC仍然无法向量化(。/p>