Ой жүгүртүүнү тездетүү жөнүндө ийгиликсиз макала

Мен макаланын аталышын дароо түшүндүрүп берем. Баштапкы план жөнөкөй, бирок реалдуу мисалды колдонуп, рефлексияны колдонууну тездетүү боюнча жакшы, ишенимдүү кеңеш берүү болчу, бирок салыштыруу учурунда рефлексия мен ойлогондой жай эмес экени, LINQ менин түнкү түшүмдөгүгө караганда жайыраак экени белгилүү болду. Бирок аягында өлчөөдө мен да ката кетиргеним билинди... Бул өмүр баяндын чоо-жайы кесилгенде жана комментарийде. Мисал кеңири таралган жана ишканада адаттагыдай эле ишке ашырылгандыктан, бул жашоонун демонстрациясы мага абдан кызыктуу болуп чыкты: макаланын негизги темасынын ылдамдыгына таасир эткен. тышкы логикага байланыштуу байкалбайт: Moq, Autofac, EF Core жана башка "бандингдер".

Мен бул макаланын таасири астында иштей баштадым: Эмне үчүн Рефлексия жай

Көрүнүп тургандай, автор тиркемени тездетүүнүн эң сонун жолу катары чагылдыруу түрүнүн ыкмаларын түздөн-түз чакыруунун ордуна түзүлгөн делегаттарды колдонууну сунуштайт. Албетте, IL эмиссиясы бар, бирок мен андан качкым келет, анткени бул тапшырманы аткаруунун эң көп эмгекти талап кылган жолу, ал каталар менен коштолот.

Ой жүгүртүү ылдамдыгы жөнүндө мен дайыма ушундай пикирде болгонумду эске алып, мен автордун корутундусуна шек келтирүүнү максат кылган эмесмин.

Мен ишканада рефлексияны жөнөкөй колдонууга көп жолугам. түрү алынат. Мүлк тууралуу маалымат алынат. SetValue ыкмасы деп аталат жана баары кубанышат. Баасы максаттуу талаага жетти, баары бактылуу. Абдан акылдуу адамдар - улгайган адамдар жана команданын жетекчилери - бир түрдөгү "универсалдуу" картачылардын мындай жөнөкөй ишке ашыруунун негизинде объектке кеңейтүүлөрүн жазышат. Маңызы адатта мындай: биз бардык талааларды алабыз, бардык касиеттерди алабыз, алардын үстүнөн кайталайбыз: эгерде типтеги мүчөлөрдүн аттары дал келсе, SetValue аткарабыз. Мезгил-мезгили менен биз каталардан улам өзгөчөлүктөрдү кармайбыз, анда биз түрлөрдүн биринде кандайдыр бир мүлктү таба алган жокпуз, бирок бул жерде да иштин натыйжалуулугун жогорулатуунун жолу бар. аракет кылуу/кармап алуу.

Мен адамдардын талдоочуларды жана карталарды кайра ойлоп табышканын көрдүм, алардан мурун келген машиналар кандай иштегени тууралуу маалымат менен толук куралданбай эле. Мен адамдар стратегиялардын артына, интерфейстердин артына, инъекциялардын артына өздөрүнүн жөнөкөй ишке ашырууларын жашырып жатышканын көрдүм, бул кийинки баканалияны шылтоолойт. Мен ушундай түшүнгөндөр менен мурдума бурулдум. Чынында, мен чыныгы аткарууну өлчөгөн жокмун, эгер мүмкүн болсо, мен аны колго алсам, ишке ашырууну "оптималдуу" кылып өзгөрттүм. Ошондуктан, төмөндө талкууланган биринчи өлчөөлөр мени катуу чаташтырды.

Рихтерди же башка идеологдорду окуп жатып, көпчүлүгүңүздөр коддогу чагылдыруу колдонмонун иштешине өтө терс таасирин тийгизген көрүнүш деген толук адилеттүү билдирүүгө туш болдуңуздар деп ойлойм.

Чакыруу чагылдырылышы CLRди керектүүсүн табуу, алардын метадайындарын алуу, талдоо ж.б. үчүн ассамблеялардан өтүүгө мажбурлайт. Мындан тышкары, ырааттуулукту басып өтүү учурунда ой жүгүртүү чоң көлөмдөгү эстутумдун бөлүнүшүнө алып келет. Биз эстутумду колдонуп жатабыз, CLR GCди ачат жана фриздер башталат. Бул байкаларлык жай болушу керек, мага ишен. Заманбап өндүрүш серверлериндеги же булут машиналарындагы эстутумдун чоң көлөмү кайра иштетүүнүн жогорку кечигүүсүнө жол бербейт. Чындыгында, эстутум канчалык көп болсо, сиз GC кантип иштээрин БИЛДИРҮҮ мүмкүн. Рефлексия, теориялык жактан алганда, ал үчүн кошумча кызыл чүпүрөк.

