В силу скромной информации на эту тему, в данной статье проведу небольшой обзор и сравнение найденных библиотек для Reflection в C++. В первую очередь эта информация будет интереса разработчикам игр.
Благодаря reflection можно:
— Легко создавать редакторы, в том числе интерфейсов, так как есть удобный доступ к мета-информации о всех свойствах ваших объектов;
— Добавить binding для многих скриптовых языков сразу (Lua, Python, JavaScript и т.д.);
— Использовать мета-информацию для автоматической сериализаци;
— Использовать как фабрику объектов, создавая нужные экземпляры, имея лишь строку с именем тип;
— Использовать в качестве более легковесной замены dynamic_cast;
— И прочее прочее прочее, в зависимости от фантазии и потребностей.
Дальше идет обзор каждой библиотеки по очереди в силу моих скромных возможностей. Для каждой:
— короткое описание;
— пример binding'а и использования для такого класса:
— результаты замера производительности на i5 3570K, Windows 8, Visual Studio 2013, Release x86 (замерялись отдельно 10 000 000 вызовов метода класса и отдельно 10 000 000 поиск метаметода+вызов).
Рассматривались только библиотеки, не требубщие дополнительных шагов построения и инструментов (вроде qt moc и gccxml).
Библиотеки перечислены в порядке возрастания личного интереса к ним.
github.com/rpavlik/luabind
Сейчас для binding'а в Lua используется Luabind (rpavlik's fork), но результирующую мета-информацию больше ни для чего особо не используешь.
Пример
Benchmark
— Invoke — 1100ms
— FindMetaMethod+Invoke — 1580ms
projects.tegesoft.com/pm/projects/camp
github.com/tegesoft/camp
Создавалось французской компанием под вдохновением от luabind. Выглядит довольно культурно и проработано.
Правда заметно не обновлялось уже года 4.
Пример
Benchmark (сбилдить не удалось, взял результаты с сайта другой библиотеки)
— Invoke — 6889ms < — совсем грустно
www.cpgf.org/document/index.html
github.com/cpgf/cpgf
Основной автор вроде китаец. Выглядит проработано, но интерфейс довольно усложнен и выглядит код совсем не лаконично. Много приставок, добавок в именовании, различных интерфейсов, правил использования (например, как и когда передается владение). На простом примере не видно, но если посмотреть tutorial, то становится сильно заметно — github.com/cpgf/cpgf/blob/develop/samples/tutorials/a01_reflect_to_global.cpp
При этом, все сведено к единому интерфейсу, что конечно радует.
Большой плюс — хорошая документация.
Из дополнительных наворотов — сериализация, готовые решения для биндинга в Lua/JavaScript/Python, tweening, своя система событий.
Багофиксы были еще в декабре, то есть проект не мертв.
Пример
Benchmark
— Invoke — 1000ms
— FindMetaMethod+Invoke — 1135ms < — быстрее, чем luabind
www.axelmenzel.de/projects/coding/rttr
Автор, вроде, немец. Ура — C++11. Активно развивается, красивый синтаксис, современные возможности, очень даже радует. В ближайшее время должна появиться новая версия с существенным рефакторингом.
Но пока, судя по коду, много для чего решение довольно прямолинейное, отсюда и значительно худшие показатели производительности.
Пример
Benchmark
— Invoke — 1780ms
— FindMetaMethod+Invoke — 2290ms
bitbucket.org/occash/umof
Автор русскоговорящий, активно отвечает на все вопросы. Создавалось, как я понял, под большим впечатлением от QT. Снова ура — C++11 (все эти constexpr и прочие радости). Активно развивается. В ближайшее время должна появиться новая версия с существенным рефакторингом и ускорением, она и тестировалась.
Условный минус — для создания мета-информации надо использовать макросы, но это связано с особенностями реализации (обьяснено позже).
Пример
Benchmark
— Invoke — 115ms < — магия (в старой версии 420, что тоже на голову выше других)
— FindMetaMethod+Invoke — 1780ms < — уже не так хорошо, но скорее всего и это будет оптимизировано
Invoke почти в 9 раз быстрее самого лучшего результата других библиотек.
Про это писал сам автор, сравнивая свое решение с другими. Статься с графиками и картинками для еще старой версии здесь — www.gamedev.net/page/resources/_/technical/general-programming/implementing-a-meta-system-in-c-r3905
Там же есть сравнение того, как какие библиотеки влияют на время компиляции и линковки проекта и насколько утяжеляют бинарник.
Самые современные, лаконичные и живые библиотеки — uMOF, RTTR.
Наиболее богатая по функциональности — cpgf.
Выдающиеся по производительности:
— uMOF (благодаря особенностям реализации, невероятно быстрый invoke и минимальный overhead в компиляции и размере бинарника);
— cpgf (на данный момент самый быстрый результат по FindMetaMethod+Invoke, что и является наиболее частым сценарием использования).
1) Какую все-таки разработчику игр выбрать библиотеку?
cpgf солидно проработан и показывает хорошие результаты, но так ли важен этот overhead для invoke? Может отдать предпочтение, например, более современному RTTR, т.к. 2290ms на 10000000 вызовов это 4366812 вызовов в секунду. 72780 вызовов на каждом кадре при 60 FPS. То есть, если на каждом кадре делается порядка 700 вызовов, то при 60 FPS это составит меньше 1% от времени кадра.
При этом uMOF показывает выдающиеся результаты, что позволило бы использовать его с максимальной интенсивностью (что и планируется). Но он еще не закончен, не хватает некоторого функционала.
2) Может быть, какая-то библиотека пропущена? Можно было бы добавить ее в обзор.
3) Что вы знаете из своего опыта про любую из этих библиотек? Здесь обзор был поверхностный, может ваш опыт говорит о каких-то существенных особенностях в пользу или против какой-то библиотеки.
Заранее большое спасибо за ваши комментарии.
— UPDATE1: Сравнение производительности библиотек с luabind не совсем корректное. Т.к. вызов метода в luabind подразумевает не только поиск мета-метода, но и работу с виртуальной машиной lua. Постараюсь обновить статью, как только будет больше полезной информации на эту тему.
Благодаря reflection можно:
— Легко создавать редакторы, в том числе интерфейсов, так как есть удобный доступ к мета-информации о всех свойствах ваших объектов;
— Добавить binding для многих скриптовых языков сразу (Lua, Python, JavaScript и т.д.);
— Использовать мета-информацию для автоматической сериализаци;
— Использовать как фабрику объектов, создавая нужные экземпляры, имея лишь строку с именем тип;
— Использовать в качестве более легковесной замены dynamic_cast;
— И прочее прочее прочее, в зависимости от фантазии и потребностей.
Дальше идет обзор каждой библиотеки по очереди в силу моих скромных возможностей. Для каждой:
— короткое описание;
— пример binding'а и использования для такого класса:
class Test
{
public:
int func(int a, int b)
{
return a + b;
}
};
— результаты замера производительности на i5 3570K, Windows 8, Visual Studio 2013, Release x86 (замерялись отдельно 10 000 000 вызовов метода класса и отдельно 10 000 000 поиск метаметода+вызов).
Рассматривались только библиотеки, не требубщие дополнительных шагов построения и инструментов (вроде qt moc и gccxml).
Библиотеки перечислены в порядке возрастания личного интереса к ним.
1) Luabind
github.com/rpavlik/luabind
Сейчас для binding'а в Lua используется Luabind (rpavlik's fork), но результирующую мета-информацию больше ни для чего особо не используешь.
Пример
luabind::module(state)
[
luabind::class_<Test>("Test")
.def("func", &Test::func)
];
local obj = Test()
obj:func(1, 2)
Benchmark
— Invoke — 1100ms
— FindMetaMethod+Invoke — 1580ms
2) Camp
projects.tegesoft.com/pm/projects/camp
github.com/tegesoft/camp
Создавалось французской компанием под вдохновением от luabind. Выглядит довольно культурно и проработано.
Правда заметно не обновлялось уже года 4.
Пример
CAMP_TYPE(Test)
camp::Class::declare<Test>("Test")
.function("func", &Test::func);
Test obj;
camp::Class t = camp::classByName("Test");
camp::Function m = t->function("func");
camp::Value v = m->call(obj, camp::Args(1, 2));
auto result = v.to<int>()
Benchmark (сбилдить не удалось, взял результаты с сайта другой библиотеки)
— Invoke — 6889ms < — совсем грустно
3) cpgf
www.cpgf.org/document/index.html
github.com/cpgf/cpgf
Основной автор вроде китаец. Выглядит проработано, но интерфейс довольно усложнен и выглядит код совсем не лаконично. Много приставок, добавок в именовании, различных интерфейсов, правил использования (например, как и когда передается владение). На простом примере не видно, но если посмотреть tutorial, то становится сильно заметно — github.com/cpgf/cpgf/blob/develop/samples/tutorials/a01_reflect_to_global.cpp
При этом, все сведено к единому интерфейсу, что конечно радует.
Большой плюс — хорошая документация.
Из дополнительных наворотов — сериализация, готовые решения для биндинга в Lua/JavaScript/Python, tweening, своя система событий.
Багофиксы были еще в декабре, то есть проект не мертв.
Пример
cpgf::GDefineMetaClass<Test>
::define("Test")
._method("func", &CpgfTest::func);
Test obj;
cpgf::GMetaClass* t = cpgf::findMetaClass("Test");
cpgf::GMetaMethod* m = t->getMethod("func");
cpgf::GVariant v = m->invoke(&obj, 1, 2);
auto result = cpgf::fromVariant<int>(v);
Benchmark
— Invoke — 1000ms
— FindMetaMethod+Invoke — 1135ms < — быстрее, чем luabind
4) RTTR
www.axelmenzel.de/projects/coding/rttr
Автор, вроде, немец. Ура — C++11. Активно развивается, красивый синтаксис, современные возможности, очень даже радует. В ближайшее время должна появиться новая версия с существенным рефакторингом.
Но пока, судя по коду, много для чего решение довольно прямолинейное, отсюда и значительно худшие показатели производительности.
Пример
RTTR_DECLARE_STANDARD_TYPE_VARIANTS(Test);
RTTR_REGISTER
{
rttr::class_<Test>()
.method("func", &Test::func);
}
Test obj;
rttr::type t = type::get("Test");
rttr::method m = t.get_method("func");
rttr::variant v = m.invoke_variadic(obj, {1, 2});
auto result = v.get_value<int>();
Benchmark
— Invoke — 1780ms
— FindMetaMethod+Invoke — 2290ms
5) uMOF
bitbucket.org/occash/umof
Автор русскоговорящий, активно отвечает на все вопросы. Создавалось, как я понял, под большим впечатлением от QT. Снова ура — C++11 (все эти constexpr и прочие радости). Активно развивается. В ближайшее время должна появиться новая версия с существенным рефакторингом и ускорением, она и тестировалась.
Условный минус — для создания мета-информации надо использовать макросы, но это связано с особенностями реализации (обьяснено позже).
Пример
U_DECLARE_API(Test, METHODS);
U_DECLARE_METHODS(Test)
{
U_METHOD(func)
};
const Api* api = U_API(Test)::api();
Method m = api->method(api->indexOfMethod("func(int,int)"));
int result;
m.invoke(&obj, result, {1, 2}));
Benchmark
— Invoke — 115ms < — магия (в старой версии 420, что тоже на голову выше других)
— FindMetaMethod+Invoke — 1780ms < — уже не так хорошо, но скорее всего и это будет оптимизировано
Invoke почти в 9 раз быстрее самого лучшего результата других библиотек.
Про это писал сам автор, сравнивая свое решение с другими. Статься с графиками и картинками для еще старой версии здесь — www.gamedev.net/page/resources/_/technical/general-programming/implementing-a-meta-system-in-c-r3905
Там же есть сравнение того, как какие библиотеки влияют на время компиляции и линковки проекта и насколько утяжеляют бинарник.
Luabind | Camp | cpgf | RTTR | uMOF | |
Invoke | 1100 | 6889 | 1000 | 1780 | 115 |
FindMetaMethod+Invoke | 1580 | x | 1135 | 2290 | 1780 |
Заключение
Самые современные, лаконичные и живые библиотеки — uMOF, RTTR.
Наиболее богатая по функциональности — cpgf.
Выдающиеся по производительности:
— uMOF (благодаря особенностям реализации, невероятно быстрый invoke и минимальный overhead в компиляции и размере бинарника);
— cpgf (на данный момент самый быстрый результат по FindMetaMethod+Invoke, что и является наиболее частым сценарием использования).
Предложения для обсуждения
1) Какую все-таки разработчику игр выбрать библиотеку?
cpgf солидно проработан и показывает хорошие результаты, но так ли важен этот overhead для invoke? Может отдать предпочтение, например, более современному RTTR, т.к. 2290ms на 10000000 вызовов это 4366812 вызовов в секунду. 72780 вызовов на каждом кадре при 60 FPS. То есть, если на каждом кадре делается порядка 700 вызовов, то при 60 FPS это составит меньше 1% от времени кадра.
При этом uMOF показывает выдающиеся результаты, что позволило бы использовать его с максимальной интенсивностью (что и планируется). Но он еще не закончен, не хватает некоторого функционала.
2) Может быть, какая-то библиотека пропущена? Можно было бы добавить ее в обзор.
3) Что вы знаете из своего опыта про любую из этих библиотек? Здесь обзор был поверхностный, может ваш опыт говорит о каких-то существенных особенностях в пользу или против какой-то библиотеки.
Заранее большое спасибо за ваши комментарии.
— UPDATE1: Сравнение производительности библиотек с luabind не совсем корректное. Т.к. вызов метода в luabind подразумевает не только поиск мета-метода, но и работу с виртуальной машиной lua. Постараюсь обновить статью, как только будет больше полезной информации на эту тему.