വലിയ ബഫറുകളിൽ (buffers) കുറഞ്ഞ അളവിൽ മാത്രം കാണപ്പെടുന്ന ഡെലിമിറ്ററുകൾ (delimiters) തിരയുമ്പോൾ .NET 10-ൽ SearchValues<T> വ്യക്തമായ നേട്ടം കാണിക്കുന്നു. 32 MB പേലോഡിൽ (payload) SearchValues 3.3 ms-ൽ പൂർത്തിയാകുമ്പോൾ, IndexOfAny 5.6 ms ആണ് എടുക്കുന്നത്.

എന്തുകൊണ്ടാണ് SearchValues വന്നത്

കഥാപാത്രങ്ങളുടെയോ (characters) ബൈറ്റുകളുടെയോ (bytes) ഒരു കൂട്ടം മുൻകൂട്ടി കണക്കാക്കുന്നതിനും (pre-compute), ആ സെറ്റിനായി ഏറ്റവും വേഗതയേറിയ സ്കാനിംഗ് അൽഗോരിതം റൺടൈം തിരഞ്ഞെടുക്കുന്നതിനും വേണ്ടിയാണ് .NET 8 SearchValues<T> അവതരിപ്പിച്ചത്. ഒരു തവണ ഒബ്ജക്റ്റ് നിർമ്മിച്ചാൽ, ഒരു സ്പാനിൽ (span) ഏതെങ്കിലും മൂല്യങ്ങൾ കണ്ടെത്താൻ അത് വീണ്ടും ഉപയോഗിക്കാം. കൈകൊണ്ട് എഴുതുന്ന മറ്റ് രീതികളേക്കാൾ വേഗതയേറിയ വെക്റ്ററൈസ്ഡ് ഇൻസ്ട്രക്ഷനുകൾ (vectorized instructions), ബ്രാഞ്ച്-ഫ്രീ ലൂപ്പുകൾ (branch-free loops) അല്ലെങ്കിൽ മറ്റ് ലോ-ലെവൽ വിദ്യകൾ റൺടൈമിലൂടെ ഉപയോഗിക്കാൻ ഇതിലൂടെ സാധിക്കുന്നു.

പ്രസക്തമായ ബെഞ്ച്മാർക്കുകൾ (Benchmarks)

ചർച്ചകൾക്ക് കാരണമായ ടെസ്റ്റുകളിൽ അഞ്ച് ഡെലിമിറ്റർ കഥാപാത്രങ്ങളുള്ള 32 MB ബഫറാണ് ഉപയോഗിച്ചത്. .NET 10-ൽ മൂന്ന് രീതികൾ പരിശോധിച്ചു:

  • IndexOfAny(char[]) – 10.2 ms
  • Manual foreach loop over the span – 19.4 ms
  • SearchValues – 9.7 ms

SearchValues, ഇൻബിൽറ്റ് ആയ IndexOfAny-നെ വെറും അര മില്ലിസെക്കൻഡ് വ്യത്യാസത്തിലാണ് മറികടന്നത്. ഫോറങ്ങളിൽ പ്രചരിക്കുന്ന "അഞ്ചിരട്ടി വേഗത" എന്ന അവകാശവാദം ഇതിലില്ല; ആധുനിക .NET ഇതിനകം തന്നെ ചെറിയ സെറ്റുകൾക്കായി IndexOfAny-നെ ഒപ്റ്റിമൈസ് ചെയ്തിട്ടുള്ളതിനാൽ ഈ വ്യത്യാസം കുറവാണ്.

ഡെലിമിറ്ററുകൾ വളരെ അപൂർവ്വമായി മാത്രം കാണപ്പെടുമ്പോൾ—ഓരോ 60 ബൈറ്റിലും ഒന്നെന്നതിന് പകരം ഓരോ 40 KB-യിലും ഒരിക്കൽ മാത്രം കാണപ്പെടുമ്പോൾ—സാഹചര്യം മാറുന്നു:

  • IndexOfAny(char[]) – 5.6 ms
  • SearchValues – 3.3 ms

ഇവിടെ SearchValues 1.7 മടങ്ങ് വേഗതയുള്ളതാണ്. കാരണം, IndexOfAny കുറഞ്ഞ വേഗതയുള്ള പാതയിലേക്ക് മാറുന്നപ്പോൾ, SearchValues വെക്റ്ററൈസ്ഡ് സ്റ്റെപ്പുകളിലൂടെ (vectorized steps) മാച്ച് ചെയ്യാത്ത ഡാറ്റയുടെ നീണ്ട ഭാഗങ്ങളിലൂടെ അതിവേഗം കടന്നുപോകുന്നു.

മറഞ്ഞിരിക്കുന്ന ചിലവ്

SearchValues സൗജന്യമല്ല. ഒബ്ജക്റ്റ് നിർമ്മിക്കുന്നത് മെമ്മറി ഉപയോഗിക്കുകയും ഇന്റേണൽ ലുക്കപ്പ് ടേബിളുകൾ (lookup tables) നിർമ്മിക്കുകയും ചെയ്യുന്നു. ഓരോ മെത്തേഡ് കോളിലായി ഇത് ഇൻസ്റ്റാന്ഷ്യേറ്റ് (instantiate) ചെയ്താൽ, ലഭിക്കുന്ന വേഗതയേക്കാൾ കൂടുതൽ സമയം ഇതിനായി ചിലവാകും. ചെറിയ വരികളിൽ ഒരു മില്യൺ തവണ സ്കാനിംഗ് റൂട്ടീൻ വിളിച്ച ഒരു ബെഞ്ച്മാർക്ക് കാണിക്കുന്നത്:

  • Static (reused) SearchValues – ആകെ 25.1 ms
  • Per-call SearchValues – ആകെ 70.2 ms

