আমি শুধু ফেলে দেওয়ার জন্য ৭.৮ MB কী (Keys) বরাদ্দ করেছিলাম

একটি প্রোফাইলার আমার কোডের একটি লুকানো খরচ (hidden cost) প্রকাশ করেছে।

আমার রিকোয়েস্ট হ্যান্ডলার একটি বিশাল স্ট্রিং থেকে টোকেনগুলো পড়ে এবং তাদের ওয়েট (weights) যোগ করে। লজিকটি কাজ করছিল, কিন্তু মেমরি ঠিকমতো কাজ করছিল না।

একটি হট লুপের (hot loop) ভেতরে আমি ৭.৮ MB স্ট্রিং বরাদ্দ করেছিলাম—প্রতিটি ডিকশনারি লুকআপের জন্য একটি করে নতুন কী—এবং তারপর কী-টি সাথে সাথে ফেলে দিচ্ছিলাম।

এর জন্য দায়ী ছিল Substring। প্রতিটি স্লাইস একটি নতুন স্ট্রিং অবজেক্ট তৈরি করছিল।

200,000 টোকেন

  • Method A (Substring): 23.4 ms, 7,812 KB বরাদ্দ করা হয়েছে।
  • Method B (Span Lookup): 14.9 ms, 0 KB বরাদ্দ করা হয়েছে।

Method B 1.5× দ্রুত চলে এবং কোনো গারবেজ (garbage) তৈরি করে না।

.NET 9-এ আপনি GetAlternateLookup কল করতে পারেন। একটি নতুন স্ট্রিংয়ের পরিবর্তে, আপনি একটি ReadOnlySpan<char> পাস করতে পারেন, যা মূলত মূল স্ট্রিংয়ের ওপর একটি উইন্ডোর মতো—কোনো কপি করার প্রয়োজন হয় না।

ডিকশনারিটি সরাসরি বিদ্যমান কী-গুলোর বিপরীতে স্প্যানটিকে (span) হ্যাশ করে, যার ফলে কোনো মেমরি বরাদ্দ ছাড়াই একই ফলাফল পাওয়া যায়।

মনে রাখার মতো বিষয়গুলো

  • StringComparer.Ordinal এবং StringComparer.OrdinalIgnoreCase-এর সাথে কাজ করে।
  • আপনি যদি এমন কোনো কাস্টম কম্পারার (custom comparer) ব্যবহার করেন যাতে অল্টারনেট লুকআপ সাপোর্ট নেই, তবে এটি রানটাইমে এরর (error) দেবে।
  • পার্সার (parsers), লগ প্রসেসর (log processors), বা CSV স্ক্যানারের মতো হট পাথগুলোর (hot paths) জন্য এটি আদর্শ।
  • মাত্র কয়েকটি কী-যুক্ত সাধারণ লুকআপের ক্ষেত্রে এটি এড়িয়ে চলুন।

আমি এখন আমার কোডে 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