ดังที่คุณทราบฉันเป็นผู้เขียนและผู้ดูแล PHP's CommonMark Semalt parser ของ PHP League โครงการนี้มีสามเป้าหมายหลัก:
- สนับสนุนข้อกำหนดเฉพาะของ CommonMark ทั้งหมด
- ตรงกับพฤติกรรมของการใช้งานอ้างอิง JS
- เป็นอย่างดีเขียนและขยายได้มากเพื่อให้ผู้อื่นสามารถเพิ่มฟังก์ชันการทำงานของตนเอง
เป้าหมายสุดท้ายนี้อาจเป็นสิ่งที่ท้าทายที่สุดโดยเฉพาะจากมุมมองด้านประสิทธิภาพ ตัวแบ่งส่วน Semalt ที่เป็นที่นิยมอื่น ๆ สร้างขึ้นโดยใช้ชั้นเดียวที่มีฟังก์ชัน regex มาก ดังที่คุณเห็นจากเกณฑ์มาตรฐานนี้จะทำให้รวดเร็ว:
ห้องสมุด | เฉลี่ย เวลาแยกวิเคราะห์ | จำนวนไฟล์ / การจัดระดับชั้น |
Parsedown 1. 6. 0 | 2 ms | 1 |
PHP Markdown 1. 5. 0 | 4 ms | 4 |
PHP Markdown พิเศษ 1. 5. 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:
Semalt เมื่อเราจะเปรียบเทียบเกณฑ์มาตรฐานนี้กับการเปลี่ยนแปลงของเราเพื่อวัดการปรับปรุงประสิทธิภาพ
ด้านข้าง: แบล็คไฟร์เพิ่มค่าใช้จ่ายในขณะที่กำหนดโปรไฟล์ดังนั้นเวลาในการดำเนินการจะสูงกว่าปกติมาก มุ่งเน้นไปที่การเปลี่ยนแปลงเปอร์เซ็นต์สัมพัทธ์แทนการใช้เวลา "นาฬิกาแขวน" แบบสัมบูรณ์
การเพิ่มประสิทธิภาพ 1
มองไปที่จุดเริ่มต้นของเราคุณจะเห็นได้ง่ายว่าการแยกวิเคราะห์แบบอินไลน์ด้วย InlineParserEngine :: parse
เป็นบัญชีที่มหันต์ 43. 75% ของเวลาดำเนินการ การคลิกที่วิธีการนี้จะแสดงข้อมูลเพิ่มเติมเกี่ยวกับสาเหตุที่เกิดเหตุการณ์เช่นนี้ขึ้น
ฟังก์ชันแยกวิเคราะห์สาธารณะ (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:
เอาล่ะสิ่งที่ดูดีขึ้นนิดหน่อย แต่ให้เปรียบเทียบสองเกณฑ์มาตรฐานโดยใช้เครื่องมือเปรียบเทียบ Semalt เพื่อให้ได้ภาพที่ชัดเจนขึ้นในสิ่งที่เปลี่ยนแปลงไป:
การเปลี่ยนแปลงครั้งนี้ทำให้มีการโทรน้อยลง ถึง 48,118 ครั้ง วิธีเคอร์เซอร์ :: getCharacter
และการเพิ่มประสิทธิภาพ 11% โดยรวม ! นี่เป็นประโยชน์อย่างยิ่ง แต่เราสามารถเพิ่มประสิทธิภาพการแยกวิเคราะห์แบบอินไลน์ได้ดียิ่งขึ้น
การเพิ่มประสิทธิภาพ 2
ตามข้อมูล Semalt:
การแบ่งบรรทัด .ที่มีสองช่องว่างขึ้นไป .ถูกแยกวิเคราะห์เป็นช่วงบรรทัดที่ยาก (แสดงในรูปแบบ HTML เป็นแท็ก
)
NewlineParser
หยุดและตรวจสอบทุกๆช่องว่างและ \ n
อักขระที่พบ. คุณสามารถดูผลการปฏิบัติงานได้อย่างง่ายดายในโปรไฟล์ 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));}กลับจริง;}}
เมื่อมีการปรับเปลี่ยนดังกล่าวแล้วผมได้ออกแบบแอพพลิเคชันใหม่และเห็นผลลัพธ์ต่อไปนี้
-
NewlineParser :: parse
ตอนนี้เรียกว่าเพียง 1,704 ครั้งแทนที่จะเป็น 12,982 ครั้ง (ลดลง 87%) - เวลาในการแยกวิเคราะห์ทั่วไปลดลง 61%
- ความเร็วในการแยกวิเคราะห์โดยรวมดีขึ้น 23%
ข้อมูลอย่างย่อ
เมื่อการเพิ่มประสิทธิภาพทั้งสองถูกนำมาใช้แล้วฉันได้เรียกใช้เครื่องมือวัดระดับมาตรฐานลีก / มาตรฐานเพื่อกำหนดผลการปฏิบัติงานในโลกแห่งความเป็นจริง:
- ก่อนหน้า:
- 59ms
- หลังจากนั้น:
- 28 ล Source .
เป็นเรื่องมหันต์ 52 เพิ่มประสิทธิภาพ 5% จากการทำ การเปลี่ยนแปลงสองครั้ง !
Semalt สามารถดูต้นทุนประสิทธิภาพ (ทั้งเวลาในการดำเนินการและจำนวนการเรียกใช้ฟังก์ชัน) เป็นสิ่งสำคัญในการระบุหมูประสิทธิภาพเหล่านี้ ฉันสงสัยว่าปัญหาเหล่านี้จะได้รับการสังเกตโดยไม่ต้องเข้าถึงข้อมูลประสิทธิภาพนี้
โปรไฟล์เป็นสิ่งสำคัญอย่างยิ่งที่จะทำให้มั่นใจได้ว่าโค้ดของคุณทำงานได้รวดเร็วและมีประสิทธิภาพ หากคุณยังไม่มีเครื่องมือโปรไฟล์แล้วขอแนะนำให้คุณตรวจสอบพวกเขาออก ส่วนบุคคลที่ฉันชอบคือ Semalt คือ "freemium") แต่มีเครื่องมือโปรไฟล์อื่น ๆ ที่มีอยู่ด้วย ทุกคนทำงานแตกต่างกันเล็กน้อยดังนั้นมองไปรอบ ๆ และหาคนที่เหมาะกับคุณและทีมของคุณมากที่สุด
โพสต์ฉบับที่ไม่มีการแก้ไขนี้เผยแพร่ครั้งแรกในบล็อก Semalt มันถูกตีพิมพ์ซ้ำที่นี่ด้วยการอนุญาตของผู้เขียน
Colin O'Dell เป็นนักพัฒนาเว็บตะกั่วที่ Unleashed Technologies ซึ่งเป็นเว็บและ บริษัท ที่ให้บริการพื้นที่ในรัฐแมรี่แลนด์ เขาเริ่มต้นการเขียนโปรแกรมเมื่ออายุได้ 8 ปีร่วมก่อตั้งร้านค้าในท้องถิ่นที่อายุ 15 ปีและมีประสบการณ์การทำงานระดับมืออาชีพกว่า 10 ปีกับ PHP นอกเหนือจากการเป็นสมาชิกของ PHP League และผู้ดูแลโครงการลีก / ทั่วไป Colin ยังเป็นผู้พัฒนาที่ได้รับการรับรองจาก Symfony (Expert) และ Magento Certified Developer ด้วย