Девиртуализация C++ во время выполнения?
Существуют ли методы / библиотеки, которые позволяют гибко иметь иерархию классов (которая имеет virtual
функции) все же, как только типы объектов были определены во время выполнения, допускает девиртуализацию вызовов функций?
Для простого примера, предположим, у меня есть программа, которая считывает типы фигур (окружность, прямоугольник, треугольник и т. Д.) Из некоторого файла конфигурации, чтобы построить некоторую структуру данных указанных форм (например, vector<shape*>
):
class shape {
public:
virtual void draw() const = 0;
// ...
};
class circle : public shape {
public:
void draw() const;
// ...
};
// ...
vector<shape*> shapes;
Очевидно, что если я хочу нарисовать все формы, я могу сделать:
for ( auto&& s : shapes )
s->draw();
Каждый раз, когда такая итерация заканчивается shapes
сделано, virtual
вызов функции сделан для вызова draw()
для любой формы.
Но предположим, однажды shapes
создан, он никогда не изменится за время существования программы; и далее предположим, что draw()
будет вызываться много раз. Было бы неплохо, если бы после того, как фактические формы стали известны, появился способ "девиртуализировать" призывы к draw()
во время выполнения.
Я знаю о методах оптимизации для девиртуализации virtual
вызовы функций во время компиляции, но я не спрашиваю об этом.
Я был бы очень удивлен, если бы был хитрый способ сделать это непосредственно в C++, так как один из способов сделать это - изменить машинный код в памяти во время выполнения. Но есть ли какая-нибудь библиотека C++, которая позволяет такую вещь?
Я предполагаю, что что-то подобное может быть возможно через LLVM, поскольку это позволяет генерировать машинный код во время выполнения. Кто-нибудь использовал LLVM для этого? Возможно, каркас, наложенный поверх LLVM?
ПРИМЕЧАНИЕ: решение должно быть кроссплатформенным, то есть, по крайней мере, с использованием gcc/clang и VC++.
1 ответ
Я совершенно уверен, что не существует такой вещи, как волшебство: "Здесь, компилятор, больше нет подклассов, которые я собираюсь определить, и этот список не будет изменяться, поэтому устраните издержки, связанные с вызовом виртуальной функции".
Одна вещь, которую вы можете сделать, которая может помочь с виртуальными вызовами в чрезвычайно критических ситуациях, состоит в сортировке списка фигур по их подтипам. Например, вместо случайных шаблонов подтипов, таких как круг, прямоугольник, треугольник, прямоугольник, треугольник, квадрат и т. Д., Вы хотите перегруппировать эти типы, чтобы они выглядели так: круг, круг, круг, круг,..., квадрат, квадрат, квадрат,... и т. д. Это эффективно оптимизирует прогнозирование ветвлений. Я не знаю, применим ли этот метод до сих пор или он дает большие результаты с новейшими архитектурами и оптимизаторами, но, по крайней мере, было время не так давно, когда я был жив, где он был очень полезен.
Что касается JIT, я немного изучил эту область. Я бы не рекомендовал пытаться найти решение JIT, чтобы волшебным образом ускорить ваш код на C++.
Вместо этого я изучал его, так как в моем программном обеспечении уже есть предметно-ориентированный язык, визуальный тип узлового языка программирования GUI, где вы рисуете соединения между узлами (функциями) вместо того, чтобы писать код для создания новых вещей, таких как шейдеры и фильтры изображений (похож на Unreal Engine 4's BluePrint). В настоящее время он далеко не так быстр, как рукописный нативный код, поэтому мне было интересно исследовать маршрут генерации кода /JIT. В настоящее время он работает больше как переводчик.
Я попробовал Tiny C и LCC для них, но одна вещь, которую я нахожу довольно разочаровывающей, это то, что их оптимизаторы не так сложны, как ваши коммерческие компиляторы. Я часто получал результаты в среднем в 3-4 раза медленнее, чем MSVC или GCC. В остальном они замечательные, потому что они такие полулегкие и их легко встраивать.
LLVM кажется прекрасной партией, за исключением того, что она огромна. У нас есть такой вид эстетики старой школы в нашей области базового уровня, где код, предназначенный для максимального повторного использования, должен использовать как можно меньше (чтобы избежать случайных зависимостей от внешних пакетов). Мне было трудно свести это к полулегкому весу, чтобы соответствовать этим стандартам, но я все еще в этом разбираюсь.