การตรวจหาฟีเจอร์ WebAssembly

ดูวิธีใช้ฟีเจอร์ WebAssembly ใหม่ล่าสุดขณะที่รองรับผู้ใช้ในทุกเบราว์เซอร์

WebAssembly 1.0 เปิดตัวไปเมื่อ 4 ปีที่แล้ว แต่การพัฒนาไม่ได้หยุดเพียงแค่นั้น ฟีเจอร์ใหม่จะเพิ่มผ่านกระบวนการกำหนดมาตรฐานข้อเสนอ โดยทั่วไปแล้ว ลำดับเวลาและลำดับการนำฟีเจอร์ใหม่ๆ ไปใช้ในเครื่องมือต่างๆ นั้นอาจแตกต่างกันอย่างมาก หากต้องการใช้ฟีเจอร์ใหม่เหล่านั้น คุณต้องตรวจสอบว่าผู้ใช้ทุกคนได้รับฟีเจอร์ดังกล่าว ในบทความนี้ คุณจะได้ทราบแนวทางในการบรรลุเป้าหมายนี้

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

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

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

การเลือกและการจัดกลุ่มฟีเจอร์

มาดูขั้นตอนต่างๆ กันโดยเลือกชุดฟีเจอร์แบบสุ่มเป็นตัวอย่าง สมมติว่าฉันพบว่าต้องการใช้ SIMD, เทรด และการจัดการข้อยกเว้นในไลบรารีเพื่อเหตุผลด้านขนาดและประสิทธิภาพ การรองรับเบราว์เซอร์มีดังนี้

ตารางแสดงการรองรับฟีเจอร์ที่เลือกของเบราว์เซอร์
ดูตารางฟีเจอร์นี้ใน webassembly.org/roadmap

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

  • เบราว์เซอร์ที่ใช้ Chrome: รองรับเธรด, SIMD และการจัดการข้อยกเว้น
  • Firefox: รองรับเธรดและ SIMD แต่ไม่รองรับการจัดการข้อยกเว้น
  • Safari: รองรับเธรด แต่ไม่รองรับ SIMD และการจัดการข้อยกเว้น
  • เบราว์เซอร์อื่นๆ: ถือว่ารองรับ WebAssembly พื้นฐานเท่านั้น

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

การคอมไพล์สําหรับชุดฟีเจอร์ต่างๆ

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

เครื่องมือและระบบบิลด์แต่ละชุดจะแตกต่างกันไป คุณจึงต้องดูเอกสารประกอบของคอมไพเลอร์ของคุณเองเพื่อดูวิธีปรับแต่งฟีเจอร์เหล่านั้น เราจะใช้ไลบรารี C++ แบบไฟล์เดียวในตัวอย่างต่อไปนี้เพื่อลดความซับซ้อน และแสดงวิธีคอมไพล์ด้วย Emscripten

ฉันจะใช้ SIMD ผ่านการจําลอง SSE2, เทรดผ่านการสนับสนุนไลบรารี Pthreads และเลือกระหว่างการจัดการข้อยกเว้น Wasm กับการใช้งาน JavaScript สำรอง ดังนี้

# First bundle: threads + SIMD + Wasm exceptions
$ emcc main
.cpp -o main.threads-simd-exceptions.mjs -pthread -msimd128 -msse2 -fwasm-exceptions
# Second bundle: threads + SIMD + JS exceptions fallback
$ emcc main
.cpp -o main.threads-simd.mjs -pthread -msimd128 -msse2 -fexceptions
# Third bundle: threads + JS exception fallback
$ emcc main
.cpp -o main.threads.mjs -pthread -fexceptions
# Fourth bundle: basic Wasm with JS exceptions fallback
$ emcc main
.cpp -o main.basic.mjs -fexceptions

โค้ด C++ เองสามารถใช้ #ifdef __EMSCRIPTEN_PTHREADS__ และ #ifdef __SSE2__ เพื่อเลือกแบบมีเงื่อนไขระหว่างการใช้งานแบบขนาน (เธรดและ SIMD) ของฟังก์ชันเดียวกันกับการใช้งานแบบอนุกรม ณ เวลาคอมไพล์ ซึ่งจะมีลักษณะดังนี้

void process_data(std::vector<int>& some_input) {
#ifdef __EMSCRIPTEN_PTHREADS__
#ifdef __SSE2__
 
// …implementation using threads and SIMD for max speed
#else
 
// …implementation using threads but not SIMD
#endif
#else
 
// …fallback implementation for browsers without those features
#endif
}

การจัดการข้อยกเว้นไม่จำเป็นต้องใช้คำสั่ง #ifdef เนื่องจากสามารถใช้งานได้ในลักษณะเดียวกับจาก C++ ไม่ว่าจะใช้การใช้งานพื้นฐานใดก็ตามที่เลือกผ่าน Flag การคอมไพล์

กำลังโหลด Bundle ที่ถูกต้อง

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

import { simd, threads, exceptions } from 'https://unpkg.com/wasm-feature-detect?module';

let initModule
;
if (await threads()) {
 
if (await simd()) {
   
if (await exceptions()) {
      initModule
= import('./main.threads-simd-exceptions.mjs');
   
} else {
      initModule
= import('./main.threads-simd.mjs');
   
}
 
} else {
    initModule
= import('./main.threads.mjs');
 
}
} else {
  initModule
= import('./main.basic.mjs');
}

const Module = await initModule();
// now you can use `Module` Emscripten object like you normally would

สรุป

ในโพสต์นี้ เราจะแสดงวิธีเลือก สร้าง และสลับระหว่างแพ็กเกจสำหรับชุดฟีเจอร์ต่างๆ

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

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