تسجيل سريع

في هذه المقالة ، وضعت معايير لمعظم مكالمات الحطابين الخاصة. لقد أجريت جميع التجارب على log4net و NLog ، على Intel Windows 10 x64 مع M.2 SSD.


يمكن الاطلاع على النتائج الأولية على جيثب . الكود في نفس المستودع (للتشغيل ، ستحتاج .Net 4.7.2 + Microsoft Visual Studio 2017+).


ماذا وكيف ولماذا - تحت خفض.


من أجل عدم القراءة لفترة طويلة ، جدول النتائج:


طريقةمتوسطخطأStdDev
KeepFileOpen = true ، ConcurrentWrites = false ، Async = true1164.677 ن26.3805 ن77.7835 ن
KeepFileOpen = true ، ConcurrentWrites = true ، Async = true1106.691 ن31.4041 ن87.5421 ن
KeepFileOpen = false ، ConcurrentWrites = false ، Async = true484.426 ن110.3406 ns103.2126 ن
KeepFileOpen = false ، ConcurrentWrites = true ، Async = true5،303.602 ن104.3022 ن102.4387 ن
KeepFileOpen = true ، ConcurrentWrites = false ، Async = false5642.301 ن73.2291 ن68.4986 ن
KeepFileOpen = true ، ConcurrentWrites = true ، Async = false11،834.892 ن82.7578 ن77.4117 ن
KeepFileOpen = false ، ConcurrentWrites = false ، Async = false731250.539 ن14،612.0117 ن27444.8998 ن
KeepFileOpen = false ، ConcurrentWrites = true ، Async = false730271.927 ن1130.0172 ن10598.1051 ن
CreateLog4NetFromString1470.662 ن19.9492 ن18.6605 ن
CreateNLogFromString228.774 ن2.1315 ن1.8895 ن
CreateLog4NetLogger21،046.294 ن284.1171 ن265.7633 ن
CreateNLogTypeOfLogger164،487.931 ن3240.4372 ن3031.1070 ن
CreateNLogDynamicLogger134459.092 ن1،882.8663 ن1761.2344 ن
FileLoggingLog4NetNoParams8251.032 ن109.3075 ن102.2463 ن
FileLoggingLog4NetSingleReferenceParam8260.452 ن145.9028 ن136.4776 ن
FileLoggingLog4NetSingleValueParam8378.693 ن121.3003 ن113.4643 ن
FileLoggingLog4NetMultipleReferencesParam9،133.136 ن89.7420 ن79.5539 ن
FileLoggingLog4NetMultipleValuesParam9،393.989 ن166.0347 ن155.3089 ن
FileLoggingNLogNetNoParams6061.837 ن69.5666 ن65.0726 ن
FileLoggingNLogNetSingleReferenceParam6،458.201 ن94.5617 ن88.4530 ن
FileLoggingNLogNetSingleValueParam6460.859 ن95.5435 ن84.6969 ن
FileLoggingNLogNetMultipleReferencesParam7236.886 ن89.7334 ن83.9367 ن
FileLoggingNLogNetMultipleValuesParam7524.876 ن82.8979 ن77.5427 ن
NoOpLog4NetNoParams12.684 ن0.0795 ن0.0743 ن
NoOpLog4NetSingleReferenceParam10.506 ن0.0571 ن0.0506 ن
NoOpLog4NetSingleValueParam12.608 ن0.1012 ن0.0946 ن
NoOpLog4NetMultipleReferencesParam48.858 ن0.3988 ن0.3730 ن
NoOpLog4NetMultipleValuesParam69.463 ن0.9444 ن0.8834 ن
NoOpNLogNetNoParams2.073 ن0.0253 ن0.0225 ن
NoOpNLogNetSingleReferenceParam2.625 ن0.0364 ن0.0340 ن
NoOpNLogNetSingleValueParam2.281 ن0.0222 ن0.0208 ن
NoOpNLogNetMultipleReferencesParam41.525 ن0.4481 ن0.4191 ن
NoOpNLogNetMultipleValuesParam57.622 ن0.5341 ن0.4996 ن

