דלג לתוכן

מדוע בחרנו ב-Rust וב-WebAssembly לכלים לחישובים הנדסיים

בדיקה טכנית של הסיבות לכך ש-ChainSolve כתוב ב-Rust וקומפילציה ל-WebAssembly, ומה זה אומר לביצועים, אמינות וניידות.

BG ben godfrey · · 5 min read
RUST

הדרישות

כאשר יצאנו לבנות את מנוע החישוב של ChainSolve, היתה לנו סט ברור של דרישות:

  1. מהירות חישוב קרובה לנייטיבית. חישובים הנדסיים עשויים להיות כרוכים במטריצות גדולות, פותרי איטרטיביים וגרפי תלות מורכבים עם מאות צמתים. המנוע חייב להעריך אלה במילישניות, לא בשניות.

  2. ביצוע בדפדפן. הכלי חייב לעבוד כולו בדפדפן ללא סבבי שרת לחישוב. מהנדסים העובדים על פרויקטים רגישים לא יכולים לשלוח נתוני חישוב קנייני לשרתים חיצוניים.

  3. יכולת עבודה בלא-חיבור. מהנדסים במתקנים לבדיקה, בחנויות מפעל וברשתות לקוחות לעיתים קרובות יש חיבור אינטרנט מוגבל או אין. הכלי חייב לעבוד בלא-חיבור לאחר שנטען.

  4. בטיחות זיכרון. כלי חישוב שמתרסק או מייצר בשקט תוצאות שגויות בגלל שחיתות זיכרון הוא גרוע מחסר שימוש. הנכונות היא אי-משא.

  5. ניידות. אותו מנוע חייב לפעול בדפדפנים, ב-Node.js לאינטגרציית CI/CD, וייתכן גם ככלי CLI נייטיבי.

דרישות אלה צמצמו את השדה באופן משמעותי.

מדוע לא JavaScript?

JavaScript היא הבחירה הברורה ליישומים המבוססים על דפדפן. זו שפת הרשת הנייטיבית, יש לה כלים מצוינים, ואקוסיסטם ענק. לקוד UI, אנו משתמשים ב-JavaScript (ספציפית TypeScript עם React). אך לעומת זאת, מנוע החישוב, ל-JavaScript יש מגבלות יסוד.

JavaScript היא שפה מוקלדת דינמית עם garbage collection. לרינדור UI ולטיפול באירועים, זה בסדר. לחישוב מספרי, זה מעניק שתי בעיות:

תקרת ביצועים. מנועי JavaScript מודרניים כמו V8 מהירים להפליא לשפה דינמית, אך הם לא יכולים להתאים ביצועים של קוד שהודר עם ahead-of-time לעבודה ספוגה מבחינה מספרית. JIT compilation מוסיף הפסקות בלתי צפויות. Garbage collection מוסיף דפקות חביון. אלה מקובלות בשכבת UI אך בעייתיות במנוע חישוב שצריך הערכה עקבית ומהירה.

דיוק מספרי. ל-JavaScript יש סוג מספר יחיד: IEEE 754 double-precision floating point. זה מספיק לרוב החישובים ההנדסיים, אך JavaScript לא מספק דרך לשלוט בהתנהגות floating-point, אין intrinsics SIMD לחישוב vectorized, ואין סוגי מספרים שלמים למקרים שבהם דרוש חשבון מספר שלם מדויק (כגון אלגוריתמי גרף בגרף התלות).

מנוע חישוב JavaScript יעבוד. זה היה מספיק לחישובים קטנים עד בינוניים. אך זה היה מגיע לתקרת ביצועים עם שרשראות מורכבות, וזה היה דורש obregations זהיר לקצה מספרי.

מדוע Rust?

Rust עומד בכל אחת מדרישות החישוב שלנו:

ביצועים. Rust מתקמפל לקוד מכונה (או WebAssembly) ללא garbage collector וללא overhead זמן ריצה. קוד מספרי ב-Rust מבצע דומה ל-C וC++. עבור מנוע החישוב של ChainSolve, זה אומר הערכת שרשרת היא בעקביות מהירה, ללא GC pauses או JIT warmup.

בטיחות זיכרון ללא garbage collection. מערכת הבעלות של Rust מבטיחה בטיחות זיכרון בזמן קומפילציה. אין null pointer dereferences, אין use-after-free bugs, אין data races. לכלי חישוב שבו הנכונות היא עליונה, זה יתרון משמעותי על C וC++.

יעד WebAssembly. Rust כולל תמיכה מהדרגה הראשונה לקומפילציה ל-WebAssembly דרך wasm-pack ו-wasm-bindgen. מודול WASM שנוצר פועל בכל דפדפן מודרני בקרוב למהירות נייטיבית. זה נותן לנו ביצוע דפדפן ויכולת בלא-חיבור ללא קורבן ביצועים.

מערכת סוג חזקה. מערכת סוג של Rust מאפשרת לנו קידוד constraints הנדסיים במערכת הסוג עצמה. UnitValue<Millimeters> לא יכול להיות מוסף בטעות ל-UnitValue<Inches>, הקומפיילר דוחה את זה. זה תופס קטגוריות שלמות של שגיאות הנדסיות בזמן קומפילציה.

