පරාවර්තනය වේගවත් කිරීම පිළිබඳ අසාර්ථක ලිපිය

මම වහාම ලිපියේ මාතෘකාව පැහැදිලි කරමි. මුල් සැලැස්ම වූයේ සරල නමුත් යථාර්ථවාදී උදාහරණයක් භාවිතා කරමින් පරාවර්තනය භාවිතය වේගවත් කිරීම සඳහා හොඳ, විශ්වාසදායක උපදෙස් ලබා දීමයි, නමුත් මිණුම් සලකුණු කිරීමේදී පරාවර්තනය මා සිතූ තරම් මන්දගාමී නොවන බව පෙනී ගියේය, මගේ බියකරු සිහින වලට වඩා LINQ මන්දගාමී වේ. ඒත් අන්තිමට තේරුනා මටත් මිම්ම වැරදුනා කියලා... මේ ජීවිත කතාවේ විස්තර කට් එක යට කමෙන්ට් වල තියෙනවා. උදාහරණයක් සාමාන්‍යයෙන් ව්‍යවසායයක සිදු කරන පරිදි ප්‍රතිපත්තිමය වශයෙන් ක්‍රියාත්මක වන අතර එය ඉතා සාමාන්‍ය දෙයක් වන බැවින්, එය මට පෙනෙන පරිදි, ජීවිතය නිරූපණය කිරීම තරමක් සිත්ගන්නා සුළු විය: ලිපියේ ප්‍රධාන විෂයයේ වේගය කෙරෙහි ඇති වූ බලපෑම බාහිර තර්කනය හේතුවෙන් සැලකිය නොහැක: Moq, Autofac, EF Core සහ වෙනත් "bandings".

මම මෙම ලිපියේ හැඟීම යටතේ වැඩ කිරීමට පටන් ගතිමි: පරාවර්තනය මන්දගාමී වන්නේ ඇයි?

ඔබට පෙනෙන පරිදි, යෙදුම විශාල ලෙස වේගවත් කිරීම සඳහා විශිෂ්ට ක්රමයක් ලෙස සෘජුවම පරාවර්තන ආකාරයේ ක්රම ඇමතීම වෙනුවට සම්පාදනය කරන ලද නියෝජිතයන් භාවිතා කිරීමට කතුවරයා යෝජනා කරයි. ඇත්ත වශයෙන්ම, IL විමෝචනය ඇත, නමුත් මම එය වළක්වා ගැනීමට කැමැත්තෙමි, මන්ද මෙය කාර්යය ඉටු කිරීමට වඩාත්ම ශ්‍රම-දැඩි ක්‍රමය වන අතර එය දෝෂ වලින් පිරී ඇත.

පරාවර්තනයේ වේගය පිළිබඳව මා සෑම විටම සමාන මතයක් දැරූ බව සලකන විට, කතුවරයාගේ නිගමන ගැන ප්රශ්න කිරීමට මම විශේෂයෙන් අදහස් නොකළෙමි.

ව්‍යවසාය තුළ මම බොහෝ විට බොළඳ ලෙස පරාවර්තනය භාවිතා කරමි. වර්ගය ගනු ලැබේ. දේපල පිළිබඳ තොරතුරු ගනු ලැබේ. SetValue ක්‍රමය හඳුන්වනු ලබන අතර සෑම කෙනෙකුම ප්‍රීති වේ. ඉලක්ක ක්ෂේත්රයේ වටිනාකම පැමිණ ඇත, හැමෝම සතුටුයි. ඉතා බුද්ධිමත් පුද්ගලයන් - ජ්‍යෙෂ්ඨයන් සහ කණ්ඩායම් නායකයින් - එක් වර්ගයක තවත් වර්ගයක “විශ්වීය” සිතියම්කරුවන් එවැනි බොළඳ ක්‍රියාත්මක කිරීමක් මත පදනම්ව, ඔවුන්ගේ දිගුව වස්තුවට ලියන්න. සාරය සාමාන්‍යයෙන් මෙයයි: අපි සියලුම ක්ෂේත්‍ර ගන්නෙමු, සියලු ගුණාංග ගන්නෙමු, ඒවා මත පුනරුච්චාරණය කරන්නෙමු: වර්ගයේ සාමාජිකයින්ගේ නම් ගැලපේ නම්, අපි SetValue ක්‍රියාත්මක කරමු. වරින් වර අපි එක් වර්ගයක යම් දේපලක් සොයා නොගත් වැරදි නිසා ව්‍යතිරේක අල්ලා ගනිමු, නමුත් මෙහි පවා කාර්ය සාධනය වැඩි දියුණු කරන ක්‍රමයක් තිබේ. උත්සාහ කරන්න / අල්ලා ගන්න.

