Yansıtma sürətləndirilməsi ilə bağlı uğursuz məqalə

Dərhal məqalənin adını açıqlayacağam. Orijinal plan sadə, lakin real nümunədən istifadə edərək əks etdirmənin istifadəsini sürətləndirmək üçün yaxşı, etibarlı məsləhət vermək idi, lakin müqayisə zamanı məlum oldu ki, əks etdirmə düşündüyüm qədər yavaş deyil, LINQ kabuslarımdan daha yavaşdır. Amma sonda məlum oldu ki, mən də ölçmələrdə səhv etmişəm... Bu həyat hekayəsinin təfərrüatları kəsim altında və şərhlərdədir. Nümunə olduqca adi olduğundan və adətən müəssisədə olduğu kimi prinsipcə həyata keçirildiyindən, mənə elə gəlir ki, həyatın nümayişi olduqca maraqlı oldu: məqalənin əsas mövzusunun sürətinə təsiri oldu. xarici məntiqə görə nəzərə çarpmır: Moq, Autofac, EF Core və başqaları "bandinqlər".

Bu məqalənin təəssüratı ilə işə başladım: Reflection niyə yavaşdır

Gördüyünüz kimi, müəllif tətbiqi çox sürətləndirmək üçün əla bir yol kimi birbaşa əksetmə tipli üsulları çağırmaq əvəzinə tərtib edilmiş nümayəndələrdən istifadə etməyi təklif edir. Əlbəttə ki, IL emissiyası var, amma bunun qarşısını almaq istərdim, çünki bu, səhvlərlə dolu olan tapşırığı yerinə yetirməyin ən çox əmək tələb edən yoludur.

Düşünmə sürəti ilə bağlı həmişə oxşar fikirdə olduğumu nəzərə alsaq, müəllifin nəticələrini şübhə altına almaq niyyətində deyildim.

Mən tez-tez müəssisədə təfəkkürün sadəlövh istifadəsinə rast gəlirəm. Növü alınır. Əmlak haqqında məlumat götürülür. SetValue metodu çağırılır və hamı sevinir. Hədəf sahəsinə dəyər gəldi, hər kəs xoşbəxtdir. Çox ağıllı insanlar - yaşlılar və komanda rəhbərləri - bu cür sadəlövh tətbiqi "universal" xəritəçilərin bir növün digərinə tətbiqinə əsaslanaraq, obyektə əlavələrini yazırlar. Mahiyyət adətən belədir: biz bütün sahələri götürürük, bütün xassələri götürürük, onların üzərində təkrar edirik: tip üzvlərinin adları uyğun gələrsə, SetValue-ni icra edirik. Zaman zaman növlərdən birində bəzi əmlak tapmadığımız səhvlər səbəbindən istisnaları tuturuq, lakin burada performansı yaxşılaşdıran bir çıxış yolu var. Çalışın/tutun.

Mən insanların özlərindən əvvəl gələn maşınların necə işlədiyinə dair məlumatla tam silahlanmadan analizatorları və xəritəçiləri yenidən kəşf etdiklərini görmüşəm. İnsanların sadəlövh tətbiqlərini strategiyaların, interfeyslərin, inyeksiyaların arxasında gizlətdiklərini görmüşəm, sanki bu, sonrakı bacchanalia üçün bəhanə gətirir. Bu cür anlayışlar qarşısında burnumu yuxarı qaldırdım. Əslində, mən real performans sızmasını ölçmədim və mümkünsə, əlimdən gələni edə bilsəm, tətbiqi daha "optimal" birinə dəyişdirdim. Buna görə aşağıda müzakirə edilən ilk ölçmələr məni ciddi şəkildə çaşdırdı.

Düşünürəm ki, bir çoxunuz Rixter və ya digər ideoloqları oxuyarkən, kodda əks olunmasının tətbiqin işinə son dərəcə mənfi təsir göstərən bir fenomen olduğuna dair tamamilə ədalətli bir ifadə ilə qarşılaşdınız.

Zəngin əks olunması CLR-ni lazım olanı tapmaq, metadatalarını çıxarmaq, təhlil etmək və s. üçün montajlardan keçməyə məcbur edir. Bundan əlavə, ardıcıllıqları keçərkən əks etdirmə böyük miqdarda yaddaşın ayrılmasına səbəb olur. Biz yaddaşdan istifadə edirik, CLR GC-ni açır və frizlər başlayır. Bu nəzərəçarpacaq dərəcədə yavaş olmalıdır, inanın. Müasir istehsal serverlərində və ya bulud maşınlarında böyük həcmdə yaddaş yüksək emal gecikmələrinə mane olmur. Əslində, yaddaş nə qədər çox olsa, GC-nin necə işlədiyinə diqqət yetirmək ehtimalınız bir o qədər çox olar. Refleksiya onun üçün nəzəri olaraq əlavə qırmızı bezdir.

