LINUX.ORG.RU

структура из массивов с минимумом накладных расходов

 , ,


1

1

Издалека начну. Есть некий хитрый контейнер, который обходится итератором. Элементы контейнера - структура определенная юзером. В структуре может быть несколько полей (в идеале разных POD-типов, но можно ограничиться пока одним).

Теперь самое сложное - в разных элементах некоторых полей может вообще не быть, может быть просто поле типа T, а может быть небольшой массив (одномерный или двумерный), и это будет меняться в runtime. При этом накладные расходы на хранение всего этого безобразия должны быть минимальны, то есть решение вида

struct E{
  std::vector<T> a;
  std::vector<T> b;
  std::vector<T> c;
  ...
};

заведомо не проходит, полей может быть много (десятки) и в большинстве элементов большей части может не быть. Кроме того желательна локальность данных.

Нужно:

  1. Понять как это вообще можно сделать.

  2. Хороший интерфейс для доступа ко всему этому.

  3. Хороший интерфейс для задания структуры пользователем.

  4. Желательно, но не необязательно - прикрутить к этому какую то интроспекцию, то есть что бы хранились типы полей и имена полей (как массив строк и sizeof-ов).


Пока что придумалось такое - в структуре заводится

std::vector<char> data; // тут лежат все данные
uint8_t sz[N][2];       // размеры полей как массивов
uint16_t off[N];        // смещения различных полей в data

где Т - число полей (в итоге расход 4 байта на поле - с этим как то можно жить) и для каждого поля name делаются функции

T& name(int i=0, j=0);
void resize_name(int szi, int szj, T defval=T());

и то и то делается макросами, то есть для юзера задание структуры выглядит как

struct E{
CELL_TABLE(10); // задаем N
ADD_FIELD(0, double, a); // номер поля, тип, имя
ADD_FIELD(1, float, b);
...
};

а доступ выглядит как

I->a() ... I->b(1) ... I->c(0,2) ...

если индекс вышел за пределы массива генерируется ошибка.

Вопросы:

  1. какие тут подводные камни, что можно улучшить?

  2. как это вообще называется (лучше назвать)?

  3. как к этому малой кровью прикрутить интроспекцию? То есть я хочу иметь возможность в runtime снаружи узнать все типы полей, имена полей, размеры типов полей.

cast @KennyMinigun , @hobbit;-)

★★★★

Последнее исправление: AntonI (всего исправлений: 1)

Ответ на: комментарий от alysnix

Переписать все на лиспе - в приведенном коде маловато скобочек)))))))

AntonI ★★★★
() автор топика
Ответ на: комментарий от AntonI

inline

В C++ inline не является указанием компилятору инлайнить метод/функцию. Всё, что делает inline — позволяет иметь несколько реализаций одного и того же метода/функции в программе. Более того, если при декларации класса сразу же делаешь внутри неё реализацию метода, этот метод автоматически является inline-методом.

Не надо указывать inline у методов, это просто визуальный мусор.

i-rinat ★★★★★
()

По-моему protocol buffers подходят идеально - там и относительно компактное представление, и опциональные поля/массивы/вложенные структуры, и всякая динамика с интроспекцией, и более-менее адекватная скорость, хотя не без оверхеда.

slovazap ★★★★★
()
Ответ на: комментарий от AntonI

Можно считать, что данные структуры небольшие и влезают в кэш.

Не забудь учесть выравнивание, чтоб всякие даблы не валялись по не кратному им смещению.

Очевидно один элемент всегда обрабатывается одним тредом

Тогда не забудь учесть чтоб элементы были выравнены еще и по кэш линиям, иначе все будет плохо (когда два треда будут читать из одной кэш линии).

Kuzy ★★★
()
Ответ на: комментарий от AntonI

Теперь самое сложное - в разных элементах некоторых полей может вообще не быть, может быть просто поле типа T, а может быть небольшой массив (одномерный или двумерный), и это будет меняться в runtime

Kuzy ★★★
()
Ответ на: комментарий от slovazap

Спасибо, интересная штука, я про нее не знал.

AntonI ★★★★
() автор топика
Ответ на: комментарий от AntonI

Мне кажется ты сам не понимаешь чего хочешь, потому что ни какой модификации в рантайме тут нет. И ты можешь использовать просто несколько векторов, вместо этого ада. И что-то мне подсказывает, что это будет работать быстрее.

Kuzy ★★★
()
Ответ на: комментарий от pon4ik

Да расположение их в одной странице памяти всех целиком, или хотя бы выравнивание по размеру страницы позволит сэкономить несколько кэш линий на этапе загрузки, но это такое себе преимущество

Пруф? На что вообще влияют страницы здесь?

Kuzy ★★★
()
Ответ на: комментарий от Kuzy

