Boost Spirit QJe lent

J’essaie d’parsingr les fichiers TPCH avec Boost Spirit QI. Ma mise en œuvre inspirée par l’exemple d’employé de Spirit QI ( http://www.boost.org/doc/libs/1_52_0/libs/spirit/example/qi/employee.cpp ). Les données sont au format csv et les jetons sont délimités par un ‘|’ personnage.

Cela fonctionne mais il est très lent (20 secondes pour 1 Go).

Voici mon grammer qi pour le fichier lineitem:

struct lineitem { int l_orderkey; int l_partkey; int l_suppkey; int l_linenumber; std::ssortingng l_quantity; std::ssortingng l_extendedprice; std::ssortingng l_discount; std::ssortingng l_tax; std::ssortingng l_returnflag; std::ssortingng l_linestatus; std::ssortingng l_shipdate; std::ssortingng l_commitdate; std::ssortingng l_recepitdate; std::ssortingng l_shipinstruct; std::ssortingng l_shipmode; std::ssortingng l_comment; }; BOOST_FUSION_ADAPT_STRUCT( lineitem, (int, l_orderkey) (int, l_partkey) (int, l_suppkey) (int, l_linenumber) (std::ssortingng, l_quantity) (std::ssortingng, l_extendedprice) (std::ssortingng, l_discount) (std::ssortingng, l_tax) (std::ssortingng, l_returnflag) (std::ssortingng, l_linestatus) (std::ssortingng, l_shipdate) (std::ssortingng, l_commitdate) (std::ssortingng, l_recepitdate) (std::ssortingng, l_shipinstruct) (std::ssortingng, l_shipmode) (std::ssortingng, l_comment)) vector* lineitems=new vector(); phrase_parse(state->dataPointer, state->dataEndPointer, (*(int_ >> "|" >> int_ >> "|" >> int_ >> "|" >> int_ >> "|" >> +(char_ - '|') >> "|" >> +(char_ - '|') >> "|" >> +(char_ - '|') >> "|" >> +(char_ - '|') >> "|" >> +(char_ - '|') >> '|' >> +(char_ - '|') >> '|' >> +(char_ - '|') >> '|' >> +(char_ - '|') >> '|' >> +(char_ - '|') >> '|' >> +(char_ - '|') >> '|' >> +(char_ - '|') >> '|' >> +(char_ - '|') >> '|' ) ), space, *lineitems ); 

Le problème semble être l’parsing du personnage. C’est beaucoup plus lent que les autres conversions. Existe-t-il un meilleur moyen d’parsingr des jetons de longueur variable en chaînes?

J’ai trouvé une solution à mon problème. Comme décrit dans cet article Boost Spirit QI, la grammaire est lente pour l’parsing de chaînes délimitées. Le goulot d’étranglement des performances est dû à la gestion des chaînes de Spirit qi. Tous les autres types de données semblent être assez rapides.

J’évite ce problème en traitant moi-même les données au lieu d’utiliser le traitement Spirit Qi.

Ma solution utilise une classe d’assistance qui propose des fonctions pour chaque champ du fichier csv. Les fonctions stockent les valeurs dans une structure. Les chaînes sont stockées dans un caractère [] s. Laisse à l’parsingur un caractère de nouvelle ligne appelé une fonction qui ajoute la structure au vecteur de résultat. L’parsingur Boost appelle cette fonction au lieu de stocker les valeurs dans un vecteur seul.

Voici mon code pour le fichier region.tbl du benchmark TCPH:

 struct region{ int r_regionkey; char r_name[25]; char r_comment[152]; }; class regionStorage{ public: regionStorage(vector* regions) :regions(regions), pos(0) {} void storer_regionkey(int const&i){ currentregion.r_regionkey = i; } void storer_name(char const&i){ currentregion.r_name[pos] = i; pos++; } void storer_comment(char const&i){ currentregion.r_comment[pos] = i; pos++; } void resetPos() { pos = 0; } void endOfLine() { pos = 0; regions->push_back(currentregion); } private: vector* regions; region currentregion; int pos; }; void parseRegion(){ vector regions; regionStorage regionstorageObject(&regions); phrase_parse(dataPointer, /*< start iterator >*/ state->dataEndPointer, /*< end iterator >*/ (*(lexeme[ +(int_[boost::bind(&regionStorage::storer_regionkey, &regionstorageObject, _1)] - '|') >> '|' >> +(char_[boost::bind(&regionStorage::storer_name, &regionstorageObject, _1)] - '|') >> char_('|')[boost::bind(&regionStorage::resetPos, &regionstorageObject)] >> +(char_[boost::bind(&regionStorage::storer_comment, &regionstorageObject, _1)] - '|') >> char_('|')[boost::bind(&regionStorage::endOfLine, &regionstorageObject)] ])), space); cout << regions.size() << endl; } 

Ce n'est pas une belle solution mais ça marche et c'est beaucoup plus rapide. (2,2 secondes pour 1 Go de données TCPH, multithread)

Le problème provient principalement de l’ajout d’éléments individuels de std::ssortingng conteneur std::ssortingng . Selon votre grammaire, pour chaque atsortingbut std::ssortingng l’allocation commence lorsqu’un caractère est rencontré et s’arrête lorsque vous trouvez un | séparateur. Donc, dans un premier temps, il y a sizeof(char)+1 octets réservés (“\ 0” terminé par un zéro). Le compilateur devra exécuter l’allocateur de std::ssortingng fonction de l’algorithme de doublage de l’allocateur! Cela signifie que la mémoire doit être réaffectée très fréquemment pour les petites chaînes. Cela signifie que votre chaîne est copiée dans une allocation de mémoire double de sa taille et que l’allocation précédente est libérée, à des intervalles de 1,2,4,6,12,24 … caractères. Pas étonnant que cela ait été lent, cela cause d’énormes problèmes avec les fréquents appels au malloc; plus de fragmentation de tas, une plus grande liste chaînée de blocs de mémoire libres, des tailles (petites) variables de ces blocs de mémoire, ce qui pose à son tour des problèmes avec un balayage plus long de la mémoire pour les allocations de l’application pendant toute sa durée de vie. tldr; les données deviennent fragmentées et largement dispersées dans la mémoire.

Preuve? Le code suivant est appelé par char_parser chaque fois qu’un caractère valide est rencontré dans votre iterator. À partir de boost 1.54

/boost/spirit/home/qi/char/char_parser.hpp

 if (first != last && this->derived().test(*first, context)) { spirit::traits::assign_to(*first, attr_); ++first; return true; } return false; 

/boost/spirit/home/qi/detail/assign_to.hpp

 // T is not a container and not a ssortingng template  static void call(T_ const& val, Atsortingbute& attr, mpl::false_, mpl::false_) { traits::push_back(attr, val); } 

/boost/spirit/home/support/container.hpp

 template  struct push_back_container { static bool call(Container& c, T const& val) { c.insert(c.end(), val); return true; } }; 

Le code de suivi de correction que vous avez publié (en modifiant votre structure en Name[Size] ) est fondamentalement identique à l’ajout d’une directive d’instruction chaîne Name.reserve(Size) . Cependant, il n’y a pas de directive pour cela pour le moment.

La solution:

/boost/spirit/home/support/container.hpp

 template  struct push_back_container { static bool call(Container& c, T const& val, size_t initial_size = 8) { if (c.capacity() < initial_size) c.reserve(initial_size); c.insert(c.end(), val); return true; } }; 

/boost/spirit/home/qi/char/char_parser.hpp

 if (first != last && this->derived().test(*first, context)) { spirit::traits::assign_to(*first, attr_); ++first; return true; } if (traits::is_container::value == true) attr_.shrink_to_fit(); return false; 

Je ne l'ai pas testé, mais je suppose qu'il peut accélérer de plus de 10 fois les parsingurs de caractères sur les atsortingbuts de chaîne, comme vous l'avez vu. Ce serait une excellente fonctionnalité d'optimisation dans une mise à jour de Boost Spirit, y compris une reserve(initial_size)[ +( char_ - lit("|") ) ] qui définit la taille de la mémoire tampon initiale.

Utilisez-vous -O2 lors de la compilation?

Les bibliothèques Boosts ont beaucoup de redondance qui sont supprimées lors de l’utilisation des indicateurs d’optimisation.

Une autre solution possible est l’utilisation de la directive sur la répétition des parsings syntaxiques: http://www.boost.org/doc/libs/1_52_0/libs/spirit/doc/html/spirit/qi/reference/directive/repeat.html.