பிரதிபலிப்பைத் துரிதப்படுத்துவது பற்றிய தோல்வியுற்ற கட்டுரை

கட்டுரையின் தலைப்பை உடனடியாக விளக்குகிறேன். எளிமையான ஆனால் யதார்த்தமான எடுத்துக்காட்டைப் பயன்படுத்தி பிரதிபலிப்பைப் பயன்படுத்துவதை எவ்வாறு விரைவுபடுத்துவது என்பது குறித்த நல்ல, நம்பகமான ஆலோசனையை வழங்குவதே அசல் திட்டம், ஆனால் தரப்படுத்தலின் போது நான் நினைத்தது போல் பிரதிபலிப்பு மெதுவாக இல்லை, LINQ எனது கனவுகளை விட மெதுவாக உள்ளது. ஆனால் கடைசியில் நானும் அளவீடுகளில் தவறு செய்துவிட்டேன் என்று தெரிந்தது... இந்த வாழ்க்கைக் கதையின் விவரங்கள் வெட்டு மற்றும் கருத்துக்களில் உள்ளன. உதாரணம் மிகவும் பொதுவானது மற்றும் பொதுவாக ஒரு நிறுவனத்தில் செய்யப்படுவது போல் கொள்கையளவில் செயல்படுத்தப்படுவதால், இது மிகவும் சுவாரஸ்யமாக மாறியது, எனக்கு தோன்றுவது போல், வாழ்க்கையின் ஆர்ப்பாட்டம்: கட்டுரையின் முக்கிய விஷயத்தின் வேகத்தில் தாக்கம் இருந்தது. வெளிப்புற தர்க்கத்தின் காரணமாக கவனிக்கப்படவில்லை: Moq, Autofac, EF கோர் மற்றும் பிற "ஸ்ட்ராப்கள்".

இந்த கட்டுரையின் உணர்வின் கீழ் நான் வேலை செய்ய ஆரம்பித்தேன்: பிரதிபலிப்பு ஏன் மெதுவாக உள்ளது

நீங்கள் பார்க்கிறபடி, பயன்பாட்டை பெரிதும் விரைவுபடுத்துவதற்கான சிறந்த வழியாக பிரதிபலிப்பு வகை முறைகளை நேரடியாக அழைப்பதற்குப் பதிலாக தொகுக்கப்பட்ட பிரதிநிதிகளைப் பயன்படுத்த ஆசிரியர் பரிந்துரைக்கிறார். நிச்சயமாக, IL உமிழ்வு உள்ளது, ஆனால் நான் அதைத் தவிர்க்க விரும்புகிறேன், ஏனெனில் இது பணியைச் செய்வதற்கு மிகவும் உழைப்பு மிகுந்த வழியாகும், இது பிழைகள் நிறைந்தது.

பிரதிபலிப்பு வேகம் குறித்து நான் எப்போதும் இதே கருத்தைக் கொண்டிருப்பதைக் கருத்தில் கொண்டு, ஆசிரியரின் முடிவுகளை நான் கேள்வி கேட்க விரும்பவில்லை.

நிறுவனத்தில் பிரதிபலிப்பின் அப்பாவியாகப் பயன்படுத்துவதை நான் அடிக்கடி சந்திக்கிறேன். வகை எடுக்கப்பட்டது. சொத்து பற்றிய தகவல்கள் எடுக்கப்படுகின்றன. SetValue முறை அழைக்கப்படுகிறது மற்றும் அனைவரும் மகிழ்ச்சியடைகிறார்கள். இலக்கு களத்தில் மதிப்பு வந்துவிட்டது, அனைவருக்கும் மகிழ்ச்சி. மிகவும் புத்திசாலிகள் - மூத்தவர்கள் மற்றும் குழுத் தலைவர்கள் - ஒரு வகைக்கு மற்றொரு வகை "உலகளாவிய" மேப்பர்களின் அப்பாவியாக செயல்படுத்துவதன் அடிப்படையில் தங்கள் நீட்டிப்புகளை ஆட்சேபிக்க எழுதுகிறார்கள். சாராம்சம் பொதுவாக இதுதான்: நாங்கள் எல்லா புலங்களையும் எடுத்துக்கொள்கிறோம், எல்லா பண்புகளையும் எடுத்துக்கொள்கிறோம், அவற்றை மீண்டும் செய்கிறோம்: வகை உறுப்பினர்களின் பெயர்கள் பொருந்தினால், நாங்கள் SetValue ஐ இயக்குகிறோம். சில வகைகளில் சில சொத்துக்களை நாங்கள் கண்டுபிடிக்காத தவறுகளால் அவ்வப்போது விதிவிலக்குகளைப் பிடிக்கிறோம், ஆனால் இங்கே கூட செயல்திறனை மேம்படுத்தும் ஒரு வழி உள்ளது. முயற்சிக்கவும்/பிடிக்கவும்.

