Neveiksmīgs raksts par refleksijas paātrināŔanu

Es tÅ«lÄ«t paskaidroÅ”u raksta nosaukumu. Sākotnējais plāns bija sniegt labus, uzticamus padomus, kā paātrināt refleksijas izmantoÅ”anu, izmantojot vienkārÅ”u, bet reālistisku piemēru, taču benchmarkinga laikā izrādÄ«jās, ka refleksija nav tik lēna, kā domāju, LINQ ir lēnāks nekā manos murgos. Bet beigās izrādÄ«jās, ka arÄ« es kļūdÄ«jos mērÄ«jumos... SÄ«kāka informācija par Å”o dzÄ«vesstāstu ir zem griezuma un komentāros. Tā kā piemērs ir diezgan ikdieniŔķs un principā Ä«stenots, kā tas parasti tiek darÄ«ts uzņēmumā, tas izrādÄ«jās diezgan interesants, kā man Ŕķiet, dzÄ«ves paraugdemonstrējums: ietekme uz raksta galvenās tēmas ātrumu bija nav manāms ārējās loÄ£ikas dēļ: Moq, Autofac, EF Core un citi "joslas".

Es sāku strādāt pēc Ŕī raksta iespaida: Kāpēc refleksija ir lēna

Kā redzat, autors iesaka izmantot apkopotus delegātus, nevis tieÅ”i izsaukt refleksijas tipa metodes, jo tas ir lielisks veids, kā ievērojami paātrināt lietojumprogrammu. Protams, ir IL emisija, bet es gribētu no tās izvairÄ«ties, jo tas ir darbietilpÄ«gākais veids, kā veikt uzdevumu, kurā ir daudz kļūdu.

Ņemot vērā to, ka man vienmēr ir bijis lÄ«dzÄ«gs viedoklis par refleksijas ātrumu, es Ä«paÅ”i nedomāju apÅ”aubÄ«t autora secinājumus.

Uzņēmumā bieži sastopos ar naivu refleksijas izmantoÅ”anu. Veids ir uzņemts. Tiek ņemta informācija par Ä«paÅ”umu. Tiek izsaukta SetValue metode un visi priecājas. VērtÄ«ba ir nonākusi mērÄ·a laukā, visi ir apmierināti. Ä»oti gudri cilvēki - seniori un komandas vadÄ«tāji - raksta savus paplaÅ”inājumus objektam, balstoties uz tik naivu realizāciju, viena veida "universālie" kartētāji uz otru. BÅ«tÄ«ba parasti ir Ŕāda: mēs ņemam visus laukus, ņemam visus rekvizÄ«tus, atkārtojam tos: ja tipa dalÄ«bnieku nosaukumi sakrÄ«t, mēs izpildām SetValue. Ik pa laikam pieÄ·eram izņēmumus kļūdu dēļ, kur neatradām kādu Ä«paÅ”umu kādā no veidiem, taču arÄ« Å”eit ir izeja, kas uzlabo veiktspēju. Izmēģini/noÄ·er.

Esmu redzējis, ka cilvēki no jauna izgudro parsētājus un kartētājus, nebÅ«dami pilnÄ«bā bruņoti ar informāciju par to, kā darbojas pirms viņiem esoŔās maŔīnas. Esmu redzējis, ka cilvēki slēpj savas naivās ievieÅ”anas aiz stratēģijām, aiz saskarnēm, aiz injekcijām, it kā tas attaisnotu turpmāko bakhanāliju. Es pagriezu degunu uz Ŕādu atziņu. PatiesÄ«bā es nenovērtēju reālo veiktspējas noplÅ«di un, ja iespējams, es vienkārÅ”i mainÄ«ju ievieÅ”anu uz ā€œoptimālākuā€, ja varēju to iegÅ«t. Tāpēc pirmie zemāk aplÅ«kotie mērÄ«jumi mani nopietni mulsināja.

Es domāju, ka daudzi no jums, lasot Rihteru vai citus ideologus, ir saskāruÅ”ies ar pilnÄ«gi godÄ«gu apgalvojumu, ka atspoguļojums kodā ir parādÄ«ba, kas ārkārtÄ«gi negatÄ«vi ietekmē lietojumprogrammas darbÄ«bu.

