क्या सील की गई कक्षाएं वास्तव में प्रदर्शन का लाभ देती हैं?


140

मैं बहुत सारे अनुकूलन युक्तियों को लेकर आया हूं जो कहते हैं कि आपको अतिरिक्त प्रदर्शन लाभ प्राप्त करने के लिए अपनी कक्षाओं को चिह्नित करना चाहिए।

मैंने प्रदर्शन अंतर की जांच के लिए कुछ परीक्षण किए और कोई भी नहीं मिला। क्या मुझसे कुछ गलत हो रही है? क्या मैं उस मामले को याद कर रहा हूं जहां सील की गई कक्षाएं बेहतर परिणाम देंगी?

क्या किसी ने परीक्षण चलाया है और कोई अंतर देखा है?

मुझे सीखने में मदद करें :)


9
मुझे नहीं लगता कि सीलबंद कक्षाएं एक संपूर्ण वृद्धि देने के उद्देश्य से थीं। तथ्य यह है कि वे आकस्मिक हो सकता है। इसके अलावा, अपने ऐप को सील किए गए वर्गों का उपयोग करने के लिए इसे फिर से सक्रिय करने के बाद प्रोफ़ाइल करें, और निर्धारित करें कि क्या यह प्रयास के लायक था। एक अनावश्यक माइक्रो-ऑप्टिमाइज़ेशन बनाने के लिए आपकी एक्स्टेंसिबिलिटी को लॉक करने से आपको लंबे समय तक खर्च करना पड़ेगा। बेशक, यदि आप प्रोफाइल करते हैं, और यह आपको अपने संपूर्ण बेंचमार्क (बजाय पूर्णता के लिए पूर्णता) से टकराता है, तो आप एक टीम के रूप में निर्णय ले सकते हैं यदि यह खर्च किए गए धन के लायक है। यदि आपने गैर-पूर्ण कारणों के लिए कक्षाएं सील कर दी हैं, तो उन्हें रखें :)
मेरलिन मॉर्गन-ग्राहम

1
क्या आपने प्रतिबिंब के साथ कोशिश की है? मैंने कहीं पढ़ा है कि प्रतिबिंब द्वारा तात्कालिकता सील की गई कक्षाओं के साथ तेज है
7

मेरी जानकारी में कोई नहीं है। सील एक अलग कारण के लिए है - एक्स्टेंसिबिलिटी को ब्लॉक करने के लिए, जो बहुत सारे मामलों में उपयोगी / आवश्यक हो सकती है। प्रदर्शन अनुकूलन यहां एक लक्ष्य नहीं था।
टॉमटॉम

... लेकिन यदि आप संकलक के बारे में सोचते हैं: यदि आपकी कक्षा सील हो जाती है, तो आप उस विधि का पता जानते हैं जिसे आप संकलन समय पर अपनी कक्षा में कहते हैं। यदि आपकी कक्षा सील नहीं है, तो आपको रन-टाइम पर विधि को हल करना होगा क्योंकि आपको एक ओवरराइड को कॉल करने की आवश्यकता हो सकती है। यह निश्चित रूप से नगण्य होने जा रहा है, लेकिन मैं देख सकता था कि कुछ अंतर होगा।
दान पुजई

हां, लेकिन ओपी ने जैसा बताया है, वैसा वास्तविक लाभ में तब्दील नहीं होता है। वास्तु अंतर / बहुत अधिक प्रासंगिक हो सकते हैं।
TomTom

जवाबों:


60

JITTER कभी-कभी सील कक्षाओं में तरीकों के लिए गैर-आभासी कॉल का उपयोग करेगा क्योंकि ऐसा कोई तरीका नहीं है जिससे उन्हें आगे बढ़ाया जा सके।

कॉलिंग प्रकार, वर्चुअल / नॉन-आर्टिकल के बारे में जटिल नियम हैं, और मैं उन सभी को नहीं जानता, इसलिए मैं वास्तव में उन्हें आपके लिए रेखांकित नहीं कर सकता, लेकिन अगर आप सीलबंद कक्षाओं और आभासी तरीकों के लिए Google पर आपको विषय पर कुछ लेख मिल सकते हैं।

ध्यान दें कि अनुकूलन के इस स्तर से आपको मिलने वाले किसी भी प्रकार के प्रदर्शन लाभ को अंतिम उपाय के रूप में माना जाना चाहिए, कोड-स्तर पर अनुकूलन करने से पहले हमेशा एल्गोरिथम स्तर पर अनुकूलन करें।

इस लिंक का उल्लेख यहां दिया गया है : सील किए गए कीवर्ड पर जुआ


2
'जुआ' लिंक दिलचस्प है कि यह तकनीकी अच्छाई की तरह लगता है लेकिन वास्तव में बकवास है। अधिक जानकारी के लिए लेख पर टिप्पणी पढ़ें। सारांश: दिए गए 3 कारण हैं संस्करण, प्रदर्शन, और सुरक्षा / भविष्यवाणियाँ - [अगली टिप्पणी देखें]
स्टीवन ए। लोवे

1
[जारी] संस्करण केवल तभी लागू होता है जब कोई उपवर्ग नहीं होते हैं, दोह, लेकिन इस तर्क को हर वर्ग तक बढ़ाते हैं और अचानक आपको कोई विरासत नहीं मिलती है और लगता है कि, भाषा अब ऑब्जेक्ट-ओरिएंटेड नहीं है (लेकिन केवल ऑब्जेक्ट-आधारित)! [अगला देखें]
स्टीवन ए। लोव

3
[जारी] प्रदर्शन उदाहरण एक मजाक है: एक आभासी विधि कॉल का अनुकूलन; एक सील किए गए वर्ग के पास पहली जगह में एक आभासी विधि क्यों होगी क्योंकि इसे उप-वर्ग नहीं किया जा सकता है? अंत में, सिक्योरिटी / प्रेडिक्टिबिलिटी का तर्क स्पष्ट रूप से मोटा है: 'आप इसका उपयोग नहीं कर सकते हैं इसलिए यह सुरक्षित / अनुमानित है'। जबरदस्त हंसी!
स्टीवन ए। लोव

