Рефлексияны жеделдету туралы сәтсіз мақала

Мен мақаланың тақырыбын бірден түсіндіремін. Бастапқы жоспар қарапайым, бірақ шынайы мысалды пайдалана отырып, рефлексияны пайдалануды тездету туралы жақсы, сенімді кеңес беру болды, бірақ салыстыру кезінде рефлексия мен ойлағандай баяу емес екені анықталды, LINQ менің қорқынышты түсімдерімнен гөрі баяуырақ. Бірақ, ақырында өлшегенде менің де қателескенім анықталды... Бұл өмір тарихының егжей-тегжейлері кесіндінің астында және түсініктемелерде. Мысал кәдімгі және әдетте кәсіпорында орындалатындай принципті түрде жүзеге асырылатындықтан, бұл өте қызықты болды, менің ойымша, өмірлік демонстрация: мақаланың негізгі тақырыбының жылдамдығына әсері болды. сыртқы логикаға байланысты байқалмайды: Moq, Autofac, EF Core және басқалар «жолақтар».

Мен осы мақаланың әсерімен жұмыс істей бастадым: Неліктен Рефлексия баяу

Көріп отырғаныңыздай, автор қосымшаны айтарлықтай жылдамдатудың тамаша тәсілі ретінде рефлексия түрінің әдістерін тікелей шақырудың орнына құрастырылған делегаттарды пайдалануды ұсынады. Әрине, IL эмиссиясы бар, бірақ мен одан аулақ болғым келеді, өйткені бұл қателіктерге толы тапсырманы орындаудың ең көп еңбекті қажет ететін тәсілі.

Мен рефлексия жылдамдығы туралы әрқашан осындай пікірде болғанымды ескере отырып, мен автордың тұжырымдарына күмән келтіргім келмеді.

Мен кәсіпорында рефлексияны аңғал пайдалануды жиі кездестіремін. Түрі алынады. Мүлік туралы ақпарат алынады. SetValue әдісі шақырылады және барлығы қуанады. Мақсатты өріске мән жетті, бәрі риза. Өте ақылды адамдар – аға буын өкілдері мен топ жетекшілері – бір түрдегі «әмбебап» карта жасаушылардың осындай аңғал іске асуына негізделе отырып, объектіге кеңейтімдерін жазады. Мәні әдетте мынада: біз барлық өрістерді аламыз, барлық қасиеттерді аламыз, олардың үстінен қайталаймыз: егер тип мүшелерінің атаулары сәйкес келсе, SetValue орындаймыз. Кейде біз түрлердің бірінде қандай да бір қасиет таппаған қателерге байланысты ерекше жағдайларды байқаймыз, бірақ мұнда өнімділікті жақсартатын шығу жолы бар. Байқап көріңіз/ұстаңыз.

Мен адамдардың өздеріне дейінгі машиналар қалай жұмыс істейтіні туралы ақпаратпен толық қаруланбай, талдаушылар мен карта жасаушыларды қайта ойлап тапқанын көрдім. Мен адамдар өздерінің аңғал іске асыруларын стратегиялардың, интерфейстердің, инъекциялардың артына жасыратынын көрдім, бұл кейінгі баканалияны ақтайтын сияқты. Мен осындай түсініктерге мұрынды болдым. Шындығында, мен нақты өнімділіктің ағып кетуін өлшеген жоқпын және егер мүмкін болса, мен оны қолыма алсам, оны «оңтайлы» етіп өзгерттім. Сондықтан төменде талқыланған алғашқы өлшемдер мені қатты шатастырды.

Менің ойымша, сіздердің көпшілігіңіз Рихтерді немесе басқа идеологтарды оқи отырып, кодтағы рефлексия қолданбаның жұмысына өте теріс әсер ететін құбылыс екендігі туралы толық әділ мәлімдемеге тап болдыңыз.

Шақыру шағылыстыру CLR-ді қажеттісін табу, метадеректерін алу, оларды талдау және т.б. үшін жинақтардан өтуге мәжбүр етеді. Сонымен қатар, тізбектерді өту кезінде шағылысу жадының үлкен көлемін бөлуге әкеледі. Біз жадты пайдаланып жатырмыз, CLR GC ашады және фриздер басталады. Бұл айтарлықтай баяу болуы керек, маған сеніңіз. Заманауи өндірістік серверлердегі немесе бұлттық машиналардағы жадтың үлкен көлемі өңдеудің жоғары кідірістерін болдырмайды. Шындығында, жад неғұрлым көп болса, GC қалай жұмыс істейтінін БАЙҚАУ ықтималдығы соғұрлым жоғары болады. Рефлексия - бұл теорияда ол үшін қосымша қызыл шүберек.

