15

Pracuję nad kompilatorem i chciałbym poprawić jego wydajność. Zauważyłem, że około 50% czasu zajmuje parsowanie plików źródłowych. Ponieważ plik źródłowy jest dość mały i po nim wykonuję wiele transformacji, wydaje mi się, że jest on doskonały.Jak test porównawczy Boost Spirit Parser?

Mój parser jest parserem Boost Spirit z lexerem (z lexerem :: pos_iterator) i gramaturą średniej wielkości. Parsuję źródło w AST.

Mój problem polega na tym, że nie mam pojęcia, co zajmuje najwięcej czasu podczas analizowania: kopie węzłów AST, lexer, reguł parsera lub pamięci.

Nie sądzę, że jest to problem z I/O, ponieważ pracuję na dysku SSD i że czytam plik całkowicie na początku, a następnie używam tylko wersji pamięci.

Próbowałem za pomocą profilarek, ale metody, że potrzeba czasu, są pewne metody z Boost, z imionami setek znaków i nie wiem dokładnie, co robią ...

tak, to czy jest preferowanym sposobem porównywać parownik Boost Spirit i jego gramatykę? A może istnieją pewne zasady, które można wykorzystać do weryfikacji wydajności w niektórych określonych punktach?

Dzięki

źródeł dla zainteresowanych:

+4

Oto zapis przez ApochiQ kto korzysta Boost.Spirit jak parser dla języka epoki. Znacznie poprawił wydajność swojego parsera między wydaniami 10 a 11 i napisał, na czym się skupił [tutaj] (http://code.google.com/p/scribblings-by-apoch/wiki/OptimizingBoostSpirit). –

+0

Analiza porównawcza WSZYSTKO polega zwykle na uruchomieniu czegoś przez "testowany kod", a następnie analizie wyników. Jeśli masz złożony system, często pomaga on utworzyć "zerowy sterownik" lub "zerowy interfejs", dzięki czemu możesz na przykład zasilać plik źródłowy i analizować go, bez wpływu na analizowane wyniki. –

+0

@ MatthieuM. Tak, znam ten artykuł. Już dawno temu podążyłem za kilkoma poradami tego wspaniałego artykułu. Ale nie wiem, jaką radę zastosować. –

Odpowiedz

12

dałem rzeczy szybkie skanowanie.

Mój profiler szybko powiedział mi, że skonstruowanie gramatyki i (szczególnie) obiektu lexera zajęło sporo zasobów.

Rzeczywiście, po prostu zmieniając jeden wiersz w SpiritParser.cpp zapisane 40% czasu wykonania (~ 28s w dół do ~ 17s):

lexer::Lexer lexer; 

do

static const lexer::Lexer lexer; 

Teraz,

  • mak gramatyka statyczna wymaga uczynienia jej bezpaństwowcem. I tak się stało się przez

    • ruchu position_begin do qi::_a (przy qi::locals) i
    • przepuszczenie w postaci dziedzicznej atrybutu we właściwym czasie

      • z EDDIGrammarValueGrammar i gramatyk, np

        start %= qi::eps [ qi::_a = qi::_r1 ] >> program; 
        
      • jak poszczególne przepisy z ValueGrammar które są używane zewnętrznie).

    ten miał kilka Suboptymalne działania niepożądane:

    • reguła debugowanie jest odkomentowanymi ponieważ lexer::pos_iterator_type ma domyślne wyjście strumieniowe przeciążenie
    • wyrażenie qi::position(position_begin) zostało „sfałszowane "z dość skomplikowanym zamiennikiem:

      auto local_pos = qi::lazy (
           boost::phoenix::construct<qi::position>(qi::_a) 
          ); 
      

      To nie wydaje się idealne. (Najlepiej, chciałoby się zastąpić qi::position przez zmodyfikowaną niestandardowego parsera dyrektywy, która wie, jak dostać się begin_position z qi :: mieszkańców (?), Więc nie byłoby potrzeby, aby wywołać ekspresję parsera leniwie?)

Anyways, implementing these further changes2ogolił kolejne ~ 15% czasu wykonania:

static const lexer::Lexer lexer; 
static const parser::EddiGrammar grammar(lexer); 

try { 
    bool r = spirit::lex::tokenize_and_parse(
      position_begin, position_end, 
      lexer, 
      grammar(boost::phoenix::cref(position_begin)), 
      program); 

luźne pomysły:

  • Czy uważane generowanie statycznego lexer (Generating the Static Analyzer)
  • Czy bierzesz za pomocą punktów oczekiwanie na potencjalnie zmniejszyć ilość nawrotów (uwaga: nie miałem nic w tej dziedzinie pomiaru)
  • Mają cię rozważane alternatywy dla Position::file i Position::theLine? Kopiowanie ciągów wydaje się większe niż to konieczne. Wolałbym przechowywać const char *. Można również spojrzeć na Boost Flyweight
  • Czy naprawdę wymagany jest wstępny pominięcie w dyrektywie qi::position?
  • (nieco non-poważny: Czy za przeniesieniem do Spirit X3 Wydaje obiecują potencjalne korzyści w postaci move semantyki.)

Nadzieja to pomaga.


[1] Podczas analizowania wszystkich testów w test/cases/*.eddi 100 razy like so (github):

for (int i=0; i<100; i++) 
    for (auto& fname : argv) 
{ 
    eddic::ast::SourceFile program; 
    std::cout << fname << ": " << std::boolalpha << parser.parse(fname, program, nullptr) << "\n"; 
} 

taktowane prosty

time ./test ../../test/cases/*.eddi | md5sum 

Z md5sum działając jako kontrola poprawności.

[2] stworzyłem żądania ściągania z proof-of-concept refaktoryzacji tutaj https://github.com/wichtounet/eddic/pull/52

+2

I nazywasz to szybkim skanowaniem oO Wielkie dzięki, to jest świetna odpowiedź. Spróbuję dziś twojej poprawki. Dla twoich luźnych pomysłów: Spróbuję statycznego lexera (nie wiedziałem o tym wcześniej), nie wiedziałem, że te punkty oczekiwania mogą zmniejszyć cofanie, spróbuję;) Dla pozycji, tak, uniknę przechowywania std :: string, rzeczywiście jest ciężki. Sprawdzę wstępne pominięcie qi: pozycja. W przypadku Spirit X3, tak, rozważałem przetestowanie tego. Teraz, gdy znowu mam czas, prawdopodobnie wypróbuję to po innych zmianach. Czy uważasz, że jest wystarczająco dojrzały dla mojego parsera? –

+0

@BaptisteWicht Szczerze mówiąc, nie powiedziałbym, że X3 jest gotowy na Twój projekt. Możesz jednak dać mu podgląd. To z pewnością może doprowadzić do "po prostu poczekaj na V3", zamiast próbować zoptymalizować wyjście z V2 :) [Tak, szybkie odnosiło się głównie do kompletnego ślepego bocznego propagowania atrybutów: <] – sehe

+0

Jestem spróbuję (prawdopodobnie nie minie jeszcze kilka tygodni). W każdym razie, połączyłem i przetestowałem twój PR, działa wspaniale. Nie byłem w stanie uzyskać tak dużo jak ty na moim komputerze, ale udało mi się uzyskać 33% poprawę. Pracuję również nad Twoimi innymi punktami. –

Powiązane problemy