16
@Steven A. Lowe - मुझे लगता है कि जेफरी रिक्टर थोड़े गोल चक्कर में यह कहना चाह रहे थे कि यदि आप अपनी कक्षा को छोड़ देते हैं तो आपको यह सोचने की ज़रूरत है कि व्युत्पन्न वर्ग इसका उपयोग कैसे कर सकते हैं / कर सकते हैं, और यदि आपके पास नहीं है समय या झुकाव इसे ठीक से करने के लिए, फिर इसे सील कर दें क्योंकि इससे भविष्य में दूसरों के कोड में परिवर्तन होने की संभावना कम है। यह बिल्कुल भी बकवास नहीं है, यह अच्छा सामान्य ज्ञान है।
ग्रेग बीच

6
एक सीलबंद वर्ग के पास एक आभासी विधि हो सकती है क्योंकि यह उस वर्ग से प्राप्त हो सकता है जो इसे घोषित करता है। जब आप बाद में, बाद में, सील किए गए वंशज वर्ग के एक चर को घोषित करते हैं, और उस पद्धति को कॉल करते हैं, तो संकलक ज्ञात कार्यान्वयन के लिए एक सीधा कॉल कर सकता है, क्योंकि यह जानता है कि ऐसा कोई तरीका नहीं है कि यह ज्ञात में से भिन्न हो सकता है- उस कक्षा के लिए संकलन-समय पर व्यवहार्य। सीलबंद / अनसॉल्व्ड के लिए, यह एक अलग चर्चा है, और मैं डिफ़ॉल्ट रूप से कक्षाओं को सील करने के कारणों से सहमत हूं।
लासे वी। कार्लसन

143

इसका उत्तर है, सील की गई कक्षाएं गैर-सील की तुलना में बेहतर प्रदर्शन नहीं करती हैं।

यह मुद्दा callबनाम callvirtIL ऑप कोड के लिए आता है । Callसे अधिक तेज़ है callvirt, और callvirtइसका उपयोग मुख्य रूप से तब किया जाता है जब आपको पता नहीं होता है कि ऑब्जेक्ट उप-वर्गित है या नहीं। ताकि लोगों को लगता है कि अगर आप एक वर्ग सील सभी सेशन कोड से बदल जाएगा calvirtsकरने के लिएcalls और तेजी से हो जाएगा।

दुर्भाग्य से callvirtअन्य चीजें हैं जो इसे उपयोगी बनाती हैं, जैसे अशक्त संदर्भों की जांच। इसका मतलब यह है कि भले ही एक वर्ग सील हो, संदर्भ अभी भी अशक्त हो सकता है और इस तरह एकcallvirt इसकी आवश्यकता है। आप इसके चारों ओर (कक्षा को सील करने की आवश्यकता के बिना) प्राप्त कर सकते हैं, लेकिन यह थोड़ा व्यर्थ हो जाता है।

संरचनाओं का उपयोग करें call क्योंकि वे उप-वर्ग नहीं हो सकते हैं और कभी भी अशक्त नहीं होते हैं।

अधिक जानकारी के लिए यह प्रश्न देखें:

पुकार और पुकार


5
AFAIK, जिन परिस्थितियों callमें उपयोग किया जाता है वे हैं: स्थिति में new T().Method(), structतरीकों के लिए, गैर-आभासी कॉल के virtualतरीकों (जैसे base.Virtual()), या staticविधियों के लिए। हर जगह उपयोग करता है callvirt
बजे

1
उह ... मुझे एहसास है कि यह पुराना है, लेकिन यह बिल्कुल सही नहीं है ... सीलबंद कक्षाओं के साथ बड़ी जीत तब है जब जेआईटी ऑप्टिमाइज़र कॉल को इनलाइन कर सकता है ... उस स्थिति में, सीलबंद वर्ग एक बड़ी जीत हो सकती है ।
ब्रायन कैनेडी

5
यह उत्तर गलत क्यों है। मोनो चैंजोग से: "सील किए गए वर्गों और विधियों के लिए विचलन अनुकूलन, आयरनपाइथन 2.0 के पिस्टन प्रदर्शन को 4% तक सुधारना। अन्य कार्यक्रम समान सुधार की उम्मीद कर सकते हैं [रोड्रिगो]।" सील कक्षाएं प्रदर्शन में सुधार कर सकती हैं, लेकिन हमेशा की तरह, यह स्थिति पर निर्भर करता है।
स्मालिवर

1
@Smilediver यह प्रदर्शन में सुधार कर सकता है, लेकिन केवल तभी जब आपके पास एक खराब JIT हो (कोई भी विचार नहीं है कि आजकल .NET JIT कितना अच्छा है - हालांकि उस संबंध में काफी बुरा हुआ करता था)। उदाहरण के लिए हॉटस्पॉट आभासी कॉल को इनलाइन करेगा और यदि आवश्यक हो तो बाद में हटा देगा - इसलिए आप केवल अतिरिक्त ओवरहेड का भुगतान करते हैं यदि आप वास्तव में कक्षा को उप-वर्ग करते हैं (और तब भी जरूरी नहीं)।
वू

1
-1 जेआईटी को समान आईएल ऑपकोड के लिए समान मशीन कोड उत्पन्न करने की आवश्यकता नहीं है। नल की जाँच और वर्चुअल कॉल ऑर्थोगोनल और कॉलविर्ट के अलग-अलग चरण हैं। सीलबंद प्रकारों के मामले में जेआईटी कंपाइलर अभी भी कॉलविर्ट के हिस्से का अनुकूलन कर सकता है। जब जेआईटी कंपाइलर गारंटी दे सकता है कि एक संदर्भ शून्य नहीं होगा तो वही होता है।
19

29

अद्यतन: .NET कोर 2.0 और .NET डेस्कटॉप 4.7.1 के रूप में, CLR अब विचलन का समर्थन करता है। यह सीलबंद कक्षाओं में विधियाँ ले सकता है और वर्चुअल कॉल्स को डायरेक्ट कॉल के साथ बदल सकता है - और यह गैर-सीलबंद कक्षाओं के लिए भी कर सकता है अगर यह पता लगा सके कि ऐसा करना सुरक्षित है।

ऐसे मामले में (सीलबंद वर्ग जिसे सीएलआर अन्यथा भक्ति के लिए सुरक्षित नहीं पाया जा सकता है), एक सीलबंद वर्ग को वास्तव में किसी प्रकार के प्रदर्शन लाभ की पेशकश करनी चाहिए।

उस ने कहा, मुझे नहीं लगता कि यह तब तक चिंताजनक होगा जब तक आप पहले से ही कोड को निर्धारित नहीं करते हैं और निर्धारित करते हैं कि आप एक विशेष रूप से गर्म रास्ते में थे, जिसे लाखों बार कहा जाता है, या ऐसा कुछ:

https://blogs.msdn.microsoft.com/dotnet/2017/06/29/performance-improvements-in-ryujit-in-net-core-and-net-framework/


मूल उत्तर:

मैंने निम्नलिखित परीक्षण कार्यक्रम बनाया, और फिर इसे देखने वाले का उपयोग करके विघटित कर दिया कि MSIL कोड क्या उत्सर्जित किया गया था।

public class NormalClass {
    public void WriteIt(string x) {
        Console.WriteLine("NormalClass");
        Console.WriteLine(x);
    }
}

public sealed class SealedClass {
    public void WriteIt(string x) {
        Console.WriteLine("SealedClass");
        Console.WriteLine(x);
    }
}

public static void CallNormal() {
    var n = new NormalClass();
    n.WriteIt("a string");
}

public static void CallSealed() {
    var n = new SealedClass();
    n.WriteIt("a string");
}

सभी मामलों में, C # संकलक (रिलीज़ बिल्ड कॉन्फ़िगरेशन में दृश्य स्टूडियो 2010) समान MSIL का उत्सर्जन करता है, जो इस प्रकार है:

L_0000: newobj instance void <NormalClass or SealedClass>::.ctor()
L_0005: stloc.0 
L_0006: ldloc.0 
L_0007: ldstr "a string"
L_000c: callvirt instance void <NormalClass or SealedClass>::WriteIt(string)
L_0011: ret 

लोग अक्सर कहते हैं कि सील-बंद कारण प्रदर्शन लाभ प्रदान करता है, यह संकलक जानता है कि कक्षा ओवरराइड नहीं है, और इस प्रकार callइसके बजाय इसका उपयोग कर सकते हैंcallvirt के रूप में यह जैसा कि ऊपर साबित virtuals, आदि के लिए जाँच करने के लिए नहीं है, यह नहीं है सच।

मेरा अगला विचार यह था कि भले ही एमएसआईएल समान है, शायद जेआईटी कंपाइलर सील वर्गों को अलग तरह से मानता है?

मैंने विजुअल स्टूडियो डिबगर के तहत रिलीज़ बिल्ड चलाया और विघटित x86 आउटपुट को देखा। दोनों मामलों में, x86 कोड समान था, जिसमें क्लास के नाम और फ़ंक्शन मेमोरी एड्रेस (जो निश्चित रूप से अलग होना चाहिए) के अपवाद के साथ थे। यह रहा

//            var n = new NormalClass();
00000000  push        ebp 
00000001  mov         ebp,esp 
00000003  sub         esp,8 
00000006  cmp         dword ptr ds:[00585314h],0 
0000000d  je          00000014 
0000000f  call        70032C33 
00000014  xor         edx,edx 
00000016  mov         dword ptr [ebp-4],edx 
00000019  mov         ecx,588230h 
0000001e  call        FFEEEBC0 
00000023  mov         dword ptr [ebp-8],eax 
00000026  mov         ecx,dword ptr [ebp-8] 
00000029  call        dword ptr ds:[00588260h] 
0000002f  mov         eax,dword ptr [ebp-8] 
00000032  mov         dword ptr [ebp-4],eax 
//            n.WriteIt("a string");
00000035  mov         edx,dword ptr ds:[033220DCh] 
0000003b  mov         ecx,dword ptr [ebp-4] 
0000003e  cmp         dword ptr [ecx],ecx 
00000040  call        dword ptr ds:[0058827Ch] 
//        }
00000046  nop 
00000047  mov         esp,ebp 
00000049  pop         ebp 
0000004a  ret 

मैंने तब सोचा कि शायद डिबगर के नीचे चलने से यह कम आक्रामक अनुकूलन का कारण बनता है?

मैंने तब किसी भी डिबगिंग वातावरण के बाहर एक स्टैंडअलोन रिलीज़ बिल्ड निष्पादन योग्य चलाया, और प्रोग्राम पूरा होने के बाद तोड़ने के लिए WinDBG + SOS का उपयोग किया, और JIT संकलित x86 कोड के असंतोष को देखें।

जैसा कि आप नीचे दिए गए कोड से देख सकते हैं, जब डिबगर के बाहर चल रहा है JIT संकलक अधिक आक्रामक है, और इसने WriteItसीधे कॉल करने वाले तरीके को इनलाइन किया है । हालांकि महत्वपूर्ण बात यह है कि सील्ड बनाम नॉन-सील्ड क्लास को कॉल करते समय यह समान था। सील या निरर्थक वर्ग के बीच कोई अंतर नहीं है।

यहां सामान्य वर्ग को बुलाते समय यह है:

Normal JIT generated code
Begin 003c00b0, size 39
003c00b0 55              push    ebp
003c00b1 8bec            mov     ebp,esp
003c00b3 b994391800      mov     ecx,183994h (MT: ScratchConsoleApplicationFX4.NormalClass)
003c00b8 e8631fdbff      call    00172020 (JitHelp: CORINFO_HELP_NEWSFAST)
003c00bd e80e70106f      call    mscorlib_ni+0x2570d0 (6f4c70d0) (System.Console.get_Out(), mdToken: 060008fd)
003c00c2 8bc8            mov     ecx,eax
003c00c4 8b1530203003    mov     edx,dword ptr ds:[3302030h] ("NormalClass")
003c00ca 8b01            mov     eax,dword ptr [ecx]
003c00cc 8b403c          mov     eax,dword ptr [eax+3Ch]
003c00cf ff5010          call    dword ptr [eax+10h]
003c00d2 e8f96f106f      call    mscorlib_ni+0x2570d0 (6f4c70d0) (System.Console.get_Out(), mdToken: 060008fd)
003c00d7 8bc8            mov     ecx,eax
003c00d9 8b1534203003    mov     edx,dword ptr ds:[3302034h] ("a string")
003c00df 8b01            mov     eax,dword ptr [ecx]
003c00e1 8b403c          mov     eax,dword ptr [eax+3Ch]
003c00e4 ff5010          call    dword ptr [eax+10h]
003c00e7 5d              pop     ebp
003c00e8 c3              ret

एक सील वर्ग बनाम:

