การเพิ่มประสิทธิภาพการเริ่มต้นระบบ JavaScript

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

เครือข่าย

เมื่อนักพัฒนาซอฟต์แวร์ส่วนใหญ่คิดถึงต้นทุนของ JavaScript พวกเขาจะคิดถึงในแง่ของต้นทุนการดาวน์โหลดและการดำเนินการ การส่ง JavaScript ที่มีขนาดใหญ่ขึ้นผ่านเครือข่ายจะใช้เวลานานขึ้นหากการเชื่อมต่อของผู้ใช้ช้า

เมื่อเบราว์เซอร์ขอทรัพยากร ระบบจะต้องดึงข้อมูลทรัพยากรนั้นแล้วคลายการบีบอัด ในกรณีของทรัพยากรอย่าง JavaScript จะต้องแยกวิเคราะห์และคอมไพล์ก่อนดำเนินการ

ซึ่งอาจเป็นปัญหาได้ เนื่องจากประเภทการเชื่อมต่อเครือข่ายที่มีประสิทธิภาพที่ผู้ใช้มีอาจไม่ใช่ 3G, 4G หรือ Wi-Fi จริงๆ คุณอาจใช้ Wi-Fi ของร้านกาแฟแต่เชื่อมต่อกับฮอตสปอตเครือข่ายมือถือด้วยความเร็ว 2G

คุณลดค่าใช้จ่ายในการโอนเครือข่ายของ JavaScript ได้โดยทำดังนี้

  • ส่งเฉพาะโค้ดที่ผู้ใช้ต้องการ
    • ใช้การแยกโค้ดเพื่อแบ่ง JavaScript ออกเป็นส่วนที่สำคัญและไม่สำคัญ เครื่องมือรวมโมดูล เช่น webpack รองรับการแยกโค้ด
    • การโหลดแบบ Lazy Loading ในโค้ดที่ไม่สำคัญ
  • การลดขนาด
  • การบีบอัด
    • อย่างน้อยที่สุด ให้ใช้ gzip เพื่อบีบอัดทรัพยากรแบบข้อความ
    • ลองใช้ Brotli ~q11 Brotli มีประสิทธิภาพเหนือกว่า gzip ในด้านอัตราส่วนการบีบอัด ซึ่งช่วยให้ CertSimple ประหยัดขนาดไบต์ JS ที่บีบอัดได้ 17% และช่วยให้ LinkedIn ประหยัดเวลาในการโหลดได้ 4%
  • การนำโค้ดที่ไม่ได้ใช้ออก
  • การแคชโค้ดเพื่อลดการเดินทางของเครือข่าย
    • ใช้การแคช HTTP เพื่อให้เบราว์เซอร์แคชการตอบกลับได้อย่างมีประสิทธิภาพ กำหนดอายุการใช้งานที่เหมาะสมสำหรับสคริปต์ (max-age) และระบุโทเค็นสำหรับตรวจสอบความถูกต้อง (ETag) เพื่อหลีกเลี่ยงการโอนไบต์ที่ไม่มีการเปลี่ยนแปลง
    • การแคช Service Worker ช่วยให้เครือข่ายแอปของคุณมีความยืดหยุ่นและช่วยให้คุณเข้าถึงฟีเจอร์ต่างๆ เช่น แคชโค้ดของ V8 ได้อย่างรวดเร็ว
    • ใช้การแคชระยะยาวเพื่อหลีกเลี่ยงการดึงข้อมูลทรัพยากรที่ไม่มีการเปลี่ยนแปลงซ้ำ หากใช้ Webpack โปรดดูการแฮชชื่อไฟล์

แยกวิเคราะห์/คอมไพล์

เมื่อดาวน์โหลดแล้ว ต้นทุนที่สูงที่สุดอย่างหนึ่งของ JavaScript คือเวลาที่เครื่องมือ JS ใช้ในการแยกวิเคราะห์/คอมไพล์โค้ดนี้ ในเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome การแยกวิเคราะห์และการคอมไพล์เป็นส่วนหนึ่งของเวลา "การเขียนสคริปต์" สีเหลืองในแผงประสิทธิภาพ

ALT_TEXT_HERE

แท็บจากล่างขึ้นบนและแผนผังการเรียกจะแสดงเวลาแยกวิเคราะห์/คอมไพล์ที่แน่นอน ดังนี้

ALT_TEXT_HERE
แผงประสิทธิภาพของเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome > Bottom-Up เมื่อเปิดใช้สถิติการเรียกใช้รันไทม์ของ V8 เราจะเห็นเวลาที่ใช้ในระยะต่างๆ เช่น แยกวิเคราะห์และคอมไพล์

แต่ทำไมเรื่องนี้จึงสำคัญ

