۷.۸ مگابایت کلید که فقط برای دور ریختن تخصیص دادم

یک پروفایلر (profiler) هزینه‌ای پنهان را در کد من آشکار کرد.

هندلر درخواست (request handler) من توکن‌ها را از یک رشته بسیار بزرگ می‌خواند و مجموع وزن آن‌ها را محاسبه می‌کند. منطق کد درست کار می‌کرد، اما حافظه نه.

داخل یک حلقه پرکاربرد (hot loop)، ۷.۸ مگابایت رشته تخصیص دادم — برای هر جستجو در دیکشنری یک کلید جدید — و بلافاصله آن کلید را دور ریختم.

مقصر Substring بود. هر برش (slice) یک شیء رشته‌ای جدید ایجاد می‌کرد.

۲۰۰,۰۰۰ توکن

  • روش A (Substring): ۲۳.۴ میلی‌ثانیه، ۷,۸۱۲ کیلوبایت تخصیص یافته.
  • روش B (Span Lookup): ۱۴.۹ میلی‌ثانیه، ۰ کیلوبایت تخصیص یافته.

روش B حدود ۱.۵ برابر سریع‌تر اجرا می‌شود و هیچ زباله‌ای (garbage) تولید نمی‌کند.

در .NET 9 می‌توانید GetAlternateLookup را فراخوانی کنید. به جای یک رشته جدید، یک ReadOnlySpan<char> پاس می‌دهید که صرفاً پنجره‌ای روی رشته اصلی است و هیچ کپی‌سازی‌ای انجام نمی‌شود.

دیکشنری، span را مستقیماً در برابر کلیدهای موجود هش می‌کند و همان نتیجه را بدون تخصیص حافظه ارائه می‌دهد.

نکاتی که باید به خاطر بسپارید

  • با StringComparer.Ordinal و StringComparer.OrdinalIgnoreCase کار می‌کند.
  • اگر از یک مقایسه‌گر (comparer) سفارشی استفاده کنید که از قابلیت alternate lookup پشتیبانی نمی‌کند، در زمان اجرا خطا (throw) می‌دهد.
  • برای مسیرهای پرکاربرد (hot paths) مانند پارسرها، پردازشگرهای لاگ یا اسکنرهای CSV ایده‌آل است.
  • برای جستجوهای ساده با تعداد کلیدهای کم، از آن صرف‌نظر کنید.

حالا در کدم به دنبال الگوی Substring که بلافاصله با TryGetValue می‌آید، می‌گردم. این الگو مقادیر عظیمی از حافظه را هدر می‌دهد.

منبع: https://dev.to/ssukhpinder/78-mb-of-keys-i-allocated-just-to-throw-away-50mn

انجمن یادگیری اختیاری: https://github.com/ssukhpinder/dev-to-code-samples/tree/main/023-dictionary-alternate-lookup