Onsuksesvolle artikel oor versnelling van refleksie

Ek sal dadelik die titel van die artikel verduidelik. Die oorspronklike plan was om goeie, betroubare raad te gee oor hoe om die gebruik van refleksie te bespoedig deur 'n eenvoudige maar realistiese voorbeeld te gebruik, maar tydens benchmarking het dit geblyk dat refleksie nie so stadig is as wat ek gedink het nie, LINQ is stadiger as in my nagmerries. Maar op die ou end het dit geblyk dat ek ook 'n fout gemaak het in die afmetings... Besonderhede van hierdie lewensverhaal is onder die snit en in die kommentaar. Aangesien die voorbeeld redelik alledaags is en in beginsel geïmplementeer word soos gewoonlik in 'n onderneming gedoen word, was dit nogal 'n interessante, soos dit vir my lyk, demonstrasie van die lewe: die impak op die spoed van die hoofonderwerp van die artikel was nie opvallend nie as gevolg van eksterne logika: Moq, Autofac, EF Core en ander "bandings".

Ek het begin werk onder die indruk van hierdie artikel: Hoekom is Refleksie stadig

Soos u kan sien, stel die skrywer voor om saamgestelde afgevaardigdes te gebruik in plaas daarvan om refleksietipe metodes direk te noem as 'n goeie manier om die toepassing aansienlik te bespoedig. Daar is natuurlik IL-emissie, maar ek wil dit graag vermy, aangesien dit die mees arbeidsintensiewe manier is om die taak uit te voer, wat belaai is met foute.

Aangesien ek nog altyd 'n soortgelyke mening oor die spoed van refleksie gehad het, was ek nie juis van plan om die skrywer se gevolgtrekkings te bevraagteken nie.

Ek kom dikwels naïewe gebruik van refleksie in die onderneming teë. Die tipe word geneem. Inligting oor die eiendom word geneem. Die SetValue-metode word genoem en almal is bly. Die waarde het in die teikenveld aangekom, almal is gelukkig. Baie slim mense - seniors en spanleiers - skryf hul uitbreidings om beswaar te maak, gebaseer op so 'n naïewe implementering "universele" karteerders van een tipe na 'n ander. Die essensie is gewoonlik dit: ons neem al die velde, neem al die eienskappe, herhaal daaroor: as die name van die tipe lede ooreenstem, voer ons SetValue uit. Van tyd tot tyd kry ons uitsonderings as gevolg van foute waar ons nie een of ander eiendom in een van die tipes gekry het nie, maar selfs hier is daar 'n uitweg wat prestasie verbeter. Probeer/vang.

Ek het gesien hoe mense ontleders en karteerders herontdek sonder om ten volle gewapen te wees met inligting oor hoe die masjiene wat voor hulle gekom het, werk. Ek het gesien hoe mense hul naïewe implementerings agter strategieë, agter koppelvlakke, agter inspuitings wegsteek, asof dit die daaropvolgende bacchanalia sou verskoon. Ek het my neus opgetrek vir sulke besef. Trouens, ek het nie die werklike prestasielek gemeet nie, en, indien moontlik, het ek eenvoudig die implementering na 'n meer "optimale" een verander as ek dit in die hande kon kry. Daarom het die eerste metings wat hieronder bespreek word, my ernstig verwar.

Ek dink baie van julle, wat Richter of ander ideoloë lees, het op 'n heeltemal regverdige stelling afgekom dat refleksie in kode 'n verskynsel is wat 'n uiters negatiewe impak op die prestasie van die toepassing het.

Oproepende refleksie dwing die CLR om deur samestellings te gaan om die een te vind wat hulle nodig het, hul metadata op te trek, hulle te ontleed, ens. Daarbenewens lei refleksie terwyl rye deurkruis word tot die toekenning van 'n groot hoeveelheid geheue. Ons gebruik geheue op, CLR ontbloot die GC en friesies begin. Dit behoort merkbaar stadig te wees, glo my. Die groot hoeveelhede geheue op moderne produksiebedieners of wolkmasjiene verhoed nie hoë verwerkingsvertragings nie. Om die waarheid te sê, hoe meer geheue, hoe groter is die kans dat jy LET OP hoe die GC werk. Refleksie is in teorie vir hom 'n ekstra rooi lap.

Ons gebruik egter almal IoC-houers en datumkaartmakers, waarvan die werkingsbeginsel ook op refleksie gebaseer is, maar daar is gewoonlik geen vrae oor hul werkverrigting nie. Nee, nie omdat die bekendstelling van afhanklikhede en abstraksie van eksterne beperkte konteksmodelle so nodig is dat ons in elk geval prestasie moet opoffer nie. Alles is eenvoudiger - dit beïnvloed regtig nie veel prestasie nie.

Die feit is dat die mees algemene raamwerke wat op refleksietegnologie gebaseer is, allerhande truuks gebruik om meer optimaal daarmee te werk. Gewoonlik is dit 'n kas. Tipies is dit uitdrukkings en afgevaardigdes wat saamgestel is uit die uitdrukkingsboom. Dieselfde outomatiseerder hou 'n mededingende woordeboek wat tipes pas met funksies wat een in 'n ander kan omskep sonder om refleksie te noem.

Hoe word dit bereik? In wese verskil dit nie van die logika wat die platform self gebruik om JIT-kode te genereer nie. Wanneer 'n metode vir die eerste keer opgeroep word, word dit saamgestel (en ja, hierdie proses is nie vinnig nie); op daaropvolgende oproepe word beheer oorgedra na die reeds saamgestelde metode, en daar sal geen noemenswaardige prestasie-afnames wees nie.

In ons geval kan jy ook JIT-samestelling gebruik en dan die saamgestelde gedrag gebruik met dieselfde prestasie as sy AOT-eweknieë. Uitdrukkings sal ons in hierdie geval help.

Die betrokke beginsel kan kortliks soos volg geformuleer word:
Jy moet die finale resultaat van refleksie in 'n kaskas as 'n afgevaardigde wat die saamgestelde funksie bevat. Dit maak ook sin om alle nodige voorwerpe met tipe inligting in die velde van jou tipe, die werker, wat buite die voorwerpe gestoor word, te kas.

Daar is logika hierin. Gesonde verstand sê vir ons dat as iets saamgestel en gekas kan word, dit gedoen moet word.

As ons vorentoe kyk, moet gesê word dat die kas in die werk met refleksie sy voordele het, selfs al gebruik jy nie die voorgestelde metode om uitdrukkings saam te stel nie. Eintlik herhaal ek hier bloot die tesisse van die skrywer van die artikel waarna ek hierbo verwys.

Nou oor die kode. Kom ons kyk na 'n voorbeeld wat gebaseer is op my onlangse pyn wat ek moes trotseer in 'n ernstige produksie van 'n ernstige kredietinstelling. Alle entiteite is fiktief sodat niemand sou raai nie.

Daar is 'n essensie. Laat daar Kontak wees. Daar is letters met 'n gestandaardiseerde liggaam, waaruit die ontleder en hidreerder dieselfde kontakte skep. 'n Brief het aangekom, ons het dit gelees, dit in sleutel-waarde-pare ontleed, 'n kontak geskep en dit in die databasis gestoor.

Dis elementêr. Kom ons sê 'n kontak het die eienskappe Volle Naam, Ouderdom en Kontakfoon. Hierdie data word in die brief oorgedra. Die besigheid wil ook ondersteuning hê om vinnig nuwe sleutels te kan byvoeg vir die kartering van entiteitseienskappe in pare in die liggaam van die brief. In die geval dat iemand 'n tikfout in die sjabloon gemaak het of as dit voor die vrystelling nodig is om dringend kartering vanaf 'n nuwe vennoot te begin, aan te pas by die nuwe formaat. Dan kan ons 'n nuwe kartering-korrelasie byvoeg as 'n goedkoop datafix. Dit wil sê, 'n lewensvoorbeeld.

Ons implementeer, skep toetse. Werke.

Ek sal nie die kode verskaf nie: daar is baie bronne, en hulle is beskikbaar op GitHub via die skakel aan die einde van die artikel. Jy kan hulle laai, hulle onherkenbaar martel en meet, soos dit in jou geval sou raak. Ek sal slegs die kode gee van twee sjabloonmetodes wat die hidrator, wat veronderstel was om vinnig te wees, van die hidrator, wat veronderstel was om stadig te wees, onderskei.

Die logika is soos volg: die sjabloonmetode ontvang pare wat deur die basiese ontlederlogika gegenereer word. Die LINQ-laag is die ontleder en die basiese logika van die hidrator, wat 'n versoek aan die databasiskonteks rig en sleutels met pare van die ontleder vergelyk (vir hierdie funksies is daar kode sonder LINQ vir vergelyking). Vervolgens word die pare na die hoofhidrasiemetode oorgedra en die waardes van die pare word ingestel op die ooreenstemmende eienskappe van die entiteit.

