
1. فحص الأداء باستخدام BenchmarkDotNet في دوت نت
عند فحص أداء التعليمات البرمجية بشكل صحيح، يساعد ذلك في اتخاذ قرارات أفضل بشأن كيفية كتابة البرنامج، وما إذا كان يعمل بسرعة أم توجد طرق أفضل لكتابة التعليمات البرمجية. وفي حال وجود نسختين من نفس التعليمات البرمجية، يمكن تحديد أي منهما أسرع وأيهما يستهلك موارد أقل.
2. ما أهمية فحص الأداء في مشروعك؟
-
- الاكتشاف المبكر للمشاكل: عند فحص الأداء بشكل دوري، يمكن تحديد المشاكل التي قد تبطئ البرنامج قبل وصولها إلى المستخدمين، مما يوفر الوقت والجهد في إصلاحها لاحقًا.
-
- تحسين تجربة المستخدم: البرنامج السريع يعني مستخدمًا راضيًا. يساعد فحص الأداء في التأكد من أن التطبيق يعمل بسلاسة ولا يتسبب في أي تأخير.
-
- توفير التكاليف: إذا كان البرنامج يستهلك الكثير من الموارد، فقد تحتاج إلى خوادم أقوى أو دفع المزيد مقابل خدمات الحوسبة السحابية. يساعد فحص الأداء في تحسين استخدام الموارد وتوفير التكاليف.
-
- مقارنة الحلول: عند وجود أكثر من طريقة لتنفيذ نفس المهمة في التعليمات البرمجية، يساعد فحص الأداء في تحديد أي طريقة أسرع وأكثر كفاءة.
3. لماذا نستخدم BenchmarkDotNet؟
- تركيبه واستخدامه سهل للغاية.
- يوفر تحليلًا إحصائيًا للتأكد من أن النتائج موثوقة وليست عشوائية.
- يقدم إحصائيات حول كيفية توزيع الذاكرة وعمل مُجمِّع البيانات المهملة (Garbage Collector).
- يُنشئ تقارير سهلة القراءة وبأكثر من تنسيق.
- يدعم إصدارات مختلفة من دوت نت كور.
4. التحليل الإحصائي (Statistical Analysis)
يساعد هذا النوع من التحليل في تحديد مدى ثبات ودقة نتائج الاختبارات.
📌 قد توجد بعض الاختلافات الطبيعية في الأداء بسبب عوامل مثل بيئة التشغيل أو الأجهزة المستخدمة.
-
المتوسط (Mean): يعني متوسط الوقت الذي استغرقته العمليات المختلفة.
- يعطي فكرة عن الأداء بشكل عام.
المتوسط = (مجموع قيم كل اختبار) / (عدد الاختبارات)
-
الانحراف المعياري (Standard Deviation): مقياس يوضح مدى تباعد النتائج عن المتوسط.
- يساعد في فهم مدى تغير النتائج.
-
الحد الأدنى (Min): أقل وقت تم قياسه في الاختبارات.
-
الحد الأقصى (Max): أعلى وقت تم قياسه في الاختبارات.
-
الوسيط (Median): هو القيمة التي تتوسط نتائج الاختبارات بعد ترتيبها من الأدنى إلى الأعلى. قد يكون أدق من المتوسط عند وجود قيم متطرفة.
أهمية التحليل الإحصائي في فحص الأداء:
-
تحليل ثبات الأداء: يوفر التحليل الإحصائي فكرة عن مدى ثبات النتائج بين مجموعة الاختبارات. يمكن أن يكشف ما إذا كان هناك تباين في الأداء بسبب عوامل مختلفة مثل ضغط النظام أو طريقة استخدام الذاكرة.
-
كشف القيم المتطرفة (Outliers): قد تظهر قيم غير طبيعية بسبب حالات معينة مثل مشاكل في الأجهزة أو أخطاء في التعليمات البرمجية. يساعد التحليل الإحصائي في العثور على هذه القيم وفصلها عن النتائج الطبيعية.
-
اتخاذ قرارات مستنيرة: عند فهم نتائج الاختبارات بشكل إحصائي، يمكن اتخاذ قرارات أفضل بشأن تحسين التعليمات البرمجية أو إجراء التغييرات اللازمة لتحسين الأداء. على سبيل المثال، إذا كان هناك تباين كبير في النتائج، يمكن البحث عن السبب ومعرفة ما إذا كان يجب تحسين الخوارزمية أو النظام.
-
مقارنة بين الحلول المختلفة: عند مقارنة أكثر من طريقة لحل نفس المشكلة، يوفر التحليل الإحصائي المعلومات اللازمة لتحديد أي طريقة أكثر كفاءة. يساعد ذلك في اتخاذ قرارات بناءً على أرقام وليس مجرد تخمينات.
5. المقارنة بين الطرق (Comparative Analysis)
يعتمد هذا النوع من التحليل على المقارنة بين الطرق المختلفة. توفر المقارنات نظرة واضحة حول أي طريقة أداءها أفضل من ناحية سرعة التنفيذ، استخدام الذاكرة، أو أي معايير أخرى مهمة. على سبيل المثال: يمكن مقارنة بين خوارزميتين لحل نفس المشكلة.
6. الوقت المستغرق (Execution Time)
هو الوقت الذي يحتاجه النظام لإنهاء تنفيذ عملية أو مجموعة عمليات.
معايير قياس الأداء:
-
ns (نانوسكند): النانوسكند هو جزء صغير جدًا من الثانية. الواحد نانوسكند يساوي 1 على مليار من الثانية (أي 10^-9 من الثانية).
- 100 نانوسكند = 0.0000001 ثانية
- يُستخدم للعمليات التي تحدث داخل المعالجات أو العمليات التي تحدث على مستوى الأجهزة.
-
ms (ميلي سكند): الميلي ثانية هي 1 على ألف من الثانية (أي 10^-3 من الثانية).
- 50 ميلي سكند = 0.05 ثانية
- يُستخدم لقياس استجابة الشبكة أو أداء تطبيقات الويب والأنظمة التي تعمل في الوقت الفعلي (Real Time Systems).
7. كيف نستخدم BenchmarkDotNet؟
الخطوة الأولى: تجهيز المشروع
أنشئ مشروعًا جديدًا:
dotnet new console -n StringConcatenationBenchmarkDotNet
cd StringConcatenationBenchmarkDotNet
أضف حزمة BenchmarkDotNet:
dotnet add package BenchmarkDotNet
الخطوة الثانية: أول اختبار أداء
لنجرب دمج اسم أول مع اسم العائلة بطرق مختلفة:
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Text;
namespace StringConcatenationBenchmarkDotNet
{
public class StringConcatBenchmarks
{
// هذه هي بيانات الاختبار
private readonly string firstName = "Saif";
private readonly string lastName = "Saidi";
// استخدام عامل الجمع "+" لدمج النصوص
[Benchmark]
public string UsingPlusOperator_Benchmark()
{
return "Hello, " + firstName + " " + lastName + "!";
}
// استخدام String Interpolation لدمج النصوص
[Benchmark]
public string UsingStringInterpolation_Benchmark()
{
return $"Hello, {firstName} {lastName}!";
}
// استخدام String.Concat لدمج النصوص
[Benchmark]
public string UsingStringConcat_Benchmark()
{
return string.Concat("Hello, ", firstName, " ", lastName, "!");
}
// استخدام StringBuilder لدمج النصوص
[Benchmark]
public string UsingStringBuilder_Benchmark()
{
var sb = new StringBuilder();
sb.Append("Hello, ");
sb.Append(firstName);
sb.Append(" ");
sb.Append(lastName);
sb.Append("!");
return sb.ToString();
}
// استخدام String.Format لدمج النصوص
[Benchmark]
public string UsingStringFormat_Benchmark()
{
return string.Format("Hello, {0} {1}!", firstName, lastName);
}
}
}
الخطوة الثالثة: تشغيل الاختبار باستخدام BenchmarkRunner
📌 ملاحظة: قم دائمًا بتشغيل اختبارات الأداء في وضع "Release" للحصول على نتائج دقيقة.
public static void Main(stringargs)
{
BenchmarkRunner.Run<StringConcatBenchmarks>();
}
dotnet run -c Release
# أو قم بتفعيل خيار Release في Visual Studio وشغل المشروع
الخطوة الرابعة: فهم النتائج والتقارير
BenchmarkDotNet v0.13.12, OS Windows 10.0.19045.3930 (22H2/November2022Update)
Intel Core i7-8665U CPU 1.90GHz (Coffee Lake), 1 CPU, 8 logical and 4 physical cores
.NET SDK 9.0.104
| Method | Mean | Error | StdDev | Median |
|--------------------------------------|----------:|----------:|----------:|----------:|
| UsingPlusOperator_Benchmark | 42.79 ns | 1.140 ns | 3.324 ns | 41.98 ns |
| UsingStringInterpolation_Benchmark | 39.73 ns | 0.996 ns | 2.937 ns | 39.19 ns |
| UsingStringConcat_Benchmark | 32.25 ns | 0.206 ns | 0.182 ns | 32.19 ns |
| UsingStringBuilder_Benchmark | 65.86 ns | 2.461 ns | 7.022 ns | 63.93 ns |
| UsingStringFormat_Benchmark | 66.32 ns | 1.417 ns | 3.807 ns | 64.61 ns |
المقاييس الرئيسية التي تشرحها النتائج:
- Method (الدالة): يوضح هذا العمود اسم الطريقة التي تم فحص أدائها.
- Mean (المتوسط): هو المعدل الحسابي للوقت الذي استغرقته العملية للتنفيذ في الاختبار، ويُقاس بوحدة الزمن المحددة (هنا النانوثانية ns).
- Error (الخطأ): هو الخطأ الإحصائي (أو هامش الخطأ) الذي يعكس التباين في النتائج. يوضح مدى إمكانية اختلاف النتائج من تجربة لأخرى.
- ملاحظة: كلما كان الخطأ أقل، كانت النتائج أكثر ثباتًا ودقة.
- StdDev (الانحراف المعياري): مقدار التباين بين النتائج: تشير القيم الأقل للانحراف المعياري إلى أن الوقت كان ثابتًا في جميع الاختبارات.
- Median (الوسيط): هي القيمة التي تتوسط نتائج الاختبارات بعد ترتيبها من الأدنى إلى الأعلى.
شرح عام للنتائج:
- الطريقة الأفضل من ناحية الأداء هي
UsingStringConcat_Benchmark
، لأنها استغرقت أقل وقت (32.25 نانوثانية) وكان لديها أقل انحراف معياري (0.182 نانوثانية)، مما يعني أنها الأكثر ثباتًا أيضًا. - الطريقتان الأقل أداءً هما
UsingStringBuilder_Benchmark
وUsingStringFormat_Benchmark
، حيث استغرقتا حوالي 65.86 و 66.32 نانوثانية على التوالي، وهما من أكثر الطرق استغراقًا للوقت مقارنة بالطرق الأخرى.
8. سمات BenchmarkDotNet (BenchmarkDotNet.Attributes)
-
[Benchmark]
: تُستخدم لتحديد الدوال التي سيتم فحص أدائها.
-
[MemoryDiagnoser]
: عند إضافة هذه السمة، يبدأ BenchmarkDotNet في تتبع كيفية توزيع الذاكرة خلال الاختبارات وعرض تقرير عن استخدام الذاكرة.
-
[Params]
: تُستخدم لتحديد قيم مختلفة للمدخلات، لفحص نفس الدالة أكثر من مرة بمدخلات مختلفة.
public class MyBenchmark
{
[Params(10, 100, 1000)] // قيم مختلفة للتجربة
public int Size;
[Benchmark]
public void MyTestMethod()
{
var list = new List<int>(Size);
// تنفيذ الاختبار مع حجم القائمة
}
}
-
[Setup]
,[GlobalSetup]
,[Cleanup]
:
- 4.1.
[Setup]
: يتم تنفيذه قبل كل اختبار، أي إذا كان هناك العديد من الاختبارات في الفئة، فسيتم تنفيذه عدة مرات. - 4.2.
[GlobalSetup]
: يتم تنفيذه مرة واحدة فقط قبل جميع الاختبارات في الفئة. يُستخدم بشكل أساسي عند الحاجة إلى إجراء إعداد عام لمرة واحدة فقط. - 4.3.
[Cleanup]
: يُستخدم لتنظيف البيانات أو تحرير الموارد بعد انتهاء جميع الاختبارات. يمكن استخدامه لإجراء إغلاق بعد تنفيذ الاختبارات.
public class MyBenchmark
{
[Setup]
public void Setup()
{
// تجهيز البيانات أو البيئة للاختبارات
}
[Benchmark]
public void MyTestMethod()
{
// الاختبار هنا
}
}
9. تقليل وقت التشغيل باستخدام ShortRunJob
ShortRunJob هو خيار سريع في BenchmarkDotNet لتقليل الوقت المستغرق في اختبارات الأداء، وهذا يسمح بالحصول على نتائج سريعة لتعليمات برمجية صغيرة أو سريعة التنفيذ، ولكن دقة الإحصائيات تكون أقل.
Job.Default
هو الإعداد الافتراضي في BenchmarkDotNet، حيث تُجرى الاختبارات بدقة مع عدد كبير من التكرارات والوقت اللازم للحصول على نتائج دقيقة إحصائيًا.
ShortRunJob
يعمل على تقليل مدة تنفيذ الاختبارات وعدد التكرارات، ويفضل استخدامه عند الحاجة إلى نتائج سريعة من اختبارات التعليمات البرمجية الصغيرة أو للتجارب السريعة.
[ShortRunJob] // اضف هذي الخاصية
public class ShortJobsBenchmarks
{
}
متى يجب تجنب استخدام ShortRunJob؟
- الاختبارات المعقدة: إذا كنت تختبر خوارزميات معقدة أو تعليمات برمجية تحتاج إلى وقت للاستقرار قبل قياس الأداء، فيجب تجنب استخدام ShortRunJob.
- الاختبارات التي تتطلب دقة عالية: إذا كان الاختبار بحاجة إلى دقة إحصائية عالية ونتائج ثابتة بمرور الوقت، فيجب استخدام الإعداد الافتراضي بدلًا من ShortRunJob.
10. تخصيص وإعداد BenchmarkDotNet يدويًا
في BenchmarkDotNet، نستخدم ManualConfig
لإجراء إعدادات مخصصة والتحكم الكامل في كيفية تنفيذ الاختبارات. عادةً، عند استخدام BenchmarkDotNet، تُنفَّذ الاختبارات باستخدام الإعدادات الافتراضية، ولكن ManualConfig
يسمح بتخصيص مجموعة من الخيارات مثل إعدادات الوظائف (jobs)، وإضافة سمات إضافية، وتخصيص إعدادات التقارير، وخيارات أخرى كثيرة.
المميزات
- تعيين Job مخصص (مثل ShortRunJob أو LongRunJob).
- تجميع التقارير وتصديرها.
- التحكم في إعدادات التسخين (warmup) والتكرار (iterations).
- إضافة سمات إضافية مثل Exporters و Analyzers.
كائن ManualConfig
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Running;
// ... باقي الكود
// إعداد التكوين اليدوي (ManualConfig)
var config = new ManualConfig()
.AddLogger(ConsoleLogger.Default); // إضافة مسجل لرؤية التقدم في وحدة التحكم
// تشغيل الاختبارات باستخدام التكوين المخصص
BenchmarkRunner.Run<StringConcatBenchmarks>(config);
إعداد التتبع Logger
AddLogger(ConsoleLogger.Default);
يمكنك استخدام أي مسجل لرؤية ما يحدث.
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Running;
// ... باقي الكود
var config = new ManualConfig()
.AddLogger(ConsoleLogger.Default);
// تشغيل الاختبارات باستخدام التكوين المخصص
BenchmarkRunner.Run<StringConcatBenchmarks>(config);
إعدادات التسخين والتكرار
Job.ShortRun
: يُستخدم ShortRunJob لتقليل وقت تنفيذ الاختبارات.Job.LongRun
: يُستخدم LongRun لزيادة وقت تنفيذ الاختبارات.AddJob(Job.Default.WithWarmupCount(5).WithIterationCount(10))
: يمكنك تحديد عدد مرات التسخين (Warm-up count) وعدد مرات التكرار (Iteration count) للاختبار.
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Jobs;
// ... باقي الكود
var config = new ManualConfig()
.AddLogger(ConsoleLogger.Default)
.AddJob(Job.ShortRun)
.AddJob(Job.Default.WithWarmupCount(5).WithIterationCount(10));
// تشغيل الاختبارات باستخدام التكوين المخصص
BenchmarkRunner.Run<StringConcatBenchmarks>(config);
إعدادات الأعمدة
يمكنك تخصيص الأعمدة المعروضة في جدول نتائج الاختبار باستخدام ManualConfig. على سبيل المثال، لإظهار أوقات التنفيذ الدنيا والقصوى، يمكنك استخدام الكود التالي:
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Columns; // تأكد من تضمين هذا النطاق للاسم
using BenchmarkDotNet.Jobs;
// ... باقي الكود
var config = new ManualConfig()
.AddLogger(ConsoleLogger.Default)
.AddJob(Job.ShortRun)
.AddJob(Job.Default.WithWarmupCount(5).WithIterationCount(10))
.AddColumn(StatisticColumn.Min) // لإظهار الحد الأدنى
.AddColumn(StatisticColumn.Max); // لإظهار الحد الأقصى
.AddColumnProvider(DefaultColumnProviders.Instance);// إضافة الأعمدة الافتراضية للتقارير
// تشغيل الاختبارات باستخدام التكوين المخصص
BenchmarkRunner.Run<StringConcatBenchmarks>(config);
11. نصائح لفحص الأداء بشكل فعال:
- فحص التعليمات البرمجية في وضع Release: قم دائمًا بتشغيل اختبارات الأداء في وضع "Release" للحصول على نتائج دقيقة، لأنه في هذا الوضع يكون الأداء مُحسَّنًا.
- استخدام بيانات واقعية: اختبر التعليمات البرمجية باستخدام بيانات حقيقية أو على الأقل بيانات مشابهة للاستخدام الفعلي.
- تشغيل الاختبارات عدة مرات: اختبر التعليمات البرمجية أكثر من مرة للحصول على نتائج ثابتة، لأنه قد يكون هناك تذبذب في الأداء في بعض الأحيان.
- مقارنة النتائج: استخدم المقارنات بين الطرق المختلفة لتحديد أي طريقة أداءها أفضل.