ALT_TEXT_HERE

การใช้เวลานานในการแยกวิเคราะห์/คอมไพล์โค้ดอาจทำให้ผู้ใช้โต้ตอบกับเว็บไซต์ได้ช้าลงอย่างมาก ยิ่งคุณส่ง JavaScript มากเท่าใด ก็จะยิ่งใช้เวลาในการแยกวิเคราะห์และคอมไพล์นานขึ้นก่อนที่เว็บไซต์จะโต้ตอบได้

เมื่อเทียบกันแบบไบต์ต่อไบต์ JavaScript มีค่าใช้จ่ายในการประมวลผลสำหรับเบราว์เซอร์มากกว่ารูปภาพหรือเว็บฟอนต์ที่มีขนาดเท่ากัน - Tom Dale

เมื่อเทียบกับ JavaScript แล้ว การประมวลผลรูปภาพที่มีขนาดเท่ากันมีต้นทุนจำนวนมาก (ยังคงต้องถอดรหัส) แต่ในฮาร์ดแวร์อุปกรณ์เคลื่อนที่โดยเฉลี่ย JS มีแนวโน้มที่จะส่งผลเสียต่อการโต้ตอบของหน้าเว็บมากกว่า

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

เมื่อเราพูดถึงการแยกวิเคราะห์และการคอมไพล์ที่ช้า บริบทเป็นสิ่งสำคัญ เรากำลังพูดถึงโทรศัพท์มือถือโดยเฉลี่ย ผู้ใช้ทั่วไปอาจมีโทรศัพท์ที่มี CPU และ GPU ช้า ไม่มีแคช L2/L3 และอาจมีหน่วยความจำจำกัด

ความสามารถของเครือข่ายและความสามารถของอุปกรณ์อาจไม่ตรงกันเสมอไป ผู้ใช้ที่มีการเชื่อมต่อไฟเบอร์ที่ยอดเยี่ยมอาจไม่มี CPU ที่ดีที่สุดในการแยกวิเคราะห์และประเมิน JavaScript ที่ส่งไปยังอุปกรณ์ของตน ในทางกลับกันก็เช่นกัน… การเชื่อมต่อเครือข่ายแย่มาก แต่ CPU เร็วมาก — Kristofer Baxter, LinkedIn

ด้านล่างนี้เราจะเห็นต้นทุนในการแยกวิเคราะห์ JavaScript (แบบง่าย) ที่คลายการบีบอัดแล้วขนาดประมาณ 1 MB ในฮาร์ดแวร์ระดับต่ำและระดับสูง โทรศัพท์ที่เร็วที่สุดในตลาดและโทรศัพท์ทั่วไปมีเวลาในการแยกวิเคราะห์/คอมไพล์โค้ดต่างกัน 2-5 เท่า

ALT_TEXT_HERE
กราฟนี้แสดงเวลาในการแยกวิเคราะห์สำหรับแพ็กเกจ JavaScript ขนาด 1 MB (ประมาณ 250 KB ที่บีบอัดด้วย gzip) ในอุปกรณ์เดสก์ท็อปและอุปกรณ์เคลื่อนที่ระดับต่างๆ เมื่อพิจารณาต้นทุนของการแยกวิเคราะห์ ให้พิจารณาตัวเลขที่คลายการบีบอัดแล้ว เช่น JS ที่บีบอัดด้วย gzip ขนาดประมาณ 250 KB จะคลายการบีบอัดเป็นโค้ดขนาดประมาณ 1 MB

แล้วเว็บไซต์ในชีวิตจริงอย่าง CNN.com ล่ะ

iPhone 8 ระดับไฮเอนด์ใช้เวลาเพียงประมาณ 4 วินาทีในการแยกวิเคราะห์/คอมไพล์ JS ของ CNN เทียบกับประมาณ 13 วินาทีสำหรับโทรศัพท์ทั่วไป (Moto G4) ซึ่งอาจส่งผลอย่างมากต่อความรวดเร็วที่ผู้ใช้จะโต้ตอบกับเว็บไซต์นี้ได้อย่างเต็มที่

ALT_TEXT_HERE
ด้านบนเราจะเห็นเวลาในการแยกวิเคราะห์ที่เปรียบเทียบประสิทธิภาพของชิป A11 Bionic ของ Apple กับ Snapdragon 617 ในฮาร์ดแวร์ Android โดยเฉลี่ย

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

ALT_TEXT_HERE
Google Analytics สามารถให้ข้อมูลเชิงลึกเกี่ยวกับประเภทอุปกรณ์เคลื่อนที่ที่ผู้ใช้จริงใช้เข้าถึงเว็บไซต์ของคุณ ซึ่งจะช่วยให้เข้าใจข้อจำกัดที่แท้จริงของ CPU/GPU ที่ใช้งานอยู่