Бирок, биз баарыбыз IoC контейнерлерин жана дата карталарын колдонобуз, алардын иштөө принциби да ой жүгүртүүгө негизделген, бирок алардын иштеши боюнча эч кандай суроолор жок. Жок, анткени тышкы чектелген контексттик моделдерден көз карандылыктарды жана абстракцияларды киргизүү ушунчалык зарыл болгондуктан, биз кандай болгон күндө да аткарууну курмандыкка чалышыбыз керек. Баары жөнөкөй - бул чынында эле аткарууга көп таасир этпейт.

Чындыгында, чагылдыруу технологиясына негизделген эң кеңири таралган алкактар ​​аны менен оптималдуу иштөө үчүн ар кандай амалдарды колдонушат. Адатта, бул кэш. Адатта булар туюнтма дарагынан түзүлгөн туюнтмалар жана делегаттар. Ошол эле автомаппер рефлексияны чакырбастан бири-бирин башка түргө айландыра алган функциялар менен типтерге дал келген атаандаш сөздүктү сактайт.

Буга кантип жетишилген? Негизи, бул JIT кодун түзүү үчүн платформа өзү колдонгон логикадан эч кандай айырмасы жок. Метод биринчи жолу чакырылганда, ал компиляцияланат (жана, ооба, бул процесс тез эмес); кийинки чакырууларда башкаруу мурунтан эле түзүлгөн ыкмага өткөрүлүп берилет жана аткарууда олуттуу төмөндөө болбойт.

Биздин учурда, сиз JIT компиляциясын колдоно аласыз, андан кийин компиляцияланган жүрүм-турумду анын AOT кесиптештери менен бирдей аткаруу менен колдоно аласыз. Бул учурда бизге билдирүүлөр жардамга келет.

Каралып жаткан принцип кыскача төмөнкүчө чагылдырууга болот:
Ой жүгүртүүнүн акыркы натыйжасын компиляцияланган функцияны камтыган делегат катары кэштешиңиз керек. Ошондой эле объекттердин сыртында сакталган сиздин типтеги, жумушчунун талааларында тип маалыматы менен бардык керектүү объекттерди кэштөө мааниси бар.

Бул жерде логика бар. Акыл-эстүүлүк бизге бир нерсени компиляциялоо жана кэштөө мүмкүн болсо, анда аны жасоо керек деп айтат.

Алдыга көз чаптырып, рефлексия менен иштөөдө кэш сунуш кылынган туюнтмаларды түзүү ыкмасын колдонбосоңуз дагы, анын артыкчылыктарына ээ экенин айтуу керек. Чынында, мен бул жерде мен жогоруда сөз кылган макаланын авторунун тезистерин кайталап жатам.

Эми код жөнүндө. Келгиле, бир олуттуу кредиттик мекеменин олуттуу өндүрүшүндө дуушар болгон менин акыркы азапка негизделген мисалды карап көрөлү. Бардык объектилер ойдон чыгарылган, ошондуктан эч ким ойлобосун.

Кандайдыр бир маңызы бар. Байланыш бар болсун. Стандартташтырылган денеси бар тамгалар бар, алардан анализдөөчү жана гидратор ушул эле байланыштарды түзүшөт. Кат келди, биз аны окуп чыктык, аны ачкыч-маани жуптарына талдап, байланыш түзүп, маалымат базасына сактап койдук.

Бул башталгыч. Байланыштын аты-жөнү, жашы жана байланыш телефону касиеттери бар дейли. Бул маалыматтар катта берилет. Бизнес ошондой эле каттын негизги бөлүгүндө жуптарга объект касиеттерин картага түшүрүү үчүн жаңы ачкычтарды тез кошуу мүмкүнчүлүгүн колдоону каалайт. Эгерде кимдир бирөө шаблондо ката кетирсе же чыгарууга чейин жаңы форматка ыңгайлашып, жаңы өнөктөштөн картаны тез арада ишке киргизүү керек болсо. Андан кийин биз арзан datafix катары жаңы карта корреляциясын кошо алабыз. Башкача айтканда, турмуштук мисал.

Биз ишке ашырабыз, тесттерди түзөбүз. Works.

Мен кодду бербейм: көптөгөн булактар ​​бар жана алар GitHub сайтында макаланын аягындагы шилтеме аркылуу жеткиликтүү. Сиз аларды жүктөй аласыз, аларды таанылгыс кылып кыйнап, өлчөй аласыз, анткени бул сиздин ишиңизге таасир этет. Мен ылдам болушу керек болгон гидратторду жай болушу керек болгон гидрататордон айырмалаган эки шаблондук ыкманын кодун гана берем.

Логика төмөнкүчө: шаблон ыкмасы негизги талдоо логикасы тарабынан түзүлгөн жуптарды кабыл алат. LINQ катмары - анализдөөчү жана гидраттордун негизги логикасы, ал маалымат базасынын контекстине суроо салат жана талдоочудан жуптар менен ачкычтарды салыштырат (бул функциялар үчүн салыштыруу үчүн LINQ жок код бар). Андан кийин, жуптар негизги гидратация ыкмасына өткөрүлүп берилет жана түгөйлөрдүн маанилери объекттин тиешелүү касиеттерине коюлат.

"Тез" (баалуу көрсөткүчтөрдөгү Fast префикси):

 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;
        }

