11 คะแนน โดย GN⁺ 2026-03-21 | 2 ความคิดเห็น | แชร์ทาง WhatsApp
  • ตัวแยกวิเคราะห์ WASM ที่เขียนด้วย Rust นั้นเร็วในเชิงโครงสร้าง แต่ โอเวอร์เฮดจากการคัดลอกข้อมูลและการซีเรียลไลซ์ที่ขอบเขตระหว่าง JS-WASM กลับกลายเป็นคอขวดด้านประสิทธิภาพ
  • การ คืนค่าอ็อบเจ็กต์โดยตรงผ่าน serde-wasm-bindgen ช้ากว่าการซีเรียลไลซ์ JSON อยู่ 9~29% เนื่องจากต้นทุนของการแปลงข้อมูลอย่างละเอียดระหว่างรันไทม์
  • เมื่อนำ ไปป์ไลน์ทั้งหมดพอร์ตไปเป็น TypeScript ก็สามารถทำให้ ประสิทธิภาพต่อการเรียกหนึ่งครั้งเร็วขึ้น 2.2~4.6 เท่า บนสถาปัตยกรรมเดียวกัน
  • ในการประมวลผลแบบสตรีมมิง ได้ ปรับปรุงจาก O(N²) → O(N) ด้วยการแคชระดับประโยค ทำให้ ความเร็วในการประมวลผลรวมเร็วขึ้น 2.6~3.3 เท่า
  • สรุปแล้ว ยืนยันได้ว่า WASM เหมาะกับงานที่คำนวณหนักและเรียกไม่บ่อย และ ไม่เหมาะกับการแยกวิเคราะห์เป็นอ็อบเจ็กต์ JS หรือฟังก์ชันที่ถูกเรียกบ่อย

โครงสร้างและข้อจำกัดของตัวแยกวิเคราะห์ Rust WASM

  • ตัวแยกวิเคราะห์ openui-lang ประกอบด้วยไปป์ไลน์ 6 ขั้นตอนที่ แปลง DSL ที่ LLM สร้างขึ้นให้เป็น React component tree
    • ขั้นตอน: autocloser → lexer → splitter → parser → resolver → mapper → ParseResult
    • แต่ละขั้นตอนทำหน้าที่โทเคไนซ์ วิเคราะห์ไวยากรณ์ ตีความตัวแปร แปลง AST เป็นต้น
  • แม้โค้ด Rust จะเร็วในตัวเอง แต่กระบวนการ คัดลอกสตริงระหว่าง JS↔WASM, ซีเรียลไลซ์ JSON และดีซีเรียลไลซ์ เกิดขึ้นทุกครั้งที่เรียก
    • คัดลอกสตริงอินพุต (JS→WASM), พาร์สภายใน Rust, ซีเรียลไลซ์ผลลัพธ์เป็น JSON, คัดลอก JSON (WASM→JS), และดีซีเรียลไลซ์ใน JS
  • โอเวอร์เฮดที่ขอบเขตนี้ครอบงำประสิทธิภาพโดยรวม และความเร็วในการคำนวณของ Rust ไม่ใช่คอขวด

การลองใช้ serde-wasm-bindgen และความล้มเหลว

  • เพื่อหลีกเลี่ยงการซีเรียลไลซ์ JSON จึงนำ serde-wasm-bindgen มาใช้เพื่อ คืนค่า struct ของ Rust เป็นอ็อบเจ็กต์ JS โดยตรง
  • แต่กลับพบว่า ช้าลง 30%
    • JS ไม่สามารถอ่านหน่วยความจำของ struct Rust ได้โดยตรง และเลย์เอาต์หน่วยความจำของแต่ละรันไทม์ต่างกัน จึงต้องมี การแปลงข้อมูลระดับฟิลด์
    • ในทางกลับกัน การซีเรียลไลซ์ JSON คือการสร้างสตริงหนึ่งครั้งภายใน Rust แล้วให้ JS จัดการผ่าน JSON.parse ที่ถูกปรับแต่งมาอย่างดี
  • ผลเบนช์มาร์ก
    Fixture JSON round-trip serde-wasm-bindgen การเปลี่ยนแปลง
    simple-table 20.5µs 22.5µs -9%
    contact-form 61.4µs 79.4µs -29%
    dashboard 57.9µs 74.0µs -28%

