สถาปัตยกรรมนอกชุดข้อความหลักช่วยปรับปรุงความน่าเชื่อถือของแอปและประสบการณ์ของผู้ใช้ได้อย่างมาก
ในช่วง 20 ปีที่ผ่านมา เว็บได้พัฒนาไปอย่างมากจากเอกสารนิ่งที่มีรูปแบบและรูปภาพเพียงไม่กี่อย่างมาเป็นแอปพลิเคชันที่ซับซ้อนและเปลี่ยนแปลงอยู่ตลอดเวลา อย่างไรก็ตาม มีหนึ่งสิ่งที่ยังคงไม่เปลี่ยนแปลงเป็นส่วนใหญ่ กล่าวคือเรามีเธรดเพียง 1 ชุดต่อแท็บเบราว์เซอร์ (มีข้อยกเว้นบางประการ) เพื่อแสดงผลเว็บไซต์และเรียกใช้ JavaScript
ด้วยเหตุนี้ เทรดหลักจึงทำงานหนักเกินไปอย่างไม่น่าเชื่อ และเมื่อเว็บแอปมีความซับซ้อนมากขึ้น เทรดหลักก็จะกลายเป็นจุดคอขวดที่สำคัญสำหรับประสิทธิภาพ ยิ่งไปกว่านั้น ระยะเวลาที่ใช้เพื่อเรียกใช้โค้ดในเทรดหลักของผู้ใช้รายหนึ่งๆ นั้นแทบจะคาดเดาไม่ได้เลย เนื่องจากความสามารถของอุปกรณ์ส่งผลต่อประสิทธิภาพอย่างมาก ความที่คาดเดาไม่ได้นี้จะเพิ่มขึ้นก็ต่อเมื่อผู้ใช้เข้าถึงเว็บจากอุปกรณ์ที่หลากหลายมากขึ้น ตั้งแต่ฟีเจอร์โฟนที่มีข้อจำกัดอย่างมาก ไปจนถึงเครื่องหลักที่ใช้กำลังสูงและมีอัตราการรีเฟรชสูง
หากต้องการให้เว็บแอปที่ซับซ้อนเป็นไปตามหลักเกณฑ์ด้านประสิทธิภาพอย่าง Core Web Vitals ซึ่งอิงตามข้อมูลเชิงประจักษ์เกี่ยวกับการรับรู้ของมนุษย์และจิตวิทยา เราก็ต้องมีวิธีในการเรียกใช้โค้ดนอกชุดข้อความหลัก (OMT)
ทำไมต้องทำงานด้านเว็บ
โดยค่าเริ่มต้น JavaScript คือภาษาแบบแยกชุดข้อความเดียวที่เรียกใช้งานในเทรดหลัก อย่างไรก็ตาม Web Worker ก็มอบช่องทางลี้ภัยจากเทรดหลัก โดยให้นักพัฒนาซอฟต์แวร์สร้างเทรดแยกต่างหากเพื่อจัดการงานจากเทรดหลัก แม้ว่าขอบเขตของ Web Worker จะมีจำกัดและไม่ได้เสนอการเข้าถึง DOM โดยตรง แต่ก็อาจเป็นประโยชน์อย่างมากหากมีงานที่ต้องทำเป็นจำนวนมากจนทำให้เทรดหลักทำงานหนักเกินไป
ในส่วนที่เกี่ยวข้องกับ Core Web Vitals การทำงานจากเทรดหลักอาจมีประโยชน์ โดยเฉพาะอย่างยิ่ง การลดภาระงานจากเทรดหลักไปยัง Web Worker จะช่วยลดการแย่งชิงเทรดหลัก ซึ่งช่วยปรับปรุงเมตริกการตอบสนองที่สำคัญ เช่น การโต้ตอบกับ Next Paint (INP) และ First Input Delay (FID) เมื่อเทรดหลักมีเวลาประมวลผลน้อยลง เทรดหลักก็จะตอบสนองต่อการโต้ตอบของผู้ใช้ได้เร็วขึ้น
ภาระงานหลักที่น้อยลง (โดยเฉพาะในช่วงเริ่มต้นใช้งาน) ยังเป็นประโยชน์ต่อ Largest Contentful Paint (LCP) อีกด้วย โดยลดงานที่ใช้เวลานาน การแสดงองค์ประกอบ LCP ต้องใช้เวลาของเทรดหลัก ไม่ว่าจะเป็นการแสดงผลข้อความหรือรูปภาพซึ่งเป็นองค์ประกอบ LCP ที่พบได้บ่อยและพบได้บ่อย และเมื่อลดงานเทรดหลักโดยรวม คุณจะมั่นใจได้ว่าองค์ประกอบ LCP ของหน้าเว็บมีแนวโน้มที่จะถูกบล็อกน้อยลงจากงานที่มีค่าใช้จ่ายสูงที่ Web Worker สามารถจัดการแทนได้
การจัดชุดข้อความด้วย Web Worker
โดยทั่วไปแพลตฟอร์มอื่นๆ จะรองรับงานพร้อมกันโดยให้คุณสร้างฟังก์ชันเทรดได้ ซึ่งจะทํางานไปพร้อมกับโปรแกรมอื่นๆ ที่เหลือ คุณเข้าถึงตัวแปรเดียวกันจากทั้งชุดข้อความได้ และสิทธิ์เข้าถึงทรัพยากรที่แชร์เหล่านี้สามารถซิงค์กับ Mutex และ Semaphores เพื่อป้องกันเงื่อนไขเกี่ยวกับเชื้อชาติได้
ใน JavaScript เราสามารถมีฟังก์ชันการทำงานที่คล้ายคลึงกันจาก Web Worker ซึ่งทำงานมาตั้งแต่ปี 2007 และได้รับการรองรับในเบราว์เซอร์หลักๆ ทั้งหมดตั้งแต่ปี 2012 ผู้ปฏิบัติงานเว็บจะทำงานพร้อมกันกับเทรดหลัก แต่แชร์ตัวแปรไม่ได้ ซึ่งต่างจากชุดข้อความของระบบปฏิบัติการ
ในการสร้าง Web Worker ให้ส่งไฟล์ไปยังเครื่องมือสร้างผู้ปฏิบัติงาน ซึ่งจะเริ่มต้นเรียกใช้ไฟล์นั้นในเธรดที่แยกต่างหาก:
const worker = new Worker("./worker.js");
สื่อสารกับ Web Worker โดยการส่งข้อความผ่าน postMessage
API ส่งค่าข้อความเป็นพารามิเตอร์ในการเรียกใช้ postMessage
แล้วเพิ่ม Listener เหตุการณ์ข้อความให้กับผู้ปฏิบัติงาน ดังนี้
main.js
const worker = new Worker('./worker.js');
worker.postMessage([40, 2]);
worker.js
addEventListener('message', event => {
const [a, b] = event.data;
// Do stuff with the message
// ...
});
หากต้องการส่งข้อความกลับไปยังเทรดหลัก ให้ใช้ postMessage
API เดียวกันใน Web Worker และตั้งค่า Listener เหตุการณ์บนเทรดหลัก ดังนี้
main.js
const worker = new Worker('./worker.js');
worker.postMessage([40, 2]);
worker.addEventListener('message', event => {
console.log(event.data);
});
worker.js
addEventListener('message', event => {
const [a, b] = event.data;
// Do stuff with the message
postMessage(a + b);
});
เป็นที่ยอมรับกันว่าแนวทางนี้ค่อนข้างจํากัด ก่อนหน้านี้ ผู้ปฏิบัติงานเว็บมักจะใช้ย้ายงานหนักเพียงชิ้นเดียวออกจากเทรดหลัก การพยายามจัดการกับการทำงานหลายอย่างด้วย Web Worker คนเดียวอาจยุ่งยากขึ้นอย่างรวดเร็ว โดยต้องเข้ารหัสทั้งพารามิเตอร์และการทำงานในข้อความด้วย และต้องทำบัญชีเพื่อจับคู่การตอบกลับคำขอ ความซับซ้อนเช่นนี้น่าจะเป็นสาเหตุที่ Web Worker ยังไม่ถูกนำมาใช้งานอย่างกว้างขวางมากขึ้น
แต่ถ้าเราสามารถลบความยากในการสื่อสารระหว่างเทรดหลักและ Web Worker ได้ รูปแบบนี้ก็อาจเหมาะสมสำหรับการใช้งานหลายๆ กรณี และโชคดีที่มีห้องสมุดที่ทำเช่นนั้นได้
Comlink: ทำให้เว็บผู้ปฏิบัติงานทำงานน้อยลง
Comlink เป็นไลบรารีที่มีเป้าหมายเพื่อให้คุณใช้ Web Worker ได้โดยไม่ต้องคิดถึงรายละเอียดของ postMessage
Comlink ให้คุณแชร์ตัวแปรระหว่าง Web Worker และเทรดหลักได้แทบจะเหมือนกับภาษาโปรแกรมอื่นๆ ที่รองรับ Threading
คุณสามารถตั้งค่า Comlink ด้วยการนำเข้า Comlink ใน Web Worker และกำหนดชุดฟังก์ชันที่จะแสดงกับเทรดหลัก จากนั้นคุณนำเข้า Comlink ในเทรดหลัก รวมผู้ปฏิบัติงาน และรับสิทธิ์เข้าถึงฟังก์ชันที่เปิดเผย
worker.js
import {expose} from 'comlink';
const api = {
someMethod() {
// ...
}
}
expose(api);
main.js
import {wrap} from 'comlink';
const worker = new Worker('./worker.js');
const api = wrap(worker);
ตัวแปร api
ในเทรดหลักจะทำงานเหมือนกับใน Web Worker ยกเว้นว่าทุกฟังก์ชันจะแสดงผลสัญญาสำหรับค่า ไม่ใช่ค่า
คุณควรย้ายโค้ดใดไปยัง Web Worker
ผู้ปฏิบัติงานเกี่ยวกับเว็บไม่มีสิทธิ์เข้าถึง DOM และ API หลายรายการ เช่น WebUSB, WebRTC หรือ Web Audio คุณจึงไม่สามารถวางแอปที่ต้องอาศัยการเข้าถึงดังกล่าวให้แก่ผู้ปฏิบัติงานได้ แต่ถึงอย่างนั้นโค้ดทุกๆ ส่วนก็ถูกย้ายไปยังผู้ปฏิบัติงานเพื่อซื้อพื้นที่ในเทรดหลักมากขึ้นสำหรับสิ่งที่ต้องมีอยู่ที่นั่น เช่น การอัปเดตอินเทอร์เฟซผู้ใช้
ปัญหาหนึ่งสำหรับนักพัฒนาเว็บคือเว็บแอปส่วนใหญ่ต้องใช้เฟรมเวิร์ก UI เช่น Vue หรือ React เพื่อจัดการทุกอย่างในแอป ทุกอย่างเป็นคอมโพเนนต์ของเฟรมเวิร์ก จึงผูกอยู่กับ DOM อยู่แล้ว ซึ่งจะทำให้การย้ายข้อมูลไปยังสถาปัตยกรรม OMT นั้นทำได้ยาก
อย่างไรก็ตาม หากเราเปลี่ยนไปใช้โมเดลที่แยกข้อกังวลเกี่ยวกับ UI ออกจากข้อกังวลอื่นๆ เช่น การจัดการของรัฐ ผู้ปฏิบัติงานเว็บก็อาจมีประโยชน์มากแม้จะใช้แอปที่อิงตามเฟรมเวิร์ก ซึ่งนั่นคือวิธีที่ใช้ PROXX
PROXX: กรณีศึกษาของ OMT
ทีม Google Chrome พัฒนา PROXX เป็นโคลนของ Minesweeper ที่เป็นไปตามข้อกำหนดของ Progressive Web App รวมถึงการทำงานแบบออฟไลน์และการสร้างประสบการณ์ที่น่าดึงดูดของผู้ใช้ น่าเสียดายที่เกมเวอร์ชันแรกๆ ทำงานได้ไม่ดีในอุปกรณ์ที่มีข้อจำกัดอย่างฟีเจอร์โฟน ซึ่งทำให้ทีมทราบว่าเทรดหลักเป็นจุดคอขวด
ทีมงานตัดสินใจใช้ Web Worker เพื่อแยกสถานะภาพของเกมออกจากตรรกะดังนี้
- เทรดหลักจะจัดการการแสดงผลภาพเคลื่อนไหวและการเปลี่ยนภาพ
- Web Worker ต้องจัดการกับตรรกะของเกม ซึ่งเป็นเพียงการคำนวณเท่านั้น
OMT มีผลกระทบที่น่าสนใจต่อประสิทธิภาพฟีเจอร์โฟนของ PROXX ในเวอร์ชันที่ไม่ใช่ OMT UI จะค้างเป็นเวลา 6 วินาทีหลังจากที่ผู้ใช้โต้ตอบด้วย ผู้ใช้ไม่ต้องรอจนครบ 6 วินาทีจึงจะทำอย่างอื่นได้
แต่ในเวอร์ชัน OMT เกมจะใช้เวลา 12 วินาทีในการอัปเดต UI แม้ว่าจะสูญเสียประสิทธิภาพ แต่กลับทำให้ได้รับความคิดเห็นจากผู้ใช้เพิ่มมากขึ้น ความล่าช้าเกิดขึ้นเนื่องจากแอปจัดส่งเฟรมมากกว่าเวอร์ชันที่ไม่ใช่ OMT ซึ่งไม่ได้จัดส่งเฟรมใดๆ เลย ผู้ใช้จะรู้ว่ากำลังจะมีบางอย่างเกิดขึ้นและสามารถเล่นต่อได้ในระหว่างการอัปเดต UI ซึ่งทำให้เกมรู้สึกดีขึ้นอย่างเห็นได้ชัด
นี่คือข้อดีข้อเสียคือ เราให้ผู้ใช้อุปกรณ์ที่ถูกจำกัดได้รับประสบการณ์ที่รู้สึกดีขึ้นโดยที่ไม่ส่งผลกระทบต่อผู้ใช้อุปกรณ์ระดับไฮเอนด์
ผลกระทบของสถาปัตยกรรม OMT
จากตัวอย่างของ PROXX แสดงให้เห็นว่า OMT ทำให้แอปทำงานบนอุปกรณ์ที่หลากหลายได้อย่างน่าเชื่อถือ แต่ไม่ได้ทำให้แอปทำงานเร็วขึ้น ดังนี้
- คุณเป็นเพียงการย้ายงานจากเทรดหลัก ไม่ได้เป็นการลดงาน
- บางครั้งค่าใช้จ่ายในการสื่อสารที่เพิ่มขึ้นระหว่าง Web Worker และเทรดหลักก็อาจทำให้การทำงานช้าลงเล็กน้อย
พิจารณาข้อดีข้อเสีย
เนื่องจากเทรดหลักประมวลผลการโต้ตอบของผู้ใช้ได้อย่างอิสระ เช่น การเลื่อนขณะที่ JavaScript ทํางาน จึงมีเฟรมที่ลดลงน้อยลง แม้ว่าเวลารอทั้งหมดจะนานขึ้นเล็กน้อย การให้ผู้ใช้รออีกสักเล็กน้อยจะเป็นการดีหากทิ้งเฟรมไปเพราะข้อผิดพลาดมีขอบน้อยกว่าสำหรับเฟรมที่ตกลง การลดเฟรมจะเกิดขึ้นในหน่วยมิลลิวินาที ขณะที่มีเพียงหลายร้อยมิลลิวินาทีก่อนที่ผู้ใช้จะเห็นว่ารอ
เนื่องจากความไม่แน่นอนของประสิทธิภาพการทำงานในอุปกรณ์ต่างๆ เป้าหมายของสถาปัตยกรรม OMT จึงเป็นเรื่องของการลดความเสี่ยง เพื่อทำให้แอปของคุณมีประสิทธิภาพมากขึ้นเมื่อต้องเผชิญกับเงื่อนไขรันไทม์ที่มีตัวแปรสูง ไม่ใช่ประโยชน์ด้านประสิทธิภาพของการโหลดพร้อมกัน ความยืดหยุ่นที่เพิ่มขึ้นและการปรับปรุง UX นั้นคุ้มค่ามากกว่าที่จะแลกกับความเร็วเล็กๆ น้อยๆ
หมายเหตุเกี่ยวกับเครื่องมือ
ผู้ปฏิบัติงานเกี่ยวกับเว็บยังไม่ใช่เครื่องมือหลัก ดังนั้นเครื่องมือโมดูลส่วนใหญ่ เช่น Webpack และ Rollup จึงไม่พร้อมให้บริการแบบสำเร็จรูป (Parcel ทำแบบนี้นะ!) โชคดีที่มีปลั๊กอินที่จะทำให้ Web Worker สามารถทำงานด้วย Webpack และ Rollup ดังนี้
- worker-plugin สำหรับ Webpack
- rollup-plugin-off-main-thread สำหรับภาพรวม
สรุป
เพื่อดูแลให้แอปของเรามีความน่าเชื่อถือและเข้าถึงได้มากที่สุดเท่าที่จะเป็นไปได้ โดยเฉพาะอย่างยิ่งในตลาดที่เป็นสากลมากขึ้นเรื่อยๆ เราต้องสนับสนุนอุปกรณ์ที่มีข้อจำกัด ซึ่งเป็นวิธีที่ผู้ใช้ส่วนใหญ่เข้าถึงเว็บทั่วโลก OMT เสนอวิธีที่มีประสิทธิภาพในการเพิ่มประสิทธิภาพในอุปกรณ์ดังกล่าวโดยไม่ส่งผลเสียต่อผู้ใช้อุปกรณ์ระดับไฮเอนด์
นอกจากนี้ OMT ยังมีข้อดีข้อเสียดังนี้
- ซึ่งเป็นการย้ายต้นทุนการดำเนินการของ JavaScript ไปยังเทรดแยกต่างหาก
- ย้ายค่าใช้จ่ายในการแยกวิเคราะห์ ซึ่งหมายความว่า UI อาจเปิดเครื่องได้เร็วขึ้น ซึ่งอาจทำให้ First Contentful Paint หรือ Time to Interactive ลดลง ซึ่งอาจส่งผลให้คะแนนของ Lighthouse เพิ่มขึ้น
ผู้ที่ทำงานด้านเว็บไม่จำเป็นต้องน่ากลัว เครื่องมืออย่าง Comlink ช่วยให้คนทำงานน้อยลงและกลายเป็นตัวเลือกที่เหมาะสมสำหรับเว็บแอปพลิเคชันที่หลากหลาย
รูปภาพหลักจาก Unsplash โดย James Peacock