Переключение между массивом структур (AoS) и структурой массивов (SoA)

Одна особенность, которая играет заметную роль во многих работах по проектированию, ориентированному на данные, заключается в том, что во многих случаях вместо AoS (массива структур):

struct C_AoS {
  int    foo;
  double bar;
};

std::vector<C_AoS> cs;
...
std::cout << cs[42].foo << std::endl;

эффективнее расположить свои данные в SoA (структуре массивов):

struct C_SoA {
  std::vector<int>    foo;
  std::vector<double> bar;
};

C_SoA cs;
...
std::cout << cs.foo[42] << std::endl;

Сейчас я ищу решение, которое позволило бы мне переключаться между AoS и SoA без изменения вызывающего интерфейса, т. е. чтобы я мог с минимальными усилиями и без дополнительных затрат времени выполнения (по крайней мере, с точки зрения чрезмерной косвенности), позвонить напр. cs[42].foo; независимо от того, какое расположение данных я использую.

Я должен отметить, что приведенный выше пример синтаксиса является идеальным случаем, который вполне может быть невозможен, но меня также очень интересуют близкие приближения. Есть берущие?


person alarge    schedule 19.03.2015    source источник
comment
Раньше я много играл с этим, генерируя кортежи во время компиляции и так далее, но потерял интерес, так как он казался немного тяжелым и громоздким. Вместо этого я нашел полезным просто написать множество локальных функций доступа (могут быть просто статические функции в исходном файле), прежде чем вы начнете экспериментировать с представлениями данных. Таким образом, вам придется изменить минимальное количество кода, чтобы исследовать совершенно другой вид представления.   -  person    schedule 23.12.2017


Ответы (1)


Я выберу этот синтаксис: cs.foo[42] в качестве единственного синтаксиса и использую typedefs для переключения между аранжировками:

Итак, очевидно, учитывая C_SoA из вашего поста, приведенный выше синтаксис работает, и мы можем иметь:

typedef C_SoA Arrangement;

Arrangement cs;

Чтобы вместо этого использовать std::vector<C_AoS>, нам нужно ввести что-то еще:

typedef std::vector<C_AoS> AOS;

template<class A, class C, class T>
struct Accessor {
    T operator[](size_t index){
            return arr[index].*pMember;
    }
    T (C::*pMember);
    A& arr;
    Accessor(A& a, T (C::*p)): arr(a), pMember(p){}
};

struct Alt_C_AoS{
    Accessor<AOS, C_AoS, int> foo;
    Accessor<AOS, C_AoS, double> bar;

    AOS aos;
    Alt_C_AoS():foo(aos, &C_AoS::foo), bar(aos, &C_AoS::bar){}
};

Теперь у нас может быть:

//Choose just one arrangement
typedef Alt_C_AoS Arrangement;
//typedef C_SoA Arrangement;

Arrangement cs;
...
std::cout << cs.foo[42] << std::endl;

По сути, это преобразует container dot member index в container index dot member.

person quamrana    schedule 19.03.2015
comment
Хороший вопрос и красивый ответ. Я только что отметил, что когда я столкнулся с этой проблемой, это было связано с тем, что естественным представлением данных было бы SoA, но при кодировании гораздо проще иметь дело с AoS, и часто кодеры предпочитают этот подход. Таким образом, я часто заканчивал с аналогичной противоположной настройкой, используя пользовательские методы доступа, а затем доступ через метод доступа, std::cout << cs[42].get_foo() << std::endl на самом деле, как гласит мантра инкапсуляции C++. - person Sigi; 19.03.2015
comment
Интересен ваш подход — рассмотрите возможность поделиться своим кодом (задали вопрос здесь). - person user1823664; 12.02.2017
comment
Недавно я написал аналогичный вопрос и ответил, абстракция с нулевой стоимостью с использованием С++ 17 (хотя это может быть и С++ 14), если вы хотите обновить этот ответ: stackoverflow.com/questions/50574639 / . Код также находится в репозитории github: github.com/crosetto/SoAvsAoS. - person Paolo Crosetto; 08.11.2018