Как стать автором
Обновить

Комментарии 45

Я так понял, что в векторе вы взяли magic_get (который нынче Boost.PFR) и развернули массив структур в структуру массивов. Интересное упражнение.

А что насчёт индексов и выборок? Для ECS же важно уметь выбирать наборы компонент. Типа для всех, содержащих компоненту Health и Collision, запустить систему нанесения урона при столкновении.

Можете посмотреть на интерфейс контейнеров в хедерах, там есть выбор .view<Types...> и .view<Nbs...>, для variant_swarm тоже есть возможность посетить только нужные типы и посмотреть на только нужные

https://github.com/kelbon/AnyAny/blob/fbb1b0ea2c9a83f00ba18b657cbb68d7ab3b82b4/include/variant_swarm.hpp#L104

Техника эта была известна ещё до Boost PFR и никаких зависимостей в библиотеке нет, можете посмотреть в noexport/data_parallel_vector_details.hpp как это реализовано

Полиморфные структуры данных и производительность

Извините, но где в статье про производительность?

Очевидно где, такая структура данных повышает локальность данных до предела, позволяет мгновенно получить доступ значениям заданных типов, убрать динамик касты и visit

Это в общем-то общее знание. А есть подтверждение в цифрах - было так, мы сделали так и стало так?

https://www.youtube.com/watch?v=n6PvvE_tEPk&t=1225s&ab_channel=CppCon
Вот в этом докладе есть какие-то измерения про потерю производительности из-за кеш мисов по vtable. Но это лишь малая часть

Ну и да, конечно же стоит упомянуть, что при той версии что с разделёнными контейнерами можно функцию и заинлайнить и даже векторизовать. С виртуальными функциями это даже теоретически невозможно. Это громадный плюс

Спасибо за видео. Но хотелось бы увидеть ваши цифры, из первых рук, так сказать.

Интересно увидеть в цифрах какие то выводы (на вычислительных задачах)
К, примеру может подойдёт учебная задача реализации RPN калькулятора?
Топик на Github reverse-polish-notation

P.S. Ещё интереснее с использованием профайлера или «даже» ассемблерного кода.

Рискую нарваться на "опять любители раста понабежали", но в Rust уже есть встроенный тип enum, который как раз variant, плюс в языке куча синтаксического сахара для работы с ним. Не ради критики или попытки обесценить (на самом деле статья огонь, жаль в последнее время мало такого пишут), но для рекламы любимого языка.

в С++ есть std::variant, только вот это не помогает сделать эффективный контейнер таких типов. Не знаю получится ли в расте реализовать нечто подобное, так как там нет перегрузок и шаблонных шаблонных параметров

Пардон, сначала невнимательно прочитал - я сначала подумал, что у вас аналог std::variant, но на самом деле это аналог std::vector<std::variant>>, но с разложением значений различных типов по отдельным векторам и сортировкой по типам (прям по канонам data oriented design). Да, в стандартной библиотеке раста такого нет, но реализовать не вижу большой проблемы - по крайней мере если не параметризовать типом самого контейнера.

НЛО прилетело и опубликовало эту надпись здесь

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

НЛО прилетело и опубликовало эту надпись здесь

"Полиморфные структуры данных и производительность"?
Насколько я понял, поставленную в первом абзаце задачу вы не решили. Где здесь, собственно, "полиморфизм", если вы решили задачу разворачивания вектора структур в структуру из векторов. Где здесь, собственно, "динамический", если у вас всё на статике и этапе компиляции?

Не, сама по себе структура прикольная - и очень полезна в числодробилках. Возможно, я её у вас даже и притырю. Просто назовите статью честно, "Преобразование AoS в SoA на двадцатых шаблонах", и будет вам почёт и уважение.
А так, я пришёл за пельменями, а меня шарлоткой накормили. Вкусно, конечно, но не то.

кажется вы пропустили середину статьи про variant_swarm, там есть динамический полиморфизм

Кажется, не пропустил. Перечень типов вами заранее определён в шаблоне. Где здесь динамика? Тем более, где здесь полиморфизм? (Ладно, это я погорячился.)

Перечень типов известен на компиляции. Где здесь полиморфизм? /s

Перечень типов известен на компиляции.

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

На самом деле, менять придётся не то, чтобы очень много - std::tuple заменить на std::map<std::type_index, ...> и добавить код расширения-сужения списка типов в рантайме. Ну и, возможно, вынести шаблонные методы доступа из класса, чтобы их можно было перегружать. Хотя, может я и излишне оптимистичен.

Вы не поняли, типы всегда известны на компиляции. Просто иногда они устроены так, что создают полиморфизм. И в variant swarm есть динамический полиморфизм, т.к. он ведёт себя как набор std::variant<Ts...>.
Для тех случаев что вы описываете в библиотеке есть другие инструменты, такие как полиморфные ссылки и референсы, any_with<...> и подобное.

Смысла в map<index> что то там я особого не вижу, потому что это не поможет компилятору инлайнить и оптимизировать код.

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

Так и std::variant - это статически полиморфный тип. Статический и динамический полиморфизм на примерах.

К вашей реализации вопросов нет. Вы введение к статье перепишите, вы там за динамику много пишите, а у вас её нет.

variant это инструмент динамического полиморфизма, вы только на рантайме знаете что лежит в variant<Ts...> его поведение на динамике меняется в зависимости от того что там лежит

int — это инструмент динамического полиморфизма, вы только в рантайме знаете, лежит ли там 0, 1 или 42. /s

Например, пусть у нас есть типы А B и C, типичная ситуация, когда виртуальный метод не делает ничего для А, но делает полезную работу для B и какую-то мелочь для C.
Если вы имеете

std::vector<Base*> x;

При итерации и вызове .foo() оптимизатору ничего не останется, кроме как честно вызывать каждую из функций, даже если она не делает совершенно ничего.
Если же вы сделали

aa::variant_swarm<A, B, C> y

то оптимизатор при вызове .visit_all видит, что сначала вызыватся в цикле .foo для A, которое ничего не делает - значит этот цикл можно вовсе выкинуть.
Затем вызывается в цикле .foo для B, это можно заинлайнить, оптимизировать и векторизовать.
Затем вызывается в цикле .foo для С, это можно также заинлайнить или убрать цикл, если возможно.
Таким образом сам по себе бенчмарк типичных ситуаций будет выглядеть абсурдно - ускорение в бесконечность раз, ведь был цикл, а теперь пусто


Ещё интересный вариант - увидеть что циклы можно объединить и схлопнуть, что иногда сильно ускоряет код.

Всё это хорошо. Но динамический полиморфизм называется динамическим потому, что я на этапе выполнения могу добавить тип D - и у меня всё отработает корректно. Откуда я его добавлю? Не_твоего_ума_дело.dll подгружу, например.

(нейросеть отвечает)
То что описываете вы это другое явление, одно из свойств какого-то вида полиморфизма, а не весь полиморфизм

В Java нет статического полиморфизма, в ней только динамический. Все методы там виртуальные - и инстанцируются jit'ом. Все типы наследуются от Object, а контейнеры хранят наследников интерфейса. По крайней мере, году в 2015 всё ещё было так.

Примите уже как данность, что всё, что имеет угловые скобки <> в C++ - это статика.

Задаёшь неправильные вопросы.
@
Получаешь бесполезные ответы.

Так спроси, что такое статический полиморфизм в C++.

И что, собственно, меня должно удивить в any_with? То, что там кто-то на std::any насыпал сверху vtable_ptr? Не, похвально, конечно, но по удобству на 5 шагов отстаёт от CRTP. Чтобы описать интерфейс, нужно туеву хучу лишних движений совершить.

CRTP это статический полиморфизм.
any_with<...> это динамический полиморфизм. В том числе это работает при подключении длл.

Советую разобраться в том о чём вы спорите

P.S. std::any уже содержит vtable с копированием и деструктором.

У вас в статье сколько упоминаний any_with? Подсказываю,

Вы в статье писали про basic_variant_swarm и data_parallel_vector, а они не про динамический полиморфизм.

Но да ладно, хватит спорить из-за фигни.

Это был ответ на ваше утверждение, что всё в С++ с <> является "статикой"

Тогда вам стоит увидеть это:
https://github.com/kelbon/AnyAny
(см. any_with<...>)

Блин, прочитал как полиаморные типы и сильно задумался.

Прочитал статью, прочитал имеющиеся на данный момент комментарии. Увидел, что недостаточно комментариев о том, что статья не имеет отношения к динамическому полиморфизму, хотя вначале его и упоминает.

Так что этот комментарий призван исправить недостаток таких комментариев. Посему: статья не имеет отношения к динамическому полиморфизму и этот самый динамический полиморфизм вначале статьи был упомянут напрасно (либо с кликбейтной целью).

На основании чего вы считаете, что variant_swarm<Ts...> не ведёт себя как контейнер полиморфных типов?

На том же основании, что и ув.тов. @iCpu. Динамический полиморфизм появляется тогда, когда мы в compile-time видим лишь абстрактный базовый тип A, а в run-time у нас может появится экземпляр любого другого типа X, который удовлетворяет интерфейсу A (в терминах C++: в compile-time мы имеем базовый тип A, в run-time любой наследник от A). Причем если в контейнере лежит сразу несколько значений (v1, v2, v3, ...), то каждый из них может принадлежать любому типу наследнику X1, X2, X3, ... Причем в том числе и тем типам-наследников, которых еще не существовало в момент компиляции нашего кода с контейнером.

Нет, динамический полиморфизм это когда объект ведёт себя по разному в зависимости от своего динамического типа. Или функция ведёт себя по разному в зависимости от динамических типов своих аргументов

А вы описываете один единственный возможный для вас полиморфизм на виртуальных функциях.

Нет, динамический полиморфизм это когда объект ведёт себя по разному в зависимости от своего динамического типа.

Мне непонятно почему здесь "Нет", т.к. то, что вы написали является прямым следствием того, что описал я. Ведь если за интерфейсом A в динамике обнаруживается X, то мы как раз и получаем то поведение, которое реализует именно X. Если за A стоит не X, а Y, то получим поведение от Y. Вот вам и "ведет себя по разному".

А вы описываете один единственный возможный для вас полиморфизм на виртуальных функциях.

Просто в C++ вы по другому динамический полиморфизм и не получите.

И нет, ни std::variant, ни std::any не являются примерами динамического полиморфизма в C++, т.к. вам всегда нужно знать точный тип, который вы собираетесь извлечь из std::variant/any.

Мне непонятно почему здесь "Нет", т.к. то, что вы написали является прямым следствием того, что описал я

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

Вы попробуйте подставить в мое описание вместо "A" своих "карасей", а вместо X -- свою "рыбу". А потом наоборот. И увидите, что "все караси рыбы, но не все рыбы караси". А так же и отсутствие логической ошибки, которая вам померещилась.

И нет, ни std::variant, ни std::any не являются примерами динамического полиморфизма в C++, т.к. вам всегда нужно знать точный тип, который вы собираетесь извлечь из std::variant/any.

То есть std::visit и тот факт, что any можно не кастовать ни к чему и оно выполнит полиморфно копирование/деструктор вас не смущает

any можно не кастовать ни к чему и оно выполнит полиморфно копирование/деструктор вас не смущает

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

время идёт, а C++ программист всё еще изобретает контейнеры

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации