Net slagge artikel oer fersnelle refleksje

Ik sil daliks de titel fan it artikel útlizze. It oarspronklike plan wie om goed, betrouber advys te jaan oer hoe't jo it gebrûk fan refleksje fersnelle kinne mei in ienfâldich mar realistysk foarbyld, mar by benchmarking die bliken dat refleksje net sa stadich is as ik tocht, LINQ is stadiger as yn myn nachtmerjes. Mar úteinlik die bliken dat ik ek in flater makke yn de mjittingen... Details fan dit libbensferhaal steane ûnder de knip en yn de kommentaren. Om't it foarbyld frij gewoan is en yn prinsipe ymplementearre lykas gewoanlik dien wurdt yn in ûndernimming, die bliken in hiel nijsgjirrige, sa't it liket my, demonstraasje fan it libben: de ynfloed op de snelheid fan it haadûnderwerp fan it artikel wie net merkber fanwege eksterne logika: Moq, Autofac, EF Core en oare "bandings".

Ik begon te wurkjen ûnder de yndruk fan dit artikel: Wêrom is Reflection traach

Lykas jo kinne sjen, suggerearret de auteur it gebrûk fan gearstalde ôffurdigen ynstee fan direkt metoaden fan refleksjetype te neamen as in geweldige manier om de applikaasje sterk te fersnellen. D'r is fansels IL-útstjit, mar ik soe it graach foarkomme, om't dit de meast arbeidsintensive manier is om de taak út te fieren, dy't fol is mei flaters.

Yn betinken nommen dat ik altyd in soartgelikense miening hân haw oer de snelheid fan refleksje, wie ik net benammen fan doel om de konklúzjes fan de skriuwer te freegjen.

Ik kom faak tsjin naïv gebrûk fan refleksje yn 'e ûndernimming. It type wurdt nommen. Ynformaasje oer it pân wurdt nommen. De SetValue-metoade wurdt neamd en elkenien is bliid. De wearde is yn it doelfjild oankommen, elkenien is bliid. Hiel tûke minsken - senioaren en teamlieders - skriuwe har tafoegings foar objekt, basearre op sa'n naïve ymplemintaasje "universele" mappers fan it iene type nei it oare. De essinsje is meastentiids dit: wy nimme alle fjilden, nimme alle eigenskippen, iterearje der oer: as de nammen fan 'e leden fan it type oerienkomme, fiere wy SetValue út. Fan tiid ta tiid wy fange útsûnderings fanwege flaters dêr't wy net fûn wat eigendom yn ien fan 'e soarten, mar ek hjir is der in útwei dy't ferbetteret prestaasjes. Besykje / fange.

Ik haw minsken sjoen parsers en mappers opnij útfine sûnder folslein bewapene te wêzen mei ynformaasje oer hoe't de masines dy't foar har kamen wurkje. Ik haw sjoen dat minsken har naïve ymplemintaasjes ferbergje efter strategyen, efter ynterfaces, efter ynjeksjes, as soe dit de folgjende bacchanalia ekskúsje. Ik draaide de noas omheech foar sokke realisaasjes. Yn feite haw ik it echte prestaasjeslek net mjitten, en, as it mooglik wie, haw ik de ymplemintaasje gewoan feroare yn in mear "optimale" as ik der yn hannen koe krije. Dêrom hawwe de earste mjittingen dy't hjirûnder besprutsen binne my serieus betize.

Ik tink dat in protte fan jo, Richter lêze as oare ideologen, in folslein earlike ferklearring tsjinkamen dat refleksje yn koade in ferskynsel is dat in ekstreem negative ynfloed hat op 'e prestaasjes fan' e applikaasje.

Refleksje oproppe twingt de CLR om troch gearkomsten te gean om dejinge te finen dy't se nedich binne, har metadata ophelje, se parse, ensfh. Dêrnjonken liedt refleksje by it trochgean fan sekwinsjes ta de tawizing fan in grut bedrach fan ûnthâld. Wy brûke ûnthâld, CLR ûntdekt de GC en friezen begjinne. It moat merkber stadich wêze, leau my. De enoarme hoemannichten ûnthâld op moderne produksjeservers as wolkmasines foarkomme gjin hege ferwurkingsfertragingen. Yn feite, hoe mear ûnthâld, hoe mear kâns dat jo NOTICE hoe't de GC wurket. Refleksje is yn teory in ekstra reade lap foar him.