Bununla belə, biz hamımız IoC konteynerlərindən və tarix xəritəçilərindən istifadə edirik, onların iş prinsipi də əks etdirməyə əsaslanır, lakin adətən onların performansı ilə bağlı heç bir sual yoxdur. Xeyr, ona görə deyil ki, asılılıqların tətbiqi və xarici məhdud kontekst modellərindən abstraksiya o qədər zəruridir ki, istənilən halda performansı qurban verməliyik. Hər şey daha sadədir - bu, həqiqətən performansa çox təsir etmir.

Fakt budur ki, əks texnologiyaya əsaslanan ən çox yayılmış çərçivələr onunla daha optimal işləmək üçün hər cür fəndlərdən istifadə edir. Adətən bu bir önbellekdir. Adətən bunlar ifadə ağacından tərtib edilmiş ifadələr və nümayəndələrdir. Eyni avtomapper, əksi çağırmadan bir-birini digərinə çevirə bilən funksiyaları olan növlərə uyğun gələn rəqabətli lüğət saxlayır.

Buna necə nail olunur? Əslində, bu, JIT kodu yaratmaq üçün platformanın özünün istifadə etdiyi məntiqdən heç də fərqlənmir. Metod ilk dəfə çağırıldıqda, o tərtib edilir (və bəli, bu proses sürətli deyil); sonrakı zənglərdə idarəetmə artıq tərtib edilmiş metoda keçirilir və performansda əhəmiyyətli azalmalar olmayacaqdır.

Bizim vəziyyətimizdə siz JIT tərtibindən də istifadə edə və sonra tərtib edilmiş davranışı AOT həmkarları ilə eyni performansla istifadə edə bilərsiniz. Bu işdə köməyimizə ifadələr gələcək.

Sözügedən prinsipi qısaca aşağıdakı kimi ifadə etmək olar:
Siz əks etdirmənin yekun nəticəsini tərtib edilmiş funksiyanı özündə birləşdirən nümayəndə kimi yaddaşda saxlamalısınız. Obyektlərdən kənarda saxlanılan növünüzün, işçinin sahələrində bütün lazımi obyektləri tip məlumatı ilə keş etmək də məntiqlidir.

Bunda məntiq var. Sağlam düşüncə bizə deyir ki, əgər nəyisə tərtib etmək və yaddaşda saxlamaq olarsa, o zaman bunu etmək lazımdır.

İrəliyə baxaraq, ifadələrin tərtib edilməsi üçün təklif olunan metoddan istifadə etməsəniz belə, əks ilə işləməkdə keşin üstünlükləri olduğunu söyləmək lazımdır. Əslində mən burada sadəcə yuxarıda istinad etdiyim məqalə müəllifinin tezislərini təkrar edirəm.

İndi kod haqqında. Ciddi bir kredit təşkilatının ciddi istehsalında üzləşdiyim son ağrılarımı əsas götürən bir misala baxaq. Bütün varlıqlar uydurmadır ki, heç kim təxmin etməsin.

Bəzi mahiyyət var. Əlaqə var olsun. Standartlaşdırılmış gövdəsi olan hərflər var ki, onlardan ayrışdırıcı və nəmləndirici eyni kontaktları yaradır. Məktub gəldi, biz onu oxuduq, açar-dəyər cütlərinə təhlil etdik, kontakt yaratdıq və verilənlər bazasında saxladıq.

Bu elementardır. Deyək ki, kontaktın Tam Adı, Yaşı və Əlaqə Telefonu xüsusiyyətləri var. Bu məlumatlar məktubda ötürülür. Biznes, həmçinin məktubun mətnində qurum xassələrini cütlərə xəritələşdirmək üçün yeni açarları tez əlavə etmək üçün dəstək istəyir. Şablonda kimsə yazı səhvi edibsə və ya buraxılışdan əvvəl yeni formata uyğunlaşaraq yeni tərəfdaşdan xəritələşdirməni təcili işə salmaq lazımdırsa. Sonra biz ucuz datafix kimi yeni xəritəçəkmə korrelyasiyası əlavə edə bilərik. Yəni həyat nümunəsi.

Biz həyata keçiririk, testlər yaradırıq. işləyir.

Kodu təqdim etməyəcəyəm: çoxlu mənbələr var və onlar məqalənin sonundakı link vasitəsilə GitHub-da mövcuddur. Siz onları yükləyə, tanınmayacaq dərəcədə işgəncə edə və ölçə bilərsiniz, çünki bu sizin vəziyyətinizə təsir edəcəkdir. Sürətli olması lazım olan nəmləndiricini yavaş olması lazım olan nəmləndiricidən fərqləndirən yalnız iki şablon metodun kodunu verim.

Məntiq aşağıdakı kimidir: şablon metodu əsas parser məntiqi tərəfindən yaradılan cütləri qəbul edir. LINQ təbəqəsi verilənlər bazası kontekstinə sorğu göndərən və açarları təhlilçinin cütləri ilə müqayisə edən (bu funksiyalar üçün müqayisə üçün LINQ olmadan kod var) təhlil edən və hidratatorun əsas məntiqidir. Sonra cütlər əsas nəmləndirmə metoduna keçir və cütlərin dəyərləri obyektin müvafiq xüsusiyyətlərinə təyin olunur.

