Warsaw C++ User Group 12 listopada 2013
Gemius Krótko o projektach w firmie.
Gemius Produkty: gemiusaudience gemiusprism gemiustraffic gemiusdirecteffect gemiusheatmap gemiusstream gemiusshopmonitor
Gemius Projekty niskopoziomowe: MooseFS rozproszony system plików Taskell system do rozpraszania zadań i kontroli zasobów VSender główna baza danych
VSender zbierane są odsłony...dużo odsłon, 12 miliardów dziennie pliki dobowe powyżej 200G...po kompresji 1:40 jedna odsłona zajmuje kilkanaście bajtów dane są sortowane, składowane i serwowane z małym opóźnieniem system korzysta z MooseFS i Taskell
CommonLib logowanie zdarzeń obsługa puli wątków system wyjątków interfejs MySQL interfejs SQLite serwis geolokalizacji serwis stref czasowych narzędzia systemowe RTL - format plików naszych baz danych
RTL paradygmat const database pliki systemu VSender są w tym formacie wykorzystywana biblioteka boost zawiera algorytmy kompresji, serializacji, format pliku: dane podzielone na bloki rekordy zgodnie z zadanym kluczem indeks po kluczu głównym CreateClass - zestaw makr ułatwiający definicję rekordu
RTL/CreateClass Definicja rekordu: CREATE_CLASS( MyRecord, ((ID) (uint64_t) (DEFAULT)) ((TS) (int32_t) (DEFAULT)) ((Name) (std::string) (DEFAULT)), ) PRINTABLE DEFAULTCONSTRUCTIBLE
Konfiguracja bazy przez parametry szablonu: template < typename RecordType, typename KeyFunctor, template <class> class Codec, typename EquivJoinPolicy, typename Defaults, typename IndexType, typename BlockFlushPolicy, typename SorterBufferPolicy, bool Stable > class DatabaseConfig;
Analogiczny przykład z języka Python: subprocess.check_call( args, *, stdin=none, stdout=none, stderr=none, shell=false)
Wiele opcji, a można: subprocess.check_call( [ seq, 1, 5 ], stdout=open( output.txt, w ))
Jak coś takiego zrobić ogólnie dla funkcji w C++? przeciążanie operator= przeciążanie operator, foo(( a=5, b=10)); foo(const Opts& = Opts()); trudniejsze ze względu na referencje można użyć boost.parameter
W przypadku szablonów: zbliżone do programowania funkcyjnego i dzięki temu łatwiejsze
Istnieje rozwiązanie książkowe: C++ Templates: The Complete Guide David Vandevoorde, Nicolai M. Josuttis oparte o dziedziczenie wirtualne dziedziczenie po klasie Defaults
Rozwiązanie ma pewne wady: nie można dziedziczyć (public) po tej samej klasie sporo nadmiarowych klas utrata kontroli przez mechanizm dziedziczenia u nas nie wszystkie cechy to typename/typedef część parametrów jest od siebie zależna
Zastosowane rozwiązanie nieksiążkowe: oparte o proste mechanizmy języka na podstawie listy typów szybko się kompiluje :)
lista typów template <typename Param, typename Tail> struct ParamList { }; struct ParamEOList { }; pusty parametr struct NopParam { };
DatabaseConfig: RecordType i KeyFunctor - bez zmian kolejne Param0, Param1, itd. - jako zwykłe typename domyślnie NopParam nawlekamy na listę (w odwrotnej kolejności):...... ParamList<Param2, ParamList<Param1, ParamList<Param0, ParamEOList> > >
Kod można wygenerować, wykorzystamy boost.preprocessor: #define CONFIG_PARAMS_COUNT 10 #define CONFIG_TEMPLATE_PARAMS... #define CONFIG_PARAMS_LIST...
Generowanie parametrów szablonu: #define CONFIG_MAKE_PARAM(Z, I, Data) \ BOOST_PP_COMMA_IF(I) typename Param ## I = NopParam #define CONFIG_TEMPLATE_PARAMS \ BOOST_PP_REPEAT(CONFIG_PARAMS_COUNT, CONFIG_MAKE_PARAM, _)
Generowanie listy parametrów: #define CONFIG_PARAMS_NODE_LHS(Z, I, Data) \ ParamList<BOOST_PP_CAT(Param, \ BOOST_PP_SUB(BOOST_PP_SUB(CONFIG_PARAMS_COUNT, I), 1)), #define CONFIG_PARAMS_NODE_RHS(Z, I, Data) > #define CONFIG_PARAMS_LIST \ BOOST_PP_REPEAT(CONFIG_PARAMS_COUNT, CONFIG_PARAMS_NODE_LHS, _) \ ParamEOList \ BOOST_PP_REPEAT(CONFIG_PARAMS_COUNT, CONFIG_PARAMS_NODE_RHS, _)
Każdy parametr ma swój sposób definiowania: struct UseFeature1 { }; struct UseFeature2 { }; template <typename Other> struct SetFeature { };
...i swój mechanizm wyciągania wartości: template <typename Params> struct ExtractFeature; Niech udostępnia np.: typedef feature type.
...i częściowe specjalizacje: a) trafiamy na nasz feature, np: template <typename Tail> struct ExtractFeature<ParamList<UseFeature1, Tail> > { typedef feature1 feature_type; };
...i częściowe specjalizacje: b) trafiamy na feature użytkownika: template <typename Other, typename Tail> struct ExtractFeature<ParamList<SetFeature<Other>, Tail> > { typedef Other feature_type; };
...i częściowe specjalizacje: c) ustawienie dla innej cechy: template <typename Param, typename Tail> struct ExtractFeature<ParamList<Param, Tail> > : ExtractFeature<Tail> { };
...i częściowe specjalizacje: d) ustawienie domyślne: template <> struct ExtractFeature<ParamEOList> { typedef default_type feature_type; };
Użycie w DatabaseConfig: template < typename RecordType, typename KeyFunctor, CONFIG_TEMPLATE_PARAMS > class DatabaseConfig { typedef CONFIG_PARAMS_LIST Params; typedef typename ExtractFeature<Params>::feature_type feature_type; /*... */
Przykład użycia.
Wcześniejszą konfigurację zapisywaliśmy: typedef DatabaseConfig< TestRecord, TestKeyFunctor, NetworkOrderCodec, JoinEquivalentWith<TestJoiner>, RecordDatabaseDefaults<3, 4096>, StandardFileIndex<TestKeyFunctor::result_type, NetworkOr StandardBlockFlushPolicy<TestRecord, TestKeyFunctor>, JoinEquivalentWith<TestJoiner>::record_sorter_buffer_pol true > TestConfig;
...a teraz tak: typedef DatabaseConfig< TestRecord, TestKeyFunctor, SetJoinPolicy<JoinEquivalentWith<TestJoiner> >, SetStable > TestConfig;
Pytania?
Dziękuję za uwagę.