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

האם זה נראה לא בסדר? אתם מזהים כאן איזו בעיה פוטנציאלית?
במבט ראשון, הקוד נראה בסדר גמור. הוא בודק אם לפחות אחד מהפריטים במערך source
קיים במערך target
. הקוד קצר, משתמש ב-API המובנה של JavaScript, ונראה כמו פתרון מצוין לצורך כמו מציאת ה-Intersection 🚀
אבל האם הוא באמת יעיל כמו שהוא נראה?
בדיקת ביצועים: איך Array.includes
מתמודד עם מערכים גדולים?
בואו ננסה את הפתרון הזה על מערכים גדולים יותר:

הרצנו את הקוד שוב, והוא עדיין עובד במהירות 💪 אין דגלים אדומים, נכון?
השוואת Array.includes
ו-Set
במערכים גדולים – איך מוביל לשיפור הביצועים?
בואו נעלה הילוך. מה לגבי מערכים עם 60,000 אייטמים?

😱 הגדלנו את זמן הריצה דרמטית! עבור מערכים בגודל הזה, יכולות לקחת שניות ארוכות עד שהריצה של הקוד תסתיים – שזה נצח במונחים של חישובים, בטח אם הם נעשים בדפדפן והורסים את חווית המשתמש.
אז מה קורה כאן? בואו ננסה לפרק את זה.
הבעיה
מקור הבעיה הוא בפעולות המקוננות:
Array.includes
מבצע חיפוש ליניארי, כלומר רץ בסיבוכיות של O(n)Array.some
עובר דרך המערךsource
, כלומר בסיבוכיות של O(m)
השילוב הזה מוביל למורכבות כוללת של O(n * m)
, מה שעשוי להפוך לאסון כשעובדים עם כמות גדולה של נתונים🥵
שיפור ביצועים ב-JavaScript: איך Set
ו-Map
מייעלים חיפושים?
כדי לפתור את זה, נוכל להפוך את המערך target
למבנה נתונים שמאפשר חיפושים בזמן קבוע, כלומר בסיבוכיות של O(1). הנה דוגמה לגישה פשוטה כזאת באמצעות שימוש במערך כאינדקס:


וואו! גם עם 120K איברים, אנחנו בקושי מרגישים את זמן הריצה 🥳
מה הקסם?
זה עובד כי כמו שהסברנו למעלה – כאן אנחנו משתמשים בחיפושים בזמן קבוע עם מבני נתונים מתאימים. הנה עוד כמה דרכים אלטרנטיביות ליישם את זה, כולן מביאות לידי ביטוי את אותו עיקרון בדיוק:
שימוש ב-Object:


ועכשיו, אותו עקרון רק עם Map:


והאופציה הטובה ביותר, חיפוש באמצעות Set:


השגת סיבוכיות של O(1)
לחיפושים היא היתרון המרכזי של האפשרויות הללו, ולכן הן עדיפות על הגישה המקורית.
שיפור ביצועים: Array.includes
מול Set
מול Map
– מי המהיר ביותר?
בהינתן ש-n
ו-m
הם כמות האייטמים במערכים source
ו-target
, ו-k
הוא הטווח של הערכים במערך target
, אז:
הגישה המקורית:
- Time Complexity: O(n * m)
- Space Complexity: O(n + m)
הגישה החלופית:
- Time Complexity: O(n + m)
- Space Complexity: O(k)
כלומר, מערך מאונדקס יעיל עבור טווחים קטנים וצפופים, אבל Set
הוא גמיש יותר ומתאים למקרים כלליים.
סיכום
מעבר מ-Array.includes
למבנה נתונים מתאים יותר כמו Set
או מערך מאונדקס יכול לשפר באופן דרמטי את הביצועים – במיוחד כשעובדים עם כמויות נתונים גדולות.
ובאופן כללי – גם שורת קוד תמימה, מסתירה לפעמים בעיות ביצועים חמורות כשנעבוד עם Scale גדול יותר. ככל שנעמיק ב״מאחורי הקלעים״ של JavaScript, נוכל לכתוב קוד שהוא לא רק נכון, אלא גם יעיל במיוחד.
רוצה עוד טיפים לאופטימיזציה של ביצועי JavaScript? תעקבו אחרי המאמרים הנוספים בסדרה! 🚀
👈 לקריאת המאמר הבא בסדרה על שיפור ביצועים בעבודה עם פורמטינג של תאריכים, מטבעות ואחוזים.
מאמרים קשורים
The Dark Side of JS Formatting
Are you aware of this Regex pitfall
Think One WebWorker is Enough? Think Again
