Անհաջող հոդված՝ արտացոլման արագացման մասին

Ես անմիջապես կբացատրեմ հոդվածի վերնագիրը. Նախնական պլանը լավ, հուսալի խորհուրդներ տալն էր, թե ինչպես արագացնել արտացոլման օգտագործումը՝ օգտագործելով պարզ, բայց իրատեսական օրինակ, բայց համեմատության ժամանակ պարզվեց, որ արտացոլումն այնքան էլ դանդաղ չէ, որքան ես կարծում էի, LINQ-ն ավելի դանդաղ է, քան իմ մղձավանջներում: Բայց վերջում պարզվեց, որ ես էլ եմ սխալվել չափումների մեջ... Այս կյանքի պատմության մանրամասները՝ կտրվածքի տակ ու մեկնաբանություններում։ Քանի որ օրինակը բավականին սովորական է և սկզբունքորեն իրականացվում է, ինչպես սովորաբար արվում է ձեռնարկությունում, պարզվեց, որ բավականին հետաքրքիր, ինձ թվում է, կյանքի ցուցադրություն. ազդեցությունը հոդվածի հիմնական թեմայի արագության վրա. արտաքին տրամաբանության պատճառով նկատելի չէ՝ Moq, Autofac, EF Core և այլ «bandings»:

Ես սկսեցի աշխատել այս հոդվածի տպավորությամբ. Ինչու է Reflection-ը դանդաղ

Ինչպես տեսնում եք, հեղինակն առաջարկում է օգտագործել կազմված պատվիրակներ՝ արտացոլման տիպի մեթոդներ ուղղակիորեն կանչելու փոխարեն՝ որպես հավելվածը մեծապես արագացնելու հիանալի միջոց: Կա, իհարկե, IL արտանետում, բայց ես կցանկանայի խուսափել դրանից, քանի որ սա առաջադրանքը կատարելու ամենաաշխատատար միջոցն է, որը հղի է սխալներով:

Հաշվի առնելով, որ ես միշտ նման կարծիք եմ ունեցել արտացոլման արագության մասին, ես առանձնապես մտադիր չէի կասկածի տակ դնել հեղինակի եզրակացությունները:

Ձեռնարկությունում հաճախ եմ բախվում արտացոլման միամիտ օգտագործմանը։ Տեսակը վերցված է. Գույքի մասին տեղեկությունները վերցված են։ SetValue մեթոդը կանչված է, և բոլորը ուրախանում են: Արժեքը հասել է թիրախային դաշտ, բոլորը գոհ են: Շատ խելացի մարդիկ՝ տարեցները և թիմի ղեկավարները, գրում են իրենց ընդլայնումները առարկելու համար՝ հիմնվելով նման միամիտ իրականացման վրա՝ մի տեսակի մյուսը «ունիվերսալ» քարտեզագրողներ: Էությունը սովորաբար սա է. մենք վերցնում ենք բոլոր դաշտերը, վերցնում ենք բոլոր հատկությունները, կրկնում ենք դրանց վրա. եթե տիպի անդամների անունները համընկնում են, մենք կատարում ենք SetValue: Ժամանակ առ ժամանակ մենք բռնում ենք բացառություններ սխալների պատճառով, երբ տեսակներից որևէ մեկում որոշակի հատկություն չենք գտել, բայց նույնիսկ այստեղ կա ելք, որը բարելավում է կատարումը: Փորձել/բռնել:

Ես տեսել եմ, թե ինչպես են մարդիկ նորից հայտնագործում վերլուծիչներ և քարտեզագրիչներ՝ ամբողջությամբ չզինված լինելով այն մասին, թե ինչպես են աշխատում իրենց առաջ եղած մեքենաները: Ես տեսել եմ, որ մարդիկ թաքցնում են իրենց միամիտ իրականացումները ռազմավարությունների, ինտերֆեյսների, ներարկումների հետևում, կարծես դա արդարացնում է հետագա bacchanalia-ն: Այսպիսի գիտակցումներից քիթս վեր էի քաշում։ Իրականում, ես չեմ չափել իրական կատարողականի արտահոսքը, և, հնարավորության դեպքում, ես պարզապես փոխեցի իրականացումը ավելի «օպտիմալի», եթե կարողանայի ձեռքս ընկնել դրա վրա: Հետևաբար, ստորև քննարկված առաջին չափումները ինձ լրջորեն շփոթեցրին:

Կարծում եմ՝ ձեզնից շատերը, կարդալով Ռիխտերին կամ այլ գաղափարախոսներ, հանդիպել են միանգամայն արդար հայտարարության, որ կոդում արտացոլումը մի երեւույթ է, որը չափազանց բացասական ազդեցություն է ունենում հավելվածի աշխատանքի վրա։

Զանգի արտացոլումը ստիպում է CLR-ին անցնել հավաքների միջով, որպեսզի գտնի իրենց անհրաժեշտը, հավաքի իր մետատվյալները, վերլուծի դրանք և այլն: Բացի այդ, հաջորդականություններն անցնելիս արտացոլումը հանգեցնում է մեծ քանակությամբ հիշողության բաշխմանը: Մենք օգտագործում ենք հիշողությունը, CLR-ն բացահայտում է GC-ն և սկսվում են ֆրիզները: Այն պետք է նկատելիորեն դանդաղ լինի, հավատացեք ինձ։ Ժամանակակից արտադրական սերվերների կամ ամպային մեքենաների հիշողության հսկայական քանակությունը չի խանգարում վերամշակման մեծ ուշացումներին: Իրականում, որքան շատ հիշողություն, այնքան ավելի հավանական է, որ դուք ՆԿԱՏԵՔ, թե ինչպես է աշխատում GC-ն: Արտացոլումը, տեսականորեն, նրա համար լրացուցիչ կարմիր լաթ է։

Այնուամենայնիվ, մենք բոլորս օգտագործում ենք IoC կոնտեյներներ և ամսաթվերի քարտեզագրիչներ, որոնց գործառնական սկզբունքը նույնպես հիմնված է արտացոլման վրա, բայց սովորաբար դրանց կատարման վերաբերյալ հարցեր չեն առաջանում: Ոչ, ոչ այն պատճառով, որ կախվածության ներմուծումն ու արտաքին սահմանափակ համատեքստի մոդելներից աբստրակցիան այնքան անհրաժեշտ է, որ մենք պետք է ամեն դեպքում զոհաբերենք կատարողականը: Ամեն ինչ ավելի պարզ է. դա իսկապես շատ չի ազդում կատարման վրա:

Փաստն այն է, որ ամենատարածված շրջանակները, որոնք հիմնված են արտացոլման տեխնոլոգիայի վրա, օգտագործում են բոլոր տեսակի հնարքներ դրա հետ ավելի օպտիմալ աշխատելու համար: Սովորաբար սա քեշ է: Սովորաբար դրանք արտահայտություններ և պատվիրակներ են, որոնք կազմվում են արտահայտության ծառից: Նույն ավտոմատ քարտեզագրիչը վարում է մրցակցային բառարան, որը համընկնում է տիպերի գործառույթների հետ, որոնք կարող են մեկը մյուսի վերածել առանց արտացոլման կանչելու:

Ինչպե՞ս է դա ձեռք բերվում: Ըստ էության, սա ոչնչով չի տարբերվում այն ​​տրամաբանությունից, որը հարթակն ինքն է օգտագործում JIT կոդ ստեղծելու համար: Երբ մեթոդն առաջին անգամ է կանչվում, այն կազմվում է (և, այո, այս գործընթացը արագ չէ), հետագա զանգերի դեպքում հսկողությունը փոխանցվում է արդեն կազմված մեթոդին, և կատարողականի զգալի անկումներ չեն լինի:

Մեր դեպքում, դուք կարող եք նաև օգտագործել JIT կոմպիլյացիան և այնուհետև օգտագործել կազմված վարքագիծը նույն կատարողականությամբ, ինչ իր AOT գործընկերները: Այս դեպքում մեզ օգնության կգան արտահայտությունները։

Քննարկվող սկզբունքը կարելի է հակիրճ ձևակերպել հետևյալ կերպ.
Դուք պետք է պահեք արտացոլման վերջնական արդյունքը որպես պատվիրակ, որը պարունակում է կազմված գործառույթը: Նաև իմաստ ունի քեշավորել բոլոր անհրաժեշտ օբյեկտները տիպի տեղեկություններով ձեր տեսակի՝ աշխատողի դաշտերում, որոնք պահվում են օբյեկտներից դուրս:

Սրա մեջ տրամաբանություն կա. Առողջ դատողությունը մեզ ասում է, որ եթե ինչ-որ բան կարելի է հավաքել և քեշ պահել, ապա դա պետք է արվի:

Առաջ նայելով, պետք է ասել, որ արտացոլման հետ աշխատելիս քեշն ունի իր առավելությունները, նույնիսկ եթե դուք չեք օգտագործում արտահայտություններ կազմելու առաջարկված մեթոդը։ Փաստորեն, այստեղ ես պարզապես կրկնում եմ հոդվածի հեղինակի այն թեզերը, որոնց վերևում անդրադարձել եմ։

Հիմա կոդի մասին։ Դիտարկենք մի օրինակ, որը հիմնված է իմ վերջին ցավի վրա, որը ես ստիպված էի բախվել լուրջ վարկային հաստատության լուրջ արտադրությունում: Բոլոր սուբյեկտները ֆիկտիվ են, որպեսզի ոչ ոք չկռահի։

Ինչ-որ էություն կա. Թող լինի Կապ: Կան ստանդարտացված մարմնով տառեր, որոնցից վերլուծիչն ու հիդրատորը ստեղծում են նույն կոնտակտները։ Նամակ եկավ, մենք կարդացինք այն, վերլուծեցինք բանալի-արժեք զույգերի, ստեղծեցինք կոնտակտ և պահեցինք տվյալների բազայում:

Դա տարրական է: Ենթադրենք, կոնտակտն ունի լրիվ անուն, տարիք և կոնտակտային հեռախոս հատկությունները: Այս տվյալները փոխանցվում են նամակում։ Բիզնեսը նաև ցանկանում է աջակցություն, որպեսզի կարողանա արագորեն ավելացնել նոր բանալիներ՝ տառերի տեքստում զույգերի ձևավորման համար: Այն դեպքում, երբ ինչ-որ մեկը կաղապարում տառասխալ է թույլ տվել, կամ եթե մինչև թողարկումը անհրաժեշտ է շտապ գործարկել քարտեզագրումը նոր գործընկերից՝ հարմարվելով նոր ձևաչափին։ Այնուհետև մենք կարող ենք ավելացնել քարտեզագրման նոր հարաբերակցություն որպես էժան տվյալների ամրացում: Այսինքն՝ կյանքի օրինակ։

Իրականացնում ենք, ստեղծում թեստեր։ Աշխատանքներ.

Ես չեմ տրամադրի կոդը. կան բազմաթիվ աղբյուրներ, և դրանք հասանելի են GitHub-ում՝ հոդվածի վերջում նշված հղման միջոցով: Դուք կարող եք դրանք բեռնել, անճանաչելիորեն տանջել և չափել, քանի որ դա կազդի ձեր դեպքում: Ես կտամ միայն երկու կաղապարային մեթոդների կոդը, որոնք տարբերում են հիդրատորը, որը պետք է արագ լիներ, հիդրատորից, որը պետք է դանդաղ լիներ:

Տրամաբանությունը հետևյալն է. կաղապարի մեթոդը ստանում է հիմնական վերլուծիչ տրամաբանությամբ գեներացված զույգեր։ LINQ շերտը վերլուծիչն է և հիդրատորի հիմնական տրամաբանությունը, որը հարցում է կատարում տվյալների բազայի համատեքստին և համեմատում է ստեղները վերլուծիչի զույգերի հետ (այս գործառույթների համար կա կոդ առանց LINQ-ի համեմատության համար): Այնուհետև զույգերը փոխանցվում են հիդրատացման հիմնական մեթոդին, և զույգերի արժեքները սահմանվում են կազմակերպության համապատասխան հատկություններին:

«Արագ» (նախածանց «Արագ» հենանիշերում).

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

Ինչպես տեսնում ենք, օգտագործվում է ստատիկ հավաքածու՝ setter-ի հատկություններով. Ստեղծված է հետևյալ կոդով.

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

Ընդհանուր առմամբ պարզ է. Մենք անցնում ենք հատկությունները, ստեղծում պատվիրակներ նրանց համար, որոնք կանչում են կարգավորիչներ և պահպանում դրանք: Հետո անհրաժեշտության դեպքում զանգում ենք։

«Դանդաղ» (Նախածանց Slow հենանիշերում).

        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-ը և ուսումնասիրենք կատարումը: Եվ հանկարծ... (սպոյլեր. սա ճիշտ արդյունքը չէ, մանրամասները՝ ստորև)

Անհաջող հոդված՝ արտացոլման արագացման մասին