Normal JIT generated code
Begin 003c0100, size 39
003c0100 55              push    ebp
003c0101 8bec            mov     ebp,esp
003c0103 b90c3a1800      mov     ecx,183A0Ch (MT: ScratchConsoleApplicationFX4.SealedClass)
003c0108 e8131fdbff      call    00172020 (JitHelp: CORINFO_HELP_NEWSFAST)
003c010d e8be6f106f      call    mscorlib_ni+0x2570d0 (6f4c70d0) (System.Console.get_Out(), mdToken: 060008fd)
003c0112 8bc8            mov     ecx,eax
003c0114 8b1538203003    mov     edx,dword ptr ds:[3302038h] ("SealedClass")
003c011a 8b01            mov     eax,dword ptr [ecx]
003c011c 8b403c          mov     eax,dword ptr [eax+3Ch]
003c011f ff5010          call    dword ptr [eax+10h]
003c0122 e8a96f106f      call    mscorlib_ni+0x2570d0 (6f4c70d0) (System.Console.get_Out(), mdToken: 060008fd)
003c0127 8bc8            mov     ecx,eax
003c0129 8b1534203003    mov     edx,dword ptr ds:[3302034h] ("a string")
003c012f 8b01            mov     eax,dword ptr [ecx]
003c0131 8b403c          mov     eax,dword ptr [eax+3Ch]
003c0134 ff5010          call    dword ptr [eax+10h]
003c0137 5d              pop     ebp
003c0138 c3              ret

मेरे लिए, यह ठोस सबूत प्रदान करता है कि सील किए गए बनाम गैर-सील वर्गों पर कॉल करने के तरीकों के बीच कोई प्रदर्शन सुधार नहीं हो सकता है ... मुझे लगता है कि मैं अब खुश हूं :)


किसी भी कारण से आपने दूसरे को डुप्लिकेट के रूप में बंद करने के बजाय दो बार इस उत्तर को पोस्ट किया? वे मेरे लिए मान्य डुप्लिकेट की तरह दिखते हैं हालांकि यह मेरा क्षेत्र नहीं है।
फ्लेक्सो

1
क्या होगा अगर इनलाइनिंग से बचने के लिए विधियाँ लंबी थीं (32 बाइट्स IL कोड से अधिक) और देखें कि क्या कॉल ऑपरेशन का उपयोग किया जाएगा? यदि यह इनबिल्ड है, तो आप कॉल को नहीं देख सकते हैं ताकि प्रभावों पर निर्णय न लिया जा सके।
20

मैं असमंजस में हूँ: callvirtजब इन विधियों को शुरू करने के लिए आभासी नहीं हैं तो वहाँ क्यों है ?
अजीब

@ यदि मुझे याद नहीं है कि मैंने यह कहाँ देखा है, लेकिन मैंने पढ़ा कि सीएलआर का उपयोग callvirtसील तरीकों के लिए किया जाता है क्योंकि यह अभी भी विधि कॉल को लागू करने से पहले ऑब्जेक्ट को अशक्त करना है, और एक बार जब आप इसमें कारक करते हैं, तो आप बस उपयोग करें callvirtcallvirtसीधे हटाने और सीधे कूदने के लिए, उन्हें ((string)null).methodCall()C ++ की तरह अनुमति देने के लिए C # को संशोधित करने की आवश्यकता होगी , या उन्हें सांख्यिकीय रूप से यह साबित करना होगा कि ऑब्जेक्ट शून्य नहीं था (जो वे कर सकते थे, लेकिन परेशान नहीं हुए)
ओरियन एडवर्ड्स

1
मशीन कोड स्तर तक खुदाई के प्रयास में जाने के लिए प्रॉप्स, लेकिन 'यह ठोस प्रदर्शन प्रदान नहीं किया जा सकता है कि ठोस सबूत प्रदान करता है' जैसे बयान देने में बहुत सावधानी बरतें । आपने जो दिखाया है वह यह है कि एक विशेष परिदृश्य के लिए देशी आउटपुट में कोई अंतर नहीं है। यह एक डेटा बिंदु है, और कोई इसे सभी परिदृश्यों के लिए सामान्यीकरण नहीं मान सकता है। शुरुआत के लिए, आपकी कक्षाएं किसी भी आभासी तरीके को परिभाषित नहीं करती हैं, इसलिए वर्चुअल कॉल की आवश्यकता नहीं होती है।
मैट क्रेग

24

जैसा कि मैं जानता हूं कि प्रदर्शन लाभ की कोई गारंटी नहीं है। लेकिन कुछ विशिष्ट स्थिति के तहत प्रदर्शन जुर्माना कम करने का मौका है सील विधि के साथ । (सील्ड क्लास सील किए जाने के सभी तरीकों को बनाता है।)

लेकिन यह संकलक कार्यान्वयन और निष्पादन पर्यावरण पर निर्भर है।


विवरण

आधुनिक सीपीयू के कई प्रदर्शन को बढ़ाने के लिए लंबी पाइपलाइन संरचना का उपयोग करते हैं। क्योंकि सीपीयू मेमोरी की तुलना में अविश्वसनीय रूप से तेज़ है, पाइपलाइन को तेज करने के लिए सीपीयू को मेमोरी से कोड प्रीफ़ैच करना पड़ता है। यदि कोड उचित समय पर तैयार नहीं है, तो पाइपलाइन निष्क्रिय हो जाएगी।

डायनेमिक डिस्पैच नामक एक बड़ी बाधा है जो इस 'प्रीफेटिंग' अनुकूलन को बाधित करती है। आप इसे सिर्फ एक सशर्त शाखा के रूप में समझ सकते हैं।

// Value of `v` is unknown,
// and can be resolved only at runtime.
// CPU cannot know which code to prefetch.
// Therefore, just prefetch any one of a() or b().
// This is *speculative execution*.
int v = random();
if (v==1) a();
else b();

CPU इस मामले में निष्पादित करने के लिए अगले कोड को प्रीफ़ैच नहीं कर सकता क्योंकि अगली कोड स्थिति अज्ञात है जब तक कि स्थिति हल नहीं हो जाती। तो यह खतरा बनाता है पाइपलाइन निष्क्रिय कारण बनता है। और निष्क्रिय द्वारा प्रदर्शन दंड नियमित रूप से बहुत बड़ा है।

ओवरराइडिंग के तरीके के मामले में भी ऐसा ही होता है। कंपाइलर वर्तमान विधि कॉल के लिए उचित विधि को निर्धारित कर सकता है, लेकिन कभी-कभी यह असंभव है। इस मामले में, उचित विधि केवल रनटाइम पर निर्धारित की जा सकती है। यह डायनेमिक प्रेषण का भी मामला है, और, गतिशील रूप से टाइप की जाने वाली भाषाओं का एक मुख्य कारण आमतौर पर स्टेटिक-टाइप्ड भाषाओं की तुलना में धीमा है।