Вам только кажется. Я вот вижу по Вашему комментарию что Вы не умеете читать код.

AntonI ★★★★
() автор топика
Ответ на: комментарий от Kuzy

Пруф хоть и сомнительной свежести, но на правду похоже. Если разные элементы массива будут располагаться на разных страницах, есть вероятность что лишние кэш миссы возникнут как минимум при доступе к структурам описывающим страницы. А если один элемент находится на двух страницах, то доступ будет ещё забавней к нему. А если на страницах привязанных к разным numa нодам, то это будет вообще цирк с конями.

Но конкретно тут это и правда ни при чём судя по всему. К тому моменту, как автору придётся этим заморачиваться, он скорее всего будет в состоянии без всякого лора понять вот э фак и чего там предлагают в ddd/dod.

pon4ik ★★★★★
()

Как вариант - можно свести эту штуку к разреженной двумерной матрице. Это очень важный объект во всяких числодробилках, так что для них понаписано очень много всяких библиотек.

annulen ★★★★★
()
Ответ на: комментарий от annulen

Можно, только в общем виде это работает довольно неспешно. Для произвольного доступа на изменение там std::map с ключем (i,j).

Для чтения лучше, зависит от специфики задачи. Ну и одна из аксиом - общее решение всегда проигрывает узкоспециализированному в области его специализации:-)

AntonI ★★★★
() автор топика
Ответ на: комментарий от qulinxao3

Вы не поверите, но абсолютное большинство xfem-кодов сделано в фортран-стайл на нескольких массивах с адищенской индексной арифметикой.

AntonI ★★★★
() автор топика
Ответ на: комментарий от AntonI

Так для методов конечных элементов обычно и используют средства работы с разреженными матрицами, Вики даже список библиотек дают:

Для  вычислений с разрежёнными матрицами создан ряд библиотек для различных языков программирования, среди них:

SparseLib++ (C++)
uBLAS (C++, в составе Boost)
SPARSPAK (Фортран)
CSparse (Си)
Модуль Sparse из библиотеки SciPy (Python).

Хотя насколько они применимы сразу к твоей задаче тоже ещё вопрос. Какие-то они для C++ заброшенные по датам, хорошо, если соберутся.

grem ★★★★★
()
Последнее исправление: grem (всего исправлений: 2)
Ответ на: комментарий от AntonI

А решение какое принято (отмечено как решено), а то дочитал тему и не понял на чём остановились?

grem ★★★★★
()

Стартовый пост не читал (всегда так делаю), но хочу сказать следущее:

  1. если пользователи хотят хранить объекты переменного размера из N байт, то дать им такую возможность, и хранить в массиве прямо байты (или даже биты).

  2. узнать, какого максимального размера бывают структуры, и сделать второй массив с размерами объектов.

Компактнее уже будет некуда, если сжатие не применять…

Einstok_Fair ★★☆
()
Последнее исправление: Einstok_Fair (всего исправлений: 2)
Ответ на: комментарий от Einstok_Fair

Ты знал, ты знал, ты не мог догадаться!:-)

AntonI ★★★★
() автор топика
Ответ на: комментарий от grem

Ну я не знаю как это ещё можно сделать:-(

Можно конечно заранее объявить параметризованный функций и дергать их из макроса, это наверное сделает код чуть более читаемым

AntonI ★★★★
() автор топика
Последнее исправление: AntonI (всего исправлений: 1)
Ответ на: комментарий от AntonI

Потом заменишь, если будет такая потребность. Работающая реализация лучше её отсутствия.

grem ★★★★★
()
Ответ на: комментарий от AntonI

а у вас там такие

технологии (? или фиг знает что оно ?) используются? AMR есть? Как параллелится? И каков может быть типичный объем данных?

sshestov
()
Ответ на: а у вас там такие от sshestov

Какие такие?:-)

AMR делаем, но не в этой задаче - для неструктурированной сетки оно как то странно…

В xfem параллелить надо солвер СЛАУ в первую очередь. А в других задачах параллелится, и хорошо весьма:-)

Ну и объём данных - для xfem небольшой (поскольку там СЛАУ - не разбежишься), а так до неск. терабайт.

AntonI ★★★★
() автор топика
Последнее исправление: AntonI (всего исправлений: 1)
Ответ на: комментарий от AntonI

Пока так (что собственно в исходном посте и предполагал).

  1. Инициализация структуры (выставление размеров) будет сопровождаться постоянным и бессмысленным копированием данных.
  2. Изменение размеров всегда копирует все данные, а не двигает только хвост после места вставки/удаления.
  3. Выравнивания нет - это и потенциальная ошибка в рантайме и неэффективно.
  4. Писать весь код в макросе - плохо и для редактирования, и для чтения и для дебага
  5. «API» и оформление кода мягко говоря кустарные.