Refleksijas izsaukÅ”ana liek CLR iziet cauri komplektiem, lai atrastu vajadzÄ«go, izvilktu metadatus, parsētu tos utt. Turklāt pārdomas, Ŕķērsojot secÄ«bas, noved pie liela apjoma atmiņas pieŔķirÅ”anas. Mēs izmantojam atmiņu, CLR atklāj GC un sākas frÄ«zes. Tam vajadzētu bÅ«t manāmi lēnam, ticiet man. Lielais atmiņas apjoms mÅ«sdienu ražoÅ”anas serveros vai mākoņa iekārtās nenovērÅ” lielu apstrādes aizkavi. PatiesÄ«bā, jo vairāk atmiņas, jo lielāka iespēja, ka PEVĒRĒSIT, kā darbojas GC. Teorētiski refleksija viņam ir Ä«paÅ”i sarkana lupata.

Taču mēs visi lietojam IoC konteinerus un datuma kartētājus, kuru darbÄ«bas princips arÄ« balstās uz refleksiju, taču par to veiktspēju parasti nerodas jautājumi. Nē, ne tāpēc, ka atkarÄ«bu ievieÅ”ana un abstrakcija no ārējiem ierobežota konteksta modeļiem ir tik nepiecieÅ”ama, ka mums jebkurā gadÄ«jumā ir jāupurē veiktspēja. Viss ir vienkārŔāk - tas patieŔām neietekmē veiktspēju.

Fakts ir tāds, ka visizplatÄ«tākie ietvari, kas ir balstÄ«ti uz refleksijas tehnoloÄ£iju, izmanto visu veidu trikus, lai ar to strādātu optimālāk. Parasti Ŕī ir keÅ”atmiņa. Parasti tās ir izteiksmes un delegāti, kas apkopoti no izteiksmju koka. Tas pats automapper uztur konkurētspējÄ«gu vārdnÄ«cu, kas saskaņo veidus ar funkcijām, kuras var pārvērst vienu citā, neizsaucot refleksiju.

Kā tas tiek panākts? BÅ«tÄ«bā tas neatŔķiras no loÄ£ikas, ko pati platforma izmanto JIT koda Ä£enerÄ“Å”anai. Kad metode tiek izsaukta pirmo reizi, tā tiek kompilēta (un, jā, Å”is process nav ātrs); nākamajos izsaukumos kontrole tiek pārnesta uz jau kompilēto metodi, un nebÅ«s bÅ«tisku veiktspējas kritumu.

MÅ«su gadÄ«jumā varat izmantot arÄ« JIT kompilāciju un pēc tam izmantot kompilēto darbÄ«bu ar tādu paÅ”u veiktspēju kā tās AOT kolēģiem. Å ajā gadÄ«jumā mums palÄ«dzēs izteicieni.

AttiecÄ«go principu var Ä«si formulēt Ŕādi:
Pārdomu gala rezultāts ir jāsaglabā keÅ”atmiņā kā delegātam, kas satur apkopoto funkciju. Ir arÄ« jēga keÅ”atmiņā saglabāt visus nepiecieÅ”amos objektus ar tipa informāciju jÅ«su tipa, darbinieka laukos, kas tiek glabāti ārpus objektiem.

Å eit ir loÄ£ika. Veselais saprāts mums saka, ka, ja kaut ko var apkopot un saglabāt keÅ”atmiņā, tad tas ir jādara.

Raugoties nākotnē, jāsaka, ka keÅ”atmiņai darbā ar refleksiju ir savas priekÅ”rocÄ«bas, pat ja jÅ«s neizmantojat piedāvāto izteiksmju apkopoÅ”anas metodi. PatiesÄ«bā Å”eit es vienkārÅ”i atkārtoju raksta autora tēzes, uz kurām atsaucos iepriekÅ”.

Tagad par kodu. Apskatīsim piemēru, kas balstīts uz manām nesenajām sāpēm, ar kurām man nācās saskarties nopietnas kredītiestādes nopietnā iestudējumā. Visas vienības ir fiktīvas, lai neviens to neuzminētu.