कुछ सीपीयू (हाल के इंटेल के x86 चिप्स सहित) तकनीक का उपयोग करता है जिसे स्थिति पर भी पाइपलाइन का उपयोग करने के लिए सट्टा निष्पादन कहा जाता है। बस निष्पादन पथ में से एक को प्राथमिकता दें। लेकिन इस तकनीक की हिट दर इतनी अधिक नहीं है। और अटकलों की विफलता पाइपलाइन स्टाल का कारण बनती है जो बहुत बड़ा प्रदर्शन जुर्माना करता है। (यह पूरी तरह से सीपीयू कार्यान्वयन द्वारा है। कुछ मोबाइल सीपीयू को ऊर्जा को बचाने के लिए इस तरह के अनुकूलन के रूप में नहीं जाना जाता है)

मूलतः, C # एक संकलित भाषा है। लेकिन हमेशा नहीं। मुझे सटीक स्थिति नहीं पता है और यह पूरी तरह से कंपाइलर कार्यान्वयन पर निर्भर है। कुछ संकलक यदि विधि के रूप में चिह्नित हैं, तो ओवरराइडिंग को रोककर गतिशील प्रेषण की संभावना को समाप्त कर सकते हैं sealed। बेवकूफ संकलक नहीं हो सकता है। यह प्रदर्शन का लाभ है sealed


यह उत्तर ( एक अनियोजित सरणी की तुलना में सॉर्ट किए गए सरणी को संसाधित करने के लिए तेज़ी से क्यों है? ) शाखा भविष्यवाणी को बहुत बेहतर बता रहा है।


1
पेंटियम वर्ग के सीपीयू अप्रत्यक्ष प्रेषण को सीधे पूर्व निर्धारित करते हैं। कभी-कभी फ़ंक्शन पॉइंटर पुनर्निर्देशन इस कारण से (अगोचर) की तुलना में तेज़ होता है।
जोशुआ

2
गैर आभासी या सीलबंद फ़ंक्शन का एक फायदा यह है कि उन्हें अधिक स्थितियों में इनलाइन किया जा सकता है।
कोडइन्चोज 10

4

<विषय से हटकर शेख़ी>

मैंने सीलबंद कक्षाएं खोलीं । यहां तक ​​कि अगर प्रदर्शन लाभ आश्चर्यजनक हैं (जो मुझे संदेह है), तो वे विरासत के माध्यम से पुन: उपयोग को रोककर ऑब्जेक्ट-उन्मुख मॉडल को नष्ट कर देते हैं । उदाहरण के लिए, थ्रेड वर्ग सील है। जबकि मैं देख सकता हूं कि कोई व्यक्ति जितना संभव हो उतना थ्रेड्स को सक्षम कर सकता है, मैं ऐसे परिदृश्यों की भी कल्पना कर सकता हूं जहां थ्रेड को उपवर्ग में सक्षम होने से बहुत लाभ होगा। वर्ग लेखकों, यदि आपको "प्रदर्शन" कारणों से अपनी कक्षाओं को सील करना चाहिए , तो कृपया बहुत कम से कम एक इंटरफ़ेस प्रदान करें ताकि हमें हर जगह रैप-एंड-रिप्लेसमेंट न करना पड़े, इसके लिए हमें एक सुविधा की आवश्यकता है जिसे आप भूल गए हैं।

उदाहरण: SafeThread को थ्रेड क्लास को लपेटना पड़ा क्योंकि थ्रेड सील है और कोई IThread इंटरफ़ेस नहीं है; SafeThread थ्रेड्स पर स्वचालित रूप से अनछुए अपवादों को फँसा देता है, थ्रेड क्लास से पूरी तरह से गायब है। [और नहीं, अनहेल्डेड अपवाद घटनाएं माध्यमिक थ्रेड्स में अनहैंड किए गए अपवादों को नहीं उठाती हैं]।

</ विषय से हटकर शेख़ी>


35
मैं प्रदर्शन कारणों से अपनी कक्षाएं सील नहीं करता। मैं उन्हें डिजाइन कारणों से सील करता हूं। वंशानुक्रम के लिए डिजाइन करना कठिन है, और यह प्रयास अधिकांश समय बर्बाद हो जाएगा। एक बात यह है कि - मैं पूरी तरह हालांकि इंटरफेस प्रदान करने के बारे सहमत दूर unsealing के वर्गों के लिए बेहतर समाधान।
जॉन स्कीट

6
एनकैप्सुलेशन आमतौर पर विरासत से बेहतर समाधान है। अपने विशिष्ट थ्रेड उदाहरण को लेने के लिए, थ्रेड अपवाद अपवाद लिस्कॉव प्रतिस्थापन सिद्धांत को तोड़ता है क्योंकि आपने थ्रेड वर्ग के प्रलेखित व्यवहार को बदल दिया है, इसलिए भले ही आप इसे प्राप्त कर सकें, यह कहना उचित नहीं होगा कि आप हर जगह SafeTread का उपयोग कर सकते हैं आप थ्रेड का उपयोग कर सकते हैं। इस मामले में, थ्रेड को किसी अन्य वर्ग में अलग करना बेहतर होगा, जिसके पास अलग-अलग दस्तावेज व्यवहार है, जो आप करने में सक्षम हैं। कभी-कभी चीजों को अपने स्वयं के अच्छे के लिए सील कर दिया जाता है।
ग्रेग बीच 10

1
@ [ग्रेग बीच]: राय, तथ्य नहीं - थ्रेड से विरासत में प्राप्त करने में सक्षम होने के लिए इसकी डिजाइन में एक विषम ओवर को ठीक करना एक बुरी बात नहीं है;; और मुझे लगता है कि आप एलएसपी से आगे निकल रहे हैं - उत्तेजक संपत्ति क्यू (एक्स) यह मामला 'अनहैंड अपवाद' प्रोग्राम को नष्ट कर देता है, जो "वांछनीय प्रॉपर्टी" नहीं है :-)
स्टीवन ए। लोवे