NoOpLogging


أولاً ، دعنا نقدر كم نقضي وقتًا في استدعاء طريقة للتسجيل ، الأمر الذي لن يؤدي في النهاية إلى شيء. في معظم الحالات (في تجربتي) ، يتم تعطيل تصحيح المطوّل على خوادم المعركة ، لكن لا أحد يزيل المكالمات.


أولاً ، النتيجة:


طريقةمتوسطخطأStdDev
NoOpLog4NetNoParams12.684 ن0.0795 ن0.0743 ن
NoOpLog4NetSingleReferenceParam10.506 ن0.0571 ن0.0506 ن
NoOpLog4NetSingleValueParam12.608 ن0.1012 ن0.0946 ن
NoOpLog4NetMultipleReferencesParam48.858 ن0.3988 ن0.3730 ن
NoOpLog4NetMultipleValuesParam69.463 ن0.9444 ن0.8834 ن
NoOpNLogNetNoParams2.073 ن0.0253 ن0.0225 ن
NoOpNLogNetSingleReferenceParam2.625 ن0.0364 ن0.0340 ن
NoOpNLogNetSingleValueParam2.281 ن0.0222 ن0.0208 ن
NoOpNLogNetMultipleReferencesParam41.525 ن0.4481 ن0.4191 ن
NoOpNLogNetMultipleValuesParam57.622 ن0.5341 ن0.4996 ن

و الكود:


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 لهما تواقيع دالة مختلفة لعدد صغير من الوسائط:


    • log4net:

     void DebugFormat(string format, object arg0) 

    • NLog:

     void Debug(string message, string argument) void Debug<TArgument>(string message, TArgument argument) 

    • النظرية: عند نقل نوع مهم إلى log4net ، يجب أن تحدث الملاكمة ، والتي تقضي ببساطة وقت المعالج ولا تؤدي إلى أي شيء. في حالة NLog ، لا يوجد مثل هذا السلوك ، لذلك يجب أن يعمل الأخير بشكل أسرع.

  • تواقيع عدد كبير من الوسائط في المكتبات هي نفسها تقريبًا ، لذلك أود أن أعرف:
    • كم أكثر كفاءة لاستدعاء الأساليب مع عدد صغير من المعلمات.
    • هل هناك اختلاف في سرعة استدعاء الأسلوب "Is ... ممكّن" بين المكتبتين

والآن تحليل النتائج:


  • نظرًا لاستخدام الوسائط العامة في NLog ، فهو يعمل بشكل أسرع للحالة عندما لا يكون التسجيل المباشر ضروريًا. وهذا هو الحال في حالة تمكين المستوى في برنامج Debug فقط على نظام الاختبار ، يمكن فقط لتغيير المكتبة تسريع البرنامج (وتحسين حياة المستخدمين).
  • إذا تم إيقاف تشغيل التسجيل وتريد استدعاء طريقة تحتوي على عدد كبير من الوسائط ، فمن الأفضل تقسيمها إلى قسمين. نتيجة لهذا ، ستعمل المكالمات المذكورة أعلاه بشكل أسرع عشر مرات.
  • عندما تكتب وظيفة يمكن أن تأخذ أي شيء ، فغالبًا ما يكون الشعور بالارتباك وإحداث وظيفة عامة أكثر فاعلية. نظرًا لمثل هذا التحسين البسيط ، سيعمل الرمز بشكل أسرع (يظهر هذا بوضوح في الفارق الزمني بين المكالمات إلى Log4NetSingleReferenceParam و Log4NetSingleValueParam )

FileLogging


لا تزال معظم البرامج (وفقًا لملاحظاتي) تسجل النتائج في ملف ، لذلك نختار هذه العملية للمقارنة. للبساطة ، نحن فقط نأخذ التكوين من قطع الاشجار عندما يتم كتابة ملف إلى الملف دون التخزين المؤقت ، دون أقفال إضافية ، الخ