Көрүнүп тургандай, орнотуучу касиеттери бар статикалык коллекция колдонулат - компиляцияланган ламбдалар, алар орнотуучу субъектти чакырышат. Төмөнкү код менен түзүлгөн:

        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();
        }

Жалпысынан түшүнүктүү. Биз касиеттерди кыдырып, алар үчүн орнотуучуларды чакырган делегаттарды түзүп, аларды сактайбыз. Анан керек болгондо чалабыз.

"Жай" (баалуу көрсөткүчтөрдөгү жай префикс):

        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;
        }

Бул жерде биз дароо касиеттерди айланып өтүп, SetValue түз чакырабыз.

Түшүнүктүүлүк үчүн жана шилтеме катары, мен алардын корреляциялык жуптарынын маанилерин түздөн-түз объект талааларына жаза турган жөнөкөй ыкманы ишке ашырдым. Префикс – Кол менен.

Эми BenchmarkDotNetти алып, аткарууну карап көрөлү. Анан күтүлбөгөн жерден... (спойлер – бул туура жыйынтык эмес, чоо-жайы төмөндө)

Ой жүгүртүүнү тездетүү жөнүндө ийгиликсиз макала

Бул жерде биз эмнени көрүп жатабыз? Fast префиксин жеңүүчү ыкмалар дээрлик бардык өтүүлөрдө Жай префикси бар ыкмаларга караганда жайыраак болуп чыгат. Бул бөлүштүрүүгө да, иштин ылдамдыгына да тиешелүү. Башка жагынан алып караганда, LINQ ыкмаларын колдонуу менен кооз жана жарашыктуу картаны ишке ашыруу, тескерисинче, өндүрүмдүүлүктү бир топ төмөндөтөт. Айырмасы тартипте. Өтүүлөрдүн ар кандай саны менен тенденция өзгөрбөйт. Бир гана айырмасы масштабда. LINQ менен ал 4 - 200 эсе жайыраак, болжол менен бирдей масштабда таштанды көп.

UPDATED

Мен өз көзүмө ишенген жокмун, бирок эң негизгиси кесиптешибиз менин көзүмө да, кодума да ишенген жок - Дмитрий Тихонов 0x1000000. Менин чечимимди эки жолу текшерип, ал эң сонун ачып, ишке ашыруудагы бир катар өзгөрүүлөрдөн улам, башынан аягына чейин өткөрүп жиберген катамды көрсөттү. Moq орнотуусунда табылган мүчүлүштүктөрдү оңдогондон кийин, бардык натыйжалар ордуна келди. Кайра тестирлөөнүн жыйынтыгы боюнча, негизги тенденция өзгөрбөйт - LINQ дагы эле ой жүгүртүүгө караганда аткарууга көбүрөөк таасир этет. Бирок, Expressions компиляциясы менен иштөө бекер жасалбаганы жакшы жана натыйжа бөлүштүрүүдө да, аткарууда да көрүнүп турат. Биринчи ишке киргизүү, статикалык талаалар инициализацияланганда, "тез" ыкмасы үчүн табигый түрдө жайыраак, бирок андан кийин кырдаал өзгөрөт.

Бул жерде кайра текшерүүнүн жыйынтыгы:

Ой жүгүртүүнү тездетүү жөнүндө ийгиликсиз макала

Корутунду: ишканада рефлексияны колдонууда трюктарга кайрылуунун өзгөчө кереги жок - LINQ өндүрүмдүүлүктү көбүрөөк жейт. Бирок, оптималдаштырууну талап кылган жогорку жүктөмдүү ыкмаларда сиз инициализаторлор жана өкүл компиляторлор түрүндө чагылдырууну сактай аласыз, алар андан кийин "тез" логиканы камсыз кылат. Ушундай жол менен сиз ой жүгүртүүнүн ийкемдүүлүгүн жана колдонмонун ылдамдыгын сактай аласыз.

Эталондук код бул жерде жеткиликтүү. Ар ким менин сөздөрүмдү эки жолу текшере алат:
HabraReflectionTests

PS: тесттердеги код IoCди колдонот, ал эми эталондордо ачык конструкцияны колдонот. Чындыгында, акыркы ишке ашырууда мен өндүрүмдүүлүккө таасир эте турган бардык факторлорду кесип салгам жана натыйжаны ызы-чуу кылам.

PPS: Колдонуучуга рахмат Дмитрий Тихонов @0x1000000 биринчи өлчөөлөргө таасир эткен Moq орнотууда менин катамды тапканым үчүн. Эгер окурмандардын кимдир бирөөнүн кармасы жетиштүү болсо, лайк басыңыз. Эркек токтоду, киши окуду, киши эки жолу текшерип, катасын көрсөттү. Бул сый-урматка, тилектештикке татыктуу деп ойлойм.

PPPS: стилдин жана дизайндын түбүнө жеткен кылдат окурманга рахмат. Мен бирдейлик жана ыңгайлуулук үчүн. Презентациянын дипломатиясы көп нерсени каалагандай калтырат, бирок мен сынды эске алдым. Мен снарядды сурайм.

Source: www.habr.com

Комментарий кошуу