මම දැකලා තියෙනවා මිනිස්සු තමන්ට කලින් ආපු යන්ත්‍ර ක්‍රියා කරන ආකාරය ගැන සම්පූර්ණයෙන් සන්නද්ධ නොවී විග්‍රහ කරන්නන් සහ සිතියම් කරන්නන් ප්‍රතිනිර්මාණය කරනවා. මිනිසුන් ඔවුන්ගේ බොළඳ ක්‍රියාවලීන් උපාය මාර්ග පිටුපස, අතුරුමුහුණත් පිටුපස, එන්නත් පිටුපස සඟවාගෙන සිටිනු මම දැක ඇත්තෙමි. එහෙම අවබෝධයෙන් මම නහය වැනුවා. ඇත්ත වශයෙන්ම, මම සැබෑ කාර්ය සාධන කාන්දුව මැනිය නොහැකි වූ අතර, හැකි නම්, මම එය මගේ අතට ගත හැකි නම්, මම සරලව ක්රියාත්මක කිරීම වඩාත් "ප්රශස්ත" ලෙස වෙනස් කළෙමි. එමනිසා, පහත සාකච්ඡා කළ පළමු මිනුම් මා බරපතල ලෙස ව්යාකූල කළේය.

මම හිතන්නේ ඔබගෙන් බොහෝ දෙනෙක්, රිච්ටර් හෝ වෙනත් දෘෂ්ටිවාදින් කියවන විට, කේතයේ පරාවර්තනය යෙදුමේ ක්‍රියාකාරිත්වයට අතිශයින් ඍණාත්මක බලපෑමක් ඇති කරන සංසිද්ධියක් බවට සම්පූර්ණයෙන්ම සාධාරණ ප්‍රකාශයක් හමු වී ඇත.

ඇමතුම් පරාවර්තනය CLR හට ඔවුන්ට අවශ්‍ය එක සොයා ගැනීමට, ඒවායේ පාර-දත්ත ඉහළට ඇදීමට, ඒවා විග්‍රහ කිරීමට යනාදිය සඳහා එකලස් කිරීම් හරහා යාමට බල කරයි. මීට අමතරව, අනුපිළිවෙල හරහා ගමන් කරන අතරතුර පරාවර්තනය විශාල මතක ප්‍රමාණයක් වෙන් කිරීමට හේතු වේ. අපි මතකය භාවිතා කරමින් සිටිමු, CLR GC අනාවරණය කර ෆ්‍රයිස් ආරම්භ වේ. එය සැලකිය යුතු ලෙස මන්දගාමී විය යුතුය, මාව විශ්වාස කරන්න. නවීන නිෂ්පාදන සේවාදායක හෝ වලාකුළු යන්ත්‍රවල ඇති විශාල මතක ප්‍රමාණය ඉහළ සැකසුම් ප්‍රමාදයන් වළක්වන්නේ නැත. ඇත්ත වශයෙන්ම, මතකය වැඩි වන තරමට, GC ක්‍රියා කරන ආකාරය ගැන ඔබ අවධානය යොමු කිරීමට වැඩි ඉඩක් ඇත. පරාවර්තනය, න්‍යායාත්මකව, ඔහුට අමතර රතු කඩමාල්ලකි.

