Nesėkmingas straipsnis apie pagreitintą refleksiją

Iš karto paaiškinsiu straipsnio pavadinimą. Pirminis planas buvo duoti gerus, patikimus patarimus, kaip paspartinti refleksijos panaudojimą naudojant paprastą, bet tikrovišką pavyzdį, tačiau atliekant lyginamąją analizę paaiškėjo, kad refleksija nėra tokia lėta, kaip maniau, LINQ lėtesnė nei mano košmaruose. Bet galiausiai paaiškėjo, kad ir aš padariau klaidą išmatavimuose... Šios gyvenimo istorijos detalės – po pjūviu ir komentaruose. Kadangi pavyzdys yra gana įprastas ir įgyvendinamas iš principo, kaip paprastai daroma įmonėje, tai pasirodė gana įdomus, kaip man atrodo, gyvenimo demonstravimas: įtaka pagrindinės straipsnio temos greičiui buvo nepastebima dėl išorinės logikos: Moq, Autofac, EF Core ir kiti "diržai".

Pradėjau dirbti iš šio straipsnio įspūdžio: Kodėl lėtas atspindys

Kaip matote, autorius siūlo naudoti sudarytus delegatus, o ne tiesiogiai iškviesti refleksijos tipo metodus, nes tai puikus būdas labai pagreitinti taikomąją programą. Žinoma, yra IL emisija, tačiau norėčiau to išvengti, nes tai yra pats darbo jėgos reikalaujantis būdas atlikti užduotį, kuri yra kupina klaidų.

Atsižvelgiant į tai, kad apie refleksijos greitį visada laikiausi panašios nuomonės, autoriaus išvadomis kvestionuoti ne itin neketinau.

Įmonėje dažnai susiduriu su naiviu refleksijos panaudojimu. Tipas paimtas. Informacija apie turtą imama. Iškviečiamas SetValue metodas ir visi džiaugiasi. Vertė atkeliavo į tikslinį lauką, visi patenkinti. Labai protingi žmonės – senjorai ir komandos vadovai – rašo savo plėtinius prie objekto, remdamiesi tokiu naivu įgyvendinimu „universalūs“ vieno tipo žemėlapiai į kitą. Esmė dažniausiai tokia: paimame visus laukus, paimame visas savybes, kartojame juos: jei tipo narių pavadinimai sutampa, vykdome SetValue. Kartkartėmis užklumpame išimčių dėl klaidų, kai neradome kokio nors turto vienoje iš tipų, tačiau ir čia yra išeitis, pagerinanti našumą. Išbandyk/pagauk.

Mačiau, kaip žmonės iš naujo išranda analizatorius ir žemėlapių sudarytojus, nebūdami visiškai apsiginklavę informacija apie tai, kaip veikia prieš juos buvusios mašinos. Mačiau, kaip žmonės slepia savo naivius įgyvendinimus už strategijų, už sąsajų, už injekcijų, tarsi tai būtų pateisinama vėlesnė bakchanalia. Aš pakėliau nosį nuo tokių supratimų. Tiesą sakant, aš neįvertinau tikrojo našumo nutekėjimo ir, jei įmanoma, paprasčiausiai pakeičiau diegimą į „optimalesnį“, jei galėčiau tai padaryti. Todėl pirmieji toliau aptariami matavimai mane rimtai supainiojo.

Manau, kad daugelis iš jūsų, skaitydami Richterį ar kitus ideologus, susidūrėte su visiškai teisingu teiginiu, kad refleksija kode yra reiškinys, turintis itin neigiamą įtaką programos veikimui.

Iškviestas atspindys verčia CLR eiti per rinkinius, kad surastų reikiamą, ištrauktų jų metaduomenis, išanalizuoti ir pan. Be to, atspindys einant sekas lemia daug atminties paskirstymo. Mes išnaudojame atmintį, CLR atskleidžia GC ir prasideda frizai. Jis turėtų būti pastebimai lėtas, patikėkite manimi. Didžiulis atminties kiekis šiuolaikiniuose gamybiniuose serveriuose ar debesies įrenginiuose neapsaugo nuo didelių apdorojimo vėlavimų. Tiesą sakant, kuo daugiau atminties, tuo didesnė tikimybė, kad PASTEBĖTE, kaip veikia GC. Teoriškai atspindys jam yra ypač raudonas skuduras.

Tačiau visi naudojame IoC konteinerius ir datos kartografus, kurių veikimo principas taip pat pagrįstas atspindžiu, tačiau dėl jų veikimo dažniausiai nekyla klausimų. Ne, ne todėl, kad priklausomybių įvedimas ir abstrakcija iš išorinių riboto konteksto modelių yra tokie reikalingi, kad bet kokiu atveju turime paaukoti našumą. Viskas paprasčiau – tai tikrai neturi didelės įtakos našumui.