ניידות. אותו קוד Rust קומפילציה ל-WASM לביצוע דפדפן, לבינאריות נייטיביות לכלים CLI, ולספריות משותפות לאינטגרציה עם כלים אחרים. codebase אחד, מטרות מרובות.

הארכיטקטורה

הארכיטקטורה של ChainSolve מפרידה בין חישוב להצגה:

+------------------------------------------+
|  Browser (TypeScript + React)            |
|  - UI components                          |
|  - Chain editor                           |
|  - Visualisation                          |
+------------------------------------------+
          |  wasm-bindgen interface  |
+------------------------------------------+
|  WASM Module (Rust)                      |
|  - Dependency graph solver               |
|  - Block evaluation engine               |
|  - Unit conversion system                |
|  - Expression parser                     |
|  - Numerical methods library             |
+------------------------------------------+

מודול ה-Rust/WASM חושף API נקי לשכבת TypeScript דרך wasm-bindgen. שכבת TypeScript מטפלת בכל חששות UI, rendering, interaction של משתמש, layout. שכבת Rust מטפלת בכל חישוב, traversal גרף, הערכת בלוק, המרת יחידה, ניתוח ביטוי.

הפרדה זו אומרת ש-UI לעולם לא חוסמת בחישוב. הערכת שרשרת מתרחשת במודול WASM, חוזרת תוצאות ל-TypeScript, וה-UI מתעדכן. עבור שרשראות גדולות מאוד, אנו מריצים את מודול WASM ב-Web Worker כדי להשאיר את חוט ה-UI לגמרי responsive.

תוצאות ביצועים

כמה benchmarks מייצגים מה-builds הפיתוח שלנו (נמדדו במחשב בינוני, M2 MacBook Air):

OperationJavaScript (baseline)Rust/WASM
Evaluate 100-block chain12 ms0.8 ms
Evaluate 1,000-block chain340 ms8 ms
Topological sort (1,000 nodes)5 ms0.3 ms
Unit conversion (10,000 values)18 ms0.6 ms
Expression parse + evaluate0.4 ms0.02 ms

היישום Rust/WASM היא בעקביות 15-40x מהירה יותר מ-JavaScript המקביל. לשרשראות קטנות, שניהם מהירים מספיק. לשרשראות גדולות (שמשתמשים בייצור בהכרח יוצרים), ההבדל הוא בין משוב מיידי לדיחוי ניתן להבחנה.

אתגרים וtrade-offs

Rust ו-WebAssembly אינם ללא trade-offs:

עקומת למידה. Rust היא שפה מורכבת יותר מ-JavaScript או Python. מערכת הבעלות, למרות שהיא חזקה, דורשת מודל נפשי שלוקח זמן לפתח. עבור מייסד יחיד, זה היה השקעה בפרודוקטיביות לטווח ארוך על חשבון velocity לטווח קצר.

גודל בינארי WASM. מודול WASM הקומפילציה הוא כיום בערך 800 KB gzipped. זו תוספת משמעותית לטעינת עמוד ראשונית. אנו מקטנים זאת עם lazy loading, מודול WASM נטען באופן asynchronous לאחר שקליפת ה-UI מורנדרת.

Debugging. Debugging קוד Rust שהודר ל-WASM משתפר אך עדיין פחות נוח מ-debugging JavaScript בחדר DevTools של דפדפן. אנו מסתמכים בעיקר על חבילת הבדיקות של Rust (פעל natively) ומשתמש ב-logging ספציפי WASM לדיבוג דפדפן.

אקוסיסטם. אקוסיסטם Rust לחישוב מספרי גדל אך פחות בשלות מאקוסיסטם NumPy/SciPy של Python. יישמנו כמה שיטות מספריות מ-scratch שם קיימות Rust crates היו בלתי מספיקות.

למרות trade-offs אלה, ההחלטה להשתמש ב-Rust ו-WebAssembly אומתה על ידי מאפייני ביצועים ואמינות של המנוע שנוצר. לכלי שבו מהנדסים יסתמכו על התוצאות להחלטות קריטיות לבטיחות, ההבטחות בזמן קומפילציה שRust מספק שוות את מורכבות הפיתוח הנוסף.

סיום

בחירת Rust ו-WebAssembly למנוע החישוב של ChainSolve הייתה החלטה ארכיטקטורית מכוונת מונעת על ידי דרישות הנדסיות, לא אופנה טכנולוגית. ביצועים קרובים לנייטיביים בדפדפן, בטיחות זיכרון ללא garbage collection, ומערכת סוג חזקה שתופסת שגיאות יחידה בזמן קומפילציה, אלה אינם nice-to-haves לכלי חישוב הנדסי. הם דרישות.

אם אתה מעוניין בפרטים הטכניים של ארכיטקטורת Rust/WASM שלנו, או אם אתה מעריך בחירות טכנולוגיה דומות לכלים הנדסיים שלך, אנו מקדמים את השיחה. הגיעו דרך דף יצירת קשר שלנו.

Written by
BG
ben godfrey
מהנדס ב-Godfrey Engineering Ltd.