വെറുതെ കളയാൻ വേണ്ടി മാത്രം ഞാൻ മാറ്റിവെച്ച 7.8 MB കീകൾ

എന്റെ കോഡിലെ ഒളിഞ്ഞിരിക്കുന്ന ഒരു ചിലവ് ഒരു പ്രൊഫൈലർ (profiler) വെളിപ്പെടുത്തി.

എന്റെ റിക്വസ്റ്റ് ഹാൻഡ്‌ലർ (request handler) ഒരു വലിയ സ്ട്രിംഗിൽ നിന്ന് ടോക്കണുകൾ വായിക്കുകയും അവയുടെ വെയ്റ്റുകൾ (weights) കൂട്ടുകയും ചെയ്യുന്നു. ലോജിക് ശരിയായിരുന്നു, പക്ഷേ മെമ്മറി പ്രശ്നമായിരുന്നു.

ഒരു ഹോട്ട് ലൂപ്പിനുള്ളിൽ (hot loop) ഞാൻ 7.8 MB സ്ട്രിംഗുകൾ മാറ്റിവെച്ചു—ഓരോ ഡിക്ഷണറി ലുക്കപ്പിനും (dictionary lookup) ഓരോ പുതിയ കീ—പിന്നീട് ആ കീ ഉടൻ തന്നെ ഒഴിവാക്കുകയും ചെയ്തു.

ഇതിന് കാരണം Substring ആയിരുന്നു. ഓരോ സ്ലൈസും (slice) പുതിയൊരു സ്ട്രിംഗ് ഒബ്‌ജക്റ്റ് സൃഷ്ടിച്ചു.

200,000 ടോക്കണുകൾ

  • മെത്തേഡ് A (Substring): 23.4 ms, 7,812 KB മാറ്റിവെച്ചു (allocated).
  • മെത്തേഡ് B (Span Lookup): 14.9 ms, 0 KB മാറ്റിവെച്ചു (allocated).

മെത്തേഡ് B 1.5 മടങ്ങ് വേഗത്തിൽ പ്രവർത്തിക്കുന്നു കൂടാതെ ഗാർബേജ് (garbage) ഉണ്ടാക്കുന്നില്ല.

.NET 9-ൽ നിങ്ങൾക്ക് GetAlternateLookup ഉപയോഗിക്കാം. ഒരു പുതിയ സ്ട്രിംഗിന് പകരം, നിങ്ങൾക്ക് ഒരു ReadOnlySpan<char> നൽകാം, ഇത് യഥാർത്ഥ സ്ട്രിംഗിലേക്കുള്ള ഒരു വിൻഡോ (window) മാത്രമാണ്—കോപ്പി ചെയ്യേണ്ടതില്ല.

ഡിക്ഷണറി ഈ സ്പാനിനെ (span) നിലവിലുള്ള കീകളുമായി നേരിട്ട് ഹാഷ് ചെയ്യുന്നു, ഇത് അലോക്കേഷൻ (allocation) ഇല്ലാതെ തന്നെ ഒരേ ഫലം നൽകുന്നു.

ഓർമ്മിക്കേണ്ട കാര്യങ്ങൾ

  • StringComparer.Ordinal, StringComparer.OrdinalIgnoreCase എന്നിവയ്‌ക്കൊപ്പം പ്രവർത്തിക്കുന്നു.
  • ആൾട്ടർനേറ്റ് ലുക്കപ്പ് സപ്പോർട്ട് ഇല്ലാത്ത ഒരു കസ്റ്റം കമ്പാരർ (custom comparer) ഉപയോഗിക്കുകയാണെങ്കിൽ റൺടൈമിൽ (runtime) എറർ വരും.
  • പാഴ്സറുകൾ (parsers), ലോഗ് പ്രോസസ്സറുകൾ (log processors), അല്ലെങ്കിൽ CSV സ്കാനറുകൾ (CSV scanners) പോലുള്ള ഹോട്ട് പാത്തുകൾക്ക് (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