Faktas yra tas, kad labiausiai paplitusios sistemos, pagrįstos atspindžio technologija, naudoja įvairiausias gudrybes, kad su jais veiktų optimaliau. Paprastai tai yra talpykla. Paprastai tai yra išraiškos ir delegatai, sudaryti iš išraiškų medžio. Tas pats automatinis žemėlapių sudarytojas palaiko konkurencingą žodyną, kuris suderina tipus su funkcijomis, kurios gali konvertuoti vieną į kitą, neiškviečiant atspindžio.

Kaip tai pasiekiama? Iš esmės tai nesiskiria nuo logikos, kurią pati platforma naudoja JIT kodui generuoti. Kai metodas iškviečiamas pirmą kartą, jis sukompiliuojamas (ir, taip, šis procesas nėra greitas); vėlesniais iškvietimais valdymas perkeliamas į jau sukompiliuotą metodą ir nebus jokių reikšmingų našumo sumažėjimų.

Mūsų atveju taip pat galite naudoti JIT kompiliavimą ir tada naudoti sukompiliuotą elgseną tokiu pat našumu kaip ir jos AOT atitikmenys. Šiuo atveju išraiškos mums padės.

Aptariamą principą galima trumpai suformuluoti taip:
Turėtumėte išsaugoti galutinį apmąstymo rezultatą kaip delegatas, kuriame yra sudaryta funkcija. Taip pat prasminga talpykloje saugoti visus reikalingus objektus su informacija apie tipus jūsų tipo, darbuotojo laukuose, kurie saugomi už objektų ribų.

Čia yra logikos. Sveikas protas sako, kad jei ką nors galima sukompiliuoti ir išsaugoti talpykloje, tai reikia padaryti.

Žvelgiant į ateitį, reikia pasakyti, kad talpykla dirbant su atspindžiu turi savo privalumų, net jei nenaudojate siūlomo išraiškų sudarymo metodo. Tiesą sakant, čia aš tiesiog kartoju straipsnio, į kurį remiuosi aukščiau, autoriaus tezes.

Dabar apie kodą. Pažiūrėkime į pavyzdį, kuris paremtas mano neseniai patirtu skausmu, su kuriuo teko susidurti rimtoje rimtos kredito įstaigos produkcijoje. Visi subjektai yra fiktyvūs, kad niekas neatspėtų.

Yra tam tikra esmė. Tebūnie Kontaktas. Yra raidžių su standartizuotu korpusu, iš kurių analizatorius ir drėkintuvas sukuria tuos pačius kontaktus. Atėjo laiškas, mes jį perskaitėme, išanalizavome į raktų-reikšmių poras, sukūrėme kontaktą ir išsaugojome duomenų bazėje.

Tai elementaru. Tarkime, kad kontaktas turi ypatybes Visas vardas, amžius ir kontaktinis telefonas. Šie duomenys perduodami laiške. Įmonė taip pat nori paramos, kad galėtų greitai pridėti naujų raktų, skirtų objekto ypatybėms susieti į poras laiško tekste. Jei kas nors padarė rašybos klaidą šablone arba jei prieš išleidimą reikia skubiai paleisti kartografavimą iš naujo partnerio, prisitaikant prie naujo formato. Tada galime pridėti naują atvaizdavimo koreliaciją kaip pigų duomenų taisymą. Tai yra gyvenimo pavyzdys.

Diegiame, kuriame testus. Veikia.

Kodo nepateiksiu: šaltinių yra daug ir juos rasite „GitHub“ per nuorodą straipsnio pabaigoje. Galite juos krauti, kankinti neatpažįstamai ir matuotis, kaip tai paveiktų jūsų atveju. Pateiksiu tik dviejų šabloninių metodų kodą, kurie skiria drėkintuvą, kuris turėjo būti greitas, nuo drėkintuvo, kuris turėjo būti lėtas.

Logika yra tokia: šablono metodas gauna poras, sugeneruotas pagrindinio analizatoriaus logikos. LINQ sluoksnis yra analizatorius ir pagrindinė hidratoriaus logika, kuri pateikia užklausą duomenų bazės kontekstui ir lygina raktus su poromis iš analizatoriaus (šioms funkcijoms palyginimui yra kodas be LINQ). Tada poros perduodamos pagrindiniam hidratacijos metodui, o porų reikšmės nustatomos pagal atitinkamas subjekto savybes.