කෙසේ වෙතත්, අපි සියල්ලෝම IoC බහාලුම් සහ දින සිතියම් භාවිතා කරන අතර, එහි මෙහෙයුම් මූලධර්මය ද පරාවර්තනය මත පදනම් වේ, නමුත් සාමාන්‍යයෙන් ඒවායේ ක්‍රියාකාරිත්වය පිළිබඳ ප්‍රශ්න නොමැත. නැත, බාහිර සීමිත සන්දර්භ ආකෘතිවලින් පරායත්තතා හඳුන්වාදීම සහ වියුක්ත කිරීම ඉතා අවශ්‍ය නිසා අපට ඕනෑම අවස්ථාවක කාර්ය සාධනය කැප කිරීමට සිදු වේ. සෑම දෙයක්ම සරලයි - එය ඇත්ත වශයෙන්ම කාර්ය සාධනයට බලපාන්නේ නැත.

කාරණය වන්නේ පරාවර්තන තාක්ෂණය මත පදනම් වූ වඩාත් පොදු රාමු එය වඩාත් ප්රශස්ත ලෙස වැඩ කිරීමට සියලු ආකාරයේ උපක්රම භාවිතා කිරීමයි. සාමාන්යයෙන් මෙය හැඹිලියකි. සාමාන්‍යයෙන් මේවා ප්‍රකාශන ගසෙන් සම්පාදනය කරන ලද ප්‍රකාශන සහ නියෝජිතයන් වේ. එම ස්වයංක්‍රීය නිෂ්පාදකයා විසින් පරාවර්තනය කැඳවීමකින් තොරව එකක් තවත් එකක් බවට පරිවර්තනය කළ හැකි ශ්‍රිත සහිත වර්ගවලට ගැලපෙන තරඟකාරී ශබ්ද කෝෂයක් පවත්වාගෙන යයි.

මෙය සාක්ෂාත් කර ගන්නේ කෙසේද? අත්‍යවශ්‍යයෙන්ම, මෙය JIT කේතය ජනනය කිරීමට වේදිකාවම භාවිතා කරන තර්කයෙන් වෙනස් නොවේ. පළමු වරට ක්‍රමයක් කැඳවූ විට, එය සම්පාදනය කරනු ලැබේ (සහ, ඔව්, මෙම ක්‍රියාවලිය වේගවත් නොවේ); පසුව ලැබෙන ඇමතුම් වලදී, පාලනය දැනටමත් සම්පාදනය කර ඇති ක්‍රමයට මාරු කරනු ලැබේ, සහ සැලකිය යුතු කාර්ය සාධන අඩුවීමක් සිදු නොවේ.

අපගේ නඩුවේදී, ඔබට JIT සම්පාදනය භාවිතා කළ හැකි අතර පසුව එහි AOT සගයන් හා සමාන කාර්ය සාධනයක් සමඟ සම්පාදනය කරන ලද හැසිරීම භාවිතා කළ හැකිය. මෙම නඩුවේ ප්රකාශයන් අපගේ උපකාරයට පැමිණෙනු ඇත.

අදාළ මූලධර්මය කෙටියෙන් පහත පරිදි සකස් කළ හැක.
සම්පාදනය කරන ලද ශ්‍රිතය අඩංගු නියෝජිතයෙකු ලෙස ඔබ පරාවර්තනයේ අවසාන ප්‍රතිඵලය හැඹිලිගත කළ යුතුය. වස්තු වලින් පිටත ගබඩා කර ඇති ඔබේ වර්ගයේ, සේවකයාගේ ක්ෂේත්‍රවල වර්ග තොරතුරු සමඟ අවශ්‍ය සියලුම වස්තූන් හැඹිලිගත කිරීම අර්ථවත් කරයි.

මේකේ තර්කයක් තියෙනවා. සාමාන්‍ය බුද්ධිය අපට පවසන්නේ යමක් සම්පාදනය කර හැඹිලිගත කළ හැකි නම් එය කළ යුතු බවයි.

ඉදිරිය දෙස බලන විට, ඔබ ප්‍රකාශන සම්පාදනය කිරීමේ යෝජිත ක්‍රමය භාවිතා නොකළත්, පරාවර්තනය සමඟ වැඩ කිරීමේදී හැඹිලියට එහි වාසි ඇති බව පැවසිය යුතුය. ඇත්ත වශයෙන්ම, මෙහි මම ඉහත සඳහන් කරන ලිපියේ කතුවරයාගේ නිබන්ධන නැවත නැවත කියමි.

