2 คะแนน โดย GN⁺ 2024-04-15 | 1 ความคิดเห็น | แชร์ทาง WhatsApp

ข้อจำกัดของ WebAssembly และความสำคัญของ Tree-shaking

  • แม้ WebAssembly จะได้รับความสนใจและความคาดหวังอย่างมาก แต่ก็ประสบความสำเร็จบนเว็บได้อย่างจำกัด

    • มีกรณีที่ประสบความสำเร็จอย่าง Photoshop แต่โดยรวมแล้วโปรเจ็กต์ที่ใช้ WebAssembly ยังมีไม่มาก
    • โดยเฉพาะแอปที่ใช้ DOM อย่างหนักนั้น WebAssembly ไม่ค่อยเหมาะนัก
    • ความแตกต่างของโมเดลการเขียนโปรแกรมระหว่าง JavaScript และ WebAssembly เป็นหนึ่งในสาเหตุหลัก
  • นอกจากภาษาอย่าง C หรือ Rust แล้ว WebAssembly ยังไม่ค่อยประสบความสำเร็จมากนัก

    • ภาษาประเภท C# มีความไม่สะดวกตรงที่ต้องพ่วงรันไทม์อย่าง garbage collector มาด้วย
    • แต่คาดว่าสถานการณ์จะดีขึ้น เพราะฟีเจอร์ใหม่ของ WebAssembly ที่รองรับ reference type และ garbage collection กำลังจะถูกนำมาใช้ในเร็ว ๆ นี้

ความสามารถในการปรับโค้ดให้เหมาะสมของคอมไพเลอร์คือกุญแจสู่ความสำเร็จของ WebAssembly

  • หาก WebAssembly จะประสบความสำเร็จบนเว็บ คอมไพเลอร์ต้องสร้างโค้ดที่เล็กและมีประสิทธิภาพได้

    • การรักษาขนาดไฟล์ให้เล็กเพียงไม่กี่กิโลไบต์เป็นเรื่องสำคัญ
    • ไม่เช่นนั้นก็อาจต้องพึ่งพาเพียงกระแสเกินจริงหรือฐานผู้ใช้เฉพาะกลุ่ม
  • ในโลกของ JavaScript มีการทำ optimization ด้านขนาดโค้ดผ่าน bundler และเครื่องมืออื่น ๆ อยู่แล้ว

    • Tree-shaking คือเทคนิคที่รวมเฉพาะฟังก์ชันและชนิดข้อมูลที่ถูกใช้งานจริงในโปรแกรมเท่านั้น
  • แม้ Tree-shaking จะเป็นอุปมาอุปไมยที่ไม่ค่อยเหมาะทั้งในเชิงพืชสวนและเชิงอัลกอริทึม แต่ก็เป็นคำที่ใช้กันอย่างแพร่หลาย

สถานะของ Tree-shaking ในภาษาอื่น ๆ

  • ในภาษาที่มีรันไทม์หนักอย่าง Go หรือ Python นั้น Tree-shaking ยังไม่ได้รับการปรับให้เหมาะสมมากนัก

    • แม้แต่โปรแกรม Go ที่ง่ายที่สุด เมื่อนำไปคอมไพล์เป็น WebAssembly ก็ยังมีขนาดเกิน 2MB
    • ส่วน Pyodide ของ Python ก็ต้องดาวน์โหลดไฟล์ราว 20MB
  • เพราะในสภาพแวดล้อมแบบเซิร์ฟเวอร์ ขนาดไบนารีมักไม่ใช่ปัญหาใหญ่

    • สำหรับสภาพแวดล้อมที่มีข้อจำกัดอย่างมือถือ จึงมีการพัฒนา toolchain แบบเบาอย่าง MicroPython และ TinyGo แยกต่างหาก
  • ตัวภาษาในเวอร์ชันสำหรับเว็บย่อมหลีกเลี่ยงไม่ได้ที่จะต่างจากของเดิม

    • เพราะการโต้ตอบกับ DOM เองก็เป็นสภาพแวดล้อมที่ค่อนข้างพิเศษ
    • ในกรณีของ ClojureScript ก็มีการจัดทำเอกสารแยกต่างหากเกี่ยวกับความแตกต่างจาก Clojure