„Greitas“ (priešdėlis Fast etalonuose):

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

Kaip matome, naudojamas statinis rinkinys su seterio savybėmis – sukompiliuotos lambdos, kurios iškviečia seterio esybę. Sukurta pagal šį kodą:

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

Apskritai tai aišku. Perkeliame ypatybes, sukuriame jiems delegatus, kurie iškviečia nustatytojus, ir juos išsaugome. Tada skambiname, kai reikia.

„Lėtas“ (priešdėlis Lėtas etalonuose):

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

Čia mes iš karto apeiname ypatybes ir tiesiogiai iškviečiame SetValue.

Aiškumo dėlei ir kaip nuorodą įdiegiau naivų metodą, kuris įrašo jų koreliacinių porų reikšmes tiesiai į objekto laukus. Priešdėlis – rankinis.

Dabar paimkime „BenchmarkDotNet“ ir patikrinkime našumą. Ir staiga... (spoileris - tai nėra teisingas rezultatas, išsami informacija pateikiama žemiau)

Nesėkmingas straipsnis apie pagreitintą refleksiją

Ką mes čia matome? Metodai, kurie triumfuoja su greitu priešdėliu, beveik visuose žingsniuose yra lėtesni nei metodai su lėtu priešdėliu. Tai galioja ir paskirstymui, ir darbo greičiui. Kita vertus, gražus ir elegantiškas žemėlapių įgyvendinimas naudojant tam skirtus LINQ metodus, kur tik įmanoma, atvirkščiai, labai sumažina produktyvumą. Skirtumas yra tvarka. Tendencija nesikeičia esant skirtingam perdavimų skaičiui. Vienintelis skirtumas yra mastelyje. Su LINQ jis veikia 4–200 kartų lėčiau, šiukšlių yra daugiau maždaug tokio paties masto.

ATNAUJINTA

Aš netikėjau savo akimis, bet dar svarbiau, kad mūsų kolega netikėjo nei mano akimis, nei mano kodu - Dmitrijus Tikhonovas 0x1000000. Dar kartą patikrinęs mano sprendimą, jis puikiai atrado ir nurodė klaidą, kurią praleidau dėl daugybės įgyvendinimo pakeitimų, nuo pradinio iki galutinio. Ištaisius aptiktą Moq sąrankos klaidą, visi rezultatai atsidūrė vietoje. Remiantis pakartotinio testo rezultatais, pagrindinė tendencija nesikeičia – LINQ vis tiek veikia našumą labiau nei atspindys. Tačiau smagu, kad darbas su Expression kompiliavimu nėra atliktas veltui, o rezultatas matomas tiek paskirstymo, tiek vykdymo laiku. Pirmasis paleidimas, kai inicijuojami statiniai laukai, natūraliai yra lėtesnis naudojant „greitąjį“ metodą, tačiau tada situacija pasikeičia.

Štai pakartotinio bandymo rezultatas:

Nesėkmingas straipsnis apie pagreitintą refleksiją

Išvada: naudojant refleksiją įmonėje, nereikia griebtis gudrybių - LINQ labiau suvalgys produktyvumą. Tačiau naudojant didelės apkrovos metodus, kuriuos reikia optimizuoti, galite išsaugoti atspindžius inicializatorių ir deleguotų kompiliatorių pavidalu, kurie suteiks „greitą“ logiką. Tokiu būdu galite išlaikyti ir atspindžio lankstumą, ir taikymo greitį.

Palyginimo kodą rasite čia. Bet kas gali dar kartą patikrinti mano žodžius:
HabraReflection Tests

PS: Testų kodas naudoja IoC, o etalonuose – aiškią konstrukciją. Faktas yra tas, kad galutiniame įgyvendinime pašalinau visus veiksnius, galinčius turėti įtakos našumui ir padaryti rezultatą triukšmingą.

PPS: Ačiū vartotojui Dmitrijus Tikhonovas @0x1000000 už tai, kad atradau savo klaidą nustatant Moq, kuri turėjo įtakos pirmiesiems matavimams. Jei kuris nors iš skaitytojų turi pakankamai karmos, prašome pamėgti. Vyras sustojo, vyras perskaitė, vyras dar kartą patikrino ir nurodė klaidą. Manau, kad tai verta pagarbos ir užuojautos.

PPPS: ačiū kruopščiam skaitytojui, kuris suprato stilių ir dizainą. Aš už vienodumą ir patogumą. Pristatymo diplomatiškumas palieka daug norimų rezultatų, tačiau į kritiką atsižvelgiau. Prašau sviedinio.

Šaltinis: www.habr.com

Добавить комментарий