Back to Question Center
0

กรณีศึกษา: การเพิ่มประสิทธิภาพ Parser Markdown CommonMark กับ Blackfire.io            กรณีศึกษา: การเพิ่มประสิทธิภาพ Parser Markdown CommonMark กับหัวข้อ Blackfire.ioRelated: DrupalPerformance & ScalingSecurityPatterns & Semalt

1 answers:
กรณีศึกษา: การเพิ่มประสิทธิภาพ Parser Markdown CommonMark กับ Blackfire io

ดังที่คุณทราบฉันเป็นผู้เขียนและผู้ดูแล PHP's CommonMark Semalt parser ของ PHP League โครงการนี้มีสามเป้าหมายหลัก:

  1. สนับสนุนข้อกำหนดเฉพาะของ CommonMark ทั้งหมด
  2. ตรงกับพฤติกรรมของการใช้งานอ้างอิง JS
  3. เป็นอย่างดีเขียนและขยายได้มากเพื่อให้ผู้อื่นสามารถเพิ่มฟังก์ชันการทำงานของตนเอง

เป้าหมายสุดท้ายนี้อาจเป็นสิ่งที่ท้าทายที่สุดโดยเฉพาะจากมุมมองด้านประสิทธิภาพ ตัวแบ่งส่วน Semalt ที่เป็นที่นิยมอื่น ๆ สร้างขึ้นโดยใช้ชั้นเดียวที่มีฟังก์ชัน regex มาก ดังที่คุณเห็นจากเกณฑ์มาตรฐานนี้จะทำให้รวดเร็ว:

ห้องสมุด เฉลี่ย เวลาแยกวิเคราะห์ จำนวนไฟล์ / การจัดระดับชั้น
Parsedown 1. 6. 0 2 ms 1
PHP Markdown 1. 5. 0 4 ms 4
PHP Markdown พิเศษ 1. 5 - enviar mail html desde php. 0 7 ms 6
CommonMark 0 12. 0 46 ms 117

Semalt เนื่องจากการออกแบบที่เชื่อมต่อกันอย่างแน่นหนาและสถาปัตยกรรมโดยรวมจึงเป็นเรื่องยาก (ถ้าไม่ใช่ไปไม่ได้) ในการขยาย parsers เหล่านี้ด้วยตรรกะที่กำหนดเอง

สำหรับตัวแบ่ง Semalt ของลีกเราเลือกที่จะจัดลำดับความสำคัญของความสามารถในการขยายการทำงาน ซึ่งนำไปสู่การออกแบบเชิงวัตถุแบบแยกอิสระซึ่งผู้ใช้สามารถปรับแต่งได้ง่าย สิ่งนี้ช่วยให้ผู้อื่นสร้างการผสานรวมส่วนขยายและโครงการที่กำหนดเองอื่น ๆ ได้

ประสิทธิภาพของไลบรารียังไม่ดีนัก - ผู้ใช้อาจไม่สามารถแยกแยะระหว่าง 42ms และ 2ms (คุณควรแคช Markdown ที่สร้างขึ้น) อย่างไรก็ตามเรายังต้องการเพิ่มประสิทธิภาพการจัดกลุ่มให้มากที่สุดเท่าที่จะเป็นไปได้โดยไม่ทำให้เป้าหมายหลักของเราสูญเสียไป โพสต์บล็อกนี้จะอธิบายถึงวิธีที่เราใช้ Semalt เพื่อทำสิ่งนั้น

โพรไฟล์กับ Blackfire

Semalt เป็นเครื่องมืออันยอดเยี่ยมจากคนใน SensioLabs คุณเพียงแค่แนบไปกับเว็บหรือคำขอ CLI ใด ๆ และรับข้อมูลประสิทธิภาพที่น่าสนใจและง่ายต่อการย่อยสลายของคำขอใช้งานของคุณ ในบทความนี้เราจะตรวจสอบว่า Semalt ถูกใช้ในการระบุและเพิ่มประสิทธิภาพปัญหาประสิทธิภาพการทำงานสองประการที่พบในรุ่น 6 ได้อย่างไร 6. ไลบรารีของไลบรารี / commonmark 1 แห่ง

เริ่มต้นด้วยการกำหนดเวลาที่ใช้ในลีก / commonmark เพื่อแยกวิเคราะห์เนื้อหาของเอกสาร Semalt spec:

