قل كلمة سيئة لـ LINQ

طوال حياتي القصيرة كمبرمج c # ، اعتقدت أن LINQ لا يتعلق في المقام الأول بأداء الشفرة ، ولكن حول أداء المبرمج ، ومدى سرعة كتابة الرمز ، وليس مدى سرعة تنفيذ المعالج لهذا الرمز. ويتم حل مشاكل الأداء لكل من الشفرة والمبرمج بعد تحديد الاختناقات. لذلك ، غالبًا ما أكتب var groupedByDate = lst.GroupBy(item => item.IssueTime.Date).Select(…).ToList()هذا ونفعله ليس بسبب الأذى أو النية الخبيثة ، ولكن من الأسهل تصحيح الشفرة. للحصول على القائمة ، ما عليك سوى وضع مؤشر الماوس فوق نص المتغير ويمكنك على الفور معرفة ما إذا كان هناك شيء هناك أم لا.


بداية


بعد قراءة المقالة "مقالة غير ناجحة حول تسريع التفكير " وتهدئة المشاعر حول "شخص ما على الإنترنت" ، تساءلت عما إذا كان من الممكن جعل رمز "LINQ to Objects" قريبًا من الأداء "اليدوي". لماذا LINQ بالضبط للكائنات؟ في عملي ، غالبًا ما أستخدم فقط LINQ إلى كائنات و LINQ إلى موفري الكيانات. أداء LINQ للكيانات ليس بالغ الأهمية بالنسبة لي. من الأهمية بمكان كيف سيكون استعلام SQL الذي تم إنشاؤه لخادم قاعدة البيانات الهدف ومدى سرعة تنفيذ الخادم له.


كأساس ، قررت استخدام مشروع مؤلف المقالة. على النقيض من الأمثلة من الإنترنت ، حيث تتم مقارنة الرمز بشكل أساسي مثل integerList.Where(item => item > 5).Sum()الرمز "اليدوي" الذي يحتوي foreach، ifوهكذا ، يبدو المثال من المقالة مثيرًا للاهتمام وحيويًا بالنسبة لي.


أول شيء فعلته هو استخدام دالة مقارنة سلسلة واحدة. في شفرة المصدر ، في الطرق التي تؤدي نفس الوظيفة ، ولكنها تقع في الزوايا المقابلة للحلقة ، يتم استخدام طرق مختلفة ، في حالة واحدة variable0.Equals(constant0, StringComparison.InvariantCultureIgnoreCase)وفي أخرى variable0.ToUpperInvariant() ==constant0.ToUpperInvariant(). إنه أمر مزعج بشكل خاص للثابت أن كل استدعاء أسلوب يتم تحويله إلى أحرف كبيرة. اخترت الخيار الثالث باستخدام في كلتا الحالتين variable0.ToUpperInvariant() ==constant0InUpperCaseInvariant.


ثم تم التخلص من جميع التعليمات البرمجية التي لم تكن مرتبطة مباشرة بمقارنة أداء LINQ والرمز اليدوي. كان أول ما تم الإمساك به هو الرمز الذي يقوم بإنشاء كائن الفئة وتهيئته Mock<HabraDbContext>. ما الفائدة من إنشاء اتصال قاعدة بيانات لكل اختبار مرة واحدة يكفي؟ تم تجاوزه اختبارات الأداء.


IStorage _db;

[GlobalSetup]
public void Setup()
{
    _db = MockHelper.InstanceDb();
}

private IStorage DBInstance => _db;

… — , ! Linq «» !


صورة


« - » . LINQ . , — .



, , LINQ vs «» . . . .


, , , ([Params(1, 10, 100, 1000)]). . . . StatisticColumnRelStdDev.


FastHydrationLinq, ManualHydrationLinq — . , , (Fast)(Manual)(Slow)HydrationLinq vs (Fast)(Manual)(Slow)Hydration, ManualHydrationLinq. FastHydrationLinq - . .