දැන් කේතය ගැන. බරපතල ණය ආයතනයක බරපතල නිෂ්පාදනයකදී මට මුහුණ දීමට සිදු වූ මගේ මෑත වේදනාව පදනම් කරගත් උදාහරණයක් බලමු. කිසිවකුට අනුමාන කිරීමට නොහැකි වන පරිදි සියලුම ආයතන ප්‍රබන්ධය.

යම් සාරයක් තිබේ. සම්බන්ධ වීමට ඉඩ දෙන්න. ප්‍රමිතිගත ශරීරයක් සහිත අකුරු ඇත, එයින් විග්‍රහකය සහ හයිඩ්‍රේටරය මෙම එකම සම්බන්ධතා නිර්මාණය කරයි. ලිපියක් ආවා, අපි එය කියවා, එය යතුරු-අගය යුගල ලෙස විග්‍රහ කර, සම්බන්ධතාවක් සාදා, එය දත්ත ගබඩාවේ සුරකිමු.

ඒක ප්‍රාථමිකයි. අපි හිතමු Contact එකක සම්පුර්ණ නම, වයස සහ Contact Phone යන ගුණාංග තියෙනවා කියලා. මෙම දත්ත ලිපියෙන් සම්ප්රේෂණය වේ. ව්‍යාපාරයට අවශ්‍ය වන්නේ ලිපියේ අන්තර්ගත කොටස් යුගල වශයෙන් සිතියම්ගත කිරීම සඳහා නව යතුරු ඉක්මනින් එක් කිරීමට හැකි වීමයි. යමෙක් අච්චුවෙහි අකුරු දෝෂයක් කර ඇත්නම් හෝ මුදා හැරීමට පෙර නව ආකෘතියට අනුවර්තනය වෙමින් නව හවුල්කරුවෙකුගෙන් සිතියම්ගත කිරීම හදිසියේ දියත් කිරීමට අවශ්‍ය වේ. එතකොට අපිට පුළුවන් අලුත් සිතියම්කරණ සහසම්බන්ධයක් ලාභ datafix එකක් විදියට එකතු කරන්න. එනම් ජීවිත ආදර්ශයකි.

අපි ක්රියාත්මක කරන්න, පරීක්ෂණ නිර්මාණය කරන්න. කටයුතු.

මම කේතය ලබා නොදෙනු ඇත: මූලාශ්‍ර රාශියක් ඇති අතර ඒවා ලිපියේ අවසානයේ ඇති සබැඳිය හරහා 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;
        }

අපට පෙනෙන පරිදි, සකසන ගුණාංග සහිත ස්ථිතික එකතුවක් භාවිතා වේ - සකසන ලද ලැම්ඩාස් සෙටර් ආයතනය ලෙස හැඳින්වේ. පහත කේතය මගින් සාදන ලදී:

        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 ගුණයකින් මන්දගාමී වේ, ආසන්න වශයෙන් එකම පරිමාණයේ කුණු වැඩි වේ.

යාවත්කාලීන කිරීම

