Не удается найти разницу в производительности между AoS и SoA в C ++ - PullRequest
2 голосов
/ 12 июня 2019

Я пытаюсь понять разницу между 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.

Ответы [ 2 ]

3 голосов
/ 12 июня 2019

Я скомпилировал ваш код с компилятором intel 18.0.1 и включил оптимизацию (-O3).Я добавил некоторое возвращаемое значение, просто чтобы убедиться, что ничего нельзя оптимизировать.

Я обнаружил, что структура массивов (SoA) примерно в два раза быстрее, чем массив структур (AoS).Это имеет смысл, поскольку количество B не будет загружаться в кэш из медленной памяти (ОЗУ), если вы используете подход SoA, но оно будет занимать кэш при подходе 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];
}
0 голосов
/ 12 июня 2019

SoA выгодно загружать или хранить ваши данные с помощью встроенных SIMD, например, см. https://software.intel.com/sites/landingpage/IntrinsicsGuide/#techs=AVX&cats=Load&expand=3317 для intel AVX.

Теперь в вашем конкретном случае вам нужно предоставить больше информации о параметрах компилятора и т. Д., Но, вероятно, ваш конкретный случай не легко для векторизации компилятором. Я бы посоветовал вам использовать независимые инструкции для каждой записи (здесь c зависит от a) для выполнения большего количества тестов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...