1
नहीं, लेकिन मेरे पास मेरे भद्दे कोड का हिस्सा है, जहां मैंने अन्य डेवलपर्स को सीलिंग नहीं करने या कोने के मामलों की अनुमति देकर मेरे सामान का दुरुपयोग करने में सक्षम बनाया। आजकल मेरा अधिकांश कोड अभिकथन और अन्य अनुबंध संबंधी सामान है। और मैं इस तथ्य के बारे में काफी खुला हूं कि मैं केवल *** में दर्द होने के लिए ऐसा करता हूं।
ट्यूरिंग पूरा

2
जब से हम यहाँ दोषारोपण विषय बंद कर रहे हैं, जैसा कि आप घृणा सील कक्षाएं, मैं घृणा निगल लिया अपवाद नहीं। जब कोई चीज ब्रेस्टअप हो जाती है तो इससे बुरा कुछ नहीं होता है लेकिन प्रोग्राम चल जाता है। जावास्क्रिप्ट मेरी पसंदीदा है। आप कुछ कोड में बदलाव करते हैं और अचानक एक बटन पर क्लिक करने से कुछ नहीं होता है। महान! ASP.NET और UpdatePanel एक और एक है; गंभीरता से, अगर मेरा बटन हैंडलर फेंकता है तो यह बहुत बड़ी बात है और इसे CRASH की जरूरत है, ताकि मुझे पता चले कि कुछ ऐसा है जिसे ठीक करने की जरूरत है! एक बटन जो कुछ भी नहीं करता है एक बटन की तुलना में अधिक बेकार है जो क्रैश स्क्रीन को लाता है!
रोमन स्टार्कोव

4

कक्षा sealedको चिह्नित करने का कोई प्रदर्शन प्रभाव नहीं होना चाहिए।

ऐसे मामले हैं जहां csccallvirt ओपकोड के बजाय एक ओपकोड का उत्सर्जन करना पड़ सकता है call। हालांकि, ऐसा लगता है कि वे मामले दुर्लभ हैं।

और यह मुझे लगता है कि जेआईटी को उसी गैर-आभासी फ़ंक्शन कॉल का उत्सर्जन करने में सक्षम होना चाहिए , जिसके callvirtलिए callवह जानता होगा , यदि वर्ग को कोई उपवर्ग (अभी तक) नहीं है। यदि विधि का केवल एक ही कार्यान्वयन मौजूद है, तो इसका कोई पता नहीं है कि इसका पता एक vtable से लोड हो रहा है - बस एक कार्यान्वयन को सीधे कॉल करें। उस मामले के लिए, JIT फ़ंक्शन को इनलाइन भी कर सकता है।

यह जेआईटी के हिस्से पर एक जुआ है, क्योंकि अगर एक उपवर्ग है है बाद में भरी हुई, JIT दूर है कि मशीन कोड फेंक और कोड फिर से संकलन, एक असली आभासी कॉल उत्सर्जक करना होगा। मेरा अनुमान है कि यह व्यवहार में अक्सर नहीं होता है।

(और हां, वीएम डिज़ाइनर वास्तव में आक्रामक रूप से इन छोटे प्रदर्शन जीत का पीछा करते हैं।)


3

सीलबंद कक्षाएं चाहिए प्रदर्शन में सुधार प्रदान । चूंकि एक सील वर्ग व्युत्पन्न नहीं किया जा सकता है, किसी भी आभासी सदस्यों को गैर-आभासी सदस्यों में बदल दिया जा सकता है।

बेशक, हम वास्तव में छोटे लाभ की बात कर रहे हैं। मैं एक वर्ग को तब तक चिह्नित नहीं करूंगा जब तक कि प्रदर्शन में सुधार नहीं हो जाता जब तक कि यह समस्या न हो।


उन्हें करना चाहिए , लेकिन ऐसा प्रतीत होता है कि वे नहीं करते हैं। यदि सीएलआर में गैर-शून्य संदर्भ प्रकारों की अवधारणा थी, तो एक सील वर्ग वास्तव में बेहतर होगा, क्योंकि संकलक callइसके बजाय उत्सर्जन कर सकता है callvirt... मुझे कई अन्य कारणों से भी गैर-शून्य संदर्भ प्रकार पसंद होंगे ... sigh :-(
ओरियन एडवर्ड्स

3

मैं "सील" वर्गों को सामान्य मामला मानता हूं और मुझे हमेशा "सील" कीवर्ड को छोड़ देने का एक कारण है।

मेरे लिए सबसे महत्वपूर्ण कारण हैं:

क) बेहतर संकलन समय की जाँच (लागू नहीं होने वाले इंटरफेस के लिए संकलित समय पर पता लगाया जाएगा, न केवल रनटाइम पर)

और, शीर्ष कारण:

b) इस तरह से मेरी कक्षाओं का दुरुपयोग संभव नहीं है

काश माइक्रोसॉफ्ट ने "मानक" को "सील" नहीं किया होता।


मेरा मानना ​​है कि "ओटिट" (बाहर निकलने के लिए) "उत्सर्जन" (उत्पादन करने के लिए) होना चाहिए?
user2864740

2

@ वैभव, आपने प्रदर्शन को मापने के लिए किस तरह के परीक्षण किए?

मुझे लगता है कि किसी को रोटर का उपयोग करना होगा और सीएलआई में ड्रिल करना होगा और यह समझना होगा कि एक सील वर्ग प्रदर्शन में कैसे सुधार करेगा।

SSCLI (रोटर)
SSCLI: साझा स्रोत सामान्य भाषा इन्फ्रास्ट्रक्चर

कॉमन लैंग्वेज इंफ्रास्ट्रक्चर (सीएलआई) ईसीएमए मानक है जो .NET फ्रेमवर्क के मूल का वर्णन करता है। साझा स्रोत सीएलआई (SSCLI), जिसे रोटर के रूप में भी जाना जाता है, ECMA CLI और ECMA C # भाषा विनिर्देश, Microsoft के .NET आर्किटेक्चर के केंद्र में तकनीकों के कार्य कार्यान्वयन के लिए स्रोत कोड का एक संकुचित संग्रह है।


परीक्षण में एक वर्ग पदानुक्रम बनाना शामिल था, कुछ तरीके जो डमी काम कर रहे थे (स्ट्रिंग हेरफेर ज्यादातर)। इनमें से कुछ तरीके आभासी थे। वे एक-दूसरे को इधर-उधर बुला रहे थे। फिर इन तरीकों को 100, 10000, और 100000 बार कॉल करना ... और समय को मापना। फिर वर्गों को चिह्नित करने के बाद इन्हें चला रहा है। और फिर से मापने। उनमें कोई अंतर नहीं।
वैभव

