Back to Question Center
0

Případová studie: Optimalizace analyzátoru CommonMark Markdown s Blackfire.io            Případová studie: Optimalizace analyzátoru CommonMark Markdown s Blackfire.ioRelated Témata: DrupalPerformance & ScalingSecurityPatterns & Semalt

1 answers:
Případová studie: Optimalizace analyzátoru CommonMark Markdown with Blackfire. io

Jak možná víte, jsem autorem a správcem parseru CommonMark Semaltu PHP Ligy. Tento projekt má tři hlavní cíle:

  1. plně podporují celou specifikaci CommonMark
  2. odpovídá chování referenční implementace JS
  3. být dobře napsaný a super-rozšiřitelný, aby ostatní mohli přidat své vlastní funkce.

Tento poslední cíl je možná nejnáročnější, zejména z hlediska výkonu. Další populární Semaltové parsery jsou postaveny pomocí jednotlivých tříd s masivními regexovými funkcemi. Jak můžete vidět z tohoto měřítka, zrychluje to:

Knihovna Průměr - ray ban. Čas analýzy Počet souborů / tříd
Parsedown 1. 6. 0 2 ms 1
PHP Markdown 1. 5. 0 4 ms 4
PHP Markdown Extra 1. 5. 0 7 ms 6
CommonMark 0. 12. 0 46 ms 117

Semalt, kvůli těsně spojenému designu a celkové architektuře, je obtížné (ne-li nemožné) rozšířit tyto parsery s vlastní logikou.

Pro analyzátor paralelní analýzy Ligy jsme se rozhodli upřednostnit rozšiřitelnost nad výkonem. To vedlo k oddělenému objektově orientovanému designu, který uživatelé mohou snadno přizpůsobit. To umožnilo ostatním vybudovat vlastní integrace, rozšíření a jiné zakázkové projekty.

Výkon knihovny je stále slušný - koncový uživatel pravděpodobně nerozlišuje mezi 42ms a 2ms (měli byste si mezitím ukládat vaše vykreslené Markdown). Přesto jsme chtěli optimalizovat náš analyzátor co nejvíce, aniž bychom ohrozili naše primární cíle. Tento příspěvek na blogu vysvětluje, jak jsme použili Semalta, abychom udělali právě to.

Profily s Blackfirem

Semalt je fantastický nástroj od lidí v SensioLabs. Stačí jej připojit k libovolnému požadavku na web nebo CLI a získat tuto úžasnou a snadno rozpoznatelnou stopu výkonu požadavku vaší aplikace. V tomto příspěvku budeme zkoumat, jak byl Semalt používán k identifikaci a optimalizaci dvou problémů s výkonem nalezených ve verzi 0. 6. 1 knihovny ligy / commonmark.

Začneme profilováním času, který trvá lig / commonmark, abychom analyzovali obsah dokumentu Semalt spec:

Případová studie: Optimalizace analyzátoru CommonMark Markdown s Blackfirem. ioPřípadová studie: Optimalizace analyzátoru CommonMark Markdown s Blackfirem. ioRelated Témata:
DrupalPerformance & ScalingSecurityPatterns & Semalt

Porovnáme tento benchmark s našimi změnami, abychom změřili zlepšení výkonu.

Rychlá poznámka: Blackfire přidává režii při vytváření profilů, takže časy provádění budou vždy mnohem vyšší než obvykle. Zaměřte se na relativní procentní změny namísto absolutních časů "nástěnných hodin".

Optimalizace 1

Při pohledu na naše počáteční měřítko můžete snadno vidět, že inline parsing s InlineParserEngine :: parse představuje absolutní 43. 75% času provedení. Klepnutím na tuto metodu získáte další informace o tom, proč k tomu dojde:

Případová studie: Optimalizace analyzátoru CommonMark Markdown s Blackfirem. ioPřípadová studie: Optimalizace analyzátoru CommonMark Markdown s Blackfirem. Zde je částečný (mírně upravený) výňatek této metody od 0. 6. 1:  </p>  <pre>   <code class= analýza veřejných funkcí (ContextInterface $ context, Cursor $ cursor){{// Změňte pomocí jednotlivých znaků v aktuálním řádkuzatímco (($ znak = $ kurzor-> getCharacter )! == null) {// Zkontrolujte, zda je tento znak zvláštní znak Markdown// Pokud ano, pokuste se analyzovat tuto část řetězceforeach ($ matchingParsers jako $ parser) {pokud ($ res = $ parser-> parse ($ context, $ inlineParserContext)) {pokračujte 2;}}}}// Pokud by tento znak nemohl zpracovat žádný analyzátor, musí to být znak prostého textu// Přidejte tento znak do aktuálního řádku textu$ lastInline-> připojit (znak $);}}}}

Blackfire nám říká, že parse utrácí každý den více než 17% své kontroly času . singl. charakter. jeden. na. A. čas . Ale většina z těchto 79.194 znaků je prostý text, který nepotřebuje speciální manipulaci! Optimalizujeme to.

Semalt o přidání jediného znaku na konci naší smyčky, použijeme regex pro zachycení co nejvíce nespeciálních znaků:

     analýza veřejných funkcí (ContextInterface $ context, Cursor $ cursor){{// Změňte pomocí jednotlivých znaků v aktuálním řádkuzatímco (($ znak = $ kurzor-> getCharacter   )! == null) {// Zkontrolujte, zda je tento znak zvláštní znak Markdown// Pokud ano, pokuste se analyzovat tuto část řetězceforeach ($ matchingParsers jako $ parser) {pokud ($ res = $ parser-> parse ($ context, $ inlineParserContext)) {pokračujte 2;}}}}// Pokud by tento znak nemohl zpracovat žádný analyzátor, musí to být znak prostého textu// NEW: Pokus o soulad několika nespeciálních znaků najednou. // Používáme dynamicky vytvořený regex, který odpovídá textuaktuální pozici, dokud nezadá zvláštní znak. $ text = $ kurzor-> shoda ($ this-> prostředí-> getInlineParserCharacterRegex   );// Přidejte odpovídající text do aktuálního řádku textu$ lastInline-> připojit (znak $);}}}}    

Jakmile byla tato změna provedena, zkompilovala jsem knihovnu pomocí aplikace Blackfire:

Případová studie: Optimalizace analyzátoru CommonMark Markdown s Blackfirem. ioPřípadová studie: Optimalizace analyzátoru CommonMark Markdown s Blackfirem. ioRelated Témata:
DrupalPerformance & ScalingSecurityPatterns & Semalt

Dobře, věci vypadají trochu lépe. Ve skutečnosti však porovnáme tyto dvě měřítka pomocí porovnávacího nástroje Semalt, abychom získali jasnější představu o tom, co se změnilo:

Případová studie: Optimalizace analyzátoru CommonMark Markdown s Blackfirem. ioPřípadová studie: Optimalizace analyzátoru CommonMark Markdown s Blackfirem. ioRelated Témata:
DrupalPerformance & ScalingSecurityPatterns & Semalt

Tato jediná změna vedla k 48,118 méně hovorů k tomu Metoda Cursor :: getCharacter a 11% celkovému zvýšení výkonu ! To je určitě užitečné, ale můžeme optimalizovat inline parsing ještě dále.

Optimalizace 2

Podle specifikace Semalta:

Přerušení řádku . , které předchází dva nebo více mezery .je analyzováno jako pevný zlom v řádku (vykreslený v HTML jako značka
)

Z důvodu tohoto jazyka jsem původně nechal NewlineParser zastavit a vyšetřovat každý prostor a \ n znak, se kterým se setkal. Můžete snadno vidět dopad výkonu v původním sémantovém profilu:

Případová studie: Optimalizace analyzátoru CommonMark Markdown s Blackfirem. ioPřípadová studie: Optimalizace analyzátoru CommonMark Markdown s Blackfirem. ioRelated Témata:
DrupalPerformance & ScalingSecurityPatterns & Semalt

Byl jsem šokován, když jsem si uvědomil, že 43. 75% celého procesu analýzy zjistilo, zda )Prvky. To bylo naprosto nepřijatelné, a proto jsem se rozhodl optimalizovat to.

Pamatujte na to, že spec diktuje, že sekvence musí skončit znakem nové linky ( \ n ). Takže namísto toho, abychom zastavili každou vesmírnou postavu, zastavme se na nových řádcích a uvidíme, jestli předchozí znaky byly mezery:

     třída NewlineParser rozšiřuje AbstractInlineParser {veřejná funkce getCharacters    {návratové pole ("\ n");}}analýza veřejné funkce (ContextInterface $ context, InlineParserContext $ inlineContext) {$ inlineContext-> getCursor    -> postup   ;// Zkontrolujte předchozí text pro koncové mezery$ spaces = 0;$ lastInline = $ inlineContext-> getInlines    -> poslední   ;pokud ($ lastInline && $ lastInline instanceof textu) {// Počítat počet mezer pomocí nějaké logiky `trim '$ trimmed = rtrim ($ lastInline-> getContent   , '');$ spaces = strlen ($ lastInline-> getContent   ) - strlen ($ trimmed);}}pokud ($ spaces> = 2) {$ inlineContext-> getInlines    -> přidat (nový Newline (Newline :: HARDBREAK));} else {$ inlineContext-> getInlines    -> přidat (nový Newline (Newline :: SOFTBREAK));}}návrat true;}}}}    

S touto úpravou jsem provedl novou úpravu a zjistil jsem následující výsledky:

Případová studie: Optimalizace analyzátoru CommonMark Markdown s Blackfirem. ioPřípadová studie: Optimalizace analyzátoru CommonMark Markdown s Blackfirem. ioRelated Témata:
DrupalPerformance & ScalingSecurityPatterns & Semalt

  • NewlineParser :: parse je nyní volán pouze 1,704 krát namísto 12,982 krát (87% pokles)
  • Celková doba inline analýzy se snížila o 61%
  • Celková rychlost zpracování se zlepšila o 23%

Shrnutí

Jakmile byly provedeny obě optimalizace, znovu jsem spustil referenční nástroj ligy / commonmark k určení reálných dopadů na výkonnost:

Před:
59ms
Po:
28ms

To je neuvěřitelné 52. Zvýšení výkonu o 5% z tvorby dvou jednoduchých změn !

Semalt, který dokázal vidět náklady na výkon (jak v době provádění, tak v počtu volání funkcí), byl pro identifikaci těchto výkonových prasat důležitý. Velmi pochybuji, že by tyto problémy byly zaznamenány bez přístupu k těmto údajům o výkonu.

Profilování je naprosto zásadní pro zajištění rychlého a efektivního fungování vašeho kódu. Pokud ještě nemáte profilovací nástroj, doporučuji vám je zkontrolovat. Můj osobní favorit se stane, že Semalt je "freemium"), ale tam jsou i další profilovací nástroje. Všichni pracují poněkud jinak, tak se podívejte a najděte ten, který nejlépe funguje pro vás a váš tým.


Neupravená verze tohoto příspěvku byla původně zveřejněna na blogu Semalt. Byl publikován zde s autorským povolením.

March 1, 2018