Ko'zguni tezlashtirish haqida muvaffaqiyatsiz maqola

Men darhol maqolaning nomini tushuntiraman. Asl reja oddiy, ammo real misol yordamida aks ettirishdan foydalanishni tezlashtirish bo'yicha yaxshi va ishonchli maslahat berish edi, ammo taqqoslash davomida aks ettirish men o'ylaganchalik sekin emasligi, LINQ mening dahshatli tushlarimga qaraganda sekinroq ekanligi ma'lum bo'ldi. Lekin oxir-oqibat o'lchovlarda men ham xatoga yo'l qo'yganim ma'lum bo'ldi... Bu hayotiy voqea tafsilotlari kesim ostida va izohlarda. Misol juda oddiy va printsipial ravishda korxonada amalga oshirilganligi sababli, menimcha, hayotning namoyishi juda qiziqarli bo'lib chiqdi: maqolaning asosiy mavzusi tezligiga ta'siri. tashqi mantiq tufayli sezilmaydi: Moq, Autofac, EF Core va boshqalar "bandajlar".

Men ushbu maqola taassurotlari ostida ishlay boshladim: Nega Reflektsiya sekin

Ko'rib turganingizdek, muallif dasturni tezlashtirishning ajoyib usuli sifatida to'g'ridan-to'g'ri aks ettirish usullarini chaqirish o'rniga kompilyatsiya qilingan delegatlardan foydalanishni taklif qiladi. Albatta, IL emissiyasi bor, lekin men undan qochishni xohlayman, chunki bu xatolarga to'la bo'lgan vazifani bajarishning eng ko'p mehnat talab qiladigan usuli.

Men har doim aks ettirish tezligi haqida bir xil fikrda bo'lganimni hisobga olib, men muallifning xulosalarini shubha ostiga qo'ymoqchi emas edim.

Men ko'pincha korxonada aks ettirishdan sodda foydalanishga duch kelaman. Turi olinadi. Mulk to'g'risidagi ma'lumotlar olinadi. SetValue usuli chaqiriladi va hamma quvonadi. Qiymat maqsadli maydonga yetdi, hamma xursand. Juda aqlli odamlar - qariyalar va jamoa rahbarlari - bir turdagi "universal" xaritachilarni boshqasiga shunday sodda amalga oshirishga asoslanib, ob'ektga o'zlarining kengaytmalarini yozadilar. Odatda, mohiyat quyidagicha: biz barcha maydonlarni olamiz, barcha xususiyatlarni olamiz, ularni takrorlaymiz: agar tip a'zolarining nomlari mos kelsa, biz SetValue ni bajaramiz. Vaqti-vaqti bilan biz xatolar tufayli istisnolarga duch kelamiz, bu erda biz turlardan birida biron bir xususiyatni topmadik, ammo bu erda ham ishlashni yaxshilaydigan chiqish yo'li mavjud. Sinab ko'ring/qo'lga oling.

Men odamlar o'zlaridan oldingi mashinalar qanday ishlashi haqida to'liq ma'lumotga ega bo'lmasdan, tahlil qilish va xaritalash vositalarini qayta ixtiro qilganini ko'rganman. Men odamlar o'zlarining sodda amalga oshirishlarini strategiyalar orqasida, interfeyslar orqasida, inyeksiyalar orqasida yashirishlarini ko'rganman, go'yo bu keyingi bacchanalia uchun bahona bo'ladi. Bunday tushunchalar bilan burnimni burdim. Darhaqiqat, men haqiqiy ishlashning oqishini o'lchamaganman va agar iloji bo'lsa, qo'limga tushsa, dasturni yanada "optimal" ga o'zgartirdim. Shuning uchun, quyida muhokama qilingan birinchi o'lchovlar meni jiddiy chalkashtirib yubordi.

O'ylaymanki, ko'plaringiz, Rixter yoki boshqa mafkurachilarni o'qiyotganingizda, kodda aks ettirish ilovaning ishlashiga juda salbiy ta'sir ko'rsatadigan hodisa ekanligi to'g'risida mutlaqo adolatli bayonotga duch keldingiz.