Serral
()
Ответ на: комментарий от Serral
  1. Это относительно редкая операция, не страшно.

  2. Это плата за быстрый доступ (работа с вектором), ну и см. П1

  3. Это не ошибка а скорее неэффективность. Несложно доработать resize так что бы выравнивание было, или следить за порядком расположения полей (большие типы сначала).

  4. Да, согласен - это обсуждали, можно ввести параметризованных ф-й и дергать их.

  5. Насчет оформления это вкусовщина, а вот если предложите API лучше - будет интересно.

AntonI ★★★★
() автор топика
Последнее исправление: AntonI (всего исправлений: 1)
Ответ на: комментарий от AntonI

если вы хотите скорости, то надо сразу отбрасывать весь STL и делать нормальные пулы. это без вариантов. это не «пока», это вообще.

Iron_Bug ★★★★★
()
Ответ на: комментарий от AntonI

если не хотите сами городить всё, возьмите ACE. это самая быстрая плюсовая библиотека, в которой более-менее грамотно всё это реализовано. но точно не STL. STL сливает по скорости всему и вся.

Iron_Bug ★★★★★
()
Ответ на: комментарий от Iron_Bug

если вы хотите скорости, то надо сразу отбрасывать весь STL и делать нормальные пулы. это без вариантов. это не «пока», это вообще.

Пулы рулят там, где есть много мелких аллокаций, а если ТС все пихает в один вектор, то пул ему погоды не сделает.

Serral
()
Ответ на: комментарий от Iron_Bug

а чем вектор от пула-то отличается? разве что тем, что тормозит на любых операциях.

В пуле невозможно изменить размер выделенного блока, надо выделять новый. Если ТС действительно нужно минимизировать расход памяти (он достаточно сумбурно описал задачу), то пул ему не подойдет.

Serral
()
Ответ на: комментарий от Serral

У правильно сделанного пула выделение/освобождение памяти проходит быстрее, это факт.

Но мне очень интересно почему @Iron_Bug считает что у пула произвольный доступ быстрее чем у вектора?

AntonI ★★★★
() автор топика
Ответ на: комментарий от AntonI

потому что пулом ты сам управляешь и когда хочешь его расширяешь параллельно процессу работы. плюс ты можешь оптимизировать всякий там поиск и т.д.

Iron_Bug ★★★★★
()
Ответ на: комментарий от Iron_Bug

Как мне это позволит ускорить произвольный доступ?

Доступ относится к любым операциям?

AntonI ★★★★
() автор топика
Ответ на: комментарий от AntonI

Но мне очень интересно почему @Iron_Bug считает что у пула произвольный доступ быстрее чем у вектора?

Может она предлагает создавать новый пул и копировать туда. Но я бы взял простой malloc, realloc, и двигал бы только часть данных.

Serral
()
Ответ на: комментарий от AntonI

доступ по индексу - нет. но работу с вставкой/удалением - да, и существенно. и кроме того ты получишь защиту от фрагментации памяти.

Iron_Bug ★★★★★
()
Последнее исправление: Iron_Bug (всего исправлений: 1)
Ответ на: комментарий от Iron_Bug

Ну и насчёт фрагментации - это дело темное, очень зависит от последовательности аллокаций/освобождений и того как сделан пул.

Менеджер памяти вообще то хорошо сделан.

AntonI ★★★★
() автор топика
Ответ на: комментарий от AntonI

кроме того, ты уменьшишь фрагментацию. и сэкономишь на ресурсах, если не хранить там сам вектор (он не нужен для последовательной работы с данными).

а также вероятно можно ускорить многопоточный доступ. стандартное тупое залочивание всего сразу - не вариант при производительных задачах.

вообще, любая сахаризация (упрощение) делают софт менее производительным. это просто аксиома. если кто-то делал что-то для общих задач - оно не оптимально. ты специфику своей задачи всегда знаешь лучше и можешь конкретно под неё заоптимизировать данные.

в любом случае, для скорости и больших потоков данных я не рекомендую STL, а рекомендую ACE.

Iron_Bug ★★★★★
()
Последнее исправление: Iron_Bug (всего исправлений: 2)
Ответ на: комментарий от Iron_Bug

В контексте данной конкретной задачи довольно бурно обсуждались плюсы и минусы SoA и AoS. Сухой остаток - нужно сделать что бы работало и сделать профилирование. Преждевременная оптимизация - зло, это более сильная аксиома чем про специализацию:-)

AntonI ★★★★
() автор топика
Ответ на: комментарий от AntonI

чтобы тебе сделать профилирование - тебе нужно что-то сравнивать :) так что выбор всегда должен быть.

я просто писала довольно много приложений, у которых овер-дохрена данных и они должны были работать с ними быстро. поэтому я пишу про то, что я знаю на практике.

Iron_Bug ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.