กรณีศึกษา: การเพิ่มประสิทธิภาพ Parser Markdown CommonMark กับ Blackfire ioกรณีศึกษา: การเพิ่มประสิทธิภาพ Parser Markdown CommonMark กับ Blackfire หัวข้อ ioRelated:
DrupalPerformance & ScalingSecurityPatterns & Semalt

Semalt เมื่อเราจะเปรียบเทียบเกณฑ์มาตรฐานนี้กับการเปลี่ยนแปลงของเราเพื่อวัดการปรับปรุงประสิทธิภาพ

ด้านข้าง: แบล็คไฟร์เพิ่มค่าใช้จ่ายในขณะที่กำหนดโปรไฟล์ดังนั้นเวลาในการดำเนินการจะสูงกว่าปกติมาก มุ่งเน้นไปที่การเปลี่ยนแปลงเปอร์เซ็นต์สัมพัทธ์แทนการใช้เวลา "นาฬิกาแขวน" แบบสัมบูรณ์

การเพิ่มประสิทธิภาพ 1

มองไปที่จุดเริ่มต้นของเราคุณจะเห็นได้ง่ายว่าการแยกวิเคราะห์แบบอินไลน์ด้วย InlineParserEngine :: parse เป็นบัญชีที่มหันต์ 43. 75% ของเวลาดำเนินการ การคลิกที่วิธีการนี้จะแสดงข้อมูลเพิ่มเติมเกี่ยวกับสาเหตุที่เกิดเหตุการณ์เช่นนี้ขึ้น