2

सील की गई कक्षाएं कम से कम थोड़ी तेज़ होंगी, लेकिन कभी-कभी तेज़ हो सकती हैं ... अगर JIT ऑप्टिमाइज़र इनलाइन कॉल कर सकता है जो अन्यथा वर्चुअल कॉल होता। इसलिए, जहां अक्सर छोटे-छोटे तरीके होते हैं, जो बहुत छोटे होते हैं, निश्चित रूप से कक्षा को सील करने पर विचार करते हैं।

हालांकि, एक वर्ग को सील करने का सबसे अच्छा कारण यह है कि "मुझे यह विरासत में मिला नहीं है, इसलिए मैं ऐसा करने के लिए डिज़ाइन किया गया था मानकर आपको जलने नहीं दूंगा, और मैं नहीं जा रहा हूं एक कार्यान्वयन में बंद होने के कारण खुद को जलाने के लिए क्योंकि मैं आपको इससे प्राप्त करने देता हूं। "

मुझे पता है कि कुछ लोगों ने कहा है कि वे सील की गई कक्षाओं से नफरत करते हैं क्योंकि वे किसी भी चीज़ से व्युत्पन्न होने का अवसर चाहते हैं ... लेकिन वह सबसे ज्यादा पसंद करने योग्य विकल्प नहीं है ... क्योंकि व्युत्पत्ति के लिए एक वर्ग को उजागर करना आपको उजागर नहीं करने की तुलना में बहुत अधिक है। उस। यह कहने के समान है कि "मेरे पास निजी वर्ग के सदस्य हैं ... मैं अक्सर वह वर्ग नहीं बना सकता जो मैं चाहता हूं क्योंकि मेरे पास पहुंच नहीं है।" एनकैप्सुलेशन महत्वपूर्ण है ... सीलिंग एनकैप्सुलेशन का एक रूप है।


सी # कंपाइलर अभी भी callvirtसील कक्षाओं पर उदाहरण के तरीकों के लिए (वर्चुअल मेथड कॉल) का उपयोग करेगा , क्योंकि यह अभी भी उन पर एक नल-ऑब्जेक्ट की जांच करना है। इनलाइनिंग के संबंध में, सीएलआर जेआईटी (और करता है) इनलाइन वर्चुअल विधि कॉल को सील और गैर-सील दोनों वर्गों के लिए कर सकता है ... हाँ। प्रदर्शन की बात एक मिथक है।
ओरियन एडवर्ड्स

1

वास्तव में उन्हें देखने के लिए आपको जेआईटी-संकलित कॉड ई (पिछले एक) का विश्लेषण करने की आवश्यकता है ।

C # कोड

public sealed class Sealed
{
    public string Message { get; set; }
    public void DoStuff() { }
}
public class Derived : Base
{
    public sealed override void DoStuff() { }
}
public class Base
{
    public string Message { get; set; }
    public virtual void DoStuff() { }
}
static void Main()
{
    Sealed sealedClass = new Sealed();
    sealedClass.DoStuff();
    Derived derivedClass = new Derived();
    derivedClass.DoStuff();
    Base BaseClass = new Base();
    BaseClass.DoStuff();
}

MIL कोड

.method private hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       41 (0x29)
  .maxstack  8
  IL_0000:  newobj     instance void ConsoleApp1.Program/Sealed::.ctor()
  IL_0005:  callvirt   instance void ConsoleApp1.Program/Sealed::DoStuff()
  IL_000a:  newobj     instance void ConsoleApp1.Program/Derived::.ctor()
  IL_000f:  callvirt   instance void ConsoleApp1.Program/Base::DoStuff()
  IL_0014:  newobj     instance void ConsoleApp1.Program/Base::.ctor()
  IL_0019:  callvirt   instance void ConsoleApp1.Program/Base::DoStuff()
  IL_0028:  ret
} // end of method Program::Main

JIT- संकलित कोड

--- C:\Users\Ivan Porta\source\repos\ConsoleApp1\Program.cs --------------------
        {
0066084A  in          al,dx  
0066084B  push        edi  
0066084C  push        esi  
0066084D  push        ebx  
0066084E  sub         esp,4Ch  
00660851  lea         edi,[ebp-58h]  
00660854  mov         ecx,13h  
00660859  xor         eax,eax  
0066085B  rep stos    dword ptr es:[edi]  
0066085D  cmp         dword ptr ds:[5842F0h],0  
00660864  je          0066086B  
00660866  call        744CFAD0  
0066086B  xor         edx,edx  
0066086D  mov         dword ptr [ebp-3Ch],edx  
00660870  xor         edx,edx  
00660872  mov         dword ptr [ebp-48h],edx  
00660875  xor         edx,edx  
00660877  mov         dword ptr [ebp-44h],edx  
0066087A  xor         edx,edx  
0066087C  mov         dword ptr [ebp-40h],edx  
0066087F  nop  
            Sealed sealedClass = new Sealed();
00660880  mov         ecx,584E1Ch  
00660885  call        005730F4  
0066088A  mov         dword ptr [ebp-4Ch],eax  
0066088D  mov         ecx,dword ptr [ebp-4Ch]  
00660890  call        00660468  
00660895  mov         eax,dword ptr [ebp-4Ch]  
00660898  mov         dword ptr [ebp-3Ch],eax  
            sealedClass.DoStuff();
0066089B  mov         ecx,dword ptr [ebp-3Ch]  
0066089E  cmp         dword ptr [ecx],ecx  
006608A0  call        00660460  
006608A5  nop  
            Derived derivedClass = new Derived();
006608A6  mov         ecx,584F3Ch  
006608AB  call        005730F4  
006608B0  mov         dword ptr [ebp-50h],eax  
006608B3  mov         ecx,dword ptr [ebp-50h]  
006608B6  call        006604A8  
006608BB  mov         eax,dword ptr [ebp-50h]  
006608BE  mov         dword ptr [ebp-40h],eax  
            derivedClass.DoStuff();
006608C1  mov         ecx,dword ptr [ebp-40h]  
006608C4  mov         eax,dword ptr [ecx]  
006608C6  mov         eax,dword ptr [eax+28h]  
006608C9  call        dword ptr [eax+10h]  
006608CC  nop  
            Base BaseClass = new Base();
006608CD  mov         ecx,584EC0h  
006608D2  call        005730F4  
006608D7  mov         dword ptr [ebp-54h],eax  
006608DA  mov         ecx,dword ptr [ebp-54h]  
006608DD  call        00660490  
006608E2  mov         eax,dword ptr [ebp-54h]  
006608E5  mov         dword ptr [ebp-44h],eax  
            BaseClass.DoStuff();
006608E8  mov         ecx,dword ptr [ebp-44h]  
006608EB  mov         eax,dword ptr [ecx]  
006608ED  mov         eax,dword ptr [eax+28h]  
006608F0  call        dword ptr [eax+10h]  
006608F3  nop  
        }