"Sürətli" (qiymətləndirmələrdə Sürətli prefiks):

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

Gördüyümüz kimi, tənzimləyici xüsusiyyətləri olan statik kolleksiya istifadə olunur - təyinedici varlığı çağıran tərtib edilmiş lambdalar. Aşağıdakı kodla yaradılmışdır:

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

Ümumiyyətlə, aydındır. Biz xassələri gəzirik, onlar üçün setters çağıran nümayəndələr yaradırıq və onları saxlayırıq. Sonra lazım olanda zəng edirik.

“Yavaş” (İstiqamətlərdə Yavaş prefiks):

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

Burada biz dərhal xassələri yan keçib birbaşa SetValue çağırırıq.

Aydınlıq üçün və istinad olaraq, korrelyasiya cütlərinin dəyərlərini birbaşa obyekt sahələrinə yazan sadəlövh bir üsul tətbiq etdim. Prefiks - Əl ilə.

İndi BenchmarkDotNet-i götürək və performansı yoxlayaq. Və birdən... (spoiler - bu düzgün nəticə deyil, təfərrüatlar aşağıdadır)

Yansıtma sürətləndirilməsi ilə bağlı uğursuz məqalə

Biz burada nə görürük? Sürətli prefiksi zəfərlə daşıyan üsullar, demək olar ki, bütün keçidlərdə Yavaş prefiksli metodlardan daha yavaş olur. Bu, həm iş bölgüsü, həm də sürət üçün doğrudur. Digər tərəfdən, mümkün olan hər yerdə bunun üçün nəzərdə tutulmuş LINQ metodlarından istifadə edərək xəritəçəkmənin gözəl və zərif tətbiqi, əksinə, məhsuldarlığı xeyli azaldır. Fərq sifarişdədir. Müxtəlif keçid sayları ilə trend dəyişmir. Fərq yalnız miqyasdadır. LINQ ilə 4 - 200 dəfə yavaşdır, təxminən eyni miqyasda daha çox zibil var.

YENİLƏNİB

Mən gözlərimə inanmadım, amma daha da əsası, həmkarımız nə gözümə, nə də koduma inandı - Dmitri tixonov 0x1000000. Həllimi iki dəfə yoxlayaraq, o, ilkindən sondan tətbiqdə bir sıra dəyişikliklərə görə qaçırdığım bir səhvi parlaq şəkildə kəşf etdi və qeyd etdi. Moq quraşdırmasında tapılan səhvi düzəltdikdən sonra bütün nəticələr yerinə düşdü. Yenidən sınaq nəticələrinə görə, əsas tendensiya dəyişmir - LINQ hələ də performansa əks etdirməkdən daha çox təsir edir. Bununla belə, İfadələrin tərtib edilməsi ilə işin boş yerə getməməsi və nəticənin həm ayırma, həm də icra müddətində görünməsi xoşdur. Statik sahələr işə salındıqda ilk işə salınma "sürətli" metod üçün təbii olaraq daha yavaş olur, lakin sonra vəziyyət dəyişir.

Budur təkrar testin nəticəsi:

Yansıtma sürətləndirilməsi ilə bağlı uğursuz məqalə

Nəticə: bir müəssisədə əks etdirmə istifadə edərkən, fəndlərə müraciət etməyə xüsusi ehtiyac yoxdur - LINQ məhsuldarlığı daha çox yeyəcək. Bununla belə, optimallaşdırma tələb edən yüksək yüklü metodlarda, siz daha sonra “sürətli” məntiqi təmin edəcək ilkinləşdiricilər və nümayəndə tərtibçiləri şəklində əksini saxlaya bilərsiniz. Bu yolla siz həm əks etdirmə çevikliyini, həm də tətbiqin sürətini saxlaya bilərsiniz.

Benchmark kodu burada mövcuddur. Hər kəs sözlərimi iki dəfə yoxlaya bilər:
HabraReflection Testləri

PS: testlərdəki kod IoC-dən istifadə edir və benchmarklarda açıq bir konstruksiyadan istifadə edir. Fakt budur ki, son tətbiqdə performansa təsir edə biləcək və nəticəni səs-küylü edə biləcək bütün amilləri kəsdim.

PPS: İstifadəçiyə təşəkkür edirik Dmitri Tixonov @0x1000000 İlk ölçmələrə təsir edən Moq-un qurulmasında mənim səhvimi aşkar etdiyim üçün. Oxuculardan hər hansı birinin kifayət qədər karması varsa, bəyənin. Adam dayandı, kişi oxudu, kişi ikiqat yoxladı və səhvi göstərdi. Hesab edirəm ki, bu, hörmətə və rəğbətə layiqdir.

PPPS: üslub və dizaynın dibinə çatan vasvası oxucuya təşəkkür edirik. Mən vahidlik və rahatlıq tərəfdarıyam. Təqdimatın diplomatiyası çox arzuolunandır, lakin mən tənqidi nəzərə aldım. Zəhmət olmasa mərmiyə gedin.

Mənbə: www.habr.com

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