เราส่ง JavaScript มากเกินไปจริงๆ หรือ เอ่อ อาจจะนะ :)

เมื่อใช้ HTTP Archive (เว็บไซต์ยอดนิยมประมาณ 5 แสนแห่ง) เพื่อวิเคราะห์สถานะของ JavaScript บนอุปกรณ์เคลื่อนที่ เราพบว่าเว็บไซต์ 50% ใช้เวลามากกว่า 14 วินาทีในการโต้ตอบ เว็บไซต์เหล่านี้ใช้เวลาถึง 4 วินาทีในการแยกวิเคราะห์และคอมไพล์ JS

ALT_TEXT_HERE

เมื่อพิจารณาถึงเวลาที่ใช้ในการดึงข้อมูลและประมวลผล JS และทรัพยากรอื่นๆ จึงไม่น่าแปลกใจที่ผู้ใช้อาจต้องรอนานก่อนที่จะรู้สึกว่าหน้าเว็บพร้อมใช้งาน เราปรับปรุงเรื่องนี้ให้ดีขึ้นได้แน่นอน

การนำ JavaScript ที่ไม่สำคัญออกจากหน้าเว็บจะช่วยลดเวลาในการส่ง การแยกวิเคราะห์และการคอมไพล์ที่ใช้ CPU มาก รวมถึงโอเวอร์เฮดของหน่วยความจำที่อาจเกิดขึ้น ซึ่งจะช่วยให้หน้าเว็บของคุณโต้ตอบได้เร็วขึ้นด้วย

เวลาดำเนินการ

การแยกวิเคราะห์และการคอมไพล์ไม่ใช่สิ่งเดียวที่อาจมีค่าใช้จ่าย การเรียกใช้ JavaScript (การเรียกใช้โค้ดที่แยกวิเคราะห์/คอมไพล์แล้ว) เป็นหนึ่งในการดำเนินการที่ต้องเกิดขึ้นในเทรดหลัก เวลาในการดำเนินการที่นานยังอาจทำให้ผู้ใช้โต้ตอบกับเว็บไซต์ได้ช้าลงด้วย

ALT_TEXT_HERE

หากสคริปต์ใช้เวลาดำเนินการนานกว่า 50 มิลลิวินาที เวลาในการตอบสนองจะล่าช้าตามระยะเวลาทั้งหมดที่ใช้ในการดาวน์โหลด คอมไพล์ และเรียกใช้ JS — Alex Russell

เพื่อแก้ไขปัญหานี้ JavaScript จะได้รับประโยชน์จากการอยู่ในกลุ่มเล็กๆ เพื่อหลีกเลี่ยงการล็อกเทรดหลัก ลองดูว่าคุณสามารถลดปริมาณงานที่ทำระหว่างการดำเนินการได้หรือไม่

ค่าใช้จ่ายอื่นๆ

JavaScript อาจส่งผลต่อประสิทธิภาพของหน้าเว็บในด้านอื่นๆ ดังนี้

  • ความทรงจำ หน้าเว็บอาจดูเหมือนกระตุกหรือหยุดชั่วคราวบ่อยครั้งเนื่องจาก GC (การเก็บขยะ) เมื่อเบราว์เซอร์เรียกคืนหน่วยความจำ การดำเนินการ JS จะหยุดชั่วคราว ดังนั้นเบราว์เซอร์ที่รวบรวมขยะบ่อยๆ จึงอาจหยุดการดำเนินการชั่วคราวบ่อยกว่าที่เราต้องการ หลีกเลี่ยงหน่วยความจำรั่วไหลและการหยุดชั่วคราวของ GC บ่อยครั้งเพื่อให้หน้าเว็บไม่กระตุก
  • ในระหว่างรันไทม์ JavaScript ที่ใช้เวลานานอาจบล็อกเทรดหลักซึ่งทำให้หน้าเว็บไม่ตอบสนอง การแบ่งงานออกเป็นส่วนย่อยๆ (โดยใช้ requestAnimationFrame() หรือ requestIdleCallback() สำหรับการกำหนดเวลา) จะช่วยลดปัญหาการตอบสนอง ซึ่งจะช่วยปรับปรุง Interaction to Next Paint (INP) ได้

รูปแบบการลดต้นทุนการส่ง JavaScript

เมื่อคุณพยายามทำให้เวลาในการแยกวิเคราะห์/คอมไพล์และการส่งผ่านเครือข่ายสำหรับ JavaScript ช้าลง มีรูปแบบที่ช่วยได้ เช่น การแบ่งกลุ่มตามเส้นทางหรือ PRPL

PRPL

