معیارهای بیان منظم سی شارپ – چگونه از اشتباهات خود اجتناب کنم!
اخیراً مقاله ای در مورد ویژگی های مختلف عملکرد استفاده از regex در سی شارپ منتشر کردم. به طور خاص، روش های انگشت شماری وجود دارد که می توانیم از عبارات منظم در سی شارپ استفاده کنیم تا ظاهراً همان کار را انجام دهیم. من در مورد تفاوتها کنجکاو بودم، بنابراین تصمیم گرفتم برخی از معیارهای بیان منظم C# را جمعآوری کنم… اما مسخره کردم.
در این مقاله، خطای خود را توضیح میدهم تا بتوانید از آن جلوگیری کنید… و البته، معیارهای جدید و بهروزرسانی کدهای بنچمارک را با استفاده از BenchmarkDotNet ارائه خواهم کرد.
معیارهای بیان منظم CSharp نامعتبر
در مقاله قبلی که در مورد معیارهای بیان منظم C# نوشتم، نتایجی داشتم که در صورت استفاده نادرست از برخی از روشهای regex سی شارپ موجود، دستاوردهای عملکردی بسیار خوبی را نشان میداد. نتایج بنچمارک به وضوح نشان داد که هزینه ایجاد یک شی regex جدید در سی شارپ گران است، و حتی اگر پرچم کامپایل را داشته باشید، گرانتر است. بنابراین اگر این کار را هر بار که میخواستید انجام دهید، متأسفانه جریمه بالایی را پرداخت میکردید.
میتوانید ویدیوی مربوط به این معیارها را در اینجا تماشا کنید تا ایده بهتری پیدا کنید، با توجه به اینکه نتایجی که میخواهیم بحث کنیم، نتایج صحیحی هستند:
https://www.youtube.com/watch?v=nw8B_E5ICMM
یک نظر عالی در یوتیوب وجود داشت که کمی پس از انتشار ویدیو منتشر شد، و این چیزی است که بیننده باید بگوید:
این به این معنی بود که باید برای بررسی به تابلوی نقاشی برگردم!
تنبل بودن مثل تکرار کننده های CSharp…
همانطور که بیننده اشاره کرد … به نظر می رسد MatchCollection
که از انجام یک مسابقه regex برمیگردد با تنبلی ارزیابی میشود. اگر قبلاً در مورد این مفهوم نشنیده اید، ممکن است گیج کننده باشد، اما همه اینها شبیه به استفاده از تکرار کننده ها در سی شارپ است که من به طور گسترده در مورد آن صحبت کرده ام. اگر میخواهید کمی در مورد ایدههای تکرارکننده در سی شارپ مطالعه کنید، میتوانید این ویدیو را برای توضیح تجربیات من ببینید:
https://www.youtube.com/watch?v=qYoZn4Td41E
داستان کوتاه: به نظر می رسد که یک خط کد یک متد را اجرا می کند، اما شما فقط یک نشانگر تابع را به یک متغیر اختصاص می دهید. نتیجه این است که به جای اینکه یک روش برای اجرای آن مقداری زمان صرف کند، در عوض باید آن را تقریباً آنی اجرا کنید.
اما آیا MatchCollection
در حقیقت یک تکرار کننده سی شارپ، یا چیز دیگری است؟
بازبینی کد بنچمارک CSharp Regex
بیایید کد زیر را در نظر بگیریم که یکی از معیارهای ساده مقاله قبلی است:
Regex regex = new(RegexPattern!, RegexOptions.Compiled);
return regex.Matches(_sourceText!);
چیزی که ما در آن روش معیار برمی گردانیم در واقع همان چیزی است که a نامیده می شود MatchCollection
. در عوض می توانیم آن کد را به صورت زیر بنویسیم:
Regex regex = new(RegexPattern!, RegexOptions.Compiled);
MatchCollection matches = regex.Matches(_sourceText!);
return matches;
خط وسط مثال کد مستقیماً در بالا به نظر می رسد که همه موارد مطابق را ارزیابی می کند، اما در واقع تقریباً بلافاصله برمی گردد. قطعاً این چیزی نیست که ما می خواستیم اندازه گیری کنیم! در عوض، من فقط هزینه ایجاد یک را اندازهگیری میکردم MatchCollection
instance در مقابل ایجاد یک نمونه شیء Regex کامل C# به اضافه a MatchCollection
نمونه، مثال. این توضیح میدهد که چرا دو معیاری که نمونهها را ایجاد کردند کاملاً تضمین شده بودند که کندتر هستند – آنها همین کار را در هنگام ایجاد یک نمونه انجام دادند. MatchCollection
به عنوان مثال، اما آنها سربار مربوط به خود را برای ایجاد عبارت منظم داشتند.
با MatchCollection چه اتفاقی میافتد؟
خب معلومه که MatchCollection
کاملاً تکرارکننده نیست، اما مطمئناً با تنبلی ارزیابی می شود. اگر به تعریف ادامه دهید و عمیق تر به کد برای MatchCollection
، موارد زیر را مشاهده خواهید کرد:
private void EnsureInitialized()
{
if (!_done)
{
GetMatch(int.MaxValue);
}
}
این روشی است به نام در بسیاری از نقاط در داخل MatchCollection
کلاس، و اساساً هر زمان که کسی بخواهد به اطلاعات مربوط به مسابقات دسترسی پیدا کند، در نهایت باید وارد آن شویم GetMatch
روش. آن روش به صورت زیر است:
private Match? GetMatch(int i)
{
Debug.Assert(i >= 0, "i cannot be negative.");
if (_matches.Count > i)
{
return _matches[i];
}
if (_done)
{
return null;
}
Match match;
do
{
match = _regex.RunSingleMatch(
RegexRunnerMode.FullMatchRequired,
_prevlen,
_input,
0,
_input.Length,
_startat)!;
if (!match.Success)
{
_done = true;
return null;
}
_matches.Add(match);
_prevlen = match.Length;
_startat = match._textpos;
} while (_matches.Count <= i);
return match;
}
اگر از این کد عبور کنید، متوجه خواهید شد که وضعیتی در طول مسابقاتی که تماس گیرنده به دنبال آن است، حفظ می شود. این بدان معناست که اگر این نمونه هنوز تا این اندازه ارزیابی نشده باشد، سپس تلاش خواهد کرد تا آن اندازه را بر اساس شاخص درخواستی ارزیابی کند. به نظر من خیلی تنبل است! اما اگر این یک هدایا نبود… ببینید سازنده چگونه حتی مجموعه ای از مسابقات را در زمان ایجاد نمی گیرد!
internal MatchCollection(Regex regex, string input, int startat)
{
if ((uint)startat > (uint)input.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeException(
ExceptionArgument.startat,
ExceptionResource.BeginIndexNotNegative);
}
_regex = regex;
_input = input;
_startat = startat;
_prevlen = -1;
_matches = new List<Match>();
_done = false;
}
لطفاً به خاطر داشته باشید که این همان چیزی است که ویژوال استودیو به من نشان می دهد، و اگر بعداً سعی کنید منبع را بررسی کنید ممکن است در کد دات نت متفاوت باشد.
شما می توانید همراه با این ویدئو از اینجا دنبال کنید:
https://www.youtube.com/watch?v=B14MzCqPFUY
کد جدید برای معیارهای بیان منظم CSharp
بیایید مستقیماً به کد BenchmarkDotNet جدید برای این سناریوهای عبارات معمولی بپریم، که بسیار شبیه به قبل هستند:
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Reflection;
using System.Text.RegularExpressions;
BenchmarkRunner.Run(
Assembly.GetExecutingAssembly(),
args: args);
[MemoryDiagnoser]
[MediumRunJob]
public partial class RegexBenchmarks
{
private const string RegexPattern = @"\b\w*(ing|ed)\b";
private string? _sourceText;
private Regex? _regex;
private Regex? _regexCompiled;
private Regex? _generatedRegex;
private Regex? _generatedRegexCompiled;
[GeneratedRegex(RegexPattern, RegexOptions.None, "en-US")]
private static partial Regex GetGeneratedRegex();
[GeneratedRegex(RegexPattern, RegexOptions.Compiled, "en-US")]
private static partial Regex GetGeneratedRegexCompiled();
[Params("pg73346.txt")]
public string? SourceFileName { get; set; }
[GlobalSetup]
public void Setup()
{
_sourceText = File.ReadAllText(SourceFileName!);
_regex = new(RegexPattern);
_regexCompiled = new(RegexPattern, RegexOptions.Compiled);
_generatedRegex = GetGeneratedRegex();
_generatedRegexCompiled = GetGeneratedRegexCompiled();
}
[Benchmark(Baseline = true)]
public int Static()
{
return Regex.Matches(_sourceText!, RegexPattern!).Count;
}
[Benchmark]
public int New()
{
Regex regex = new(RegexPattern!);
return regex.Matches(_sourceText!).Count;
}
[Benchmark]
public int New_Compiled()
{
Regex regex = new(RegexPattern!, RegexOptions.Compiled);
return regex.Matches(_sourceText!).Count;
}
[Benchmark]
public int Cached()
{
return _regex!.Matches(_sourceText!).Count;
}
[Benchmark]
public int Cached_Compiled()
{
return _regexCompiled!.Matches(_sourceText!).Count;
}
[Benchmark]
public int Generated()
{
return GetGeneratedRegex().Matches(_sourceText!).Count;
}
[Benchmark]
public int Generated_Cached()
{
return _generatedRegex!.Matches(_sourceText!).Count;
}
[Benchmark]
public int Generated_Compiled()
{
return GetGeneratedRegexCompiled().Matches(_sourceText!).Count;
}
[Benchmark]
public int Generated_Cached_Compiled()
{
return _generatedRegexCompiled!.Matches(_sourceText!).Count;
}
}
اگر این روشهای معیار را با مقاله قبلی مقایسه کنید، متوجه میشوید که بسیار نزدیک هستند با یک تفاوت بسیار ظریف: من یک عدد صحیح را به جای عدد برمیگردانم. MatchCollection
. اگر این و آنچه قبلاً در کد به آن اشاره کردم را در نظر بگیرید، Count
یکی از خواص در MatchCollection
که در نهایت سعی میکند مسابقات زیر سرپوش را ارزیابی کند – همانطور که در این مورد، رفتار تنبل را وادار به ارزیابی میکند.
ما می توانیم با فاکتورگیری در این قطعه کد زیر مشاهده کنیم:
/// <summary>
/// Returns the number of captures.
/// </summary>
public int Count
{
get
{
EnsureInitialized();
return _matches.Count;
}
}
نتایج جدید معیارهای بیان منظم CSharp
اکنون که پاک شدم و اعتراف کردم که معیارهایم را کاملاً خراب کردم و کد به روز شده را به شما ارائه کردم، بیایید این تحلیل را دوباره انجام دهیم. در اینجا معیارهای جدید اجرا شده هستند:
حالا در اینجا چند نکته جالب وجود دارد که باید به آنها توجه کنید:
فراخوانی روش استاتیک در واقع بدترین گزینه در بین همه گزینه ها است. در حالی که تقریباً یکسان است، همانطور که این نتایج معیار نشان می دهد، ایجاد یک regex جدید فقط کمی سریعتر است. در زیر هود، فراخوانی متد استاتیک تلاش میکند تا Regex را به یک حافظه پنهان، که به نظر میرسد مجموعه ای استاتیک از عبارات منظم ایجاد شده است، دریافت کند یا به آن اضافه کند. بیشتر در مورد این به زودی
به نظر میرسد اکنون در این معیارها، سربار کامپایل regex ارزش انجام دادن دارد، حتی اگر هر بار که برای یافتن موارد منطبق میروید، این کار را انجام دهید. حدود 56٪ افزایش عملکرد! ما باید الگوی مورد استفاده من، محتویات داده هایی که در حال آزمایش هستند و طول کلی آن داده ها را نیز در نظر داشته باشیم.
صرفاً کش کردن regex در یک فیلد، مزایای عملکرد بسیار کم را نشان می دهد، اگر نه فقط یک خطای گرد کردن، برای این تنظیم. باز هم، این می تواند تا حد زیادی به دلیل اندازه داده ها و الگوی مورد استفاده باشد، و شاید مجموعه بسیار کوتاهی از داده ها برای اسکن، ذخیره سازی را به نسبت سودمندتر کند.
اکنون وارد قلمرو جادویی میشویم که در آن میتوانیم ببینیم که کش کردن یک regex کامپایل شده عملکردی را به ما میدهد که 310 درصد از خط پایه است. عدم نیاز به کامپایل مجدد در اینجا بسیار مفید است!
همانطور که پیشبینی شد، مانند مقاله اصلی، همه رجکسهای تولید شده با یکدیگر همتراز هستند و در این تنظیمات آزمایشی دقیقاً برابر با regex ذخیره شده کامپایلشده عمل میکنند.
اما صبر کن ذخیره وجود دارد!
من می خواستم قبل از انتشار این مقاله بعدی در مورد معیارهای بیان منظم سی شارپ، به دلیل اولین لغزشی که داشتم، حتی بیشتر مراقب باشم. به یاد آوردم که کلاس regex دارای a است RegexCache
درون کد دات نت که در حال اجراست – و از آنجایی که ما بارها و بارها از یک الگوی مشابه استفاده می کنیم… آیا چیزها را در پشت صحنه ذخیره می کنیم؟
به نظر می رسد، می توانیم اندازه کش را با انجام کارهای زیر به صفر تغییر دهیم:
Regex.CacheSize = 0;
اگر این خط کد را به ابتدای اجرای محک اضافه کنم، می توانم مطمئن باشم که کش خاموش است. اما برای اطمینان، این ساعت دیباگ را پس از ایجاد یک regex فقط برای بررسی عقلانی در ویژوال استودیو اضافه کردم:
typeof(RegexCache)
.GetField("s_cacheList", BindingFlags.Static | BindingFlags.NonPublic)
.GetValue(null)
نتیجه این کار دیدن یک کش خالی بود، که دقیقاً همان چیزی است که من به آن امیدوار بودم. یک نکته سریع این است RegexCache
داخلی است، و من مطمئن نبودم که بهترین راه برای دریافت بازتاب برای کار در کد برای دسترسی به آن (یعنی مطمئن نیستم که کدام اسمبلی را ببینم)، اما این به درستی در لیست تماشای اشکال زدایی حل می شود! اما اکنون احتمالاً به نتایج علاقه مند شده اید:
جالب اینجاست که واقعاً هیچ تفاوتی وجود ندارد. من گمان میکنم که این تا حد زیادی به این دلیل است که متنی که ما در حال بررسی آن هستیم به اندازهای بزرگ است که زمان لازم برای ارزیابی regex مطابقت دارد، بیشتر زمان لازم برای ایجاد اشیاء regex را کوچکتر میکند. این دقیقا همان چیزی است که بود نه در مقاله اصلی نشان داده شده است که در آن زمان ارزیابی اساساً صفر بود زیرا ما هرگز مسابقات را ارزیابی نکردیم.
بسته بندی معیارهای بیان منظم CSharp
خوب، حالا وقت آن است که چند درس یاد بگیریم…
اشکالی ندارد. ما همه انسان هستیم
اشتباهات یک راه عالی برای به دست گرفتن مالکیت و نشان دادن به دیگران است که می توانیم از خرابکاری رشد کنیم!
شکست خوردن یکی از بهترین راه های یادگیری است. من مطلقاً هیچ ایده ای نداشتم
MatchCollection
پس از احتمالاً بیش از 10 سال استفاده از Regex در سی شارپ تنبل بود.
در پایان روز، این معیارهای عبارات منظم سی شارپ جدید هنوز برای درک اینکه کامپایل regex شما چه تاثیری می تواند داشته باشد بسیار مفید هستند! در حالت ایده آل، شما نمی خواهید نمونه های regex جدید را بازسازی کنید، به خصوص با پرچم کامپایل… اما عملکرد regex های کامپایل شده عالی است.
اگر این را مفید دیدید و به دنبال فرصتهای یادگیری بیشتر هستید، مشترک خبرنامه هفتگی رایگان مهندسی نرمافزار من شوید و ویدیوهای رایگان من را در YouTube بررسی کنید! با دیگر مهندسان نرم افزار همفکر خود آشنا شوید و به انجمن Discord من بپیوندید!
محتوای Dev Leader بیشتری می خواهید؟
- اگر قبلاً این کار را نکرده اید، در این پلتفرم دنبال کنید!
- در خبرنامه رایگان هفتگی مهندسی نرم افزار و متمرکز بر دات نت من مشترک شوید. من شامل مقالات انحصاری و دسترسی زودهنگام به ویدیوها هستم:
به صورت رایگان مشترک شوید - به دنبال دوره های آموزشی هستید؟ پیشنهادات من را بررسی کنید:
مشاهده دوره ها - کتابهای الکترونیکی و منابع دیگر:
مشاهده منابع - صدها ویدیوی کامل را در کانال یوتیوب من تماشا کنید:
از کانال دیدن کنید - برای صدها مقاله در مورد موضوعات مختلف مهندسی نرم افزار (از جمله قطعه کد) از وب سایت من دیدن کنید:
از وب سایت بازدید کنید - مخزن را با نمونه کدهای بسیاری از مقالات و ویدیوهای من در GitHub بررسی کنید:
مشاهده مخزن