النتائج:


طريقةمتوسطخطأStdDev
FileLoggingLog4NetNoParams8251.032 ن109.3075 ن102.2463 ن
FileLoggingLog4NetSingleReferenceParam8260.452 ن145.9028 ن136.4776 ن
FileLoggingLog4NetSingleValueParam8378.693 ن121.3003 ن113.4643 ن
FileLoggingLog4NetMultipleReferencesParam9،133.136 ن89.7420 ن79.5539 ن
FileLoggingLog4NetMultipleValuesParam9،393.989 ن166.0347 ن155.3089 ن
FileLoggingNLogNetNoParams6061.837 ن69.5666 ن65.0726 ن
FileLoggingNLogNetSingleReferenceParam6،458.201 ن94.5617 ن88.4530 ن
FileLoggingNLogNetSingleValueParam6460.859 ن95.5435 ن84.6969 ن
FileLoggingNLogNetMultipleReferencesParam7236.886 ن89.7334 ن83.9367 ن
FileLoggingNLogNetMultipleValuesParam7524.876 ن82.8979 ن77.5427 ن

كود مستخدم:


  • log4net:

 var roller = new RollingFileAppender(); roller.ImmediateFlush = true; roller.RollingStyle = RollingFileAppender.RollingMode.Once; roller.MaxFileSize = 128 * 1000 * 1000; 

  • NLog:

 new FileTarget($"target_{_logIndex++}") { ArchiveAboveSize = 128 * 1000 * 1000, MaxArchiveFiles = 16, AutoFlush = true, ConcurrentWrites = false, KeepFileOpen = false }; 

كما ترى ، فإن تكوين أدوات قطع الأشجار يشبه إلى حد ما ، ووفقًا للنتائج:


  • NLog أسرع قليلاً من log4net ، في مكان ما حوالي 15٪.
  • وفقا للاختبارات ، اتضح أنه أكثر كفاءة لتسجيل عدد أقل من المعلمات. ومع ذلك ، لا ينبغي لأحد أن ينسى أنه مع عدد أكبر من المعلمات ، تم توسيع السلسلة الناتجة أيضًا. لذلك ، يقارن الجدول بشكل صحيح NLog مقابل log4net.

NLog - أنواع مختلفة من الأقفال


طريقةمتوسطخطأStdDev
KeepFileOpen = true ، ConcurrentWrites = false ، Async = false5642.301 ن73.2291 ن68.4986 ن
KeepFileOpen = true ، ConcurrentWrites = true ، Async = false11،834.892 ن82.7578 ن77.4117 ن
KeepFileOpen = false ، ConcurrentWrites = false ، Async = false731250.539 ن14،612.0117 ن27444.8998 ن
KeepFileOpen = false ، ConcurrentWrites = true ، Async = false730271.927 ن1130.0172 ن10598.1051 ن

كود المصدر:


 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 هي أيضا قادرة على أداء جميع عمليات الإدخال / الإخراج على مؤشر ترابط آخر ، على الفور تحرير واحد الحالي. وهو يفعل ذلك بكفاءة ، مع الحفاظ على ترتيب الأحداث ، وإسقاط جميع البيانات في الكتل ، وفي كل كتلة عدد صحيح هو رقم الحدث (بحيث لا يتم الحصول على خطوط اقتصاص) ، وهلم جرا.


نتائج الطرق غير المحظورة المختلفة:


طريقةمتوسطخطأStdDev
KeepFileOpen = true ، ConcurrentWrites = false ، Async = true1164.677 ن26.3805 ن77.7835 ن
KeepFileOpen = true ، ConcurrentWrites = true ، Async = true1106.691 ن31.4041 ن87.5421 ن
KeepFileOpen = false ، ConcurrentWrites = false ، Async = true484.426 ن110.3406 ns103.2126 ن
KeepFileOpen = false ، ConcurrentWrites = true ، Async = true5،303.602 ن104.3022 ن102.4387 ن

