Здравствуйте, maks1180, Вы писали:
M>1) Что будет быстрее работать вызов через v-table или такая конструция через if () myfunc1() else myfunc2() или вызов через switch().
M>Как мне кажется (я не тестировал), но при вызове через v-table процессор не может предсказать адрес перехода и поэтому кэш будет чаще промахиваться.
Спекулятивно исполнить может. Предсказать -- смотря как сделано, потенциальная возможность есть.
Но на самом деле это всё не столь критично и не столь принципиально. Это -- десятки тактов.
А вот то, что компилятор не может "предсказать", что там за переход через vtable будет -- куда серьёзней.
В случае switch-case многообразие вариантов по крайней мере резко снижается, оно становится вообще перечислимым,
и код в целом начинает поддаваться оптимизации. Это принципиальный момент, который позволит работу регистрового
аллокатора распространить вниз по всей цепочке вызовов, а не тупо следовать ABI, включить inline-фунцкии,
провести глобальную оптимизацию на всю глубину вызова. И это уже могут быть не десятки тактов, а тысячи.
M>2) Есть ли v-table у struct ? Если есть будет ли чем отличаться по производительности от v-table class ?
Ничем, можешь сам сделать. В структуре указатель на статическую другую структуру сгенерированную
шаблоном, в которой лежат указатели на нужные функции. Не уверен, что такой вариант чем-то лучше
таблиц виртуальных функций, но вообще было бы интересно знать. Допускаю существование нюансов, по которым лучше
(компилятор по какой-то причине не делает девиртуализацию, но для констант знает, что они собственно константы
и может вызов по указателю заменить на прямой вызов).
M>3) Подскажите приемлимое решение, есть сетевое приложение:
M>а) на каждое входящее соединение создаём объект класс ClientCommon и далее он отвечает за работу с этим соединением.
M>б) после общения с ним мы понимает кто он и должны создать заместо ClientCommon либо Client1 либо Client2
M>для этих классов я буду вызывать: функцию OnRecv() при получении данных для них и OnClose() при разрыве соединения.
M>Но код в этих функциях будет отличаться для ClientCommon, Client1, Client2.
M>Как это лучше реализовать ?
В общем случае нужно понять, имеет ли смысл вообще какая-либо оптимизация, а если имеет,
то что является узким местом. Запросто отнюдь не динамическая диспетчеризация.
Динамическая диспетчеризация является краеугольным камнем, от неё так просто не откажешься
и чем-то, непонятно чем, так просто не заменишь. Не зависимо от языка, фреймворка и т.п.
Варианты со свитч-кейсами же архитектурно плохое решение, т.к. при дальнейшей модификации
кода может выясниться, что где-то их забыли, где-то их нет, где-то оно разрослось до неприличных
размеров (и switch-case по огромной таблице выполняется сильно медленее, чем вызов по указателю).
Можно пытаться снижать количество точек где производится динамическая диспетчеризация,
и в пределе достигнуть одной такой точки, где сразу делается ветвление по одной из множества
веток, для каждой из которых существуют свои отдельные реализации классов/функций уже не требующих
динамической диспетчеризации (но раздувающие объём кода). Что обычно достигается с помощью шаблонов.
Скорей баланс где-то посередине. Виртуализировать очень мелкие операции дорого, и наоборот,
крупные шаблонные классы в свою очередь могут съесть очень много памяти.
Ещё может быть такой подход, что реализуется какой-то обобщённый класс/функция, которая
может работать с любым произвольным данным ей другим классом, для чего свойства последнего
класса выражаются отдельным классом и отдельно передаются в виде аргумента. Если свойства
выражаются функциями -- опять получается динамическая диспетчеризация. Но если свойства
могут быть выражены константами, то вместо неё остаются максимум только ветвления. И запросто,
что часть свойств может быть выражена именно что константами, а не виртуальными функциями,
что позволит снизить количество точек где нужна динамическая диспетчеризация.
M>Как я вижу это сделать: объявляю union { ClientCommon, Client1, Client2 } вызываю сначала конструктор для ClientCommon,
M>потом нужно как-то на этом же месте в памяти создать объект Client1 или Client2 от ClientCommon.
Это какое-то велосипедостроительство. Напоминает виртуальное наследование (когда поряок вызова конструкторов ручной).
Может его тогда и стоит использовать. Тем более что виртуальный деструктор уже весьма полезен.
Но непонятно зачем, почему сразу ClientN не сконструировать? Или почему нужнo именно
наследование (когда ClientCommon является базовым классом), а не агрегация (когда ClientCommon
создаётся в сторонке и по ссылке передаётся в новый создаваемый ClientN, владение тоже передается).
М> ВАЖНО именно на этом же месте создать, так как адрес уже привязан в epoll.
M>Как это сделать ? Вроде через "new" можно указать адрес памяти ?
В случае юниона и виртуального наследования использование placement new уже не имеет смысла
(потому, что у юниона был какой-то конструктор и он что-то там уже наконструировал).
Оно имеет смысл когда вместо юниона под низом лежит тупо char[100500], например. Что тоже
имеет смысл, чтоб избавиться от юниона, тем более если не известно, что в этом массиве
будет потом положено (юнион заранее не напишешь). Но оно вообще нужно, ручное управление
памятью? Аллокатор из библиотеки, в общем и целом, не так уж и плох. Если 100500 запросов
в секунду в разных тредах -- может вручную и лучше, или через отдельный специальный аллокатор.
И этот массив, его нужно выравнить, через alignof, на std::max_align_t или что-то вроде того.
Юнион сам выровняет всё как надо. Но с юнионом нужно помнить, что если обратился к одному полю --
то всё, к другим обращаться не имеешь права. Так только в голом C можно, но не в C++.
M>Или лучше сделать через if else заместо v-table ? В этом случаи есть шанс что gcc проинлайнит все вызовы и будет быстрее работать.
if (object->type == XXX) do_something();
else if (object->type == YYY) do_something_another();
Вот так ^^^ точно делать не стоит, кроме очень узких мест и для классов где всего три типа может быть
и четвёртый никогда не появится. Потому, что дальнейшая эволюция кода все такие места превратит
в бесконечный источник проблем. Я выше расписал возможный вариант: снижение точек диспетчеризации,
выделение классов несущих (статические, не зависимые от конкретного инстанса) свойства другого класса (traits),
использование этих классов в обобщённых классах или функциях.