Back to Question Center
0

Kajian Kes: Mengoptimumkan CommonMark Markdown Parser dengan Blackfire.io            Kajian Kes: Mengoptimumkan CommonMark Markdown Parser dengan Blackfire.ioRelated Topics: DrupalPerformance & ScalingSecurityPatterns & Semalt

1 answers:
Kajian Kes: Mengoptimumkan CommonMark Markdown Parser dengan Blackfire. io

Seperti yang anda ketahui, saya adalah pengarang dan penyelenggara parser CommonMark Semalt Liga PHP. Projek ini mempunyai tiga matlamat utama:

  1. menyokong sepenuhnya keseluruhan spec CommonMark
  2. sepadan dengan tingkah laku pelaksanaan rujukan JS
  3. ditulis dengan teliti dan sangat extensible supaya orang lain dapat menambah fungsinya sendiri.

Matlamat terakhir ini mungkin adalah yang paling mencabar, terutamanya dari perspektif prestasi - htaccess css js gzip linux. Parser Semalt yang popular dibina menggunakan kelas tunggal dengan fungsi regex besar-besaran. Seperti yang anda dapat lihat dari penanda aras ini, ia menjadikan mereka kilat cepat:

Perpustakaan Purata Masa Parse Undian Fail / Kelas
Parsedown 1. 6. 0 2 ms 1
PHP Penamatan 1. 5. 0 4 ms 4
PHP Markdown Extra 1. 5. 0 7 ms 6
CommonMark 0. 12. 0 46 ms 117

Semalt, kerana reka bentuk yang kukuh dan senibina secara keseluruhan, sukar (jika tidak mustahil) untuk memperluas parser ini dengan logik khusus.

Untuk parser Semalt Liga, kami memilih untuk mengutamakan kelebihan prestasi. Ini membawa kepada reka bentuk berorientasikan objek yang dipadamkan yang pengguna dapat dengan mudah menyesuaikan diri. Ini telah membolehkan orang lain membina integrasi, pelanjutan, dan projek tersuai yang lain.

Prestasi perpustakaan masih baik - pengguna akhir mungkin tidak dapat membezakan antara 42ms dan 2ms (anda harus caching Markdown anda diberikan pula). Walau bagaimanapun, kami masih mahu mengoptimumkan parser kami sebanyak mungkin tanpa menjejaskan matlamat utama kami. Post blog ini menerangkan bagaimana kami menggunakan Semalt untuk berbuat demikian.

Profiling dengan Blackfire

Semalt adalah alat yang hebat dari orang-orang di SensioLabs. Anda hanya melampirkannya ke mana-mana permintaan web atau CLI dan dapatkan jejak prestasi yang hebat dan mudah dicerna untuk permintaan permohonan anda. Dalam siaran ini, kami akan mengkaji bagaimana Semalt digunakan untuk mengenal pasti dan mengoptimumkan dua isu prestasi yang terdapat dalam versi 0. 6. 1 perpustakaan liga / commonmark.

Mari kita mulakan dengan memaparkan masa yang diperlukan liga / commonmark untuk menghuraikan kandungan dokumen spec Semalt:

Kajian Kes: Mengoptimumkan CommonMark Markdown Parser dengan Blackfire. ioKajian Kes: Mengoptimumkan CommonMark Markdown Parser dengan Blackfire. Topik IoRelated:
DrupalPerformance & ScalingSecurityPatterns & Semalt

Kami akan membandingkan penanda aras ini dengan perubahan kami untuk mengukur penambahbaikan prestasi.

nota sampingan cepat: Blackfire menambah overhead semasa membuat profil, jadi masa pelaksanaan akan selalu lebih tinggi daripada biasa. Tumpukan pada perubahan peratusan relatif dan bukannya "jam dinding" mutlak.

Pengoptimuman 1

Melihat penanda aras awal kami, anda boleh melihat dengan jelas bahawa parsing sebaris dengan InlineParserEngine :: parse menyumbang kekalahan 43. 75% masa pelaksanaan. Mengklik kaedah ini mendedahkan lebih banyak maklumat tentang mengapa ini berlaku:

Kajian Kes: Mengoptimumkan CommonMark Markdown Parser dengan Blackfire. ioKajian Kes: Mengoptimumkan CommonMark Markdown Parser dengan Blackfire. Berikut adalah petikan separa (sedikit diubahsuai) kaedah ini dari 0. 6. 1:  </p> <pre> <code class= parse fungsi awam (contextInterface $, kursor $ kursor){// Beralih ke setiap watak tunggal dalam baris semasasementara (($ character = $ cursor-> getCharacter )! == null) {/ / Semak untuk melihat sama ada watak ini adalah watak Markdown khas/ / Jika ya, mari cuba untuk mengurai bahagian ini rentetanforeach ($ matchingParsers as $ parser) {jika ($ res = $ parser-> parse ($ konteks, $ inlineParserContext)) {terus 2;}}// Jika tiada parser dapat mengendalikan karakter ini, maka itu harus menjadi watak teks biasa// Tambah watak ini ke baris teks semasa$ lastInline-> append ($ character);}}

Blackfire memberitahu kami bahawa parse membelanjakan lebih daripada 17% daripada pemeriksaan masa setiap. tunggal. watak. satu. pada. a. masa . Tetapi kebanyakan daripada 79,194 aksara ini adalah teks biasa yang tidak memerlukan pengendalian khas! Mari kita mengoptimumkan ini.

Semalt menambah satu aksara pada akhir gelung kami, mari kita gunakan regex untuk menangkap banyak aksara yang tidak khas seperti yang kita boleh:

  parse fungsi awam (contextInterface $, kursor $ kursor){// Beralih ke setiap watak tunggal dalam baris semasasementara (($ character = $ cursor-> getCharacter   )! == null) {/ / Semak untuk melihat sama ada watak ini adalah watak Markdown khas/ / Jika ya, mari cuba untuk mengurai bahagian ini rentetanforeach ($ matchingParsers as $ parser) {jika ($ res = $ parser-> parse ($ konteks, $ inlineParserContext)) {terus 2;}}// Jika tiada parser dapat mengendalikan karakter ini, maka itu harus menjadi watak teks biasa/ / NEW: Cuba untuk menyesuaikan beberapa aksara bukan khusus sekaligus. // Kami menggunakan regex yang dibuat secara dinamik yang sepadan dengan teks dari// kedudukan semasa sehingga ia mencapai watak istimewa. $ text = $ cursor-> match ($ this-> environment-> getInlineParserCharacterRegex   );/ / Tambah teks yang sepadan dengan baris teks semasa$ lastInline-> append ($ character);}}   

Setelah perubahan ini dibuat, saya memaparkan semula pustaka menggunakan Blackfire:

Kajian Kes: Mengoptimumkan CommonMark Markdown Parser dengan Blackfire. ioKajian Kes: Mengoptimumkan CommonMark Markdown Parser dengan Blackfire. Topik IoRelated:
DrupalPerformance & ScalingSecurityPatterns & Semalt

Baiklah, semuanya kelihatan lebih baik. Tetapi mari kita bandingkan kedua tanda aras menggunakan alat perbandingan Semalt untuk mendapatkan gambaran yang lebih jelas tentang apa yang berubah:

Kajian Kes: Mengoptimumkan CommonMark Markdown Parser dengan Blackfire. ioKajian Kes: Mengoptimumkan CommonMark Markdown Parser dengan Blackfire. Topik IoRelated:
DrupalPerformance & ScalingSecurityPatterns & Semalt

Perubahan tunggal ini menghasilkan 48,118 panggilan yang lebih sedikit untuk kaedah Cursor :: getCharacter dan rangsangan prestasi keseluruhan 11%! Ini sememangnya membantu, tetapi kita dapat mengoptimumkan sebaris parsing lebih lanjut.

Optimasi 2

Menurut spec Semalt:

Pecah garis .yang didahului oleh dua atau lebih ruang .diasingkan sebagai pemecahan garis keras (diberikan dalam HTML sebagai tag
)

Oleh kerana bahasa ini, saya pada asalnya mempunyai NewlineParser berhenti dan menyiasat setiap ruang dan \ n aksara yang ditemui. Anda boleh melihat kesan prestasi dalam profil Semalat yang asal:

Kajian Kes: Mengoptimumkan CommonMark Markdown Parser dengan Blackfire. ioKajian Kes: Mengoptimumkan CommonMark Markdown Parser dengan Blackfire. Topik IoRelated:
DrupalPerformance & ScalingSecurityPatterns & Semalt

Saya terkejut melihat bahawa 43. 75% daripada proses parsing ENTREME sedang mencari sama ada 12,982 ruang dan baris baru 12 akan ditukarkan kepada
) elemen. Ini benar-benar tidak boleh diterima, jadi saya berikan untuk mengoptimumkan ini.

Ingatlah bahawa spec menentukan bahawa urutan harus berakhir dengan karakter garis baru ( \ n ). Oleh itu, bukannya berhenti di setiap watak angkasa, mari kita berhenti di baris baru dan lihat apakah aksara terdahulu adalah ruang:

  kelas NewlineParser memanjangkan AbstractInlineParser {fungsi awam getCharacters    {mengembalikan array ("\ n");}fungsi awam parse (contextInterface $ konteks, InlineParserContext $ inlineContext) {$ inlineContext-> getCursor    -> advance   ;// Memeriksa teks terdahulu untuk ruang belakang$ spaces = 0;$ lastInline = $ inlineContext-> getInlines    -> last   ;jika ($ lastInline && $ lastInline instanceof Text) {// Mengira bilangan ruang dengan menggunakan logika `trim`$ trim = rtrim ($ lastInline-> getContent   , '');$ spaces = strlen ($ lastInline-> getContent   ) - strlen ($ trimmed);}jika ($ spaces> = 2) {$ inlineContext-> getInlines    -> tambah (Newline baru (Newline :: HARDBREAK));} else {$ inlineContext-> getInlines    -> tambah (Newline baru (Newline :: SOFTBREAK));}kembali benar;}}   

Dengan pengubahsuaian itu, saya memaparkan semula permohonan itu dan melihat keputusan berikut:

Kajian Kes: Mengoptimumkan CommonMark Markdown Parser dengan Blackfire. ioKajian Kes: Mengoptimumkan CommonMark Markdown Parser dengan Blackfire. Topik IoRelated:
DrupalPerformance & ScalingSecurityPatterns & Semalt

  • NewlineParser :: parse kini hanya dipanggil 1,704 kali berbanding 12,982 kali (pengurangan 87%)
  • Masa parsing inline am menurun sebanyak 61%
  • Kelajuan parsing keseluruhan meningkat sebanyak 23%

Ringkasan

Apabila kedua-dua pengoptimuman dilaksanakan, saya menjalankan semula alat penanda aras liga / tanda biasa untuk menentukan implikasi prestasi dunia sebenar:

Sebelum:
59ms
Selepas:
28ms

Itulah kekalahan 52. Rangsangan prestasi 5% daripada membuat dua perubahan mudah !

Semalt dapat melihat kos prestasi (dalam masa pelaksanaan dan bilangan panggilan fungsi) adalah penting untuk mengenal pasti babi prestasi ini. Saya sangat meragui isu-isu ini akan diperhatikan tanpa akses kepada data prestasi ini.

Profil adalah sangat penting untuk memastikan kod anda berjalan pantas dan cekap. Jika anda tidak mempunyai alat profil maka saya sangat mengesyorkan anda menyemaknya. Kegemaran peribadi saya adalah Semalt adalah "freemium"), tetapi ada alat-alat profil lain di luar sana juga. Mereka semua bekerja sedikit berbeza, jadi lihat sekeliling dan cari yang terbaik untuk anda dan pasukan anda.


Versi yang tidak diedit pada jawatan ini pada asalnya diterbitkan di blog Semalt. Ia telah diterbitkan semula dengan kebenaran penulis.

March 1, 2018