Search
Write a publication
Pull to refresh

Comments 16

А не моглиу бы вы сказать пару слов о том, как это работает и почему это эффективнее вирутальных функций? Какой-то аналог 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 указателями и соответственно аллоцировано непойми где, что кстати добавляет ещё уровень косвенности при вызове


Бенчмарки есть, результат примерно такой:

create/copy из-за аллокаций, invoke из-за эффекта описанного выше с указателями
create/copy из-за аллокаций, invoke из-за эффекта описанного выше с указателями

Где код бенчей посмотреть? В моем случае на синтетике тоже был выигрыш, но даже на демо приложении просадка составила около 25% в среднем из-за невозможности инлайнинга. Я очень сомневаюсь что хотя бы одна реализация подобного подхода может обогнать оптимизирующий компилятор, если дать ему видеть весь код (в заголовках) и использовать lto.

из-за невозможности инлайнинга

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

код тут https://github.com/kelbon/anyany_benchmark

было бы неплохо увидеть полноценную демку с реальными замерами, а не синтетику

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

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

Класический пример когда вы создаете какой-нибудь TenantStore

как здесь например, будет ли вас заботить эффективность виртуальных функций? Очень сомневаюсь!

Не AnyAny едины, есть еще похожая реализация этого подхода, которая вроде как обещает избавить от ручного заполнения VTable на каждый трейт (не ииспользовал в бою, но выглядит интересно на первый взгляд):

https://github.com/microsoft/proxy

Но вообще, с рефлексией C++26 это дело можно будет здорово так автоматизировать, осталось только дождаться реализаций))

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

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

P.S. там все ссылки в ридми ведут на 404

Я все таки не понял откуда повышение производительности тут. Только за счет того, что указатель на объект лежит рядом с указателем на VTable?
Если так смотреть, то это ведь все равно не прямой вызов методов. Плюс если компилятор где-то мог сделать девиртуализацию, то тут уже не сможет.

А эти 10000 объектов для теста - они ведь неслучайные? В реальной системе такой подход может дать другие цифры

Была хвалебная статья про первый Pentium, что он выполняет 8к БПФ за какие-то смешные сотни микросекунд. Тест включал в себя генерацию массива, и запуск бпф над этим массивом 1000 раз подряд. Время выполнения 1000 бпф делился на 1000. Угадайте, во сколько раз однократное бпф было медленнее, чем одно из 1000?)

Sign up to leave a comment.

Articles