මම මගේ ඇස් විශ්වාස කළේ නැත, නමුත් වඩාත් වැදගත් ලෙස, අපගේ සගයා මගේ ඇස් හෝ මගේ කේතය විශ්වාස කළේ නැත - Dmitry Tikhonov 0x1000000. මගේ විසඳුම දෙවරක් පරීක්ෂා කර, ඔහු විශිෂ්ට ලෙස සොයාගෙන, ක්‍රියාත්මක කිරීමේ වෙනස්කම් ගණනාවක් නිසා මුල සිට අවසාන දක්වා මට මග හැරුණු දෝෂයක් පෙන්වා දුන්නේය. Moq සැකසුම තුළ සොයාගත් දෝෂය නිවැරදි කිරීමෙන් පසුව, සියලු ප්රතිඵල නිසි තැනට වැටුණි. නැවත පරීක්ෂා කිරීමේ ප්‍රතිඵලවලට අනුව, ප්‍රධාන ප්‍රවණතාවය වෙනස් නොවේ - LINQ තවමත් පරාවර්තනයට වඩා කාර්ය සාධනයට බලපායි. කෙසේ වෙතත්, ප්‍රකාශන සම්පාදනය කිරීමේ කාර්යය නිෂ්ඵල ලෙස සිදු නොවීම සතුටක් වන අතර, ප්‍රති result ලය වෙන් කිරීමේ සහ ක්‍රියාත්මක කිරීමේ කාලය යන දෙකෙහිම දෘශ්‍යමාන වේ. පළමු දියත් කිරීම, ස්ථිතික ක්ෂේත්ර ආරම්භ කරන විට, "වේගවත්" ක්රමය සඳහා ස්වභාවිකවම මන්දගාමී වේ, නමුත් පසුව තත්වය වෙනස් වේ.

නැවත පරීක්ෂණයේ ප්‍රතිඵලය මෙන්න:

පරාවර්තනය වේගවත් කිරීම පිළිබඳ අසාර්ථක ලිපිය

නිගමනය: ව්‍යවසායයක පරාවර්තනය භාවිතා කරන විට, උපක්‍රම භාවිතා කිරීමට විශේෂ අවශ්‍යතාවයක් නොමැත - LINQ ඵලදායිතාව වැඩි වශයෙන් අනුභව කරයි. කෙසේ වෙතත්, ප්‍රශස්තිකරණය අවශ්‍ය වන අධි බර ක්‍රම වලදී, ඔබට ආරම්භක සහ නියෝජිත සම්පාදක ආකාරයෙන් පරාවර්තනය සුරැකිය හැක, එවිට "වේගවත්" තර්කනය ලබා දෙනු ඇත. මේ ආකාරයෙන් ඔබට පරාවර්තනයේ නම්‍යශීලී බව සහ යෙදුමේ වේගය යන දෙකම පවත්වා ගත හැකිය.

මිණුම් සලකුණු කේතය මෙහි ඇත. ඕනෑම කෙනෙකුට මගේ වචන දෙවරක් පරීක්ෂා කළ හැක:
හබ්රා පරාවර්තන පරීක්ෂණ

PS: පරීක්ෂණ වල කේතය IoC භාවිතා කරන අතර මිණුම් සලකුණු වල එය පැහැදිලි නිර්මාණයක් භාවිතා කරයි. කාරණය වන්නේ අවසාන ක්රියාත්මක කිරීමේදී මම කාර්ය සාධනයට බලපෑම් කළ හැකි සියලු සාධක කපා හැරීම සහ ප්රතිඵලය ඝෝෂාකාරී වීමයි.

PPS: පරිශීලකයාට ස්තූතියි Dmitry Tikhonov @0x1000000 පළමු මිනුම් වලට බලපෑ Moq පිහිටුවීමේ මගේ දෝෂය සොයා ගැනීම සඳහා. පාඨකයන්ට ප්‍රමාණවත් කර්මයක් ඇත්නම් එයට ලයික් කරන්න. මිනිහා නැවැත්තුවා, මිනිහා කියෙව්වා, මිනිහා දෙපාරක් චෙක් කරලා වැරැද්ද පෙන්නුවා. මෙය ගෞරවය හා අනුකම්පාව ලැබිය යුතු යැයි මම සිතමි.

PPPS: ශෛලියේ සහ මෝස්තරයේ පහළට පැමිණි සූක්ෂම පාඨකයාට ස්තූතියි. මම ඒකාකාරී බව සහ පහසුව සඳහා සිටිමි. ඉදිරිපත් කිරීමේ රාජ්‍ය තාන්ත්‍රිකභාවය අපේක්ෂා කිරීමට බොහෝ දේ ඉතිරි කරයි, නමුත් මම විවේචන සැලකිල්ලට ගත්තෙමි. මම ප්‍රක්ෂේපණය ඉල්ලමි.

මූලාශ්රය: www.habr.com

අදහස් එක් කරන්න