ToArray, ToDictionary IEnumerable<T> . , FastContactHydrator2. - Action<Contact, string> c ConcurrentDictionary<string, Action<Contact, string>> IEnumerable<KeyValuePair<string, Action<Contact, string>>>. GetContact2, Sum, .


protected override Contact GetContact2(IEnumerable<PropertyToValueCorrelation> correlations)
{
    var contact = new Contact();
    int dummySum = _propertySettersArray.Join(correlations, propItem => propItem.Key, corrItem => corrItem.PropertyName, (prop, corr) => { prop.Value(contact, corr.Value); return 1; }).Sum();
    return contact;
}

ParseWithLinq


public static IEnumerable<KeyValuePair<string, string>> ParseWithLinq2(string rawData, string keyValueDelimiter = ":", string pairDelimiter = ";")
    => rawData.Split(pairDelimiter)
    .Select(x => x.Split(keyValueDelimiter, StringSplitOptions.RemoveEmptyEntries))
    .Select(x => x.Length == 2 ? new KeyValuePair<string, string>(x[0].Trim(), x[1].Trim())
                                : new KeyValuePair<string, string>(_unrecognizedKey, x[0].Trim()));

.


FastContactHydrator2, , , , -, ( ParseWithLinq ParseWithoutLinq). . , , ToArray. 10 var result = new List<PropertyToValueCorrelation>(10). 10? 42 , 10 . Fair .


. . GetFakeData . , .
, , « » (RelStdDev). N=1000.


:


-  `ManualHydration`, `SlowHydration`, `FastHydration`, `ManualHydrationLinq`, `SlowHydrationLinq`, `FastHydrationLinq` -     ,  ;

  • ManualHydrationFair, ManualHydrationFairLinq, FastHydrationFairLinq — , ;
  • FastHydrationLinq2 — , , - , LINQ;
  • N , ;
  • Ratio FastHydrationLinq2.

صورة
Linq-


? — FastHydrationLinq2 ManualHydrationLinq Ratio. 26,361.37 μs 26,422.17 μs N=1000. / N. ManualHydrationFairLinq, 8% , . FastHydrationFairLinq, 1% -.


Fair-. , ManualHydrationFairLinq 8% . FastHydrationFairLinq FastHydrationLinq 12%. Linq .


, . . , MockHelper.InstanceDb() Setup, . — , GetFakeData, . , — . MockHelper.InstanceDb() .


N, MakeGarbage. False , True — .


صورة


N

image


Voilà, , . — N=1. 10% 82% .


… .



, , «» — Linq-, :


صورة
N=1 MakeGarbage=True «»


, , , LINQ .
«Gen X» «Allocated» N=1 MakeGarbage=True o , FastHydrationLinq2


|                                |      Gen 0 |   Gen 1 | Gen 2 |   Allocated |
|«»     |    20.5078 |  0.4883 |     - |    63.95 KB |
|Linq-        |    20.7520 |       - |     - |    63.69 KB |

ManualHydrationFairLinq .


. , , — .
, ? . , N [Params(1, 100)] MakeGargabe=True. «» — Linq-, . — , — Linq-, Linq — — — Linq. — . .


, , — Linq- . , . paint.net, . paint.net — paint.net . paint.net — «» , — Linq- .


N 1, 10, 100, 1000 MakeGarbage [ParamsAllValues]. paint.net, — «» . — Linq- . paint.net Visual Studio — «» . , 80- . , Linq- 2%.



بعد كتابة الاختبارات وتحليل النتائج ، لم يتغير رأيي حول LINQ - فاستخدامه في إنتاجي في كتابة التعليمات البرمجية أعلى منه بدونه ؛ LINQ لأداء الكائنات على ما يرام. استخدام تأجيل تنفيذ رمز LINQ كوسيلة لتحسين الأداء لا معنى له.


إذا كانت هناك مشاكل في الأداء في أحد المشاريع بسبب جامع القمامة - إصابة ولادة .net ، فربما كان اختيار هذه التقنية ليس الحل الأفضل.


كود الاختبار الخاص بي متاح هنا .


All Articles