【问题标题】:Executable 3 times slower on Windows (Mingw64) than Linux在 Windows (Mingw64) 上的执行速度比 Linux 慢 3 倍
【发布时间】:2018-11-06 19:12:29
【问题描述】:

我正在开发一个在 Windows 上比在 Linux 上慢 3-4 倍的游戏引擎。 我试图分析应用程序,除了 Windows 上的一切似乎更慢之外,我没有看到特别的问题。 我提取了应用程序的一小部分。我用g++ -O3 perf.cpp 编译它并执行如下:a.exe 500000000。结果如下:

  • Linux:10 秒(平均 5 次执行):g++ 8.2 [也使用 g++ 7.3 测试]
  • Windows:27 秒(5 次执行的平均值):g++(x86_64-posix-seh-rev0,由 MinGW-W64 项目构建)8.1.0 [也使用 g++ 7.1 测试]

源代码:

#include <iostream>
#include <cmath>
#include <vector>
#include <chrono>

struct Vector{
    float X, Y, Z;

    Vector(float X, float Y, float Z) : X(X), Y(Y), Z(Z){}

    Vector vector(const Vector &target) const{
        return Vector(target.X - X, target.Y - Y, target.Z - Z);
    }

    float dotProduct(const Vector &v) const{
        return (X*v.X + Y*v.Y + Z*v.Z);
    }
};

float compute(const std::vector<Vector> &v){
    Vector vec1 = v[0].vector(v[2]);
    Vector vec2 = v[1].vector(v[0]);
    return vec1.dotProduct(vec2);
}

int main(int argc, char *argv[]){
    unsigned int loopMax = atoi(argv[1]);

    Vector va(1.5f, 3.0f, 8.0f*loopMax);
    Vector vb(1.2f, 2.3f, 11.0f*loopMax);
    Vector vc(8.2f, 5.0f, 12.0f*loopMax);

    auto frameStartTime = std::chrono::high_resolution_clock::now();

    float res = 0.0f;
    for(unsigned int i=0; i<loopMax; ++i)
    {       
        res += compute({va, vb, vc});
    }

    auto frameEndTime = std::chrono::high_resolution_clock::now();
    auto diffTimeMicroSeconds = std::chrono::duration_cast<std::chrono::microseconds>(frameEndTime - frameStartTime).count();

    std::cout<<"Time: "<<diffTimeMicroSeconds / 1000000.0 <<" sec, res: "<<res<<std::endl;
    return 0;
}

我知道一次迭代的差异是荒谬的(

什么可以解释这种差异?如何发现问题?

【问题讨论】:

  • 为这两种情况生成的程序集可能会有所帮助。此外,您应该将代码作为文本粘贴到您的问题中。
  • 您是否在 Windows 上尝试过不同的编译器?我的意思是,gcc 相当不错,但在 Windows 上,Visual Studio 编译器通常更好。也许也可以试试 clang。
  • 在循环中,std::vector&lt;Vector&gt; 参数可能会分配给compute。这可能会导致依赖于操作系统/标准库实现的性能。
  • @eukaryota 这可能是它 - 堆分配是有保证的,现在只有 clang 可以做堆省略 AFAIK。
  • -march=native -flto 怎么样?

标签: c++ mingw-w64


【解决方案1】:

尝试在循环之外构造compute 的参数。如果编译器没有省略 std::vector&lt;Vector&gt; 参数的构造,这很可能会导致堆分配:

std::vector<Vector> arg{va, vb, vc};
for(unsigned int i=0; i<loopMax; ++i)
{       
    res += compute(arg);
}

如果有一个堆分配,它很可能会比循环内容的其余部分花费更多的时间。堆分配需要多长时间可能会因系统和实现而有很大差异。删除它可能会显着提高这两种情况下的性能。

如果在实际代码中每次都需要构造向量,那么您应该考虑使用固定大小的数组(原始数组或 std::array),它不会在堆上分配,而是在堆栈上分配,这要快得多。这似乎适用,因为您在 compute 实现中使用了这三个元素。

如果你在编译时不知道向量的长度并且需要每次在热循环中重建它,那么你也许可以重用堆- 分配的空间,如果您可以指定最大长度的正确猜测,则更是如此:

std::vector<Vector> arg;
arg.reserve(1000); // Allocate for up to 1000 element
for(unsigned int i=0; i<loopMax; ++i)
{   
    arg.clear();
    arg.push_back(va);
    [...]
    arg.push_back(vn);
    res += compute(arg);
}

【讨论】:

  • 确实,我并不是说代码优化得非常好。我已经尝试将 3 个参数传递给方法而不是向量,并且差异很大。我关心的是Linux和Windows之间的执行时间差异。在 Windows 上内存分配可以慢 3 倍吗?这对我来说似乎很大。你怎么看?
  • @OlivierMartin 正如已经说过的,没有看到生成的程序集,很难说。编译器也可能对差异负责。我不知道 3 倍的差异是否合理,但对我来说这听起来并不太令人惊讶。您实际上只是在对分配调用进行基准测试。 already try to pass 3 arguments to method instead of vector 是什么意思?不过,您仍然使用compute({va, vb, vc}) 进行堆分配,因为这是使用给定初始化列表构造函数参数类型的新参数的简写。
  • 我的意思是,我将方法签名更改为“compute(const Vector &v0, const Vector &v1, const Vector &v2)”并使用“compute(va, vb, vc)”调用方法。我将尝试获取程序集(以前从不这样做)并尝试另一个编译器。谢谢你的回答。
  • @OlivierMartin 在这种情况下,堆分配应该消失了,我希望结果会更加相似。如果仍然不是这种情况,请将新代码与程序集一起发布。
  • 不进行堆分配,结果类似。没想到OS之间的分配有这么大的区别!我将检查我的整个应用程序并确保缓慢的分配可以解释我的执行时间。谢谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-15
  • 1970-01-01
  • 1970-01-01
  • 2012-09-20
  • 2016-08-13
  • 2014-09-12
相关资源
最近更新 更多