Ir kaut kāda bÅ«tÄ«ba. Lai ir Kontakts. Ir burti ar standartizētu korpusu, no kuriem analizētājs un mitrinātājs izveido tos paÅ”us kontaktus. Atnāca vēstule, mēs to izlasÄ«jām, parsējām atslēgu un vērtÄ«bu pāros, izveidojām kontaktpersonu un saglabājām datu bāzē.

Tas ir elementāri. Pieņemsim, ka kontaktpersonai ir rekvizÄ«ti Pilns vārds, vecums un Kontakttālrunis. Å ie dati tiek pārsÅ«tÄ«ti vēstulē. Uzņēmums arÄ« vēlas saņemt atbalstu, lai vēstules pamattekstā varētu ātri pievienot jaunas atslēgas entÄ«tijas rekvizÄ«tu kartÄ“Å”anai pa pāriem. GadÄ«jumā, ja kāds veidnē ir pieļāvis drukas kļūdu vai ja pirms izlaiÅ”anas ir nepiecieÅ”ams steidzami palaist kartÄ“Å”anu no jauna partnera, pielāgojoties jaunajam formātam. Pēc tam mēs varam pievienot jaunu kartÄ“Å”anas korelāciju kā lētu datu labojumu. Tas ir, dzÄ«ves piemērs.

IevieŔam, veidojam testus. Darbojas.

Es nesniegÅ”u kodu: ir daudz avotu, un tie ir pieejami vietnē GitHub, izmantojot saiti raksta beigās. JÅ«s varat tos ielādēt, spÄ«dzināt lÄ«dz nepazÄ«Å”anai un izmērÄ«t, kā tas ietekmētu jÅ«su gadÄ«jumu. Es doÅ”u tikai divu veidņu metožu kodu, kas atŔķir mitrinātāju, kuram vajadzēja bÅ«t ātram, no mitrinātāja, kuram vajadzēja bÅ«t lēnam.

LoÄ£ika ir Ŕāda: veidnes metode saņem pārus, ko Ä£enerē pamata parsētāja loÄ£ika. LINQ slānis ir parsētājs un hidratora pamata loÄ£ika, kas veic pieprasÄ«jumu datu bāzes kontekstam un salÄ«dzina atslēgas ar parsētāja pāriem (Ŕīm funkcijām salÄ«dzināŔanai ir kods bez LINQ). Pēc tam pāri tiek nodoti galvenajai hidratācijas metodei, un pāru vērtÄ«bas tiek iestatÄ«tas uz attiecÄ«gajām entÄ«tijas Ä«paŔībām.

ā€œÄ€trsā€ (etalonos prefikss 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;
        }

Kā redzam, tiek izmantota statiskā kolekcija ar settera Ä«paŔībām ā€“ kompilētas lambdas, kas izsauc setera entÄ«tiju. Izveidots ar Ŕādu kodu:

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

Kopumā ir skaidrs. Mēs Ŕķērsojam rekvizÄ«tus, izveidojam tiem delegātus, kas izsauc iestatÄ«tājus, un saglabājam tos. Tad sazvanāmies, kad nepiecieÅ”ams.

ā€œLēnsā€ (prefikss Lēns etalonos):

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

Å eit mēs nekavējoties apietam rekvizÄ«tus un tieÅ”i izsaucam SetValue.

SkaidrÄ«bas labad un kā atsauci es ieviesu naivu metodi, kas ieraksta to korelācijas pāru vērtÄ«bas tieÅ”i entÄ«tiju laukos. Prefikss - rokasgrāmata.

Tagad ņemsim BenchmarkDotNet un pārbaudÄ«sim veiktspēju. Un pēkŔņi... (spoileris - tas nav pareizais rezultāts, sÄ«kāka informācija zemāk)

Neveiksmīgs raksts par refleksijas paātrināŔanu