மக்கள் தங்களுக்கு முன் வந்த இயந்திரங்கள் எவ்வாறு செயல்படுகின்றன என்பதைப் பற்றிய தகவல்களை முழுமையாகப் பயன்படுத்தாமல் பாகுபடுத்திகள் மற்றும் வரைபடங்களை மீண்டும் கண்டுபிடிப்பதை நான் பார்த்திருக்கிறேன். உத்திகளுக்குப் பின்னால், இடைமுகங்களுக்குப் பின்னால், ஊசிகளுக்குப் பின்னால் மக்கள் தங்கள் அப்பாவியான செயலாக்கங்களை மறைப்பதை நான் பார்த்திருக்கிறேன். அத்தகைய உணர்தல்களில் நான் என் மூக்கைத் திருப்பினேன். உண்மையில், உண்மையான செயல்திறன் கசிவை நான் அளவிடவில்லை, முடிந்தால், அதை என் கைகளில் பெற முடிந்தால், செயல்படுத்தலை மிகவும் "உகந்ததாக" மாற்றினேன். எனவே, கீழே விவாதிக்கப்பட்ட முதல் அளவீடுகள் என்னைக் குழப்பியது.

உங்களில் பலர், ரிக்டர் அல்லது பிற கருத்தியலாளர்களைப் படிக்கும்போது, ​​குறியீட்டில் பிரதிபலிப்பு என்பது பயன்பாட்டின் செயல்திறனில் மிகவும் எதிர்மறையான தாக்கத்தை ஏற்படுத்தும் ஒரு நிகழ்வு என்ற முற்றிலும் நியாயமான அறிக்கையைக் கண்டிருப்பதாக நான் நினைக்கிறேன்.

அழைப்பு பிரதிபலிப்பு 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;
        }

நாம் பார்க்க முடியும் என, செட்டர் பண்புகளுடன் நிலையான சேகரிப்பு பயன்படுத்தப்படுகிறது - செட்டர் நிறுவனத்தை அழைக்கும் தொகுக்கப்பட்ட லாம்ப்டாஸ். பின்வரும் குறியீட்டின் மூலம் உருவாக்கப்பட்டது:

        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 உற்பத்தித்திறனை அதிகம் சாப்பிடும். இருப்பினும், உகப்பாக்கம் தேவைப்படும் உயர்-சுமை முறைகளில், நீங்கள் துவக்கி மற்றும் பிரதிநிதி கம்பைலர்கள் வடிவில் பிரதிபலிப்பைச் சேமிக்கலாம், இது "வேகமான" தர்க்கத்தை வழங்கும். இந்த வழியில் நீங்கள் பிரதிபலிப்பு நெகிழ்வுத்தன்மை மற்றும் பயன்பாட்டின் வேகம் இரண்டையும் பராமரிக்க முடியும்.

முக்கிய குறியீடு இங்கே கிடைக்கிறது. எனது வார்த்தைகளை எவரும் இருமுறை சரிபார்க்கலாம்:
ஹப்ரா பிரதிபலிப்பு சோதனைகள்

PS: சோதனைகளில் உள்ள குறியீடு IoC ஐப் பயன்படுத்துகிறது, மேலும் வரையறைகளில் அது வெளிப்படையான கட்டமைப்பைப் பயன்படுத்துகிறது. உண்மை என்னவென்றால், இறுதி செயலாக்கத்தில் செயல்திறனைப் பாதிக்கும் மற்றும் முடிவை சத்தமாக மாற்றக்கூடிய அனைத்து காரணிகளையும் நான் துண்டித்தேன்.

பிபிஎஸ்: பயனருக்கு நன்றி டிமிட்ரி டிகோனோவ் @0x1000000 Moq ஐ அமைப்பதில் எனது பிழையைக் கண்டறிந்ததற்காக, இது முதல் அளவீடுகளைப் பாதித்தது. வாசகர்களில் யாருக்காவது போதுமான கர்மா இருந்தால், லைக் செய்யவும். மனிதன் நிறுத்தினான், மனிதன் வாசித்தான், மனிதன் இருமுறை சரிபார்த்து தவறைச் சுட்டிக்காட்டினான். இது மரியாதைக்கும் அனுதாபத்திற்கும் உரியது என்று நினைக்கிறேன்.

PPPS: நடை மற்றும் வடிவமைப்பின் அடிப்பகுதிக்கு வந்த நுணுக்கமான வாசகருக்கு நன்றி. நான் சீரான மற்றும் வசதிக்காக இருக்கிறேன். விளக்கக்காட்சியின் இராஜதந்திரம் விரும்பத்தக்கதாக உள்ளது, ஆனால் நான் விமர்சனத்தை கணக்கில் எடுத்துக்கொண்டேன். நான் எறிபொருளைக் கேட்கிறேன்.

ஆதாரம்: www.habr.com

கருத்தைச் சேர்