繁体   English   中英

为什么在这种情况下,我的 Java 代码比我的 C++ 代码运行得快?

[英]Why in this case, my Java code runs faster than my C++ code?

我写了一个小基准,程序在其中创建了 10 8个二维std::vector结构{float, float} ,然后将它们的长度的平方相加。

这是 C++ 代码:

#include <iostream>
#include <chrono>
#include <vector>
#include <array>
#include <cmath>
    
using namespace std;
using namespace std::chrono;
    
const int COUNT = pow(10, 8);
    
class Vec {
public:
    float x, y;
    
    Vec() {}
    
    Vec(float x, float y) : x(x), y(y) {}
    
    float len() {
        return x * x + y * y;
    }
};
    
int main() {
    vector <Vec> vecs;
    
    for(int i = 0; i < COUNT; ++i) {
        vecs.emplace_back(i / 3, i / 5);
    }
    
    auto start = high_resolution_clock::now();
    
    // This loop is timed
    float sum = 0;
        for(int i = 0; i < COUNT; ++i) {
        sum += vecs[i].len();
    }
    
    auto stop = high_resolution_clock::now();
    
    cout << "finished in " << duration_cast <milliseconds> (stop - start).count()
         << " milliseconds" << endl;
    cout << "result: " << sum << endl;
    
    return 0;
}

为此,我使用了这个 makefile(g++ 版本 7.5.0):

build:
 g++ -std=c++17 -O3 main.cpp -o program #-ffast-math 
    
run: build
 clear
 ./program

这是我的 Java 代码:

public class MainClass {
    static final int COUNT = (int) Math.pow(10, 8);

    static class Vec {
        float x, y;

        Vec(float x, float y) {
            this.x = x;
            this.y = y;
        }

        float len() {
            return x * x + y * y;
        }
    }

    public static void main(String[] args) throws InterruptedException {

        Vec[] vecs = new Vec[COUNT];

        for (int i = 0; i < COUNT; ++i) {
            vecs[i] = new Vec(i / 3, i / 5);
        }

        long start = System.nanoTime();

        // This loop is timed
        float sum = 0;
        for (int i = 0; i < COUNT; ++i) {
            sum += vecs[i].len();
        }

        long duration = System.nanoTime() - start;
        System.out.println("finished in " + duration / 1000000 + " milliseconds");
        System.out.println("result: " + sum);
    }
}

使用 Java 11.0.4 编译和运行

以下是结果(几次运行的平均值,在 ubuntu 18.04 16bit 上运行):

c++:  262 ms
java: 230 ms

为了使 c++ 代码更快,我尝试了以下一些方法:

  • 使用std::array代替std::vector
  • 使用普通数组而不是std::vector
  • for循环中使用迭代器

然而,以上都没有带来任何改善。

我注意到一些有趣的事情:

  1. 当我计算整个main() function (分配+计算)时,C++ 会好得多。 然而,这可能是由于 JVM 的预热时间所致。
  2. 对于较少数量的对象,例如 10 7 , C++ 稍微快一些(几毫秒)。
  3. 开启-ffast-math可使 C++ 程序比 Java 快几倍,但计算结果略有不同。 此外,我在一些帖子中读到使用这个标志是不安全的。

在这种情况下,我可以以某种方式修改我的 C++ 代码并使其与 Java 一样快或快吗?

尝试这个:

    float sum = std::transform_reduce(
        std::execution::par_unseq,
        begin(vecs), end(vecs),
        0.f,
        std::plus<>{},
        [](auto&& x){
            return x.len();
        }
    );

这明确地告诉 C++ 编译器你在做什么,你可以使用额外的线程,每个循环迭代不依赖于其他循环,并且你想在float中完成工作。

这确实意味着与您要求的相比,添加可能会出现乱序,因此 output 值可能不完全相同。

一侧带有原始循环的实时示例,另一侧带有乱序添加的权限。


进一步的调查:

所以我旋转了一个神螺栓

在其中,我比较了使用和不使用强制矢量化和-ffast-math情况。 强制矢量化和-ffast-math会产生相同的汇编代码。

问题是蓄能器。 一次将一个事物添加到总和中并进行所有 IEEE 舍入会给您一个不同的值,而不是一次以更高精度的浮点值累积它们 N,然后将结果整体存储回浮点数。

如果您执行-ffast-math ,您将获得 2 倍的速度和不同的累积。 如果将float sum替换为double sum ,您将得到与--ffast-math和 vectorizaton on相同的答案

基本上,clang 向量化器看不到一种简单的方法来对总和的累加进行向量化,而不会破坏精确的浮点精度浮点要求。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM