Comments 16
к сожалению сбился со счёта сколько раз это было реализовано, что ж, кину ещё раз ))
https://github.com/kelbon/AnyAny
А не моглиу бы вы сказать пару слов о том, как это работает и почему это эффективнее вирутальных функций? Какой-то аналог vtable-то все-равно должен быть, ведь программа же должна по объекту как-то в реалтайме выбрать, какую же функцию вызвать. Т.е. указатель на метод в объекте будет.
эффективнее виртуальных функций
в первую очередь оно позволяет делать то что виртуальным функциям недоступно, не менять типы которые используются как полиморфные, работать со значениями а не указателями или просто добавить копирование своему полиморфному типу
Эффективность тоже можно получить за счёт того что не выделяется память под маленькие объекты и что указатель на значение лежит не в объекте, а рядом с указателем на vtable, что позволяет их грузить одновременно, а не по очереди
работать со значениями а не указателями
Я это утверждение не понял. AnyAny ведь внутри тоже будет использовать указатель?
на примере
struct Draw {
template<typename T>
static void do_invoke(const T& self) {
self.draw();
}
};
self это ссылка (читай указатель).
Эффективность тоже можно получить за счёт того что не выделяется память под маленькие объекты и что указатель на значение лежит не в объекте, а рядом с указателем на vtable, что позволяет их грузить одновременно, а не по очереди
До конца тоже не понял. Для классического полиморфизма не важно как и где выделяется память же, главное ссылку на объект заиметь.
Я наверное плохо читал, у AnyAny есть бенчмарки? Особенно интересно учитывая, что автор этой статьи сделал опровержение и эффективности все таки не обнаружено.
По пунктам:
Указатель конечно есть (this), но разница в том где лежит указатель.
При использовании virtual есть некий vtable и указатель на него в объекте:
vtable[fn1, fn2...]
object [vtable_ptr, fields...]
this (указывает на object)
При вызове функции fn1 процессору нужно загрузить сначала память object (this), потом когда память загрузится взять оттуда vtable_ptr и vtable, потом вызвать по указателю fn1
При использовании стирания типа как в anyany всё немного иначе:
vtable[f1, f2...]
object[fields...]
anyany::ref[object_ptr, vtable_ptr]
Тут процессору чтобы вызвать f1 можно взять одновременно лежащие рядом object_ptr и vtable_ptr и загрузить не по очереди, а параллельно, отсюда выигрыш при вызове методов
В остальном на уровне железа подходы через virtual и стирание типов очень похожи, разница больше на уровне интерфейса и синтаксиса
Второй уровень оптимизации и происходит на уровне интерфейса: в anyany объекте используется SO оптимизация, чтобы не аллоцировать объекты, тогда как в 99% кода с virtual всё обложено unique/shared указателями и соответственно аллоцировано непойми где, что кстати добавляет ещё уровень косвенности при вызове
Бенчмарки есть, результат примерно такой:

Где код бенчей посмотреть? В моем случае на синтетике тоже был выигрыш, но даже на демо приложении просадка составила около 25% в среднем из-за невозможности инлайнинга. Я очень сомневаюсь что хотя бы одна реализация подобного подхода может обогнать оптимизирующий компилятор, если дать ему видеть весь код (в заголовках) и использовать lto.
из-за невозможности инлайнинга
никто не говорит, что какая-то реализация рантайм полиморфизма будет быстрее чем обычные функции. А virtual функцию заинлайнить мягко говоря тоже непросто
было бы неплохо увидеть полноценную демку с реальными замерами, а не синтетику
вообще-то, если виртуальные функции используются там, где их эффективность или не эффективность может на что-то влиять, это, скорее всего, само по себе уже ошибка.
Класический пример когда вы создаете какой-нибудь TenantStore
как здесь например, будет ли вас заботить эффективность виртуальных функций? Очень сомневаюсь!
О, Rust в C++ завезли!
Не AnyAny едины, есть еще похожая реализация этого подхода, которая вроде как обещает избавить от ручного заполнения VTable на каждый трейт (не ииспользовал в бою, но выглядит интересно на первый взгляд):
https://github.com/microsoft/proxy
Но вообще, с рефлексией C++26 это дело можно будет здорово так автоматизировать, осталось только дождаться реализаций))
Мне нравится наблюдать как с годами у них интерфейс всё усложняется и объяснения всё удлиняются, количество неймспейсов, звёздочек и докладов растёт, но лучше не становится
Я все таки не понял откуда повышение производительности тут. Только за счет того, что указатель на объект лежит рядом с указателем на VTable?
Если так смотреть, то это ведь все равно не прямой вызов методов. Плюс если компилятор где-то мог сделать девиртуализацию, то тут уже не сможет.
А эти 10000 объектов для теста - они ведь неслучайные? В реальной системе такой подход может дать другие цифры
Была хвалебная статья про первый Pentium, что он выполняет 8к БПФ за какие-то смешные сотни микросекунд. Тест включал в себя генерацию массива, и запуск бпф над этим массивом 1000 раз подряд. Время выполнения 1000 бпф делился на 1000. Угадайте, во сколько раз однократное бпф было медленнее, чем одно из 1000?)
Fail Case: Реализация полиморфизма без virtual на C++: концепты, трейты и Ref (и почему я отказался от этого подхода)