Ի՞նչ ենք մենք տեսնում այստեղ: Մեթոդները, որոնք հաղթականորեն կրում են Արագ նախածանցը, պարզվում է, որ ավելի դանդաղ են գրեթե բոլոր անցումներում, քան Slow նախածանցով մեթոդները: Սա ճիշտ է ինչպես տեղաբաշխման, այնպես էլ աշխատանքի արագության համար: Մյուս կողմից, քարտեզագրման գեղեցիկ և էլեգանտ իրականացումը, օգտագործելով LINQ մեթոդները, որոնք նախատեսված են դրա համար, որտեղ հնարավոր է, ընդհակառակը, զգալիորեն նվազեցնում է արտադրողականությունը: Տարբերությունը կարգի է. Միտումը չի փոխվում տարբեր քանակի փոխանցումներով: Տարբերությունը միայն մասշտաբի մեջ է: LINQ-ով այն 4-200 անգամ ավելի դանդաղ է, մոտավորապես նույն մասշտաբով ավելի շատ աղբ կա:

Թարմացվել

Ես չէի հավատում իմ աչքերին, բայց ավելի կարևոր է, որ մեր գործընկերը չէր հավատում ոչ իմ աչքերին, ոչ իմ ծածկագրին. Դմիտրի Տիխոնով 0x1000000. Կրկնակի ստուգելով իմ լուծումը՝ նա փայլուն կերպով հայտնաբերեց և մատնանշեց մի սխալ, որը ես բաց եմ թողել իրականացման մի շարք փոփոխությունների պատճառով՝ սկզբից մինչև վերջնական: Moq setup-ում հայտնաբերված սխալը շտկելուց հետո բոլոր արդյունքներն իրենց տեղը եկան: Ըստ վերստուգման արդյունքների՝ հիմնական միտումը չի փոխվում. LINQ-ը դեռ ավելի շատ ազդում է աշխատանքի վրա, քան արտացոլման վրա: Այնուամենայնիվ, հաճելի է, որ Expression կոմպիլյացիայի հետ աշխատանքն իզուր չի արվում, և արդյունքը տեսանելի է ինչպես տեղաբաշխման, այնպես էլ կատարման ժամանակում։ Առաջին գործարկումը, երբ ստատիկ դաշտերը սկզբնավորվում են, բնականաբար ավելի դանդաղ է «արագ» մեթոդի համար, բայց հետո իրավիճակը փոխվում է:

Ահա վերստուգման արդյունքը.

Անհաջող հոդված՝ արտացոլման արագացման մասին

Եզրակացություն. ձեռնարկությունում արտացոլումն օգտագործելիս հատուկ կարիք չկա հնարքների դիմելու. LINQ-ն ավելի շատ կխլի արտադրողականությունը: Այնուամենայնիվ, բարձր բեռնվածության մեթոդներում, որոնք պահանջում են օպտիմալացում, դուք կարող եք պահպանել արտացոլումը սկզբնավորիչների և պատվիրակողների տեսքով, որոնք այնուհետև կապահովեն «արագ» տրամաբանություն: Այս կերպ Դուք կարող եք պահպանել և՛ արտացոլման ճկունությունը, և՛ կիրառման արագությունը:

Հենանիշի կոդը հասանելի է այստեղ: Յուրաքանչյուր ոք կարող է կրկնակի ստուգել իմ խոսքերը.
Habra ReflectionTests

Հ.Գ. թեստերի կոդը օգտագործում է IoC, իսկ հենանիշերում՝ բացահայտ կառուցվածք: Փաստն այն է, որ վերջնական իրականացման ժամանակ ես կտրեցի բոլոր գործոնները, որոնք կարող էին ազդել աշխատանքի վրա և արդյունքը աղմկոտ դարձնել:

PPS: Շնորհակալություն օգտվողին Դմիտրի Տիխոնով @0x1000000 Moq-ի տեղադրման իմ սխալը հայտնաբերելու համար, որն ազդել է առաջին չափումների վրա: Եթե ​​ընթերցողներից որևէ մեկն ունի բավարար կարմա, խնդրում ենք հավանել այն: Մարդը կանգ առավ, մարդը կարդաց, տղամարդը կրկնակի ստուգեց ու մատնանշեց սխալը։ Կարծում եմ՝ սա հարգանքի ու կարեկցանքի է արժանի։

PPPS. շնորհակալություն բծախնդիր ընթերցողին, ով հասավ ոճի և դիզայնի հիմքերին: Ես միատեսակության և հարմարավետության կողմնակից եմ։ Ներկայացման դիվանագիտությունը շատ ցանկալի է թողնում, բայց ես հաշվի առա քննադատությունը։ Խնդրում եմ արկը։

Source: www.habr.com

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