Дегенмен, біз барлығымыз IoC контейнерлері мен күнді салыстыру құралдарын пайдаланамыз, олардың жұмыс принципі де шағылыстыруға негізделген, бірақ әдетте олардың өнімділігі туралы сұрақтар туындамайды. Жоқ, сыртқы шектеулі контекстік үлгілерден тәуелділік пен абстракцияны енгізу соншалықты қажет болғандықтан, біз кез келген жағдайда өнімділікті құрбан етуіміз керек. Барлығы қарапайым - бұл өнімділікке айтарлықтай әсер етпейді.

Шындығында, рефлексия технологиясына негізделген ең кең таралған құрылымдар онымен оңтайлы жұмыс істеу үшін барлық трюктерді пайдаланады. Әдетте бұл кэш. Әдетте бұл өрнектер ағашынан құрастырылған өрнектер мен делегаттар. Бірдей автомаппер рефлексияны шақырмай-ақ бірін екіншісіне түрлендіруге болатын функциялары бар түрлерге сәйкес келетін бәсекеге қабілетті сөздікті сақтайды.

Бұған қалай қол жеткізілді? Негізінде бұл JIT кодын жасау үшін платформаның өзі қолданатын логикадан еш айырмашылығы жоқ. Әдіс алғаш рет шақырылғанда, ол құрастырылады (және, иә, бұл процесс жылдам емес); келесі шақыруларда басқару бұрыннан құрастырылған әдіске ауыстырылады және өнімділіктің айтарлықтай төмендеуі болмайды.

Біздің жағдайда сіз JIT компиляциясын пайдалана аласыз, содан кейін құрастырылған әрекетті оның AOT аналогтарымен бірдей өнімділікпен пайдалана аласыз. Бұл жағдайда бізге өрнектер көмекке келеді.

Қарастырылып отырған принципті қысқаша былайша тұжырымдауға болады:
Рефлексияның соңғы нәтижесін құрастырылған функция бар делегат ретінде кэштеу керек. Сондай-ақ, нысандардан тыс сақталған түріңіздің, жұмысшының өрістеріндегі тип ақпараты бар барлық қажетті нысандарды кэштеу мағынасы бар.

Бұл жерде логика бар. Қарапайым ақыл бізге бірдеңені құрастыруға және кэштеуге болатын болса, оны жасау керек екенін айтады.

Болашаққа қарап, өрнектерді құрастырудың ұсынылған әдісін қолданбасаңыз да, рефлексиямен жұмыс істеудегі кэштің артықшылықтары бар екенін айту керек. Негізі, бұл жерде мен жоғарыда сілтеме жасаған мақала авторының тезистерін қайталап отырмын.

Енді код туралы. Менің жақында ауыр несиелік мекеменің елеулі өндірісінде көрген ауыртпалығыма негізделген мысалды қарастырайық. Ешкім болжай алмайтындай барлық нысандар жалған.

Кейбір мәні бар. Байланыс болсын. Стандартталған корпусы бар әріптер бар, олардан талдаушы мен гидратор дәл осындай контактілерді жасайды. Хат келді, біз оны оқыдық, оны кілт-мән жұптарына талдадық, контакт құрдық және оны дерекқорға сақтадық.

Бұл бастауыш. Контактінің Толық аты, Жасы және Байланыс телефоны қасиеттері бар делік. Бұл деректер хатта беріледі. Бизнес сонымен қатар хат мәтініндегі жұптарға нысан сипаттарын салыстыру үшін жаңа кілттерді жылдам қосу мүмкіндігін қолдауды қалайды. Егер біреу үлгіде қате жіберсе немесе шығарылым алдында жаңа пішімге бейімделе отырып, жаңа серіктестен карта жасауды шұғыл іске қосу қажет болса. Содан кейін біз арзан деректерді түзету ретінде жаңа салыстыру корреляциясын қоса аламыз. Яғни, өмірлік мысал.

Біз іске асырамыз, сынақтар жасаймыз. Жұмыстар.

Мен кодты бермеймін: көптеген көздер бар және олар GitHub сайтында мақаланың соңындағы сілтеме арқылы қол жетімді. Сіз оларды жүктей аласыз, танымастай азаптай аласыз және өлшей аласыз, себебі бұл сіздің жағдайыңызға әсер етеді. Мен тек жылдам болуы керек болатын гидраторды баяу болуы керек гидратордан ажырататын екі үлгі әдісінің кодын беремін.