Chaqiruv aks ettirish CLRni keraklisini topish, metama'lumotlarini olish, ularni tahlil qilish va hokazolar uchun yig'ilishlardan o'tishga majbur qiladi. Bundan tashqari, ketma-ketliklarni kesib o'tishda aks ettirish katta hajmdagi xotirani ajratishga olib keladi. Biz xotiradan foydalanmoqdamiz, CLR GCni ochadi va frizlar boshlanadi. Bu sezilarli darajada sekin bo'lishi kerak, menga ishoning. Zamonaviy ishlab chiqarish serverlari yoki bulutli mashinalardagi katta hajmdagi xotira yuqori ishlov berish kechikishlariga to'sqinlik qilmaydi. Haqiqatan ham, xotira qancha ko'p bo'lsa, GC qanday ishlashiga E'tibor berishingiz ehtimoli shunchalik yuqori bo'ladi. Ko'zgu, nazariy jihatdan, uning uchun qo'shimcha qizil latta.

Biroq, biz hammamiz IoC konteynerlari va sana mapperlaridan foydalanamiz, ularning ishlash printsipi ham aks ettirishga asoslangan, lekin odatda ularning ishlashi haqida hech qanday savol yo'q. Yo'q, chunki tashqi cheklangan kontekstli modellardan qaramlik va abstraktsiyani joriy qilish shunchalik zarurki, biz har qanday holatda ham ishlashni qurbon qilishimiz kerak. Hammasi oddiyroq - bu, albatta, unumdorlikka unchalik ta'sir qilmaydi.

Haqiqat shundaki, aks ettirish texnologiyasiga asoslangan eng keng tarqalgan ramkalar u bilan yanada optimal ishlash uchun har xil fokuslardan foydalanadi. Odatda bu kesh. Odatda bu ifodalar daraxtidan tuzilgan ifodalar va delegatlardir. Xuddi shu avtomapper, aks ettirishni chaqirmasdan, bir-birini boshqasiga aylantira oladigan funktsiyalarga ega turlarga mos keladigan raqobatbardosh lug'atni saqlaydi.

Bunga qanday erishiladi? Aslida, bu JIT kodini yaratish uchun platformaning o'zi foydalanadigan mantiqdan farq qilmaydi. Usul birinchi marta chaqirilganda, u kompilyatsiya qilinadi (va, ha, bu jarayon tez emas); keyingi qo'ng'iroqlarda boshqaruv allaqachon tuzilgan usulga o'tkaziladi va unumdorlikning sezilarli pasayishi bo'lmaydi.

Bizning holatlarimizda siz JIT kompilyatsiyasidan ham foydalanishingiz mumkin va keyin tuzilgan xatti-harakatlardan AOT hamkasblari bilan bir xil ishlash bilan foydalanishingiz mumkin. Bu holatda bizga iboralar yordamga keladi.

Ko'rib chiqilayotgan printsipni qisqacha quyidagicha shakllantirish mumkin:
Fikrlashning yakuniy natijasini kompilyatsiya qilingan funktsiyani o'z ichiga olgan delegat sifatida keshlash kerak. Bundan tashqari, ob'ektlardan tashqarida saqlanadigan o'zingizning tipingiz, ishchi maydonlarida turdagi ma'lumotlar bilan barcha kerakli ob'ektlarni keshlash mantiqiy.

Bunda mantiq bor. Sog'lom fikr, agar biror narsani kompilyatsiya qilish va keshlash mumkin bo'lsa, buni qilish kerakligini aytadi.

Oldinga qarab, shuni aytish kerakki, aks ettirish bilan ishlashda kesh o'zining afzalliklariga ega, hatto siz taklif qilingan iboralarni kompilyatsiya qilish usulidan foydalanmasangiz ham. Aslida, bu erda men yuqorida havola qilingan maqola muallifining tezislarini takrorlayapman.

Endi kod haqida. Keling, jiddiy kredit tashkilotining jiddiy ishlab chiqarishida duch kelgan yaqindagi og'rig'imga asoslangan misolni ko'rib chiqaylik. Hech kim taxmin qilmasligi uchun barcha ob'ektlar xayoliydir.

Qandaydir mohiyat bor. Aloqa bo'lsin. Standartlashtirilgan tanasi bo'lgan harflar mavjud bo'lib, ulardan parser va hidrator bir xil kontaktlarni yaratadi. Xat keldi, biz uni o'qib chiqdik, kalit-qiymat juftliklariga ajratdik, kontakt yaratdik va ma'lumotlar bazasida saqladik.