Ko mēs Å”eit redzam? Metodes, kas triumfē ar prefiksu Fast, gandrÄ«z visos piegājienos izrādās lēnākas nekā metodes ar prefiksu Lēnā. Tas attiecas gan uz sadalÄ«jumu, gan uz darba ātrumu. No otras puses, skaista un eleganta kartÄ“Å”anas ievieÅ”ana, izmantojot Å”im nolÅ«kam paredzētās LINQ metodes, kur vien iespējams, gluži pretēji, ievērojami samazina produktivitāti. AtŔķirÄ«ba ir kārtÄ«bā. Tendence nemainās ar dažādu piespēļu skaitu. VienÄ«gā atŔķirÄ«ba ir mērogā. Ar LINQ tas ir 4 - 200 reizes lēnāks, ir vairāk atkritumu apmēram tādā paŔā mērogā.

UPDATED

Es neticēju savām acÄ«m, bet vēl svarÄ«gāk ir tas, ka mÅ«su kolēģis neticēja ne manām acÄ«m, ne manam kodam - Dmitrijs Tihonovs 0x1000000. Divreiz pārbaudÄ«jis manu risinājumu, viņŔ lieliski atklāja un norādÄ«ja uz kļūdu, kuru es palaidu garām vairāku ievieÅ”anas izmaiņu dēļ, sākot no sākotnējās lÄ«dz galÄ«gajai. Pēc atrastās kļūdas novērÅ”anas Moq iestatÄ«jumos visi rezultāti nonāca vietā. Saskaņā ar atkārtotās pārbaudes rezultātiem galvenā tendence nemainās ā€“ LINQ joprojām vairāk ietekmē veiktspēju nekā atspoguļojumu. Tomēr patÄ«kami, ka darbs ar Expression kompilāciju netiek veikts velti, un rezultāts ir redzams gan sadalē, gan izpildes laikā. Pirmā palaiÅ”ana, kad tiek inicializēti statiskie lauki, protams, ir lēnāka ā€œÄtrajaiā€ metodei, bet pēc tam situācija mainās.

Šeit ir atkārtotas pārbaudes rezultāts:

Neveiksmīgs raksts par refleksijas paātrināŔanu

Secinājums: izmantojot refleksiju uzņēmumā, nav Ä«paÅ”as vajadzÄ«bas Ä·erties pie trikiem - LINQ vairāk apēdÄ«s produktivitāti. Tomēr augstas slodzes metodēs, kurām nepiecieÅ”ama optimizācija, varat saglabāt pārdomas inicializatoru un deleģēto kompilatoru veidā, kas pēc tam nodroÅ”inās ā€œÄtruā€ loÄ£iku. Tādā veidā jÅ«s varat saglabāt gan refleksijas elastÄ«bu, gan aplikācijas ātrumu.

Etalona kods ir pieejams Å”eit. Ikviens var vēlreiz pārbaudÄ«t manus vārdus:
HabraReflection Tests

PS: kods testos izmanto IoC, un etalonos tas izmanto skaidru konstrukciju. Fakts ir tāds, ka galÄ«gajā ievieÅ”anā es nogriezu visus faktorus, kas varētu ietekmēt veiktspēju un padarÄ«t rezultātu trokŔņainu.

PPS: Paldies lietotājam Dmitrijs Tihonovs @0x1000000 par to, ka atklāju savu kļūdu Moq iestatÄ«Å”anā, kas ietekmēja pirmos mērÄ«jumus. Ja kādam no lasÄ«tājiem ir pietiekama karma, lÅ«dzu, patÄ«k. VÄ«rietis apstājās, vÄ«rietis lasÄ«ja, vÄ«rietis vēlreiz pārbaudÄ«ja un norādÄ«ja uz kļūdu. Es domāju, ka tas ir cieņas un lÄ«dzjÅ«tÄ«bas vērts.

PPPS: paldies rÅ«pÄ«gajam lasÄ«tājam, kurÅ” saprata stilu un dizainu. Esmu par vienveidÄ«bu un ērtÄ«bas. Prezentācijas diplomātija atstāj daudz ko vēlēties, taču kritiku ņēmu vērā. Es lÅ«dzu Ŕāviņu.

Avots: www.habr.com

Pievieno komentāru