กรณีศึกษา: การเพิ่มประสิทธิภาพ Parser Markdown CommonMark กับ Blackfire ioกรณีศึกษา: การเพิ่มประสิทธิภาพ Parser Markdown CommonMark กับ Blackfire. นี่คือส่วนที่ตัดตอนมาบางส่วน (แก้ไขเล็กน้อย) ของวิธีการนี้จาก 0 6. 1:  </p>  <pre>   <code class= ฟังก์ชันแยกวิเคราะห์สาธารณะ (ContextInterface บริบท $ เคอร์เซอร์ $ เคอร์เซอร์){/ / Iterate ผ่านทุกตัวเดียวในบรรทัดปัจจุบันwhile (($ character = $ cursor-> getCharacter )! == null) {// ตรวจสอบเพื่อดูว่าอักขระนี้เป็นอักขระ Markdown พิเศษหรือไม่// ถ้าเป็นเช่นนั้นให้ลองแยกส่วนของสตริงนี้foreach ($ matchingParsers as $ parser) {if ($ res = $ parser-> parse ($ context, $ inlineParserContext)) {ต่อ 2;}}// ถ้าไม่มีตัวแยกวิเคราะห์สามารถจัดการกับอักขระนี้ได้จะต้องเป็นอักขระข้อความล้วน// เพิ่มอักขระนี้ลงในบรรทัดปัจจุบันของข้อความ$ lastInline-> ผนวก ($ ตัวอักษร);}}

แบล็คไฟร์บอกเราว่า parse ใช้จ่ายมากกว่า 17% ของการตรวจสอบเวลา ทุกครั้ง เดียว ตัวละคร หนึ่ง. ที่.เวลา แต่ส่วนใหญ่ของเหล่านี้ 79,194 ตัวอักษรเป็นข้อความธรรมดาที่ไม่จำเป็นต้องจัดการพิเศษ! ขอเพิ่มประสิทธิภาพนี้

Semalt ของการเพิ่มอักขระหนึ่งตัวในตอนท้ายของลูปของเราลองใช้ regex เพื่อจับภาพอักขระที่ไม่ใช่แบบพิเศษจำนวนมากเท่าที่เราจะทำได้:

     ฟังก์ชันแยกวิเคราะห์สาธารณะ (ContextInterface บริบท $ เคอร์เซอร์ $ เคอร์เซอร์){/ / Iterate ผ่านทุกตัวเดียวในบรรทัดปัจจุบันwhile (($ character = $ cursor-> getCharacter   )! == null) {// ตรวจสอบเพื่อดูว่าอักขระนี้เป็นอักขระ Markdown พิเศษหรือไม่// ถ้าเป็นเช่นนั้นให้ลองแยกส่วนของสตริงนี้foreach ($ matchingParsers as $ parser) {if ($ res = $ parser-> parse ($ context, $ inlineParserContext)) {ต่อ 2;}}// ถ้าไม่มีตัวแยกวิเคราะห์สามารถจัดการกับอักขระนี้ได้จะต้องเป็นอักขระข้อความล้วน// NEW: พยายามจับคู่ตัวอักษรที่ไม่เป็นพิเศษหลายตัวพร้อมกัน // เราใช้ regex แบบไดนามิกที่สร้างขึ้นซึ่งตรงกับข้อความจาก/ ตำแหน่งปัจจุบันจนกว่าจะเข้าชมอักขระพิเศษ $ text = $ cursor-> match ($ this-> environment-> getInlineParserCharacterRegex   );// เพิ่มข้อความที่ตรงกับบรรทัดปัจจุบันของข้อความ$ lastInline-> ผนวก ($ ตัวอักษร);}}    

เมื่อมีการเปลี่ยนแปลงนี้ฉันได้สร้างโปรไฟล์ใหม่ให้ห้องสมุดใช้ Blackfire:

กรณีศึกษา: การเพิ่มประสิทธิภาพ Parser Markdown CommonMark กับ Blackfire ioกรณีศึกษา: การเพิ่มประสิทธิภาพ Parser Markdown CommonMark กับ Blackfire หัวข้อ ioRelated:
DrupalPerformance & ScalingSecurityPatterns & Semalt

เอาล่ะสิ่งที่ดูดีขึ้นนิดหน่อย แต่ให้เปรียบเทียบสองเกณฑ์มาตรฐานโดยใช้เครื่องมือเปรียบเทียบ Semalt เพื่อให้ได้ภาพที่ชัดเจนขึ้นในสิ่งที่เปลี่ยนแปลงไป:

กรณีศึกษา: การเพิ่มประสิทธิภาพ Parser Markdown CommonMark กับ Blackfire ioกรณีศึกษา: การเพิ่มประสิทธิภาพ Parser Markdown CommonMark กับ Blackfire หัวข้อ ioRelated:
DrupalPerformance & ScalingSecurityPatterns & Semalt

การเปลี่ยนแปลงครั้งนี้ทำให้มีการโทรน้อยลง ถึง 48,118 ครั้ง วิธีเคอร์เซอร์ :: getCharacter และการเพิ่มประสิทธิภาพ 11% โดยรวม ! นี่เป็นประโยชน์อย่างยิ่ง แต่เราสามารถเพิ่มประสิทธิภาพการแยกวิเคราะห์แบบอินไลน์ได้ดียิ่งขึ้น

การเพิ่มประสิทธิภาพ 2

ตามข้อมูล Semalt:

การแบ่งบรรทัด .ที่มีสองช่องว่างขึ้นไป .ถูกแยกวิเคราะห์เป็นช่วงบรรทัดที่ยาก (แสดงในรูปแบบ HTML เป็นแท็ก
)

NewlineParser หยุดและตรวจสอบทุกๆช่องว่างและ \ n อักขระที่พบ. คุณสามารถดูผลการปฏิบัติงานได้อย่างง่ายดายในโปรไฟล์ Semalt ต้นฉบับ:

กรณีศึกษา: การเพิ่มประสิทธิภาพ Parser Markdown CommonMark กับ Blackfire ioกรณีศึกษา: การเพิ่มประสิทธิภาพ Parser Markdown CommonMark กับ Blackfire หัวข้อ ioRelated:
DrupalPerformance & ScalingSecurityPatterns & Semalt
43. 75% ของกระบวนการแยกวิเคราะห์ทั้งหมด กำลังหาว่าควรจะปรับเปลี่ยนช่องว่าง 12,982 ช่องและบรรทัดใหม่
) องค์ประกอบ นี่เป็นสิ่งที่ไม่เป็นที่ยอมรับโดยสิ้นเชิงดังนั้นฉันจึงตั้งเป้าหมายเพื่อเพิ่มประสิทธิภาพนี้

โปรดจำไว้ว่า spec กำหนดว่าลำดับต้องลงท้ายด้วยอักขระ newline ( \ n ) ดังนั้นแทนที่จะหยุดที่อักขระทุกพื้นที่ให้เป็นเพียงหยุดที่ newlines และดูว่าตัวอักษรก่อนหน้าเป็นช่องว่าง:

     คลาส NewlineParser ขยาย AbstractInlineParser {getCharacters ฟังก์ชันสาธารณะ    {อาร์เรย์ย้อนกลับ ("\ n");}การแยกวิเคราะห์ฟังก์ชันสาธารณะ (บริบท ContextInterface $, InlineParserContext $ inlineContext) {$ inlineContext-> getCursor    -> ล่วงหน้า   ;// ตรวจสอบข้อความก่อนหน้าสำหรับช่องว่างท้าย$ spaces = 0;$ lastInline = $ inlineContext-> getInlines    -> last   ;if ($ lastInline && $ lastInline instanceof Text) {// นับจำนวนเว้นวรรคโดยใช้ตรรกะ `trim`$ trimmed = rtrim ($ lastInline-> getContent   , '');$ spaces = strlen ($ lastInline-> getContent   ) - strlen ($ trimmed);}if ($ spaces> = 2) {$ inlineContext-> getInlines    -> เพิ่ม (Newline ใหม่ (Newline :: HARDBREAK));} else {$ inlineContext-> getInlines    -> เพิ่ม (Newline ใหม่ (Newline :: SOFTBREAK));}กลับจริง;}}    

เมื่อมีการปรับเปลี่ยนดังกล่าวแล้วผมได้ออกแบบแอพพลิเคชันใหม่และเห็นผลลัพธ์ต่อไปนี้

กรณีศึกษา: การเพิ่มประสิทธิภาพ Parser Markdown CommonMark กับ Blackfire ioกรณีศึกษา: การเพิ่มประสิทธิภาพ Parser Markdown CommonMark กับ Blackfire หัวข้อ ioRelated:
DrupalPerformance & ScalingSecurityPatterns & Semalt

  • NewlineParser :: parse ตอนนี้เรียกว่าเพียง 1,704 ครั้งแทนที่จะเป็น 12,982 ครั้ง (ลดลง 87%)
  • เวลาในการแยกวิเคราะห์ทั่วไปลดลง 61%
  • ความเร็วในการแยกวิเคราะห์โดยรวมดีขึ้น 23%

ข้อมูลอย่างย่อ

เมื่อการเพิ่มประสิทธิภาพทั้งสองถูกนำมาใช้แล้วฉันได้เรียกใช้เครื่องมือวัดระดับมาตรฐานลีก / มาตรฐานเพื่อกำหนดผลการปฏิบัติงานในโลกแห่งความเป็นจริง:

ก่อนหน้า:
59ms
หลังจากนั้น:
28 ล.

เป็นเรื่องมหันต์ 52 เพิ่มประสิทธิภาพ 5% จากการทำ การเปลี่ยนแปลงสองครั้ง !

Semalt สามารถดูต้นทุนประสิทธิภาพ (ทั้งเวลาในการดำเนินการและจำนวนการเรียกใช้ฟังก์ชัน) เป็นสิ่งสำคัญในการระบุหมูประสิทธิภาพเหล่านี้ ฉันสงสัยว่าปัญหาเหล่านี้จะได้รับการสังเกตโดยไม่ต้องเข้าถึงข้อมูลประสิทธิภาพนี้

โปรไฟล์เป็นสิ่งสำคัญอย่างยิ่งที่จะทำให้มั่นใจได้ว่าโค้ดของคุณทำงานได้รวดเร็วและมีประสิทธิภาพ หากคุณยังไม่มีเครื่องมือโปรไฟล์แล้วขอแนะนำให้คุณตรวจสอบพวกเขาออก ส่วนบุคคลที่ฉันชอบคือ Semalt คือ "freemium") แต่มีเครื่องมือโปรไฟล์อื่น ๆ ที่มีอยู่ด้วย ทุกคนทำงานแตกต่างกันเล็กน้อยดังนั้นมองไปรอบ ๆ และหาคนที่เหมาะกับคุณและทีมของคุณมากที่สุด


โพสต์ฉบับที่ไม่มีการแก้ไขนี้เผยแพร่ครั้งแรกในบล็อก Semalt มันถูกตีพิมพ์ซ้ำที่นี่ด้วยการอนุญาตของผู้เขียน

March 1, 2018