سوف تكون المقارنة بين الحجب والنهج غير المتزامنة أكثر ، ولكن هنا - فقط الأخير.


رمز AsyncTargetWrapper :


 new AsyncTargetWrapper(fileTargetWithConcurrentWritesAndCloseFileAsync) { OverflowAction = AsyncTargetWrapperOverflowAction.Block, QueueLimit = 10000 } 

كما ترون ، فإن إعدادات المجمّع لا تستغرق وقتًا طويلاً حتى تفريغ مباشر للملف. وبالتالي ، يتم تخزين مخزن مؤقت كبير ، مما يعني أن جميع العمليات كثيفة الاستخدام للموارد مثل "ملف مفتوح" يتم تنفيذها مرة واحدة للكتلة بأكملها. ومع ذلك ، فإن مثل هذه الخوارزمية تتطلب ذاكرة إضافية (والكثير).


الاستنتاجات:


  • إذا تم استخدام الإخراج غير المتزامن ، فلا يهم نوع إعدادات الإخراج التي يتم استخدامها مع الملف. يمكنك فتح وإغلاق الملف في كل مرة ، مع وجود مخزن مؤقت كبير سيكون غير محسوس تقريبًا.
  • جميع القياسات صحيحة فقط للحالة عند مسح البيانات على القرص بنفس سرعة ملء المخازن المؤقتة (قمت بهذا بسبب نظام الملفات السريع + الإيقاف المؤقت الطبيعي بين القياسات).

تسجيل متزامن وغير متزامن


النتائج:طريقةمتوسطخطأStdDevمتوسط
KeepFileOpen = true ، ConcurrentWrites = false ، Async = true1835.730 ن55.3980 ن163.3422 ن1791.901 ن
FileLoggingLog4NetNoParams7076.251 ن41.5518 ن38.8676 ن7075.394 ن
FileLoggingNLogNetNoParams5438.306 ن42.0170 ن37.2470 ن5427.805 ن
NoOpLog4NetNoParams11.063 ن0.0141 ن0.0125 ن11.065 ن
NoOpNLogNetNoParams1.045 ن0.0037 ن0.0033 ن1.045 ن

الاستنتاجات:


  • على الرغم من القرص السريع (في حالتي - M.2 SSD) ، تؤدي الكتابة إلى ملف في دفق آخر إلى تسريع العمل عدة مرات. إذا كان التطبيق الخاص بك يكتب على الأقراص الصلبة ، وحتى يعمل على جهاز افتراضي ، فإن المكسب سيكون أكبر.
  • ومع ذلك ، على الرغم من التشغيل السريع للرمز غير المتزامن ، فإن الافتقار إلى قطع الأشجار يعطي ربحًا أكبر (وإن كان مختلفًا قليلاً ، حسب المكتبة).

إنشاء قطع الاشجار


النتائج:


طريقةمتوسطخطأStdDev
CreateLog4NetFromString1470.662 ن19.9492 ن18.6605 ن
CreateNLogFromString228.774 ن2.1315 ن1.8895 ن
CreateLog4NetLogger21،046.294 ن284.1171 ن265.7633 ن
CreateNLogTypeOfLogger164،487.931 ن3240.4372 ن3031.1070 ن
CreateNLogDynamicLogger134459.092 ن1،882.8663 ن1761.2344 ن

ما تم اختباره:


 [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) ). هذا سوف يعمل بشكل أسرع حقا.
  • إذا كانت إعادة تعيين بيانات الملف في الكود الخاص بك مسموحًا بها وفي الوقت نفسه تعمل مع شيء آخر ، فمن الأفضل أن تحصل على الخلط ودعم ذلك. نعم ، يبدو من الواضح أن التنفيذ الموازي يمكن أن يسرع العمل ، ومع ذلك ، في حالة عمليات الإدخال / الإخراج ، فإن هذا النهج يعطي أيضًا زيادة في الأداء على الأجهزة ذات الأقراص البطيئة.

Source: https://habr.com/ru/post/ar456488/


All Articles