7.8MB של מפתחות שהקציתי רק כדי לזרוק לפח

פרופיילר (Profiler) חשף עלות נסתרת בקוד שלי.

מטפל הבקשות (request handler) שלי קורא טוקנים מתוך מחרוזת ענקית ומסכם את המשקלים שלהם. הלוגיקה עבדה, אבל הזיכרון לא.

בתוך לולאה חמה (hot loop), הקציתי 7.8MB של מחרוזות — מפתח חדש עבור כל חיפוש במילון — ואז זרקתי את המפתח מיד.

האשם היה Substring. כל חתך (slice) יצר אובייקט מחרוזת חדש.

200,000 טוקנים

  • שיטה A (Substring): 23.4ms, הקצאה של 7,812KB.
  • שיטה B (Span Lookup): 14.9ms, הקצאה של 0KB.

שיטה B רצה מהר פי 1.5 ואינה מייצרת אשפה (garbage).

ב-.NET 9 ניתן לקרוא ל-GetAlternateLookup. במקום מחרוזת חדשה, מעבירים ReadOnlySpan<char>, שהוא בסך הכל "חלון" על המחרוזת המקורית — ללא העתקה.

המילון מבצע האש (hash) ל-span ישירות מול המפתחות הקיימים, ומספק את אותה תוצאה ללא ההקצאה.

דברים שחשוב לזכור

  • עובד עם StringComparer.Ordinal ו-StringComparer.OrdinalIgnoreCase.
  • מטיל שגיאה בזמן ריצה (runtime) אם משתמשים ב-comparer מותאם אישית שאינו תומך ב-alternate lookup.
  • אידיאלי עבור נתיבים חמים (hot paths) כמו פארסרים (parsers), מעבדי לוגים או סורקי CSV.
  • דלגו על זה בחיפושים פשוטים עם מספר קטן של מפתחות.

עכשיו אני צד בקוד שלי שילוב של Substring ואחריו TryGetValue. התבנית הזו מבזבזת כמויות אדירות של זיכרון.

Source: https://dev.to/ssukhpinder/78-mb-of-keys-i-allocated-just-to-throw-away-50mn

Optional learning community: https://github.com/ssukhpinder/dev-to-code-samples/tree/main/023-dictionary-alternate-lookup