في هذه المقالة ، وضعت معايير لمعظم مكالمات الحطابين الخاصة. لقد أجريت جميع التجارب على log4net و NLog ، على Intel Windows 10 x64 مع M.2 SSD.
يمكن الاطلاع على النتائج الأولية على جيثب . الكود في نفس المستودع (للتشغيل ، ستحتاج .Net 4.7.2 + Microsoft Visual Studio 2017+).
ماذا وكيف ولماذا - تحت خفض.
من أجل عدم القراءة لفترة طويلة ، جدول النتائج:
NoOpLogging
أولاً ، دعنا نقدر كم نقضي وقتًا في استدعاء طريقة للتسجيل ، الأمر الذي لن يؤدي في النهاية إلى شيء. في معظم الحالات (في تجربتي) ، يتم تعطيل تصحيح المطوّل على خوادم المعركة ، لكن لا أحد يزيل المكالمات.
أولاً ، النتيجة:
و الكود:
void Log4NetNoParams() => _log4Net.Debug("test"); void Log4NetSingleReferenceParam() => _log4Net.DebugFormat("test {0}", _stringArgument); void Log4NetSingleValueParam() => _log4Net.DebugFormat("test {0}", _intArgument); void Log4NetMultipleReferencesParam() => _log4Net.DebugFormat( "test {0} {1} {2} {3} {4} {5} {6} {7} {8}", _stringArgument, _stringArgument, _stringArgument, _stringArgument, _stringArgument, _stringArgument, _stringArgument, _stringArgument, _stringArgument); void Log4NetMultipleValuesParam() => _log4Net.DebugFormat( "test {0} {1} {2} {3} {4} {5} {6} {7} {8}", _intArgument, _intArgument, _intArgument, _intArgument, _intArgument, _intArgument, _intArgument, _intArgument, _intArgument); void NLogNetNoParams() => _nlog.Debug("test"); void NLogNetSingleReferenceParam() => _nlog.Debug("test {0}", _stringArgument); void NLogNetSingleValueParam() => _nlog.Debug("test {0}", _intArgument); void NLogNetMultipleReferencesParam() => _nlog.Debug( "test {0} {1} {2} {3} {4} {5} {6} {7} {8}", _stringArgument, _stringArgument, _stringArgument, _stringArgument, _stringArgument, _stringArgument, _stringArgument, _stringArgument, _stringArgument); void NLogNetMultipleValuesParam() => _nlog.Debug( "test {0} {1} {2} {3} {4} {5} {6} {7} {8}", _intArgument, _intArgument, _intArgument, _intArgument, _intArgument, _intArgument, _intArgument, _intArgument, _intArgument);
أولاً ، دعونا نحدد سبب اختيار مثل هذه الاختبارات:
- تم إجراء التجارب على المكتبات الأكثر شعبية.
NLog و log4net لهما تواقيع دالة مختلفة لعدد صغير من الوسائط:
void DebugFormat(string format, object arg0)
void Debug(string message, string argument) void Debug<TArgument>(string message, TArgument argument)
- النظرية: عند نقل نوع مهم إلى log4net ، يجب أن تحدث الملاكمة ، والتي تقضي ببساطة وقت المعالج ولا تؤدي إلى أي شيء. في حالة NLog ، لا يوجد مثل هذا السلوك ، لذلك يجب أن يعمل الأخير بشكل أسرع.
- تواقيع عدد كبير من الوسائط في المكتبات هي نفسها تقريبًا ، لذلك أود أن أعرف:
- كم أكثر كفاءة لاستدعاء الأساليب مع عدد صغير من المعلمات.
- هل هناك اختلاف في سرعة استدعاء الأسلوب "Is ... ممكّن" بين المكتبتين
والآن تحليل النتائج:
- نظرًا لاستخدام الوسائط العامة في NLog ، فهو يعمل بشكل أسرع للحالة عندما لا يكون التسجيل المباشر ضروريًا. وهذا هو الحال في حالة تمكين المستوى في برنامج Debug فقط على نظام الاختبار ، يمكن فقط لتغيير المكتبة تسريع البرنامج (وتحسين حياة المستخدمين).
- إذا تم إيقاف تشغيل التسجيل وتريد استدعاء طريقة تحتوي على عدد كبير من الوسائط ، فمن الأفضل تقسيمها إلى قسمين. نتيجة لهذا ، ستعمل المكالمات المذكورة أعلاه بشكل أسرع عشر مرات.
- عندما تكتب وظيفة يمكن أن تأخذ أي شيء ، فغالبًا ما يكون الشعور بالارتباك وإحداث وظيفة عامة أكثر فاعلية. نظرًا لمثل هذا التحسين البسيط ، سيعمل الرمز بشكل أسرع (يظهر هذا بوضوح في الفارق الزمني بين المكالمات إلى
Log4NetSingleReferenceParam
و Log4NetSingleValueParam
)
FileLogging
لا تزال معظم البرامج (وفقًا لملاحظاتي) تسجل النتائج في ملف ، لذلك نختار هذه العملية للمقارنة. للبساطة ، نحن فقط نأخذ التكوين من قطع الاشجار عندما يتم كتابة ملف إلى الملف دون التخزين المؤقت ، دون أقفال إضافية ، الخ
النتائج:
كود مستخدم:
var roller = new RollingFileAppender(); roller.ImmediateFlush = true; roller.RollingStyle = RollingFileAppender.RollingMode.Once; roller.MaxFileSize = 128 * 1000 * 1000;
new FileTarget($"target_{_logIndex++}") { ArchiveAboveSize = 128 * 1000 * 1000, MaxArchiveFiles = 16, AutoFlush = true, ConcurrentWrites = false, KeepFileOpen = false };
كما ترى ، فإن تكوين أدوات قطع الأشجار يشبه إلى حد ما ، ووفقًا للنتائج:
- NLog أسرع قليلاً من log4net ، في مكان ما حوالي 15٪.
- وفقا للاختبارات ، اتضح أنه أكثر كفاءة لتسجيل عدد أقل من المعلمات. ومع ذلك ، لا ينبغي لأحد أن ينسى أنه مع عدد أكبر من المعلمات ، تم توسيع السلسلة الناتجة أيضًا. لذلك ، يقارن الجدول بشكل صحيح NLog مقابل log4net.
NLog - أنواع مختلفة من الأقفال
كود المصدر:
new FileTarget($"target_{_logIndex++}") { ArchiveAboveSize = 128 * 1000 * 1000, MaxArchiveFiles = 16, AutoFlush = true, ConcurrentWrites = XXXXX, KeepFileOpen = YYYYY };
إذا وضعنا جميع المجموعات الممكنة بدلاً من XXXXX و YYYYY ، فسنحصل على الاختبار من الجدول.
النتائج يمكن التنبؤ بها إلى حد ما:
- إذا قمت بتمكين ConcurrentWrites ، فسيقوم النظام باستمرار بإعطاء Mutex ، وهو غير مجاني. ولكن ، كما نرى ، فإن كتابة سطر واحد إلى ملف يكافئ تقريبًا قفل نظام واحد.
- إغلاق وفتح ملف ، كما نرى ، يؤثر على أداء النظام أكثر. في الأمثلة مع
KeepFileOpen=true
لكل عملية تسجيل ، أنشأنا ملفًا (جنبًا إلى جنب مع Handle) ، وكتبنا على القرص ، يسمى Flush ، و KeepFileOpen=true
، وقمنا أيضًا بالكثير من عمليات غطاء المحرك. نتيجة لذلك ، تنخفض السرعة مئات المرات.
تسجيل غير متزامن وأساليب قفل مختلفة
مكتبة NLog هي أيضا قادرة على أداء جميع عمليات الإدخال / الإخراج على مؤشر ترابط آخر ، على الفور تحرير واحد الحالي. وهو يفعل ذلك بكفاءة ، مع الحفاظ على ترتيب الأحداث ، وإسقاط جميع البيانات في الكتل ، وفي كل كتلة عدد صحيح هو رقم الحدث (بحيث لا يتم الحصول على خطوط اقتصاص) ، وهلم جرا.
نتائج الطرق غير المحظورة المختلفة:
سوف تكون المقارنة بين الحجب والنهج غير المتزامنة أكثر ، ولكن هنا - فقط الأخير.
رمز AsyncTargetWrapper
:
new AsyncTargetWrapper(fileTargetWithConcurrentWritesAndCloseFileAsync) { OverflowAction = AsyncTargetWrapperOverflowAction.Block, QueueLimit = 10000 }
كما ترون ، فإن إعدادات المجمّع لا تستغرق وقتًا طويلاً حتى تفريغ مباشر للملف. وبالتالي ، يتم تخزين مخزن مؤقت كبير ، مما يعني أن جميع العمليات كثيفة الاستخدام للموارد مثل "ملف مفتوح" يتم تنفيذها مرة واحدة للكتلة بأكملها. ومع ذلك ، فإن مثل هذه الخوارزمية تتطلب ذاكرة إضافية (والكثير).
الاستنتاجات:
- إذا تم استخدام الإخراج غير المتزامن ، فلا يهم نوع إعدادات الإخراج التي يتم استخدامها مع الملف. يمكنك فتح وإغلاق الملف في كل مرة ، مع وجود مخزن مؤقت كبير سيكون غير محسوس تقريبًا.
- جميع القياسات صحيحة فقط للحالة عند مسح البيانات على القرص بنفس سرعة ملء المخازن المؤقتة (قمت بهذا بسبب نظام الملفات السريع + الإيقاف المؤقت الطبيعي بين القياسات).
تسجيل متزامن وغير متزامن
الاستنتاجات:
- على الرغم من القرص السريع (في حالتي - M.2 SSD) ، تؤدي الكتابة إلى ملف في دفق آخر إلى تسريع العمل عدة مرات. إذا كان التطبيق الخاص بك يكتب على الأقراص الصلبة ، وحتى يعمل على جهاز افتراضي ، فإن المكسب سيكون أكبر.
- ومع ذلك ، على الرغم من التشغيل السريع للرمز غير المتزامن ، فإن الافتقار إلى قطع الأشجار يعطي ربحًا أكبر (وإن كان مختلفًا قليلاً ، حسب المكتبة).
إنشاء قطع الاشجار
النتائج:
ما تم اختباره:
[Benchmark] public object CreateLog4NetFromString() { return LogManager.GetLogger("my-logger_" + (Interlocked.Increment(ref _log4NetStringLogIndex) % 1000)); } [Benchmark] public object CreateNLogFromString() { return NLog.LogManager.GetLogger("my-logger_" + (Interlocked.Increment(ref _nLogStringLogIndex) % 1000)); } [Benchmark] public object CreateLog4NetLogger() { return new [] { LogManager.GetLogger(typeof(BaseTest)), // x16 times }; } [Benchmark] public object CreateNLogTypeOfLogger() { return new[] { NLog.LogManager.GetCurrentClassLogger(typeof(BaseTest)), // x16 times }; } [Benchmark] public object CreateNLogDynamicLogger() { return new[] { NLog.LogManager.GetCurrentClassLogger(), // x16 times }; }
ملاحظة مهمة: لسوء الحظ ، كان من الصعب بالنسبة لي وضع معيار استنساخي لم يؤد إلى "نفاد الذاكرة" ، ولكنه من شأنه أن ينشئ أجهزة تسجيل مختلفة (أي لأنواع مختلفة ولخطوط مختلفة وما إلى ذلك).
ومع ذلك ، بعد دراسة عمل المكتبات ، وجدت أنه يتم تنفيذ معظم العمليات الصعبة تقريبًا لإنشاء مفتاح تسجيل (على سبيل المثال ، تحديد اسم ، ومسح الوسائط العامة ، وما إلى ذلك).
علاوة على ذلك ، من أجل تثبيت معيار إنشاء مسجل لـ log4net ، كان من الضروري إجراء عملية واحدة ، ولكن ليس هناك 16 عملية (أي يتم إرجاع مجموعة مكونة من 16 كائنًا متطابقًا). إذا لم تقم بإرجاع أي شيء ، فقم بتحسين التنفيذ بالنسبة لي (على ما يبدو ، عدم إرجاع النتيجة) ، مما أدى إلى نتائج غير صحيحة.
والاستنتاجات:
- يتم إنشاء قطع الأشجار بسرعة أكبر من الأوتار (NLog أسرع مرة أخرى ، ومع ذلك ، فإن الفرق بين المكتبات صغير ، مع الأخذ في الاعتبار أن قطع الأشجار يتم إنشاؤه ليس هكذا فحسب ، ولكن للعمل اللاحق معهم).
- log4net أسرع من NLog عند تهيئة المشروع. ربما يكون هذا بسبب التخزين المؤقت الإضافي على جانب NLog ، مما يساعد على تسريع المكالمات المباشرة إلى
Debug
أو Info
، إلخ. في الواقع ، يعرف كل ILogger
الإجابة على نفسه: ما إذا كان يجب استدعاء الطرق التالية أم لا (وهذا يتطلب نوعًا من الربط على الأقل للتكوين العام). بسبب نظام العمل هذا ، تم استخدام Out Of Memory بواسطتي في معظم الاختبارات (إذا كنت تستخدم خطوطًا مختلفة ، إلخ). LogManager.GetCurrentClassLogger()
أبطأ من LogManager.GetLogget(typeof(XXX))
. هذا منطقي ، حتى مطوري NLog لا ينصحون باستدعاء الطريقة الأولى في حلقة.- والأهم من ذلك: غالبًا ما تؤثر سرعة كل هذه الطرق على البداية الباردة للتطبيق عند
private static readonly ILogger Log = LogManager.GetCurrentClassLogger()
حقول النموذج private static readonly ILogger Log = LogManager.GetCurrentClassLogger()
. وهذا يعني أنه لا يؤثر بشكل مباشر على أداء النظام.
استنتاج
ما هي أفضل طريقة للتعامل مع السجلات:
- إذا كان من الممكن عدم تسجيل الدخول على الإطلاق ، فسيكون هذا هو الأسرع (وهو أمر واضح حتى الآن).
- إذا كان للمشروع العديد من مكالمات المسجل التي لا تفريغ البيانات إلى ملف (إلى وحدة التحكم ، وما إلى ذلك) ، فإن NLog يكون أسرع. بالإضافة إلى ذلك ، فإنه يخصص كائنات أقل على كومة الذاكرة المؤقتة.
- إذا كنت لا تزال بحاجة إلى الكتابة إلى ملف ، فإن NLog يعمل بشكل أسرع بشكل غير متزامن. نعم ، إنها تستهلك ذاكرة أكبر (مقارنة بـ NLog في الوضع المتزامن ، حيث أنه وفقًا لقياساتي السابقة ، لا تحاول log4net حتى إعادة استخدام المصفوفات و
Stream
's). ومع ذلك ، سيتمكن البرنامج من العمل بشكل أسرع. - إنشاء مسجل ليست عملية مجانية ، لذلك من الأفضل في كثير من الأحيان إنشاء ذلك باستخدام حقل ثابت. لا ينطبق هذا على الإنشاء من سلسلة ، أي شيء مثل
LogManager.GetLogger("123")
. تعمل مثل هذه الاستدعاءات بشكل أسرع ، مما يعني أنه يمكن إنشاء مسجل لحالات كبيرة من الكائنات (على سبيل المثال ، "مسجل واحد لسياق الاستعلام"). - إذا كنت ترغب في إخراج الكثير من المعلمات إلى السجل ، ولكن في معظم الحالات لن يكون هناك تفريغ بيانات مباشر إلى الملف ، فمن الأفضل إجراء عدة مكالمات. لذلك ، لن تنشئ NLog كائنات إضافية على الكومة إذا لم تكن هناك حاجة إليها.
استنتاجات الكود:
- إذا قبلت طريقتك كائنًا اعتباطيًا (أي
object
) ولم تفعل شيئًا في معظم الحالات (وهذا صحيح بالنسبة للعقود / المصادقون) ، فمن الأكثر Something<TArg>(TArg arg)
التفاف المكالمات في نموذج عام (على سبيل المثال ، إجراء طرق من النموذج Something<TArg>(TArg arg)
). هذا سوف يعمل بشكل أسرع حقا. - إذا كانت إعادة تعيين بيانات الملف في الكود الخاص بك مسموحًا بها وفي الوقت نفسه تعمل مع شيء آخر ، فمن الأفضل أن تحصل على الخلط ودعم ذلك. نعم ، يبدو من الواضح أن التنفيذ الموازي يمكن أن يسرع العمل ، ومع ذلك ، في حالة عمليات الإدخال / الإخراج ، فإن هذا النهج يعطي أيضًا زيادة في الأداء على الأجهزة ذات الأقراص البطيئة.