การเปลี่ยนไปใช้ TypeScript และประสิทธิภาพที่ดีขึ้น

  • พอร์ตโครงสร้าง 6 ขั้นตอนเดิม ไปเป็น TypeScript ทั้งหมด ตัดขอบเขต WASM ออก และ รันโดยตรงบน V8 heap
  • ผลเบนช์มาร์กแบบต่อการเรียกหนึ่งครั้ง
    Fixture TypeScript WASM เร็วขึ้น
    simple-table 9.3µs 20.5µs 2.2 เท่า
    contact-form 13.4µs 61.4µs 4.6 เท่า
    dashboard 19.4µs 57.9µs 3.0 เท่า
  • เพียงแค่เอา WASM ออกก็ลดต้นทุนต่อการเรียกได้มาก แต่ความไม่มีประสิทธิภาพของโครงสร้างสตรีมมิงยังคงอยู่

ปัญหา O(N²) ของการพาร์สแบบสตรีมมิงและการปรับปรุง

  • เมื่อเอาต์พุตของ LLM ถูกส่งมาเป็นหลายชังก์ จะเกิดความไม่มีประสิทธิภาพแบบ O(N²) จากการพาร์สสตริงสะสมทั้งหมดใหม่ทุกครั้ง
    • ตัวอย่าง: เอกสาร 1000 ตัวอักษร พาร์สทีละ 20 ตัวอักษร 50 ครั้ง → ต้องประมวลผลรวม 25,000 ตัวอักษร
  • แนวทางแก้คือการนำ incremental caching ระดับประโยค มาใช้
    • แคชประโยคที่สมบูรณ์แล้ว และพาร์สใหม่เฉพาะประโยคที่ยังดำเนินอยู่
    • รวม AST ที่แคชไว้กับ AST ใหม่ก่อนคืนผลลัพธ์
  • เบนช์มาร์กในระดับสตรีมทั้งหมด
    Fixture TS แบบ naive TS แบบ incremental เร็วขึ้น
    simple-table 69µs 77µs ไม่มี
    contact-form 316µs 122µs 2.6 เท่า
    dashboard 840µs 255µs 3.3 เท่า
  • ยิ่งมีหลายประโยคมากเท่าไร ผลของแคชก็ยิ่งมากขึ้น และปรับปรุง throughput โดยรวมให้เป็นเชิงเส้นได้

บทเรียนจากการใช้ WASM

  • กรณีที่เหมาะสม
    • งานที่คำนวณหนักและมีปฏิสัมพันธ์น้อย: การประมวลผลภาพ·วิดีโอ การเข้ารหัส การจำลองฟิสิกส์ ออดิโอ codec เป็นต้น
    • การพอร์ตไลบรารีเนทีฟเดิมมาใช้: SQLite, OpenCV, libpng เป็นต้น
  • กรณีที่ไม่เหมาะสม
    • การพาร์สข้อความที่มีโครงสร้างให้เป็นอ็อบเจ็กต์ JS: ต้นทุนการซีเรียลไลซ์ครอบงำทั้งหมด
    • ฟังก์ชันที่มีอินพุตสั้นแต่ถูกเรียกบ่อย: ต้นทุนที่ขอบเขตสูงกว่าต้นทุนการคำนวณ
  • บทเรียนสำคัญ
    1. ต้อง ทำโปรไฟล์หาตำแหน่งคอขวดก่อน แล้วค่อยเลือกภาษา
    2. การ ส่งอ็อบเจ็กต์โดยตรงด้วย serde-wasm-bindgen มีต้นทุนสูงกว่า
    3. การปรับปรุงความซับซ้อนของอัลกอริทึมมีประสิทธิภาพมากกว่าการเปลี่ยนภาษา
    4. WASM และ JS ไม่ได้แชร์ heap ร่วมกัน และต้นทุนการแปลงข้อมูลจะมีอยู่เสมอ

