SearchValues: ابزاری برای بهبود مقیاسپذیری در عملیات رشتهای
نویسنده: وحید نصیری
تاریخ: ۱۴۰۴/۰۸/۰۸ ۰۹:۴۵
آدرس: www.dntips.ir
SearchValues که نه تنها عملکرد را ۵ برابر بهبود میبخشد، بلکه اجازه میدهد بهجای افزودن سرور، چند ماشین را خاموش کنیم. این بهبود در .NET 9 و .NET 10 حتی چشمگیرتر شده است.string.IndexOfAny() یا حلقههای سفارشی، در بارهای کم (دهها درخواست در ثانیه) خوب کار میکنند. اما در هزاران درخواست در ثانیه، به آرامی مشکلساز میشوند. برای نمونه در یک سرویس تجمیع لاگ، که لاگهای برنامه را در زمان واقعی پردازش میکند، در هر لاگ نیاز به:IndexOfAny() یک راهحل عمومی است، در حالی که این مسئله از تخصصیسازی سود میبرد.char[] delimiters = { ',', ';', '|' };
int index = logMessage.IndexOfAny(delimiters);SearchValues<T>؛ بهینهسازی هوشمند و یکبار مصرفSearchValues در .NET 8 معرفی شد و هنوز بسیاری از توسعهدهندگان از آن بیخبرند. چطور کار میکند؟| مجموعه | الگوریتم بهینه |
| کاراکترهای متراکم | Bitmap Lookup |
| مجموعههای پراکنده | شاخهبندی بهینه |
| مجموعههای بزرگ | SIMD Vectorization |
private static readonly SearchValues<char> Delimiters =
SearchValues.Create([',', ';', '|']);ReadOnlySpan<char> span = logMessage.AsSpan(); int index = span.IndexOfAny(Delimiters);
نکته کلیدی: نمونه را static readonly نگه دارید. ساخت مکرر در حلقههای داغ، مزیت را نابود میکند.SearchValues از نوعهای char و byte را داشتیم. نوع string آن در .NET 9 اضافه شد:// قبل (Regex)
var regex = new Regex("(?i)error|warning|critical", RegexOptions.Compiled);
bool found = regex.IsMatch(logMessage);
// بعد (SearchValues)
private static readonly SearchValues<string> LogKeywords =
SearchValues.Create(["error", "warning", "critical"], StringComparison.OrdinalIgnoreCase);
bool found = logMessage.AsSpan().ContainsAny(LogKeywords);SearchValues استفاده میکند.PermuteVar64x8x2 با خروجی نصف شدن چرخههای CPUUnzipEven ارزانتر برای عملکرد بهتر در Graviton و Ampereمزیت ارتقاء از .NET 9 به .NET 10 → خروجی ۱۰-۲۰٪ سریعتر بدون تغییر کد
using System;
using System.Buffers;
using System.Text;
public class LogSanitizer
{
private static readonly SearchValues<char> DangerousCharacters =
SearchValues.Create("\0\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0E\x0F" +
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F");
private static readonly SearchValues<char> SensitiveMarkers =
SearchValues.Create("=:@");
public static string SanitizeLogEntry(string logEntry)
{
ReadOnlySpan<char> span = logEntry.AsSpan();
// مسیر سریع: اگر کاراکتر خطرناک نباشد
if (!span.ContainsAny(DangerousCharacters))
{
return ProcessSensitiveData(logEntry);
}
// مسیر کند: بازسازی بدون کاراکترهای خطرناک
var builder = new StringBuilder(logEntry.Length);
foreach (char c in span)
{
if (!DangerousCharacters.Contains(c))
builder.Append(c);
}
return ProcessSensitiveData(builder.ToString());
}
private static string ProcessSensitiveData(string log)
{
ReadOnlySpan<char> span = log.AsSpan();
int markerIndex = span.IndexOfAny(SensitiveMarkers);
if (markerIndex == -1) return log;
var result = new StringBuilder(log.Length);
result.Append(span.Slice(0, markerIndex + 1));
result.Append("[REDACTED]");
// در نسخه واقعی، 20 کاراکتر بعدی را حذف کنید
return result.ToString();
}
}| معیار | قبل | بعد | بهبود |
| زمان پردازش ۱۰٬۰۰۰ لاگ | ~۴۵۰ میلیثانیه | ~۸۵ میلیثانیه | ۵.۳ برابر |
| زمان پاکسازی | - | - | ۸۷٪ کاهش |
| تخصیص حافظه (مسیر تمیز) | دارد | صفر | GC خوابیده |
SearchValues<T>استفاده کنیم؟| شرایط | توصیه |
| جستجو برای ۳+ کاراکتر/مقدار | استفاده کنید |
| کد در مسیر داغ (per-request, حلقه تنگ) | ایدهآل |
| ورودی متغیر طول | سود بیشتر |
| CPU با SIMD (۵-۷ سال اخیر) | ۵-۱۰ برابر سریعتر |
| جستجوی ۱-۲ کاراکتر | IndexOf کافی است |
| اجرای یکبار (startup, error path) | ارزش ندارد |
| گلوگاه I/O | اولویت نیست |
نکته: هشدار CA1870: این تحلیلگر کد، امکان تبدیلIndexOfAny(array)را بهSearchValuesهشدار میدهد. اما همیشه پروفایل کنید؛ تحلیلگر جایگزین پروفایلر نیست.
SearchValues یکی از بیشترین ROIهای بهینهسازی نسبت به تلاش است:جستجوی مکرر + چندین مقدار + ورودی متغیر طول
SearchValues را اعمال کنیدSearchValues یک ابزار نیست؛ یک تغییر دهنده بازی در مقیاس است.