ประเด็นถกเถียงเกี่ยวกับอัลกอริทึม Tree-shaking

  • คอมไพเลอร์ Hoot Scheme ที่ผู้เขียนกำลังพัฒนาอยู่ในตอนนี้ สร้างโค้ด Wasm ได้ราว 70KB

    • การรวมเฉพาะนิยามของฟังก์ชัน (procedure) นั้นค่อนข้างทำได้ไม่ยาก
    • แต่ยังมีจุดยากอยู่หลายประการดังนี้
  • ในโมเดลการประเมินผล letrec* นั้น binding มีทั้งความเป็น recursive และมีลำดับ จึงทำให้คอมไพเลอร์วิเคราะห์ได้ยาก

    • ในกรณีของ record type นั้น vtable callback ทำให้ต้องเก็บโค้ดจำนวนมากเอาไว้
  • หากใช้ฟังก์ชันที่มีความเป็น polymorphic สูงอย่าง display ก็จะดึงโค้ดที่เกี่ยวข้องจำนวนมากเข้ามาด้วย

    • การใช้ฟังก์ชันที่เฉพาะเจาะจงอย่าง write-string จะดีกว่า
  • หากต้องการ Tree-shaking ที่เหมาะสมที่สุด จำเป็นต้องมี flow analysis

    • ถ้ารู้ได้ว่า display จะไม่ถูกส่งอาร์กิวเมนต์เป็น bitvector ก็สามารถลบโค้ดที่เกี่ยวข้องออกได้
  • ใน Python เรื่องนี้ยิ่งยากกว่า เพราะมีฟีเจอร์แบบ dynamic อย่าง dynamic dispatch, __getattr__ เป็นต้น

    • โครงสร้างโมดูลของ Python เองก็เป็นอีกปัจจัยที่ทำให้ Tree-shaking ซับซ้อนขึ้น

สรุป

  • การรองรับ GC ทำให้สามารถเขียนโปรแกรม DOM บน WebAssembly ด้วยภาษาอื่นนอกเหนือจาก JavaScript ได้
  • แต่หากต้องการทำให้ผลลัพธ์มีขนาดเล็กพอ ก็ยังต้องลงทุนอย่างมากกับ toolchain ของแต่ละภาษา
  • จำเป็นต้องมีทั้งการพัฒนา toolchain แยกต่างหากที่ใช้อัลกอริทึม Tree-shaking และการปรับแต่ง standard library ให้เหมาะสม

ความเห็นของ GN⁺

  • เมื่อ WebAssembly รองรับ GC ก็ทำให้สามารถใช้ภาษาที่หลากหลายในการพัฒนาเว็บได้มากขึ้น แต่ดูเหมือนว่ายังมีอุปสรรคมากมายหากจะยก toolchain ของภาษาเดิมมาใช้ตรง ๆ คงต้องมีทั้ง implementation ของภาษาและเทคนิค optimization ที่ออกแบบมาเฉพาะสำหรับสภาพแวดล้อมเว็บ

  • หากต้องการให้ Tree-shaking ทำงานได้ดีในภาษาที่เป็น dynamic typing ก็ดูเหมือนว่า static analysis จะเป็นสิ่งจำเป็น แต่สำหรับภาษาอย่าง Python ที่มีฟีเจอร์แบบ dynamic มาก ก็อาจไม่ใช่เรื่องง่าย บางทีการออกแบบภาษาใหม่ที่เอื้อต่อ static analysis ตั้งแต่ต้นก็อาจเป็นอีกแนวทางหนึ่ง

  • โปรเจ็กต์เชิงทดลองอย่าง Hoot หรือ TinyGo น่าจะเป็นตัวอย่างอ้างอิงที่ดี แต่การนำโปรเจ็กต์เหล่านี้ไปใช้กับผลิตภัณฑ์จริงอาจยังเร็วเกินไป คงต้องค่อย ๆ ปรับปรุงกันไปทีละขั้น

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

  • นอกจากนี้ยังอาจลองคิดถึงการใส่ความสามารถแบบ Tree-shaking เข้าไปในตัว WebAssembly เองได้เหมือนกัน แต่เพราะแต่ละภาษามีความต้องการต่างกัน เรื่องนี้จึงไม่น่าจะง่ายนัก และถ้าในอนาคตมีภาษาที่รองรับ Tree-shaking ได้ดีมาก ๆ การเขียนโค้ดด้วยภาษานั้นโดยตรงอาจจะคุ้มกว่า ก็ยังน่าสนใจว่าบทบาทระหว่าง WebAssembly กับภาษาโปรแกรมจะถูกแบ่งกันอย่างไรในท้ายที่สุด

