เอนจิน JavaScript V8 ของ Google ได้ส่งมอบการอัปเกรดประสิทธิภาพครั้งใหญ่ให้กับ JSON.stringify โดยเพิ่มความเร็วมากกว่าสองเท่า แม้ว่าสิ่งนี้จะฟังดูเหมือนการปรับปรุงทางเทคนิคที่ฝังลึกอยู่ในโค้ดเบราว์เซอร์ แต่มันแก้ไขหนึ่งในคอขวดที่เจ็บปวดที่สุดที่นักพัฒนา Node.js ต้องเผชิญทุกวัน
การปรับปรุงประสิทธิภาพ: ประสิทธิภาพของ JSON.stringify เร็วขึ้นกว่า 2 เท่า ตามการวัดผลจากเบนช์มาร์ก JetStream2 json-stringify-inspector
ตัวฆ่าประสิทธิภาพที่ซ่อนอยู่ในแอปพลิเคชัน Node.js
เป็นเวลาหลายปีที่ JSON.stringify ได้บีบคอประสิทธิภาพเซิร์ฟเวอร์ Node.js อย่างเงียบๆ นักพัฒนาที่สร้าง API และเว็บเซอร์วิสได้ค้นพบสิ่งนี้อย่างยากลำบาก - สิ่งที่ควรจะเป็นขั้นตอนการ serialization ข้อมูลง่ายๆ กลับกลายเป็นการสูญเสียประสิทธิภาพที่ใหญ่ที่สุดในแอปพลิเคชันของพวกเขา
ปัญหานี้ส่งผลกระทบอย่างหนักเป็นพิเศษเนื่องจากการออกแบบ event loop แบบ single-threaded ของ Node.js เมื่อเซิร์ฟเวอร์ของคุณต้องการ serialize การตอบกลับขนาดใหญ่ ทุกอย่างอื่นจะต้องรอ โมเดลการทำงานแบบ cooperative multitasking นี้หมายความว่าการดำเนินการ JSON ที่ช้าเพียงครั้งเดียวสามารถหยุดแอปพลิเคชันทั้งหมดของคุณได้ ทำให้เกิดปัญหาประสิทธิภาพประเภทที่ทำให้นักพัฒนาที่มีประสบการณ์ตั้งคำถามกับการเลือกใช้เทคโนโลยีของพวกเขา
จากการวิเคราะห์ประสิทธิภาพ node ครั้งแรกของฉันเมื่อปีที่แล้ว JSON.stringify เป็นหนึ่งในอุปสรรคที่ใหญ่ที่สุดต่อเกือบทุกอย่างเกี่ยวกับเซอร์วิส node ที่มีประสิทธิภาพ
นักพัฒนาหลายคนได้ลองใช้วิธีแก้ไขปัญหาเช่นการแบ่งออบเจ็กต์ขนาดใหญ่เป็นชิ้นเล็กๆ หรือการถ่ายโอนงานไปยัง thread แยกต่างหาก แต่โซลูชันเหล่านี้มักจะสร้างปัญหามากกว่าที่จะแก้ไข บางครั้งทำให้โหลด CPU เพิ่มขึ้นสามเท่าในขณะที่พยายามลดมัน
การปรับให้เหมาะสมอย่างชาญฉลาดภายใต้ฝากระโปรง
ทีมวิศวกรรม V8 แก้ไขปัญหานี้โดยการสร้าง fast path สำหรับสถานการณ์ serialization ทั่วไป ข้อมูลเชิงลึกที่สำคัญนั้นง่าย: การดำเนินการ JSON ส่วนใหญ่เกี่ยวข้องกับออบเจ็กต์ข้อมูลธรรมดาที่ไม่มีพฤติกรรมซับซ้อนหรือผลข้างเคียง โดยการตรวจจับกรณีง่ายๆ เหล่านี้ V8 สามารถข้ามการตรวจสอบความปลอดภัยที่มีราคาแพงและใช้ code path ที่ปรับให้เหมาะสมสูง
การปรับปรุงเจาะลึกไปในรายละเอียดทางเทคนิค ทีมได้เปลี่ยนจาก memory buffer เดี่ยวขนาดใหญ่เป็นแบบแบ่งส่วน ทำให้ไม่ต้องทำการคัดลอกหน่วยความจำที่มีต้นทุนสูง พวกเขายังอัปเกรดอัลกอริทึมหลักสำหรับการแปลงตัวเลขเป็นสตริง โดยเปลี่ยนจากระบบ Grisu3 เก่าไปเป็นวิธีการ Dragonbox ที่มีประสิทธิภาพมากกว่า
บางทีอย่างชาญฉลาดที่สุดคือพวกเขาได้เพิ่มหน่วยความจำให้กับโครงสร้างออบเจ็กต์ที่ติดตามว่าชื่อ property ต้องการการ escape อักขระพิเศษหรือไม่ เมื่อทำการ serialize อาร์เรย์ของออบเจ็กต์ที่คล้ายกัน - รูปแบบทั่วไปในการตอบกลับ API - ระบบสามารถข้ามการตรวจสอบซ้ำๆ และคัดลอกชื่อ property โดยตรง
ผลข้างเคียง: การดำเนินการที่สามารถเรียกพฤติกรรมที่ไม่คาดคิดระหว่าง serialization เช่นการรันโค้ดที่กำหนดเองหรือทำให้เกิดรอบการทำความสะอาดหน่วยความจำ
Dragonbox: อัลกอริทึมสมัยใหม่ที่ออกแบบมาเป็นพิเศษสำหรับการแปลงตัวเลขเป็นสตริงที่รวดเร็วและแม่นยำ
การปรับปรุงทางเทคนิคที่สำคัญ:
- เส้นทางเร็วที่ปราศจากผลข้างเคียงด้วยการออกแบบแบบวนซ้ำ (ไม่ใช่แบบเรียกตัวเอง)
- ตัวแปลงสตริงแบบเทมเพลตสำหรับการจัดการตัวอักษรหนึ่งไบต์และสองไบต์
- คำสั่ง SIMD สำหรับการตรวจจับการหลีกเลี่ยงสตริง
- การปรับปรุงเลนด่วนโดยใช้แฟล็กคลาสที่ซ่อนอยู่
- อัลกอริทึม Dragonbox แทนที่ Grisu3 สำหรับการแปลงตัวเลขเป็นสตริง
- ระบบบัฟเฟอร์แบบแบ่งส่วนแทนที่บัฟเฟอร์ต่อเนื่องเดียว
ข้อกังวลด้านความปลอดภัยและความเข้ากันได้
การเพิ่มประสิทธิภาพมาพร้อมกับข้อจำกัดที่สำคัญบางประการ fast path ทำงานได้เฉพาะกับ serialization ที่ตรงไปตรงมา - ไม่มีการจัดรูปแบบที่กำหนดเอง ไม่มีฟังก์ชันการแปลงข้อมูล และไม่มีออบเจ็กต์ที่มีเมธอด serialization พิเศษ เมื่อ V8 พบฟีเจอร์เหล่านี้ มันจะกลับไปใช้ general-purpose serializer ที่ช้ากว่าแต่ครอบคลุมมากกว่า
สมาชิกชุมชนบางคนได้ตั้งคำถามเกี่ยวกับผลกระทบด้านความปลอดภัย การปรับให้เหมาะสมอย่างก้าวร้าวใน parser และ serializer ในอดีตได้สร้างช่องโหว่ แม้ว่าวิธีการของ V8 ในการกลับไปใช้โค้ดที่พิสูจน์แล้วสำหรับกรณีที่ซับซ้อนควรจะลดความเสี่ยงเหล่านี้ให้น้อยที่สุด
ข้อกำหนดของ Fast Path:
- ไม่มี replacer หรือ space arguments
- เฉพาะ data objects และ arrays ธรรมดาเท่านั้น
- ไม่มี custom .toJSON() methods
- ไม่มี indexed properties บน objects
- เฉพาะ string types แบบง่าย (ไม่มี ConsString representations)
ผลกระทบในโลกจริง
การปรับปรุงเหล่านี้มาถึงใน V8 เวอร์ชัน 13.8 ซึ่งมาพร้อมกับ Chrome 138 สำหรับนักพัฒนา Node.js สิ่งนี้แสดงถึงก้าวสำคัญในการทำให้เซิร์ฟเวอร์ JavaScript แข่งขันได้มากขึ้นกับทางเลือกอื่นที่สร้างด้วย Go หรือ Java
จังหวะเวลาไม่อาจจะดีไปกว่านี้แล้ว เมื่อแอปพลิเคชันเว็บจัดการข้อมูลที่ซับซ้อนมากขึ้นและการตอบกลับ API มีขนาดใหญ่ขึ้น ประสิทธิภาพ serialization ได้กลายเป็นคอขวดที่สำคัญ การอัปเกรด V8 นี้จะไม่แก้ไขความท้าทายด้าน concurrency ทั้งหมดของ Node.js แต่มันขจัดหนึ่งในอุปสรรคที่ใหญ่ที่สุดในการสร้างแอปพลิเคชันเซิร์ฟเวอร์ที่รวดเร็วและตอบสนองได้
สำหรับนักพัฒนาส่วนใหญ่ ประโยชน์จะเป็นไปโดยอัตโนมัติ fast path จะเปิดใช้งานอย่างโปร่งใสสำหรับกรณีการใช้งานทั่วไป เช่น การตอบกลับ API และการแคช configuration โดยให้ประสิทธิภาพที่ดีขึ้นโดยไม่ต้องเปลี่ยนแปลงโค้ดใดๆ