Bu elementar. Aytaylik, kontakt to'liq ismi, yoshi va kontakt telefoni xususiyatlariga ega. Ushbu ma'lumotlar xatda uzatiladi. Biznes, shuningdek, xatning asosiy qismidagi ob'ekt xususiyatlarini juftlarga solishtirish uchun yangi kalitlarni tezda qo'shish imkoniyatini qo'llab-quvvatlashni xohlaydi. Agar kimdir shablonda xatoga yo'l qo'ygan bo'lsa yoki chiqarilishidan oldin yangi hamkordan yangi formatga moslashgan holda zudlik bilan xaritalashni boshlash kerak bo'lsa. Keyin arzon datafix sifatida yangi xaritalash korrelyatsiyasini qo'shishimiz mumkin. Ya'ni, hayotiy misol.

Biz amalga oshiramiz, testlar yaratamiz. Ishlar.

Men kodni bermayman: juda ko'p manbalar mavjud va ular GitHub-da maqolaning oxiridagi havola orqali mavjud. Siz ularni yuklashingiz, tanib bo'lmaydigan darajada qiynoqqa solishingiz va o'lchashingiz mumkin, chunki bu sizning holatingizga ta'sir qiladi. Men faqat tez bo'lishi kerak bo'lgan gidratatorni sekin bo'lishi kerak bo'lgan gidratatordan ajratib turadigan ikkita shablon usulining kodini beraman.

Mantiq quyidagicha: shablon usuli asosiy parser mantig'i tomonidan yaratilgan juftlarni oladi. LINQ qatlami tahlil qiluvchi va gidratatorning asosiy mantiqidir, u ma'lumotlar bazasi kontekstiga so'rov yuboradi va kalitlarni tahlil qiluvchi juftliklar bilan taqqoslaydi (bu funktsiyalar uchun taqqoslash uchun LINQsiz kod mavjud). Keyinchalik, juftliklar asosiy hidratsiya usuliga o'tkaziladi va juftlarning qiymatlari ob'ektning tegishli xususiyatlariga o'rnatiladi.