PRPL (Push, Render, Pre-cache, Lazy-load) เป็นรูปแบบที่เพิ่มประสิทธิภาพสำหรับการโต้ตอบผ่านการแยกโค้ดและการแคชอย่างเข้มงวด ดังนี้

ALT_TEXT_HERE

มาดูผลลัพธ์ที่อาจเกิดขึ้นกัน

เราวิเคราะห์เวลาในการโหลดของเว็บไซต์บนอุปกรณ์เคลื่อนที่และ Progressive Web App ยอดนิยมโดยใช้สถิติการเรียกใช้รันไทม์ของ V8 ดังที่เราเห็น เวลาในการแยกวิเคราะห์ (แสดงเป็นสีส้ม) เป็นส่วนสำคัญที่เว็บไซต์เหล่านี้ใช้เวลาไปมาก

ALT_TEXT_HERE

Wego ซึ่งเป็นเว็บไซต์ที่ใช้ PRPL สามารถรักษาเวลาในการแยกวิเคราะห์เส้นทางให้ต่ำได้ ทำให้โต้ตอบได้อย่างรวดเร็ว เว็บไซต์อื่นๆ จำนวนมากที่กล่าวถึงข้างต้นได้นำการแยกโค้ดและงบประมาณด้านประสิทธิภาพมาใช้เพื่อพยายามลดต้นทุน JS

การบูตสแตรปแบบต่อเนื่อง

เว็บไซต์จำนวนมากเพิ่มประสิทธิภาพการมองเห็นเนื้อหาโดยแลกกับการสูญเสียความสามารถในการโต้ตอบ เพื่อให้ได้การแสดงผลครั้งแรกที่รวดเร็วเมื่อคุณมีแพ็กเกจ JavaScript ขนาดใหญ่ บางครั้งนักพัฒนาซอฟต์แวร์จะใช้การแสดงผลฝั่งเซิร์ฟเวอร์ จากนั้น "อัปเกรด" เพื่อแนบเครื่องจัดการเหตุการณ์เมื่อดึงข้อมูล JavaScript ในที่สุด

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

การใช้ Progressive Bootstrapping อาจเป็นแนวทางที่ดีกว่า ส่งหน้าเว็บที่มีฟังก์ชันการทำงานขั้นต่ำ (ประกอบด้วย HTML/JS/CSS ที่จำเป็นสำหรับเส้นทางปัจจุบันเท่านั้น) เมื่อมีทรัพยากรมากขึ้น แอปจะโหลดแบบ Lazy Loading และปลดล็อกฟีเจอร์เพิ่มเติมได้

ALT_TEXT_HERE
Progressive Bootstrapping โดย Paul Lewis

การโหลดโค้ดตามสัดส่วนของสิ่งที่อยู่ในมุมมองคือเป้าหมายสูงสุด PRPL และ Progressive Bootstrapping เป็นรูปแบบที่ช่วยให้บรรลุเป้าหมายนี้ได้

บทสรุป

ขนาดการส่งข้อมูลมีความสำคัญอย่างยิ่งสำหรับเครือข่ายระดับล่าง เวลาในการแยกวิเคราะห์เป็นสิ่งสำคัญ สำหรับอุปกรณ์ที่เชื่อมโยงกับ CPU การรักษาอัตราเหล่านี้ให้ต่ำจึงเป็นสิ่งสำคัญ

ทีมต่างๆ ประสบความสำเร็จในการนำงบประมาณด้านประสิทธิภาพที่เข้มงวดมาใช้เพื่อรักษาเวลาในการส่งและการแยกวิเคราะห์/คอมไพล์ JavaScript ให้อยู่ในระดับต่ำ ดูบทความ "Can You Afford It?: Real-world Web Performance Budgets" เพื่อดูคำแนะนำเกี่ยวกับงบประมาณสำหรับอุปกรณ์เคลื่อนที่

ALT_TEXT_HERE
การพิจารณาว่าการตัดสินใจด้านสถาปัตยกรรมที่เราทำนั้นจะเหลือ "พื้นที่ว่าง" สำหรับ JS มากน้อยเพียงใดสำหรับตรรกะของแอป

หากคุณกำลังสร้างเว็บไซต์ที่กำหนดเป้าหมายเป็นอุปกรณ์เคลื่อนที่ ให้พยายามอย่างเต็มที่เพื่อพัฒนาบนฮาร์ดแวร์ตัวแทน รักษาเวลาในการแยกวิเคราะห์/คอมไพล์ JavaScript ให้ต่ำ และใช้งบประมาณด้านประสิทธิภาพเพื่อให้มั่นใจว่าทีมของคุณจะสามารถติดตามต้นทุน JavaScript ได้

ดูข้อมูลเพิ่มเติม