Логика келесідей: үлгі әдісі негізгі талдаушы логикасы арқылы жасалған жұптарды қабылдайды. LINQ деңгейі талдаушы және гидратордың негізгі логикасы болып табылады, ол дерекқор контекстіне сұраныс жасайды және кілттерді талдаушыдан жұптармен салыстырады (бұл функциялар үшін салыстыру үшін LINQ жоқ код бар). Әрі қарай, жұптар негізгі гидратация әдісіне беріледі және жұптардың мәндері нысанның сәйкес қасиеттеріне орнатылады.

«Жылдам» (бағдарламалардағы 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;
        }

Көріп отырғанымыздай, баптауыш қасиеттері бар статикалық жинақ пайдаланылады - орнатушы нысанын шақыратын құрастырылған ламбдалар. Келесі кодпен жасалған:

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

Жалпы түсінікті. Біз сипаттарды айналып өтіп, олар үшін орнатушыларды шақыратын делегаттарды жасаймыз және оларды сақтаймыз. Сосын қажет кезде хабарласамыз.

«Баяу» (баяу префикс эталондарда):

        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-ті алып, өнімділікті қарастырайық. Және кенеттен... (спойлер – бұл дұрыс нәтиже емес, толығырақ төменде)

Рефлексияны жеделдету туралы сәтсіз мақала

Мұнда не көріп тұрмыз? Жылдам префиксті жеңетін әдістер барлық өтулерде Баяу префиксі бар әдістерге қарағанда баяуырақ болады. Бұл бөлуге де, жұмыс жылдамдығына да қатысты. Екінші жағынан, мүмкіндігінше осы мақсатқа арналған LINQ әдістерін қолданып карталаудың әдемі және талғампаз орындалуы, керісінше, өнімділікті айтарлықтай төмендетеді. Айырмашылық тәртіпте. Әртүрлі өту сандарымен тренд өзгермейді. Жалғыз айырмашылық - масштабта. LINQ көмегімен ол 4 - 200 есе баяу, шамамен бірдей масштабта қоқыс көп.

Жаңарған

Мен өз көзіме сенбедім, бірақ ең бастысы, әріптесіміз менің көзіме де, менің кодыма да сенбеді - Дмитрий Тихонов 0x1000000. Менің шешімімді екі рет тексеріп, ол керемет түрде тапты және іске асырудағы бірқатар өзгерістерге байланысты жіберіп алған қатені көрсетті, бастапқыдан соңғыға дейін. Moq орнатуында табылған қатені жөндегеннен кейін барлық нәтижелер орнына түсті. Қайта сынау нәтижелеріне сәйкес, негізгі тренд өзгермейді - LINQ әлі де рефлексиядан гөрі өнімділікке көбірек әсер етеді. Дегенмен, өрнектерді құрастыру жұмысының бекер жасалмағаны және нәтиже бөлуде де, орындау уақытында да көрінетіні жақсы. Статикалық өрістер инициализацияланған кезде бірінші іске қосу «жылдам» әдіс үшін табиғи түрде баяуырақ, бірақ содан кейін жағдай өзгереді.

Міне, қайта тестілеудің нәтижесі:

Рефлексияны жеделдету туралы сәтсіз мақала

Қорытынды: кәсіпорында рефлексияны пайдаланған кезде трюктерге жүгінудің қажеті жоқ - LINQ өнімділікті көбірек жейді. Дегенмен, оңтайландыруды қажет ететін жоғары жүктеме әдістерінде сіз инициализаторлар және өкілдік құрастырушылар түрінде көріністі сақтай аласыз, олар кейін «жылдам» логиканы қамтамасыз етеді. Осылайша сіз рефлексия икемділігін де, қолданбаның жылдамдығын да сақтай аласыз.

Эталондық код осында қол жетімді. Кез келген адам менің сөздерімді екі рет тексере алады:
HabraReflectionTests

PS: сынақтардағы код IoC пайдаланады, ал эталондарда ол айқын құрылымды пайдаланады. Өйткені, түпкілікті іске асыруда мен өнімділікке әсер ететін және нәтижені шулы ететін барлық факторларды кесіп тастадым.

PPS: Пайдаланушыға рахмет Дмитрий Тихонов @0x1000000 Moq орнатудағы қателігімді анықтағаны үшін, ол бірінші өлшемдерге әсер етті. Оқырмандардың біреуінің кармасы жеткілікті болса, лайк басыңыз. Адам тоқтады, адам оқыды, адам екі рет тексеріп, қатесін көрсетті. Бұл құрмет пен жанашырлыққа лайық деп ойлаймын.

PPPS: стиль мен дизайнның түбіне жеткен мұқият оқырманға рахмет. Мен біркелкілік пен ыңғайлылықты жақтаймын. Тұсаукесер дипломатиясы көп нәрсені қалаусыз қалдырады, бірақ мен сынды ескердім. Мен снарядты сұраймын.

Ақпарат көзі: www.habr.com

пікір қалдыру