Превключване напред и назад между масив от структури (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
Написах подобен въпрос и отговор наскоро, абстракция с нулеви разходи, използвайки C++17 (може да бъде също и C++14), в случай че искате да актуализирате този отговор: stackoverflow.com/questions/50574639 /. Кодът също е в репо на github: github.com/crosetto/SoAvsAoS - person Paolo Crosetto; 08.11.2018