[英]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
循环中使用迭代器然而,以上都没有带来任何改善。
我注意到一些有趣的事情:
main()
function (分配+计算)时,C++ 会好得多。 然而,这可能是由于 JVM 的预热时间所致。-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.