Wy brûke lykwols allegear IoC-konteners en datummappers, wêrfan it bestjoeringsprinsipe ek basearre is op refleksje, mar d'r binne normaal gjin fragen oer har prestaasjes. Nee, net om't it ynfieren fan ôfhinklikens en abstraksje fan eksterne beheinde kontekstmodellen sa needsaaklik binne dat wy yn elts gefal prestaasjes opofferje moatte. Alles is ienfâldiger - it hat echt gjin ynfloed op prestaasjes.

It feit is dat de meast foarkommende kaders dy't basearre binne op refleksjetechnology allerhanne trúkjes brûke om der optimaal mei te wurkjen. Meastal is dit in cache. Typysk binne dit ekspresjes en ôffurdigen gearstald út 'e ekspresjebeam. Deselde automapper ûnderhâldt in kompetitive wurdboek dat oerienkomt mei typen mei funksjes dy't inoar yn 'e oare kinne omsette sûnder refleksje te neamen.

Hoe wurdt dit berikt? Yn essinsje is dit net oars as de logika dy't it platfoarm sels brûkt om JIT-koade te generearjen. As in metoade foar de earste kear wurdt oanroppen, wurdt it kompilearre (en, ja, dit proses is net fluch); op folgjende oproppen wurdt kontrôle oerbrocht nei de al kompilearre metoade, en d'r sille gjin signifikante prestaasjesferlies wêze.

Yn ús gefal kinne jo ek JIT-kompilaasje brûke en dan it kompilearre gedrach brûke mei deselde prestaasjes as syn AOT-tsjinhingers. Utdrukkingen sille ús yn dit gefal helpe.

It oanbelangjende prinsipe kin koart formulearre wurde as folget:
Jo moatte it einresultaat fan refleksje yn 'e cache as delegearre mei de kompilearre funksje. It makket ek sin om alle nedige objekten te cache mei typeynformaasje yn 'e fjilden fan jo type, de arbeider, dy't bûten de objekten binne opslein.

D'r is logika yn dit. Gezond ferstân fertelt ús dat as wat kin wurde gearstald en cache, dan moat it dien wurde.

Foarútsjen moat sein wurde dat de cache yn wurkjen mei refleksje syn foardielen hat, sels as jo de foarstelde metoade foar it kompilearjen fan útdrukkingen net brûke. Eigentlik werhelje ik hjir gewoan de proefskriften fan 'e skriuwer fan it artikel wêr't ik hjirboppe ferwize.

No oer de koade. Litte wy nei in foarbyld sjen dat basearre is op myn resinte pine dy't ik te krijen hie yn in serieuze produksje fan in serieuze kredytynstelling. Alle entiteiten binne fiktyf, sadat gjinien soe riede.

Der is wat essinsje. Lit der Kontakt wêze. D'r binne letters mei in standerdisearre lichem, wêrfan de parser en hydrator deselde kontakten meitsje. In brief kaam, wy lêze it, parsed it yn kaai-wearde pearen, makke in kontakt, en bewarre it yn de databank.

It is elemintêr. Litte wy sizze dat in kontaktpersoan de eigenskippen hat Folsleine namme, Leeftyd en Kontakttillefoan. Dizze gegevens wurde oerdroegen yn 'e brief. It bedriuw wol ek stipe om fluch nije kaaien ta te foegjen foar it yn kaart bringen fan entiteitseigenskippen yn pearen yn it lichem fan 'e brief. Yn it gefal dat immen in typflater makke yn 'e sjabloan of as it foar de frijlitting nedich is om driuwend mapping fan in nije partner te starten, oanpasse oan it nije formaat. Dan kinne wy ​​​​in nije mapping-korrelaasje tafoegje as in goedkeap datafix. Dat is, in libbensfoarbyld.

Wy implementearje, meitsje tests. Wurket.

Ik sil de koade net leverje: d'r binne in protte boarnen, en se binne beskikber op GitHub fia de keppeling oan 'e ein fan it artikel. Jo kinne se lade, martelje se sûnder erkenning en mjitte se, lykas it soe beynfloedzje yn jo gefal. Ik sil allinne jou de koade fan twa sjabloan metoaden dy't ûnderskiede de hydrator, dat wie nei alle gedachten te wêzen fluch, út de hydrator, dat wie nei alle gedachten te wêzen stadich.

