在 C++ 中找不到 AoS 和 SoA 之间的性能差异

Posted

技术标签:

【中文标题】在 C++ 中找不到 AoS 和 SoA 之间的性能差异【英文标题】:Can't find a performance difference between AoS and SoA in C++ 【发布时间】:2019-06-12 11:38:49 【问题描述】:

我试图从实际意义上理解 AoS 和 SoA 之间的区别。

我已经在 C# 中尝试过,但没有产生任何结果,所以现在我在 C++ 中尝试。

#include <stdlib.h>
#include <chrono>
#include <iostream>
#include <math.h>

const int iterations = 40000000;

class Entity 
public:
    float a, b, c;
;

struct Entities 
public:
    float a[iterations];
    float b[iterations];
    float c[iterations];
;

void AoSTest(int iterations, Entity enArr[]);

void SoATest(int iterations, Entities* entities);

int main()

    Entity* enArr = new Entity[iterations];

    Entities* entities = new Entities;

    int A = rand() - 50;
    int B = rand() - 50;
    int C = rand() - 50;

    for (int i = 0; i < iterations; i++)
    
        enArr[i].a = A;
        enArr[i].b = B;
        enArr[i].c = C;

        entities->a[i] = A;
        entities->b[i] = B;
        entities->c[i] = C;
    

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

    AoSTest(iterations, enArr);
    //SoATest(iterations, entities);

    auto finish = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double> elapsed = finish - start;

    //std::cout << std::to_string(elapsed.count()) + "time";
    std::cout << std::to_string(std::chrono::duration_cast<std::chrono::seconds>(finish - start).count()) + "s";


void AoSTest(int iterations, Entity enArr[]) 
    for (int i = 0; i < iterations; i++)
       
        enArr[i].a = sqrt(enArr[i].a * enArr[i].c);
        enArr[i].c = sqrt(enArr[i].c * enArr[i].a);
        //std::cout << std::to_string(sqrt(enArr[i].a) + sqrt(enArr[i].b)) + "\n";
    


void SoATest(int iterations, Entities* entities) 
    for (int i = 0; i < iterations; i++)
    
        entities->a[i] = sqrt(entities->a[i] * entities->c[i]);
        entities->c[i] = sqrt(entities->c[i] * entities->a[i]);
        //std::cout << std::to_string(sqrt(entities->a[i]) + sqrt(entities->b[i])) + "\n";
    

我的想法是,由于理论上数据布局应该不同,因此应该有性能差异......

我不明白为什么有人说如果它对上下文如此敏感,到目前为止我认为它有很多好处。

它完全依赖于 SIMD 还是某些特定的优化选项?

我在 Visual Studio 中运行它。

【问题讨论】:

你是如何构建它的?东西被优化了吗? godbolt.org 会发生什么? 您是否构建了它并启用了优化?输出是什么? 似乎不会有性能差异。它更多地是关于你如何为你的记忆建模——这对你来说更容易理解。 【参考方案1】:

我使用英特尔编译器 18.0.1 编译了您的代码并开启了优化 (-O3)。我添加了一些返回值,只是为了确保没有任何东西可以被优化掉。

我发现数组结构 (SoA) 的速度大约是结构数组 (AoS) 的两倍。这是有道理的,因为如果您使用 SoA 方法,数量 B 将不会从慢速内存 (RAM) 加载到缓存中,但使用 AoS 方法它将占用缓存。请注意,我将时间分辨率更改为纳秒。否则,我总是得到0s 作为输出。

#include <stdlib.h>
#include <chrono>
#include <iostream>
#include <math.h>
const int iterations = 40000000;

class Entity 
  public:
    float a, b, c;
;

struct Entities 
  public:
    float a[iterations];
    float b[iterations];
    float c[iterations];
;

int AoSTest(int iterations, Entity enArr[]);

int SoATest(int iterations, Entities* entities);

int main() 
    Entity* enArr = new Entity[iterations];

    Entities* entities = new Entities;

    int A = rand() - 50;
    int B = rand() - 50;
    int C = rand() - 50;

    for (int i = 0; i < iterations; i++) 
        enArr[i].a = A;
        enArr[i].b = B;
        enArr[i].c = C;

        entities->a[i] = A;
        entities->b[i] = B;
        entities->c[i] = C;
    

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

    //    const auto ret = AoSTest(iterations, enArr);
    const auto ret = SoATest(iterations, entities);

    auto finish = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double> elapsed = finish - start;

    std::cout << std::to_string(std::chrono::duration_cast<std::chrono::nanoseconds>(finish - start).count()) + "ns "
              << "ret=" << ret;


int AoSTest(int iterations, Entity enArr[]) 
    for (int i = 0; i < iterations; i++) 
        enArr[i].a = sqrt(enArr[i].a * enArr[i].c);
        enArr[i].c = sqrt(enArr[i].c * enArr[i].a);
    
    return enArr[iterations - 1].c;


int SoATest(int iterations, Entities* entities) 
    for (int i = 0; i < iterations; i++) 
        entities->a[i] = sqrt(entities->a[i] * entities->c[i]);
        entities->c[i] = sqrt(entities->c[i] * entities->a[i]);
    
    return entities->c[iterations - 1];

【讨论】:

我正在使用 Visual Studio 并将运行时检查设置为默认值并将优化设置为 Ox 并看到 AoS 为 0s 而 SoA 为 8s?我不知道你是如何编译它的。我试图找出一种方法来理解它是应用程序,我只是觉得必须使用特定的编译器和非常特定的设置来编译代码很奇怪,而且几乎没用? 为了完整起见,我只是参考了我使用的编译器。结果不应该因编译器而有太大差异。尽管如此,如果您编译以调试或发布代码,您会发现巨大的差异。您需要启用最高版本优化级别(适用于所有市长编译器 -O3),以衡量最佳案例性能。【参考方案2】:

SoA 有利于使用 SIMD 内在函数加载或存储您的数据,参见例如https://software.intel.com/sites/landingpage/IntrinsicsGuide/#techs=AVX&cats=Load&expand=3317 用于英特尔 AVX。

现在,在您的具体情况下,您需要提供有关编译器选项等的更多信息,但编译器可能不容易对您的具体情况进行矢量化。 我建议您对每个条目使用独立的指令(这里 c 取决于 a)来执行更多测试。

【讨论】:

是的 SIMD 对我来说很有意义,但 SoA 不应该有任何影响吗?对我来说,这两种结构运行相同是零意义的。我在这方面完全是菜鸟,但如果它是如此具体,那么编译器绝对很烂。

以上是关于在 C++ 中找不到 AoS 和 SoA 之间的性能差异的主要内容,如果未能解决你的问题,请参考以下文章

由于在 Bjarne Stroustrup“使用 c++ 的编程和实践”中找不到符号而导致的链接错误

MinGW C++ 编译器在系统路径中找不到 OpenCV

在 Rad Studio XE 6 中找不到 C++ Builder 项目

C++ 在命名空间中找不到函数

C++ 调试在 Visual Studio 中找不到“=”运算符

stat() 在 C++ 中找不到文件