"Vinnig" (Virvoegsel Vinnig in maatstawwe):

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

Soos ons kan sien, word 'n statiese versameling met setter-eienskappe gebruik - saamgestelde lambda's wat die setter-entiteit noem. Geskep deur die volgende kode:

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

Oor die algemeen is dit duidelik. Ons deurkruis die eiendomme, skep afgevaardigdes vir hulle wat opstellers oproep, en stoor hulle. Dan bel ons wanneer nodig.

"Stadig" (Voorvoegsel Stadig in maatstawwe):

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

Hier omseil ons dadelik die eiendomme en skakel SetValue direk.

Vir duidelikheid en as verwysing het ek 'n naïewe metode geïmplementeer wat die waardes van hul korrelasiepare direk in die entiteitsvelde skryf. Voorvoegsel – Handleiding.

Kom ons neem nou BenchmarkDotNet en ondersoek die prestasie. En skielik ... (bederf - dit is nie die korrekte resultaat nie, besonderhede is hieronder)

Onsuksesvolle artikel oor versnelling van refleksie

Wat sien ons hier? Metodes wat die Fast-voorvoegsel seëvierend dra, blyk stadiger te wees in byna alle passe as metodes met die Slow-voorvoegsel. Dit geld vir beide toekenning en spoed van werk. Aan die ander kant, 'n pragtige en elegante implementering van kartering met behulp van LINQ metodes bedoel vir hierdie waar moontlik, inteendeel, aansienlik verminder produktiwiteit. Die verskil is van orde. Die neiging verander nie met verskillende getalle passe nie. Die enigste verskil is in skaal. Met LINQ is dit 4 - 200 keer stadiger, daar is meer vullis op ongeveer dieselfde skaal.

UPDATED

Ek het nie my oë geglo nie, maar nog belangriker, ons kollega het nie my oë of my kode geglo nie - Dmitri Tikhonov 0x1000000. Nadat hy my oplossing dubbel gekontroleer het, het hy 'n fout ontdek wat ek gemis het as gevolg van 'n aantal veranderinge in die implementering, aanvanklik tot finaal. Nadat die gevind fout in die Moq-opstelling reggestel is, het al die resultate in plek geval. Volgens die hertoetsresultate verander die hoofneiging nie – LINQ beïnvloed steeds prestasie meer as refleksie. Dit is egter lekker dat die werk met Expression-samestelling nie tevergeefs gedoen word nie, en die resultaat is sigbaar in beide toekenning en uitvoeringstyd. Die eerste bekendstelling, wanneer statiese velde geïnisialiseer word, is natuurlik stadiger vir die "vinnige" metode, maar dan verander die situasie.

Hier is die uitslag van die hertoets:

Onsuksesvolle artikel oor versnelling van refleksie

Gevolgtrekking: wanneer refleksie in 'n onderneming gebruik word, is daar geen spesifieke behoefte om tot truuks toe te gryp nie - LINQ sal produktiwiteit meer opvreet. In hoëladingsmetodes wat optimalisering vereis, kan jy egter refleksie in die vorm van initialiseerders en delegeersamestellers stoor, wat dan "vinnige" logika sal verskaf. Op hierdie manier kan jy beide die buigsaamheid van refleksie en die spoed van die toepassing handhaaf.

Die maatstafkode is hier beskikbaar. Enigiemand kan my woorde dubbel kontroleer:
HabraRefleksietoetse

NS: die kode in die toetse gebruik IoC, en in die maatstawwe gebruik dit 'n eksplisiete konstruk. Die feit is dat ek in die finale implementering alle faktore afgesny het wat prestasie kan beïnvloed en die resultaat raserig maak.

PPS: Dankie aan die gebruiker Dmitri Tikhonov @0x1000000 omdat ek my fout ontdek het met die opstel van Moq, wat die eerste metings beïnvloed het. As enige van die lesers genoeg karma het, hou asseblief daarvan. Die man het stilgehou, die man gelees, die man het dubbel gekontroleer en die fout uitgewys. Ek dink dit is respek en simpatie werd.

PPPS: dankie aan die noukeurige leser wat tot die onderkant van die styl en ontwerp gekom het. Ek is vir eenvormigheid en gerief. Die diplomasie van die aanbieding laat veel te wense oor, maar ek het die kritiek in ag geneem. Ek vra vir die projektiel.

Bron: will.com

Voeg 'n opmerking