SearchValues آخر کار .NET 10 میں بڑے بفرز میں کم تعداد میں موجود ڈیلیمیٹرز کو اسکین کرنے کے دوران ایک قابلِ پیمائش برتری دکھاتا ہے، جہاں 32 MB کے پی لوڈ پر SearchValues 3.3 ms میں مکمل ہو جاتا ہے جبکہ IndexOfAny کو 5.6 ms لگتے ہیں۔

SearchValues کیوں متعارف کرایا گیا

.NET 8 نے SearchValues کو حروف (یا بائٹس) کے ایک سیٹ کو پہلے سے کمپیوٹ (pre-compute) کرنے کے لیے متعارف کرایا تاکہ رن ٹائم اس سیٹ کے لیے تیز ترین اسکیننگ الگورتھم کا انتخاب کر سکے۔ آبجیکٹ کو ایک بار بنائیں، اور پھر جب بھی آپ کو کسی span میں ان ویلیوز میں سے کسی کو تلاش کرنے کی ضرورت ہو، اسے دوبارہ استعمال کریں۔ رن ٹائم ویکٹرائزڈ ہدایات (vectorized instructions)، برانچ فری لوپس (branch-free loops)، یا دیگر لو-لیول ٹرکس استعمال کر سکتا ہے جن کا مقابلہ ہاتھ سے لکھے گئے متبادل بہت زیادہ محنت کے بغیر نہیں کر سکتے۔

اہم بینچ مارکس

وہ ٹیسٹ جنہوں نے اس بحث کو جنم دیا، ان میں پانچ ڈیلیمیٹر حروف پر مشتمل 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 کو آپٹیمائز (optimize) کرتا ہے، جس سے فرق کم ہو جاتا ہے۔

جب ڈیلیمیٹرز کم ہوں—یعنی ہر 60 بائٹس کے بجائے ہر 40 KB میں ایک بار ظاہر ہوں—تو صورتحال بدل جاتی ہے:

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

اب SearchValues 1.7 × تیز ہے کیونکہ انجن ویکٹرائزڈ اقدامات کے ساتھ غیر مماثل ڈیٹا کے طویل سلسلے کو تیزی سے عبور کر لیتا ہے، جبکہ IndexOfAny ایک کم جارحانہ راستے پر چلا جاتا ہے۔

پوشیدہ قیمت

SearchValues مفت نہیں ہے۔ آبجیکٹ کی تعمیر میموری مختص کرتی ہے اور اندرونی لک اپ ٹیبلز بناتی ہے۔ اگر آپ اسے ہر میتھڈ کال پر انسٹینشئیٹ (instantiate) کرتے ہیں، تو اس کا اوور ہیڈ رن ٹائم کے کسی بھی فائدے کو ختم کر دے گا۔ ایک بینچ مارک جس نے مختصر لائنوں پر اسکیننگ روٹین کو دس لاکھ بار کال کیا، اس نے دکھایا:

  • Static (reused) SearchValues – کل 25.1 ms
  • Per-call SearchValues – کل 70.2 ms

ہر کال پر آبجیکٹ بنانا پورے آپریشن کو SearchValues استعمال نہ کرنے کے مقابلے میں تین گنا سست بنا دیتا ہے۔ انسٹنس کو ایک static readonly فیلڈ میں محفوظ کریں یا کالز کے دوران اسے دوبارہ استعمال کریں۔

کب SearchValues کا استعمال کریں

  • بڑے ان پٹس، کم میچز – ویکٹرائزڈ راستہ اس وقت بہترین کارکردگی دکھاتا ہے جب اسکینر ڈیلیمیٹر سے ٹکرائے بغیر طویل حصوں کو چھوڑ کر آگے بڑھ سکے۔
  • ایک ہی سیٹ کے ساتھ بار بار اسکیننگ – اگر ایک ہی حروف کو کئی بار تلاش کیا جاتا ہے، تو ایک بار کی سیٹ اپ فائدہ مند ثابت ہوتی ہے۔

کب IndexOfAny کے ساتھ رہنا چاہیے

  • مختصر اسٹرنگز یا زیادہ میچز – اضافی سیٹ اپ کی قیمت معمولی رفتار کے فائدے سے زیادہ ہو جاتی ہے۔
  • وہ کوڈ جو پہلے سے IndexOfAny استعمال کر رہا ہو – جدید .NET کی امپلیمنٹیشن پہلے ہی چھوٹے کیریکٹر سیٹس کے لیے انتہائی ٹیون شدہ ہے، اس لیے SearchValues کو شامل کرنے سے شاید کوئی خاص فرق نہ پڑے۔

عام غلطیاں

  • تیز رفتار لوپس (hot loops) میں تعمیر کو شامل کرنا – اس سے اوپر دکھایا گیا تین گنا سلو ڈاؤن ہوتا ہے۔
  • مینول لوپس کے ذریعے رن ٹائم کو "ہوشیار" بنانے کی کوشش کرنا – مینول foreach ورژن دونوں بلٹ ان طریقوں سے دو گنا سست تھا، جو اس بات کی تصدیق کرتا ہے کہ SIMD ہدایات کے گہرے علم کے بغیر .NET لائبریریوں کو شکست دینا مشکل ہے۔
  • عالمگیر رفتار میں اضافے کا خیال رکھنا – ڈینس ڈیٹا پر آدھے ملی سیکنڈ کی جیت یہ ثابت کرتی ہے کہ SearchValues کوئی عالمگیر چیٹ کوڈ نہیں ہے؛ اس کے فوائد ڈیٹا پر منحصر ہیں۔

خلاصہ

SearchValues<T> صرف اس وقت واضح فائدہ فراہم کرتا ہے جب آپ بڑے بفرز میں غیر معمولی حروف کے لیے اسکین کرتے ہیں اور ایک بار کی تعمیر کی قیمت برداشت کر سکتے ہیں۔ روزمرہ کے اسٹرنگ سرچ کے زیادہ تر حالات کے لیے—مختصر ان پٹس، بار بار ہونے والے میچز، یا وہ کوڈ جو پہلے سے IndexOfAny پر انحصار کرتا ہے—بلٹ ان میتھڈ ہی سادہ اور تیز تر انتخاب رہتا ہے۔ SearchValues کو منتخب طریقے سے استعمال کریں، انسٹنس کو کیش کریں، اور آپ اصل کارکردگی کا فائدہ حاصل کرتے ہوئے پوشیدہ نقصان سے بچ جائیں گے۔

Source code and full test suite: https://dev.to/ssukhpinder/when-searchvalues-actually-pays-off-310l