ഓരോ തവണയും ഒബ്ജക്റ്റ് നിർമ്മിക്കുന്നത് SearchValues ഉപയോഗിക്കാത്തതിനേക്കാൾ മൂന്നിരട്ടി വേഗത കുറയ്ക്കുന്നു. അതിനാൽ ഇതിന്റെ ഇൻസ്റ്റൻസ് ഒരു static readonly ഫീൽഡിൽ സൂക്ഷിക്കുകയോ അല്ലെങ്കിൽ ഓരോ തവണയും വീണ്ടും ഉപയോഗിക്കുകയോ ചെയ്യുക.

എപ്പോഴാണ് SearchValues ഉപയോഗിക്കേണ്ടത്

  • വലിയ ഇൻപുട്ടുകൾ, കുറഞ്ഞ മാച്ചുകൾ (matches) – ഡെലിമിറ്ററുകൾ കാണുന്നതിന് മുൻപുള്ള നീണ്ട ഭാഗങ്ങൾ സ്കാനർക്ക് ഒഴിവാക്കാൻ കഴിയുമ്പോൾ വെക്റ്ററൈസ്ഡ് പാത്ത് മികച്ച ഫലം നൽകുന്നു.
  • ഒരേ സെറ്റ് ഉപയോഗിച്ചുള്ള ആവർത്തിച്ചുള്ള സ്കാനുകൾ – ഒരേ കഥാപാത്രങ്ങൾ തന്നെ പലതവണ തിരയുന്നുണ്ടെങ്കിൽ, ഒരു തവണയുള്ള സെറ്റപ്പ് ലാഭകരമാണ്.

എപ്പോഴാണ് IndexOfAny ഉപയോഗിക്കേണ്ടത്

  • ചെറിയ സ്ട്രിംഗുകൾ അല്ലെങ്കിൽ കൂടുതൽ മാച്ചുകൾ – അധികമായ സെറ്റപ്പ് ചിലവ് ലഭിക്കുന്ന ചെറിയ വേഗതയേക്കാൾ കൂടുതലായിരിക്കും.
  • നിലവിൽ IndexOfAny ഉപയോഗിക്കുന്ന കോഡ് – ആധുനിക .NET-ന്റെ ഇംപ്ലിമെന്റേഷൻ ചെറിയ ക്യാരക്ടർ സെറ്റുകൾക്കായി മികച്ച രീതിയിൽ ട്യൂൺ ചെയ്തിട്ടുള്ളതാണ്, അതിനാൽ SearchValues-ലേക്ക് മാറുന്നത് വലിയ മാറ്റമുണ്ടാക്കില്ല.

സാധാരണ പിഴവുകൾ

  • Hot loops-നുള്ളിൽ നിർമ്മാണം ഉൾപ്പെടുത്തുന്നത് – മുകളിൽ കാണിച്ചതുപോലെ മൂന്നിരട്ടി വേഗത കുറയാൻ ഇത് കാരണമാകും.
  • മാനുവൽ ലൂപ്പുകൾ ഉപയോഗിച്ച് റൺടൈമിനെ മറികടക്കാൻ ശ്രമിക്കുന്നത് – മാനുവൽ foreach പതിപ്പ് ഇൻബിൽറ്റ് രീതികളേക്കാൾ ഇരട്ടി വേഗത കുറഞ്ഞതായിരുന്നു. SIMD ഇൻസ്ട്രക്ഷനുകളെക്കുറിച്ചുള്ള ആഴത്തിലുള്ള അറിവില്ലാതെ .NET ലൈബ്രറികളെ മറികടക്കുക പ്രയാസമാണെന്ന് ഇത് തെളിയിക്കുന്നു.
  • എല്ലാ സാഹചര്യത്തിലും വേഗത കൂടുമെന്ന് കരുതുന്നത് – ഡെൻസിറ്റി കൂടിയ ഡാറ്റയിൽ ലഭിച്ച അര മില്ലിസെക്കൻഡ് നേട്ടം കാണിക്കുന്നത് SearchValues എല്ലാത്തിനും ഒരു മാന്ത്രിക വിദ്യയല്ല എന്നാണ്; ഇതിന്റെ ഗുണങ്ങൾ ഡാറ്റയെ ആശ്രയിച്ചിരിക്കുന്നു.

ചുരുക്കത്തിൽ (Takeaway)

വലിയ ബഫറുകളിൽ അപൂർവ്വമായി മാത്രം കാണപ്പെടുന്ന കഥാപാത്രങ്ങൾ തിരയുമ്പോൾ മാത്രമാണ് SearchValues<T> വ്യക്തമായ നേട്ടം നൽകുന്നത്. ദൈനംദിന സ്ട്രിംഗ് സെർച്ച് സാഹചര്യങ്ങളിൽ—ചെറിയ ഇൻപുട്ടുകൾ, frequent matches, അല്ലെങ്കിൽ നിലവിൽ IndexOfAny ഉപയോഗിക്കുന്ന കോഡുകൾ—ഇൻബിൽറ്റ് മെത്തേഡ് തന്നെയാണ് ലളിതവും വേഗതയേറിയതും. SearchValues ആവശ്യാനുസരണം മാത്രം ഉപയോഗിക്കുക, അതിന്റെ ഇൻസ്റ്റൻസ് കാഷെ (cache) ചെയ്യുക; അങ്ങനെ നിങ്ങൾക്ക് മികച്ച പ്രകടനം നേടാനും മറഞ്ഞിരിക്കുന്ന ചിലവുകൾ ഒഴിവാക്കാനും സാധിക്കും.

സോഴ്സ് കോഡും പൂർണ്ണമായ ടെസ്റ്റ് സ്യൂട്ടും: https://dev.to/ssukhpinder/when-searchvalues-actually-pays-off-310l