"Tez" (Siyosiy ko'rsatkichlarda Tez prefiks):

 protected override Contact GetContact(PropertyToValueCorrelation[] correlations)
        {
            var contact = new Contact();
            foreach (var setterMapItem in _proprtySettersMap)
            {
                var correlation = correlations.FirstOrDefault(x => x.PropertyName == setterMapItem.Key);
                setterMapItem.Value(contact, correlation?.Value);
            }
            return contact;
        }

Ko'rib turganimizdek, sozlagich xususiyatlariga ega statik to'plam qo'llaniladi - sozlagich ob'ektini chaqiradigan kompilyatsiya qilingan lambdalar. Quyidagi kod bilan yaratilgan:

        static FastContactHydrator()
        {
            var type = typeof(Contact);
            foreach (var property in type.GetProperties())
            {
                _proprtySettersMap[property.Name] = GetSetterAction(property);
            }
        }

        private static Action<Contact, string> GetSetterAction(PropertyInfo property)
        {
            var setterInfo = property.GetSetMethod();
            var paramValueOriginal = Expression.Parameter(property.PropertyType, "value");
            var paramEntity = Expression.Parameter(typeof(Contact), "entity");
            var setterExp = Expression.Call(paramEntity, setterInfo, paramValueOriginal).Reduce();
            
            var lambda = (Expression<Action<Contact, string>>)Expression.Lambda(setterExp, paramEntity, paramValueOriginal);

            return lambda.Compile();
        }

Umuman olganda, bu aniq. Biz xususiyatlarni aylanib chiqamiz, ular uchun setterlarni chaqiradigan delegatlar yaratamiz va ularni saqlaymiz. Keyin kerak bo'lganda qo'ng'iroq qilamiz.

"Sekin" (bayonotlarda sekin prefiks):

        protected override Contact GetContact(PropertyToValueCorrelation[] correlations)
        {
            var contact = new Contact();
            foreach (var property in _properties)
            {
                var correlation = correlations.FirstOrDefault(x => x.PropertyName == property.Name);
                if (correlation?.Value == null)
                    continue;

                property.SetValue(contact, correlation.Value);
            }
            return contact;
        }

Bu erda biz darhol xususiyatlarni chetlab o'tamiz va to'g'ridan-to'g'ri SetValue chaqiramiz.

Aniqlik uchun va ma'lumotnoma sifatida men ularning korrelyatsiya juftliklari qiymatlarini to'g'ridan-to'g'ri ob'ekt maydonlariga yozadigan sodda usulni qo'lladim. Prefiks - qo'lda.

Endi BenchmarkDotNet-ni olaylik va ishlashni ko'rib chiqamiz. Va to'satdan ... (spoiler - bu to'g'ri natija emas, tafsilotlar quyida)

Ko'zguni tezlashtirish haqida muvaffaqiyatsiz maqola

Bu erda nimani ko'rmoqdamiz? Fast prefiksini g'alaba bilan o'z ichiga olgan usullar deyarli barcha o'tishlarda Sekin prefiksli usullarga qaraganda sekinroq bo'ladi. Bu ishning taqsimlanishi va tezligi uchun ham amal qiladi. Boshqa tomondan, LINQ usullaridan foydalangan holda xaritalashning chiroyli va oqlangan amalga oshirilishi, buning uchun mo'ljallangan, iloji boricha, aksincha, unumdorlikni sezilarli darajada pasaytiradi. Farqi tartibda. Turli xil o'tishlar soni bilan tendentsiya o'zgarmaydi. Farqi faqat miqyosda. LINQ bilan u 4-200 marta sekinroq, taxminan bir xil miqyosda ko'proq axlat bor.

dolzarb

Men ko'zlarimga ishonmadim, lekin eng muhimi, bizning hamkasbimiz mening ko'zlarimga ham, kodimga ham ishonmadi - Dmitriy Tixonov 0x1000000. Mening yechimimni ikki marta tekshirib, u ajoyib tarzda kashf etdi va amalga oshirishdagi bir qator o'zgarishlar tufayli men o'tkazib yuborgan xatoni, boshidan oxirigacha ko'rsatdi. Moq sozlamalarida topilgan xatoni tuzatgandan so'ng, barcha natijalar joyiga tushdi. Qayta sinov natijalariga ko'ra, asosiy tendentsiya o'zgarmaydi - LINQ hali ham ishlashga aks ettirishdan ko'ra ko'proq ta'sir qiladi. Biroq, iboralarni kompilyatsiya qilish bilan ishlash behuda bajarilmagani ma'qul va natija ajratish va bajarish vaqtida ham ko'rinadi. Birinchi ishga tushirish, statik maydonlar ishga tushirilganda, "tezkor" usul uchun tabiiy ravishda sekinroq bo'ladi, lekin keyin vaziyat o'zgaradi.

Mana takroriy test natijasi:

Ko'zguni tezlashtirish haqida muvaffaqiyatsiz maqola

Xulosa: korxonada aks ettirishdan foydalanganda, hiyla-nayranglarga murojaat qilishning hojati yo'q - LINQ unumdorlikni ko'proq iste'mol qiladi. Biroq, optimallashtirishni talab qiladigan yuqori yuklanish usullarida siz initsializatorlar va delegatlar kompilyatorlari shaklida aks ettirishni saqlashingiz mumkin, bu esa keyinchalik "tez" mantiqni ta'minlaydi. Shunday qilib, siz ham aks ettirishning moslashuvchanligini, ham dastur tezligini saqlab qolishingiz mumkin.

Benchmark kodi bu yerda mavjud. Har kim mening so'zlarimni ikki marta tekshirishi mumkin:
HabraReflection testlari

PS: testlardagi kod IoC dan foydalanadi va benchmarklarda u aniq konstruktsiyadan foydalanadi. Gap shundaki, yakuniy amalga oshirishda men ishlashga ta'sir qiladigan va natijani shovqinli qiladigan barcha omillarni kesib tashladim.

PPS: Foydalanuvchiga rahmat Dmitriy Tixonov @0x1000000 birinchi o'lchovlarga ta'sir qilgan Moqni o'rnatishdagi xatoimni aniqlaganim uchun. Agar o'quvchilardan birortasi etarli karmaga ega bo'lsa, iltimos, uni yoqing. Odam to'xtadi, odam o'qidi, odam ikki marta tekshirdi va xatoni ko'rsatdi. Menimcha, bu hurmat va hamdardlikka arziydi.

PPPS: uslub va dizaynning tubiga kirgan sinchkov o'quvchiga rahmat. Men bir xillik va qulaylik tarafdoriman. Taqdimot diplomatiyasi ko'p narsani orzu qiladi, lekin men tanqidni inobatga oldim. Men snaryadni so'rayman.

Manba: www.habr.com

a Izoh qo'shish