0066091A  nop  
0066091B  lea         esp,[ebp-0Ch]  
0066091E  pop         ebx  
0066091F  pop         esi  
00660920  pop         edi  
00660921  pop         ebp  

00660922  ret  

जबकि वस्तुओं का निर्माण समान है, सील और व्युत्पन्न / आधार वर्ग के तरीकों को लागू करने के लिए निष्पादित निर्देश थोड़ा अलग है। डेटा को रजिस्टरों या रैम (मूव इंस्ट्रक्शन) में ले जाने के बाद, सीलबंद विधि का आह्वान, ड्राफ्ट ptr [ecx], ecx (cmp अनुदेश) के बीच तुलना निष्पादित करें और फिर विधि को कॉल करें जबकि व्युत्पन्न / आधार वर्ग सीधे विधि को निष्पादित करें। ।

Torbj¨orn Granlund, इंस्ट्रूमेंट लेटेंसी और AMD और Intel x86 प्रोसेसर के लिए थ्रूपुट द्वारा लिखी गई रिपोर्ट के अनुसार, Intel Pentium 4 में निम्न निर्देश की गति हैं:

  • mov : में विलंबता के रूप में 1 चक्र है और प्रोसेसर इस प्रकार के प्रति चक्र 2.5 निर्देशों को बनाए रख सकता है
  • cmp : में विलंबता के रूप में 1 चक्र होता है और प्रोसेसर इस प्रकार के प्रति चक्र 2 निर्देशों को बनाए रख सकता है

लिंक : https://gmplib.org/~tege/x86-timing.pdf

इसका मतलब है कि, आदर्श रूप से , एक सील विधि को लागू करने के लिए आवश्यक समय 2 चक्र है, जबकि व्युत्पन्न या आधार वर्ग विधि को लागू करने के लिए आवश्यक समय 3 चक्र है।

कंपाइलरों के अनुकूलन ने सीलबंद और नहीं-सील किए गए वर्ग के प्रदर्शन के बीच इतना अंतर कर दिया है कि हम प्रोसेसर हलकों के बारे में बात कर रहे हैं और इस कारण से अधिकांश अनुप्रयोगों के लिए अप्रासंगिक हैं।


-10

इस कोड को चलाएं और आप देखेंगे कि सील की गई कक्षाएं 2 गुना तेज हैं:

class Program
{
    static void Main(string[] args)
    {
        Console.ReadLine();

        var watch = new Stopwatch();
        watch.Start();
        for (int i = 0; i < 10000000; i++)
        {
            new SealedClass().GetName();
        }
        watch.Stop();
        Console.WriteLine("Sealed class : {0}", watch.Elapsed.ToString());

        watch.Start();
        for (int i = 0; i < 10000000; i++)
        {
            new NonSealedClass().GetName();
        }
        watch.Stop();
        Console.WriteLine("NonSealed class : {0}", watch.Elapsed.ToString());

        Console.ReadKey();
    }
}

sealed class SealedClass
{
    public string GetName()
    {
        return "SealedClass";
    }
}

class NonSealedClass
{
    public string GetName()
    {
        return "NonSealedClass";
    }
}

आउटपुट: सील वर्ग: 00: 00: 00.1897568 नॉनसेल्ड वर्ग: 00: 00: 00.3826678


9
इसके साथ कुछ समस्याएं हैं। सबसे पहले, आप पहले और दूसरे टेस्ट के बीच स्टॉपवॉच को रीसेट नहीं कर रहे हैं। दूसरा, जिस तरीके से आप कॉल कर रहे हैं, इसका मतलब है कि सभी ऑप कोड कॉलविर्ट नहीं होंगे, इसलिए टाइप कोई फर्क नहीं पड़ता।
कैमरन मैकफारलैंड

1
रुस्लान, आप घड़ी देखना भूल गए। पहला टेस्ट चलाने के बाद। ()। o_O:]

हां, ऊपर दिए गए कोड में बग हैं। फिर दोबारा, जो कोड में गलतियों को कभी भी लागू नहीं करने के लिए अपने बारे में डींग मार सकता है? लेकिन इस उत्तर में अन्य सभी की तुलना में एक महत्वपूर्ण बात है: यह प्रश्न में प्रभाव को मापने की कोशिश करता है। और यह उत्तर भी आप सभी के लिए कोड का निरीक्षण करता है और [मास-] डाउनवोट (मुझे आश्चर्य है कि बड़े पैमाने पर डाउनवोटिंग क्यों आवश्यक है?)। अन्य उत्तरों के विपरीत। यह मेरी राय में सम्मान का हकदार है ... कृपया ध्यान दें कि उपयोगकर्ता नौसिखिया है, इसलिए बहुत स्वागत योग्य दृष्टिकोण भी नहीं है। कुछ सरल सुधार और यह कोड हम सभी के लिए उपयोगी है। फिक्स + शेयर निश्चित संस्करण, यदि आप हिम्मत करते हैं
रोलांड पिहलाकास

मैं @RolandPihlakas से असहमत हूं। जैसा कि आपने कहा था, "जो कोड में गलतियों को पेश नहीं करने के लिए अपने बारे में डींग मार सकता है"। उत्तर -> नहीं, कोई नहीं। लेकिन वह बात नहीं है। मुद्दा यह है, यह एक गलत जानकारी है जो एक और नए प्रोग्रामर को भ्रमित कर सकती है। बहुत से लोग आसानी से याद कर सकते हैं कि स्टॉपवॉच रीसेट नहीं किया गया था। वे विश्वास कर सकते हैं कि बेंचमार्क जानकारी सत्य है। क्या यह अधिक हानिकारक नहीं है? कोई व्यक्ति जो उत्तर की तलाश में है, शायद वह भी इन टिप्पणियों को नहीं पढ़ सकता है, बल्कि वह एक उत्तर देखेगा और वह / वह ऐसा मान सकता है।
इमरान हुसैन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.