ผลลัพธ์สุดท้าย: การเปลี่ยนไปใช้ TypeScript และ incremental caching ทำให้ ประสิทธิภาพต่อการเรียกดีขึ้น 2.2~4.6 เท่า และทั้งสตรีมดีขึ้น 2.6~3.3 เท่า

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

 
bbulbum 2026-03-23

หรือจริง ๆ แล้วมีเจตนาจะเหน็บแนมบทความปรับปรุงประสิทธิภาพของ Rust แบบขั้นสูงกันแน่นะ..

 
GN⁺ 2026-03-21
ความคิดเห็นจาก Hacker News
  • ประเด็นสำคัญจริง ๆ ไม่ใช่ TypeScript แทน Rust แต่เป็นการแก้อัลกอริทึมแบบสตรีมมิงจาก O(N²) เป็น O(N)
    การเปลี่ยนแปลงแค่นี้ที่ทำผ่านการแคชระดับ statement ก็ทำให้เร็วขึ้นถึง 3.3 เท่าแล้ว
    แยกจากการเลือกภาษา ปัจจัยหลักที่ผู้ใช้รับรู้ได้ในแง่ latency ที่ดีขึ้นก็คือส่วนนี้
    รู้สึกว่าพาดหัวประเมินจุดทางวิศวกรรมที่น่าสนใจนี้ต่ำเกินไป

    • โปรเจ็กต์ uv ก็เหมือนกัน ผู้คนมักตะโกนว่า “rust rulez!” แต่ประโยชน์ที่แท้จริงมาจาก การปรับปรุงอัลกอริทึม ไม่ใช่ภาษา
    • ขอบคุณที่ช่วยเจาะผ่านคลิกเบตแล้วชี้แก่นแท้ให้เห็น
      ตัวบทความเองก็น่าสนใจ แต่ช่วงนี้เริ่มเหนื่อยกับ พาดหัวล่อคลิก ที่มากเกินไป
    • การเขียนเป็น n² ดูจะพูดเกินจริงไปหน่อย
      เขาวัดเวลาแต่ละรอบเรียกแล้วใช้ค่า median แต่ในเบราว์เซอร์มีตรรกะป้องกัน timing attack ใน JS engine อยู่ เลยสงสัยเรื่องความแม่นยำ
    • สุดท้ายแล้วผมคิดว่ามันใกล้เคียงกับ พาดหัวที่ทำให้เข้าใจผิด มากกว่า
  • คำพูดแนวว่า “เขียนโค้ดใหม่จากภาษา L ไปเป็น M แล้วเร็วขึ้น” เป็นผลลัพธ์ที่แทบจะแน่นอนอยู่แล้ว
    เพราะมันเป็นโอกาสให้แก้ความยุ่งเหยิงและการตัดสินใจที่ผิดพลาด รวมถึงนำ แนวทางที่ดีกว่า ที่เพิ่งมีมาใช้
    จริง ๆ ต่อให้ L=M ก็เหมือนกัน การเพิ่มความเร็วมักไม่ได้มาจากภาษา แต่เกิดจากกระบวนการ รีไรต์และออกแบบใหม่

    • ถ้าตอนนี้มีบุคคลที่สามซึ่งไม่รู้ต้นฉบับ เอาเวอร์ชัน TypeScript ไปรีไรต์เป็น Rust ใหม่ ประสิทธิภาพอาจดีขึ้นอีกก็ได้
    • มักเห็นบ่อยว่าแม้เขียนใหม่ด้วยภาษาเดิมก็ยังได้ ผลลัพธ์ที่ดีขึ้น
  • ผมเคยขุดลึกลงไปอีกเพื่อปรับปรุงประสิทธิภาพของการซีเรียลไลซ์ออบเจ็กต์ตรงรอยต่อระหว่าง Rust กับ JS
    แนวทางของ serde ดูไม่ค่อยดีในเชิงประสิทธิภาพ และผมได้สรุปความพยายามในการปรับปรุงเรื่องนี้ไว้ในบล็อกโพสต์ของผม

  • ผมสงสัยว่าทำไม Open UI ถึงไม่ทำงานเกี่ยวกับ WASM
    แต่พอเห็นว่าบริษัทใหม่ครั้งนี้ใช้ชื่อว่า Open UI ก็เลยยิ่งสับสน
    เดิมที Open UI W3C Community Group เป็นกลุ่มที่ทำมาตรฐานอย่าง HTML popover, select ที่ปรับแต่งได้, invoker command และ accordion มานานกว่า 5 ปี
    พวกเขาทำงานได้ยอดเยี่ยมมากจริง ๆ

  • เขาบอกว่ารวม serde-wasm-bindgen เข้าไปเพราะอยาก “ข้ามการไป-กลับของ JSON” แต่สุดท้ายมันก็ดูเหมือน การประดิษฐ์ JSON แบบไบนารีขึ้นมาใหม่
    ทุกวันนี้ JSON ของ V8 ถูกปรับแต่งมาอย่างหนักอยู่แล้ว และอิมพลีเมนต์อย่าง simdjson ก็ประมวลผลได้ระดับกิกะไบต์ต่อวินาที
    ผมเลยคิดว่าโอกาสที่ JSON จะเป็นคอขวดนั้นต่ำ

  • ผมชอบ ดีไซน์ ของบล็อกนั้นมากจริง ๆ
    ชอบ sidebar แบบ ‘scrollspy’ ที่ไฮไลต์หัวข้อตามตำแหน่งสก์รอลล์เป็นพิเศษ
    Claude บอกว่าน่าจะทำด้วย fumadocs.dev

    • น่าสนใจ ผมเองก็คิดว่าอีกไม่นานคงต้องทำ เว็บเอกสารดี ๆ สักเว็บเหมือนกัน
  • ผมยังไม่ค่อยเข้าใจว่าตัวพาร์เซอร์ Rust WASM นี้มีจุดประสงค์อะไร
    ในบทความอธิบายส่วนนี้ไม่ชัด เลยอยากได้คำอธิบายเพิ่ม

    • เขาบอกว่าใช้ ภาษาสำหรับงานเฉพาะทาง เพื่อกำหนด UI component ที่ LLM สร้างขึ้น
      ดูเหมือนว่าจุดประสงค์คือป้องกันข้อมูลรั่วไหลจาก prompt injection
      พาร์เซอร์จะคอมไพล์ chunk ที่สตรีมมาจาก LLM เพื่อประกอบ UI แบบเรียลไทม์
      ก่อนหน้านี้พอมี chunk ใหม่มาก็รีสตาร์ตพาร์เซอร์ตั้งแต่ต้นทุกครั้ง แต่พอเปลี่ยนเป็น การประมวลผลแบบ incremental (ระหว่างพอร์ตจาก Rust ไป TypeScript) ประสิทธิภาพก็ดีขึ้นมาก
  • ผมสงสัยว่าเดี๋ยวนี้ TypeScript ไม่ได้รันบนฐาน Golang แล้วหรือ

    • น่าจะหมายถึงโปรเจ็กต์ที่กำลังเขียน TypeScript compiler ใหม่ด้วย Go
  • พูดเล่นนะ แต่ถ้าเขียนกลับไปเป็น Rust อีกที อาจได้ ประสิทธิภาพเพิ่ม 3 เท่า อีกรอบก็ได้ /s