1 ความคิดเห็น

 
GN⁺ 2024-04-15
ความคิดเห็นจาก Hacker News

สรุปได้ดังนี้:

  • ในโปรเจกต์ OpenEtG มีความพยายามดังต่อไปนี้เพื่อคงขนาดไบนารี WASM ที่เขียนด้วย Rust ให้ต่ำกว่า 400KB
    • ใช้เลขคงที่แทน float
    • ใช้ Vec แทน HashMap
    • ลดการใช้สตริงให้น้อยที่สุด
    • ใช้อัลโลเคเตอร์ขนาดเล็ก (talc)
    • ลดจำนวน dependency ให้เหลือน้อยที่สุด (ใช้เพียง rand, fxhash)
    • หลีกเลี่ยงความหลากหลายของ generic
    • ออกแบบอัลกอริทึมโดยคำนึงถึงขนาด
  • Tree-shaking เป็นชื่อเรียกที่ไม่ถูกต้องนัก และในคอมไพเลอร์ Virgil เรียกสิ่งนี้ว่า Reachability Analysis โดยในกระบวนการคอมไพล์จะไล่สำรวจจากจุดเริ่มต้น main และรวมเฉพาะโค้ดที่เข้าถึงได้ลงในไบนารีสุดท้าย
  • ด้วย WasmGC ทำให้ Java และ Kotlin สามารถสร้างไบนารี WASM ขนาดเล็กเพียงประมาณ 2-3KB ได้ แต่ต้องระวังในการเลือก API
  • การจัดการ DOM ด้วย WASM ยังคงพึ่งพา JS อย่างมาก
  • เหตุผลที่เกิดคำว่า Tree Shaking ขึ้นมา เป็นเพราะมี Dead Code Elimination อยู่ก่อนแล้วมานาน
  • ปัญหาขนาดโค้ดของ WASM เกิดจากการที่ต้อง bundle ทั้ง language runtime และ standard library เข้าไปด้วย
  • เพื่อแก้ปัญหานี้ อาจพิจารณาใช้ shared library และ dynamic linking
    • Pyodide เป็นตัวอย่างเด่นที่รองรับ dynamic linking
    • หากเบราว์เซอร์โหลด language runtime ยอดนิยมไว้ล่วงหน้า หน้าเว็บก็อาจแชร์ runtime นั้นร่วมกันได้
  • ภาษา Zig เหมาะกับการสร้างไบนารี WASM ขนาดเล็ก แต่ถ้าต่ำกว่า 100KB แล้ว ขนาดก็ไม่ใช่ปัจจัยสำคัญ
  • GC ในตัวไม่ได้สำคัญกับทุกแอป และการสร้างเว็บแอปที่ไม่มี GC ก็น่าจะดีกว่า
  • ปัจจัยความสำเร็จของแอปที่ใช้ WASM ก็ยังคงเป็นการเพิ่มประสิทธิภาพ
  • มีการเขียนโปรแกรม DOM ด้วยภาษาอื่นนอกเหนือจาก JS มานานแล้ว ผ่านภาษา compile-to-JS อย่าง ClojureScript, TypeScript, ReasonML เป็นต้น
  • ก่อนยุค WASM ก็มีการคอมไพล์ภาษาในตระกูล C เพื่อใช้งานบนเว็บผ่าน asm.js และ emscripten อยู่แล้ว
  • Google Maps และ Google Earth เป็นแอปตัวอย่างเด่นที่ใช้ WASM