De logika is as folget: de sjabloanmetoade ûntfangt pearen generearre troch de basale parserlogika. De LINQ-laach is de parser en de basislogika fan 'e hydrator, dy't in fersyk oan 'e databankkontekst makket en toetsen fergeliket mei pearen fan' e parser (foar dizze funksjes is d'r koade sûnder LINQ foar ferliking). Folgjende wurde de pearen trochjûn oan 'e wichtichste hydratisaasjemetoade en wurde de wearden fan' e pearen ynsteld op 'e oerienkommende eigenskippen fan' e entiteit.

"Fast" (foarheaksel Fast yn benchmarks):

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

Sa't wy sjen kinne, wurdt in statyske kolleksje mei setter-eigenskippen brûkt - kompilearre lambda's dy't de setter-entiteit neame. Oanmakke troch de folgjende koade:

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

Yn it algemien is it dúdlik. Wy trochsnije de eigenskippen, meitsje ôffurdigen foar harren dy't setters neame, en bewarje se. Dan belje wy as it nedich is.

"Slow" (foarheaksel Slow yn benchmarks):

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

Hjir geane wy ​​fuortendaliks de eigenskippen om en neame SetValue direkt.

Foar dúdlikens en as referinsje haw ik in naïve metoade ymplementearre dy't de wearden fan har korrelaasjepearen direkt yn 'e entiteitsfjilden skriuwt. Foarheaksel - Hânlieding.

Litte wy no BenchmarkDotNet nimme en de prestaasjes ûndersykje. En ynienen ... (spoiler - dit is net it goede resultaat, details binne hjirûnder)

Net slagge artikel oer fersnelle refleksje

Wat sjogge wy hjir? Metoaden dy't triomfantlik it Fast foarheaksel drage, blike yn hast alle passes stadiger te wêzen as metoaden mei it Slow foarheaksel. Dit is wier foar sawol tawizing as snelheid fan wurk. Oan 'e oare kant, in prachtige en elegante ymplemintaasje fan mapping mei LINQ metoaden bedoeld foar dit wêr mooglik, krekt oarsom, gâns ferminderet de produktiviteit. It ferskil is fan oarder. De trend feroaret net mei ferskillende oantallen passes. It ienige ferskil is yn skaal. Mei LINQ is it 4 - 200 kear stadiger, der is mear jiskefet op likernôch deselde skaal.

UPDATED

Ik leaude myn eagen net, mar noch wichtiger, ús kollega leaude myn eagen noch myn koade net - Dmitry Tikhonov 0x1000000. Nei't er myn oplossing dûbel kontrolearre, ûntduts en wiisde hy briljant op in flater dy't ik miste fanwegen in oantal wizigingen yn 'e ymplemintaasje, fan earste oant lêste. Nei it reparearjen fan de fûn bug yn 'e Moq-opset foelen alle resultaten yn plak. Neffens de retestresultaten feroaret de haadtrend net - LINQ hat noch altyd ynfloed op prestaasjes mear as refleksje. It is lykwols moai dat it wurk mei Expression-kompilaasje net om 'e nocht dien wurdt, en it resultaat is sichtber sawol yn allocaasje as útfieringstiid. De earste lansearring, as statyske fjilden wurde inisjalisearre, is fansels stadiger foar de "snelle" metoade, mar dan feroaret de situaasje.

Hjir is it resultaat fan 'e hertest:

Net slagge artikel oer fersnelle refleksje

Konklúzje: by it brûken fan refleksje yn in ûndernimming is d'r gjin spesjale needsaak om ta tricks te brûken - LINQ sil de produktiviteit mear ite. Yn metoaden mei hege lading dy't optimisaasje fereaskje, kinne jo lykwols refleksje bewarje yn 'e foarm fan initializers en delegearre kompilatoren, dy't dan "snelle" logika sille leverje. Op dizze manier kinne jo sawol de fleksibiliteit fan refleksje as de snelheid fan 'e applikaasje behâlde.

De benchmarkkoade is hjir te krijen. Elkenien kin myn wurden dûbel kontrolearje:
HabraReflectionTests

PS: de koade yn 'e tests brûkt IoC, en yn' e benchmarks brûkt it in eksplisite konstruksje. It feit is dat ik yn 'e definitive ymplemintaasje alle faktoaren ôfsnien dy't de prestaasjes kinne beynfloedzje en it resultaat lûd meitsje.

PPS: Mei tank oan de brûker Dmitry Tikhonov @0x1000000 foar it ûntdekken fan myn flater by it ynstellen fan Moq, dy't de earste mjittingen beynfloede. As ien fan 'e lêzers genôch karma hat, like it dan asjebleaft. De man bleau stean, de man lies, de man kontrolearre dûbeld en wiisde op de flater. Ik tink dat dit respekt en sympaty wurdich is.

PPPS: tank oan 'e sekuere lêzer dy't de styl en ûntwerp oan 'e boaiem kaam. Ik bin foar uniformiteit en gemak. De diplomasy fan de presintaasje lit in soad te winskjen oer, mar ik haw de krityk yn rekken brocht. Ik freegje om it projektyl.

Boarne: www.habr.com

Add a comment