STM32 MCU से तेज़ प्रदर्शन प्राप्त करना


11

मैं STM32F303VC डिस्कवरी किट के साथ काम कर रहा हूं और मैं इसके प्रदर्शन से थोड़ा हैरान हूं। सिस्टम से परिचित होने के लिए, मैंने इस MCU की बिट-बैंगिंग स्पीड का परीक्षण करने के लिए एक बहुत ही सरल प्रोग्राम लिखा है। कोड को निम्नानुसार तोड़ा जा सकता है:

  1. एचएसआई घड़ी (8 मेगाहर्ट्ज) चालू है;
  2. पीएलएल को एचएसआई / 2 * 16 = 64 मेगाहर्ट्ज प्राप्त करने के लिए 16 के प्रीस्कूलर के साथ शुरू किया गया है;
  3. PLL को SYSCLK के रूप में नामित किया गया है;
  4. SYSCLK की निगरानी MCO पिन (PA8) पर की जाती है, और पिन (PE10) में से एक को अनंत लूप में टॉगल किया जाता है।

इस कार्यक्रम का स्रोत कोड नीचे प्रस्तुत किया गया है:

#include "stm32f3xx.h"

int main(void)
{
      // Initialize the HSI:
      RCC->CR |= RCC_CR_HSION;
      while(!(RCC->CR&RCC_CR_HSIRDY));

      // Initialize the LSI:
      // RCC->CSR |= RCC_CSR_LSION;
      // while(!(RCC->CSR & RCC_CSR_LSIRDY));

      // PLL configuration:
      RCC->CFGR &= ~RCC_CFGR_PLLSRC;     // HSI / 2 selected as the PLL input clock.
      RCC->CFGR |= RCC_CFGR_PLLMUL16;   // HSI / 2 * 16 = 64 MHz
      RCC->CR |= RCC_CR_PLLON;          // Enable PLL
      while(!(RCC->CR&RCC_CR_PLLRDY));  // Wait until PLL is ready

      // Flash configuration:
      FLASH->ACR |= FLASH_ACR_PRFTBE;
      FLASH->ACR |= FLASH_ACR_LATENCY_1;

      // Main clock output (MCO):
      RCC->AHBENR |= RCC_AHBENR_GPIOAEN;
      GPIOA->MODER |= GPIO_MODER_MODER8_1;
      GPIOA->OTYPER &= ~GPIO_OTYPER_OT_8;
      GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR8;
      GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR8;
      GPIOA->AFR[0] &= ~GPIO_AFRL_AFRL0;

      // Output on the MCO pin:
      //RCC->CFGR |= RCC_CFGR_MCO_HSI;
      //RCC->CFGR |= RCC_CFGR_MCO_LSI;
      //RCC->CFGR |= RCC_CFGR_MCO_PLL;
      RCC->CFGR |= RCC_CFGR_MCO_SYSCLK;

      // PLL as the system clock
      RCC->CFGR &= ~RCC_CFGR_SW;    // Clear the SW bits
      RCC->CFGR |= RCC_CFGR_SW_PLL; //Select PLL as the system clock
      while ((RCC->CFGR & RCC_CFGR_SWS_PLL) != RCC_CFGR_SWS_PLL); //Wait until PLL is used

      // Bit-bang monitoring:
      RCC->AHBENR |= RCC_AHBENR_GPIOEEN;
      GPIOE->MODER |= GPIO_MODER_MODER10_0;
      GPIOE->OTYPER &= ~GPIO_OTYPER_OT_10;
      GPIOE->PUPDR &= ~GPIO_PUPDR_PUPDR10;
      GPIOE->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR10;

      while(1)
      {
          GPIOE->BSRRL |= GPIO_BSRR_BS_10;
          GPIOE->BRR |= GPIO_BRR_BR_10;

      }
}

कोड को V2 के साथ GNU ARM एंबेडेड टूलचैन का उपयोग करके -O1 ऑप्टिमाइज़ेशन के साथ संकलित किया गया था। एक आस्टसीलस्कप के साथ जांच की गई पीए 8 (मको) और पीई 10 पर संकेत इस तरह दिखते हैं: यहाँ छवि विवरण दर्ज करें

SYSCLK सही ढंग से कॉन्फ़िगर किया गया प्रतीत होता है, क्योंकि MCO (नारंगी वक्र) लगभग 64 MHz (आंतरिक घड़ी के त्रुटि मार्जिन पर विचार) का दोलन दिखाता है। मेरे लिए अजीब हिस्सा PE10 (नीला वक्र) पर व्यवहार है। अनंत (1) लूप में प्राथमिक 3-चरण संचालन (यानी बिट-सेट / बिट-रीसेट / रिटर्न) करने के लिए 4 + 4 + 5 = 13 घड़ी चक्र लगते हैं। यह अन्य अनुकूलन स्तरों पर और भी बदतर हो जाता है (जैसे -O2, -O3, ar -Os): सिग्नल के कम भाग में कई अतिरिक्त घड़ी चक्र जोड़े जाते हैं, यानी PE10 के गिरने और उठने वाले किनारों के बीच (LSI को किसी भी तरह से सक्षम करना) इस स्थिति को दूर करने के लिए)।

क्या इस MCU से यह व्यवहार अपेक्षित है? मैं एक कार्य की कल्पना करूँगा, जो 2-4 गुना तेज होने के लिए सेट और रीसेट करने में सरल है। क्या चीजों को गति देने का एक तरीका है?


क्या आपने तुलना करने के लिए कुछ अन्य MCU के साथ प्रयास किया है?
मार्को बुरिच

3
आप क्या हासिल करने की कोशिश कर रहे हैं? यदि आप एक तेज़ दोलन उत्पादन चाहते हैं तो आपको टाइमर का उपयोग करना चाहिए। यदि आप तेज धारावाहिक प्रोटोकॉल के साथ इंटरफेस करना चाहते हैं, तो आपको संबंधित हार्डवेयर परिधीय का उपयोग करना चाहिए।
जोनास श्फर

2
किट के साथ शानदार शुरुआत !!
स्कॉट सेडमन

आपको केवल बीएसआरआर या बीआरआर रजिस्टर नहीं करना चाहिए क्योंकि वे केवल लिखे जाते हैं।
P__J__

जवाबों:


25

यहाँ वास्तव में सवाल यह है: सी प्रोग्राम से जो मशीन कोड आप उत्पन्न कर रहे हैं, वह क्या है और यह आपकी अपेक्षा से भिन्न कैसे है।

यदि आपके पास मूल कोड तक पहुंच नहीं है, तो यह रिवर्स इंजीनियरिंग (मूल रूप से कुछ के साथ शुरू होने वाला radare2 -A arm image.bin; aaa; VV) में एक अभ्यास होगा , लेकिन आपको कोड मिल गया है, जिससे यह सब आसान हो जाएगा।

सबसे पहले, इसे उस -gझंडे के साथ संकलित करें CFLAGS(जहाँ आप भी निर्दिष्ट करते हैं -O1)। फिर, उत्पन्न विधानसभा को देखें:

arm-none-eabi-objdump -S yourprog.elf

ध्यान दें कि बेशक दोनों का नाम है objdump रूप बाइनरी साथ-साथ आपकी मध्यवर्ती ईएलएफ फ़ाइल दोनों अलग-अलग हो सकती हैं।

आमतौर पर, आप केवल उस हिस्से को छोड़ सकते हैं जहां जीसीसी कोडांतरक को आमंत्रित करता है और बस असेंबली फ़ाइल को देखता है। बस -SGCC कमांड लाइन में जोड़ें - लेकिन यह सामान्य रूप से आपके निर्माण को तोड़ देगा, इसलिए आप शायद इसे बाहर ही करेंगे अपने आईडीई के करेंगे।

मैंने आपके कोड के थोड़े थोड़े संस्करण की असेंबली की :

arm-none-eabi-gcc 
    -O1 ## your optimization level
    -S  ## stop after generating assembly, i.e. don't run `as`
    -I/path/to/CMSIS/ST/STM32F3xx/ -I/path/to/CMSIS/include
     test.c

और निम्नलिखित मिला (अंश, लिंक ऊपर पूर्ण कोड):

.L5:
    ldr r2, [r3, #24]
    orr r2, r2, #1024
    str r2, [r3, #24]
    ldr r2, [r3, #40]
    orr r2, r2, #1024
    str r2, [r3, #40]
    b   .L5

जो एक लूप है (अंत में बिना शर्त कूदो .L5 और शुरुआत में .L5 लेबल देखें)।

हम यहां जो देखते हैं, वह हम हैं

  • पहले ldr(लोड रजिस्टर) + 24 बाइट्स r2में संग्रहीत मेमोरी स्थान पर मूल्य के साथ रजिस्टर r3। यह देखने के लिए बहुत आलसी होने के नाते: के स्थान की बहुत संभावना है BSRR
  • फिर लगातार साथ रजिस्टर , जो कि रजिस्टर में 10 वीं बिट की स्थापना के अनुरूप होता है, और करने के लिए परिणाम बारे में ही।ORr21024 == (1<<10)r2
  • फिर str(स्टोर) उस मेमोरी लोकेशन में परिणाम, जो हमने पहले चरण में पढ़ा है
  • और फिर एक अलग स्मृति स्थान के लिए एक ही दोहराएं, आलसीपन से बाहर: सबसे अधिक संभावना BRRका पता।
  • अंत में b(शाखा) पहले कदम पर वापस।

इसलिए हमारे पास शुरू करने के लिए 7 निर्देश हैं, तीन नहीं। केवल bएक बार ही होता है, और इस प्रकार बहुत संभावना है कि एक विषम संख्या में चक्र हो रहा है (हमारे पास कुल 13 हैं, इसलिए कहीं न कहीं एक चक्र चक्र से आना आवश्यक है)। चूँकि 13 से नीचे की सभी विषम संख्याएँ 1, 3, 5, 7, 9, 11 हैं, और हम 13-6 से बड़े किसी भी संख्या को नियंत्रित कर सकते हैं (यह मानते हुए कि सीपीयू एक चक्र से कम समय में किसी निर्देश का निष्पादन नहीं कर सकता ), हम जानते हैं कि b1, 3, 5 या 7 सीपीयू चक्र लेता है।

हम कौन हैं, मैंने निर्देश के एआरएम के दस्तावेज़ीकरण पर ध्यान दिया और उन्होंने एम 3 के लिए कितना चक्र लिया :

  • ldr 2 चक्र लेता है (ज्यादातर मामलों में)
  • orr 1 चक्र लगता है
  • str 2 चक्र लेता है
  • b2 से 4 चक्र लगते हैं। हम जानते हैं कि यह एक विषम संख्या होनी चाहिए , इसलिए इसे 3, यहाँ लेना होगा

आपके अवलोकन से सभी पंक्तियाँ:

13=2(सीएलआर+सीआरआर+सीरोंटीआर)+सी=2(2+1+2)+3=25+3

जैसा कि ऊपर की गणना से पता चलता है, आपके पाश को तेजी से बनाने का एक तरीका शायद ही होगा - एआरएम प्रोसेसर पर आउटपुट पिन आमतौर पर मेमोरी मैप किए जाते हैं , सीपीयू कोर रजिस्टर नहीं, इसलिए आपको सामान्य लोड से गुजरना होगा - संशोधित - स्टोर दिनचर्या आप उन लोगों के साथ कुछ भी करना चाहते हैं।

आप जो कुछ भी कर सकते हैं, वह नहीं पढ़ा जाता है ( |=संक्षेप में पढ़ना पड़ता है) पिन के मूल्य को हर लूप पुनरावृत्ति, लेकिन बस इसके लिए एक स्थानीय चर का मान लिखें, जिसे आप हर लूप पुनरावृत्ति को टॉगल करते हैं।

ध्यान दें कि मुझे लगता है कि आप 8bit माइक्रो से परिचित हो सकते हैं, और केवल 8 बिट मूल्यों को पढ़ने का प्रयास कर रहे हैं, उन्हें स्थानीय 8 बिट चर में संग्रहीत करेंगे, और उन्हें 8 बिट विखंडू में लिखेंगे। मत करो। एआरएम एक 32 बिट वास्तुकला है, और 32 बिट शब्द के 8 बिट को निकालने से अतिरिक्त निर्देश हो सकते हैं। यदि आप कर सकते हैं, तो बस पूरे 32 बिट शब्द को पढ़ें, जो आपको चाहिए उसे संशोधित करें और इसे पूरे के रूप में वापस लिखें। क्या यह संभव है कि आप जो लिख रहे हैं उस पर निर्भर करता है, यानी आपके मेमोरी-मैप किए गए GPIO की लेआउट और कार्यक्षमता। STM32F3 डेटाशीट / उपयोगकर्ता गाइड के बारे में जानकारी के लिए परामर्श करें कि जो 32 बिट में स्टोर किया गया है जिसमें आप टॉगल करना चाहते हैं।


अब, मैंने आपके मुद्दे को "कम" अवधि के साथ लंबे समय तक पुन: पेश करने की कोशिश की, लेकिन मैं बस नहीं कर सका - लूप बिल्कुल मेरे कंपाइलर संस्करण के साथ -O3जैसा दिखता है -O1। आपको खुद ऐसा करना पड़ेगा! हो सकता है कि आप GCC के कुछ प्राचीन संस्करण का उपयोग कर रहे हों।


4
जैसा कि आप कहते हैं, बस ( =बजाय |=) के भंडारण नहीं होगा , बिल्कुल ओपी के लिए देख रहे हैं हो सकता है? एआरएम के पास बीआरआर और बीएसआरआर रजिस्टर अलग-अलग होने के कारण रीड-मॉडिफाई-राइट की आवश्यकता नहीं है। इस मामले में, स्थिरांक को लूप के बाहर रजिस्टरों में संग्रहीत किया जा सकता है, इसलिए आंतरिक लूप सिर्फ 2 स्ट्रेट्स और एक शाखा होगा, इसलिए पूरे दौर के लिए 2 + 2 +3 = 7 चक्र?
तिमो

धन्यवाद। यह वास्तव में काफी कुछ साफ कर दिया। यह सोचना जल्दबाजी थी कि केवल 3 घड़ी चक्रों की आवश्यकता होगी - 6 से 7 चक्र कुछ ऐसे थे जिनकी मैं वास्तव में उम्मीद कर रहा था। -O3त्रुटि सफाई और समाधान के पुनर्निर्माण के बाद गायब हो गए हैं लगता है। बहरहाल, मेरे विधानसभा कोड में इसके भीतर एक अतिरिक्त UTXH निर्देश है: .L5: ldrh r3, [r2, #24] uxth r3, r3 orr r3, r3, #1024 strh r3, [r2, #24] @ movhi ldr r3, [r2, #40] orr r3, r3, #1024 str r3, [r2, #40] b .L5
KR

1
uxthवहाँ है क्योंकि GPIO->BSRRL(गलत तरीके से) आपके हेडर में 16 बिट रजिस्टर के रूप में परिभाषित किया गया है। STM32CubeF3 पुस्तकालयों से हेडर के हाल के संस्करण का उपयोग करें , जहां बीएसआरआरएल और बीएसआरआरएच नहीं है, लेकिन एक एकल 32 बिट BSRRरजिस्टर है। @Marcus में स्पष्ट रूप से सही हेडर हैं, इसलिए उनका कोड एक हाफ़ लोड करने और उसे विस्तारित करने के बजाय पूर्ण 32 बिट एक्सेस करता है।
बेरेन्डी - विरोध

एक ही बाइट को लोड करना अतिरिक्त निर्देश क्यों लेगा? एआरएम वास्तुकला है LDRBऔर STRBकि प्रदर्शन बाइट एक एकल अनुदेश में / लिखने पढ़ता है, नहीं?
psmears

1
M3 कोर कर सकते हैं बिट-बैंडिंग का समर्थन (यह सुनिश्चित नहीं है कि यह विशेष कार्यान्वयन करता है), जहां 1 एमबी का परिधीय मेमोरी स्पेस 32 एमबी क्षेत्र के लिए अलियास है। प्रत्येक बिट में एक असतत शब्द पता होता है (बिट 0 केवल प्रयोग किया जाता है)। शायद अभी भी सिर्फ एक लोड / स्टोर की तुलना में धीमी है।
शॉन हुलिएन

8

BSRRऔर BRRरजिस्टरों की स्थापना और व्यक्तिगत बंदरगाह बिट्स रीसेट करने के लिए कर रहे हैं:

GPIO पोर्ट बिट सेट / रीसेट रजिस्टर (GPIOx_BSRR)

...

(एक्स = ए..एच) बिट्स १५: ०

BSy: पोर्ट x सेट बिट y (y = 0..15)

ये बिट्स केवल-लेखन हैं। इन बिट्स के लिए पढ़ा गया मान 0x0000 लौटाता है।

0: संबंधित ODRx बिट पर कोई कार्रवाई नहीं

1: इसी ODRx बिट सेट करता है

जैसा कि आप देख सकते हैं, इन रजिस्टरों को पढ़ना हमेशा 0 देता है, इसलिए आपका कोड क्या है

GPIOE->BSRRL |= GPIO_BSRR_BS_10;
GPIOE->BRR |= GPIO_BRR_BR_10;

प्रभावी ढंग से है GPIOE->BRR = 0 | GPIO_BRR_BR_10 , लेकिन अनुकूलक कि पता नहीं है, तो यह एक अनुक्रम उत्पन्न करता है LDR, ORR, STRएक भी दुकान के बजाय निर्देश।

आप महज पढ़-लिखकर महंगे रीड-मॉडिफाई-राइट ऑपरेशन से बच सकते हैं

GPIOE->BSRRL = GPIO_BSRR_BS_10;
GPIOE->BRR = GPIO_BRR_BR_10;

समान रूप से विभाज्य 8 में लूप को संरेखित करने से आपको कुछ और सुधार मिल सकता है । लूप asm("nop");से पहले एक या मोड निर्देश डालने का प्रयास करें while(1)


1

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

top:
   subs r0,#1
   bne top

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

लूप के संरेखण को बदलने से नाटकीय रूप से प्रदर्शन अलग-अलग हो सकता है, विशेष रूप से एक छोटे लूप के साथ जैसे कि यदि यह एक के बजाय दो लाईन लाइनें लेता है, तो आप उस अतिरिक्त लागत को खाते हैं, जैसे कि माइक्रोकंट्रोलर पर जहां फ्लैश सीपीयू की तुलना में धीमा होता है या 3 और फिर घड़ी को ऊपर उठाने से अनुपात 3 या 4 या 5 से भी अधिक हो जाता है, अतिरिक्त फ़िशिंग जोड़ने से।

आपके पास कैश नहीं होने की संभावना है, लेकिन अगर आपके पास यह है कि यह कुछ मामलों में मदद करता है, लेकिन यह दूसरों में दर्द होता है और / या इससे कोई फर्क नहीं पड़ता है। शाखा की भविष्यवाणी जो आपके यहां हो सकती है या नहीं (शायद नहीं) केवल पाइप में डिज़ाइन की गई चीज़ों के रूप में देख सकती है, इसलिए भले ही आपने लूप को शाखा में बदल दिया हो और अंत में बिना शर्त शाखा हो (शाखा के भविष्यवक्ता के लिए आसान) उपयोग) वह सब करता है जो आपको बचा रहा है कि अगली घड़ियों पर कई घड़ियां (पाइप का आकार जहां से यह आम तौर पर कितना गहरा भविष्यवक्ता देख सकता है) प्राप्त होगा और / या यह सिर्फ मामले में एक प्रीफैच नहीं करता है।

लाने और कैश लाइनों के संबंध में संरेखण को बदलकर आप यह प्रभावित कर सकते हैं कि शाखा के भविष्यवक्ता आपकी मदद कर रहे हैं या नहीं, और यह कि समग्र प्रदर्शन में देखा जा सकता है, भले ही आप केवल दो निर्देशों का परीक्षण कर रहे हों या उन दोनों को कुछ नोड्स के साथ। ।

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

बीएसआरआर रजिस्टर का उपयोग करना सीखने के बाद, फ्लैश के बजाय रैम (कॉपी और जंप) से अपना कोड चलाने का प्रयास करें, जो आपको कुछ और किए बिना निष्पादन में 2 से 3 गुना प्रदर्शन को बढ़ावा देना चाहिए।


0

क्या इस MCU से यह व्यवहार अपेक्षित है?

यह आपके कोड का एक व्यवहार है।

  1. आपको BRR / BSRR रजिस्टरों को लिखना चाहिए, जैसा कि आप अभी पढ़ते हैं, संशोधित नहीं करते हैं।

  2. आप लूप ओवरहेड को भी उकसाते हैं। अधिकतम प्रदर्शन के लिए, BRR / BSRR परिचालनों को बार-बार दोहराएं → उन्हें कई बार लूप में कॉपी-पेस्ट करें ताकि आप एक लूप ओवरहेड से पहले कई सेट / रीसेट चक्रों से गुजरें।

संपादित करें: IAR के तहत कुछ त्वरित परीक्षण।

BRR / BSRR को लिखने के माध्यम से एक फ्लिप मध्यम अनुकूलन के तहत 6 निर्देश और अनुकूलन के उच्चतम स्तर के तहत 3 निर्देश लेता है; RMW'ng के माध्यम से एक फ्लिप 10 निर्देश / 6 निर्देश लेता है।

लूप ओवरहेड अतिरिक्त।


एक एकल बिट सेट / रीसेट चरण में बदलने |=से =9 घड़ी चक्र ( लिंक ) की खपत होती है । असेंबली कोड 3 निर्देश लंबा है:.L5 strh r1, [r3, #24] @ movhi str r2, [r3, #40] b .L5
KR

1
मैन्युअल रूप से छोरों को अनियंत्रित न करें । यह व्यावहारिक रूप से एक अच्छा विचार नहीं है। इस विशेष मामले में, यह विशेष रूप से विनाशकारी है: यह तरंग को गैर-आवधिक बनाता है। इसके अलावा, फ्लैश में कई बार समान कोड होना जरूरी नहीं है। यह यहां लागू नहीं हो सकता (यह हो सकता है!), लेकिन लूप अनरोलिंग एक ऐसी चीज है जो बहुत से लोगों को लगता है कि मदद करता है, जो कि कंपाइलर ( gcc -funroll-loops) बहुत अच्छी तरह से कर सकते हैं, और जब गाली दी जाती है (जैसे यहां) आप जो चाहते हैं उसका उलटा प्रभाव पड़ता है।
मार्कस मुलर

एक अनंत लूप को लगातार समय के व्यवहार को बनाए रखने के लिए कभी भी प्रभावी ढंग से अनियंत्रित नहीं किया जा सकता है।
मार्कस मुलर

1
@ MarcusMüller: अनंत लूप कभी-कभी लगातार समय को बनाए रखते हुए उपयोगी रूप से अनियंत्रित हो सकते हैं यदि लूप के कुछ दोहराव में कोई बिंदु होते हैं जहां एक निर्देश का कोई प्रभाव नहीं दिखाई देगा। उदाहरण के लिए, यदि somePortLatchएक पोर्ट नियंत्रित करता है जिसके निचले 4 बिट्स आउटपुट के लिए सेट हैं, तो while(1) { SomePortLatch ^= (ctr++); }कोड में अनियंत्रित होना संभव हो सकता है जो 15 मानों को आउटपुट करता है और फिर उस समय शुरू करने के लिए वापस लूप करता है जब यह अन्यथा उसी मूल्य को दो बार पंक्ति में आउटपुट करता है।
सुपरकैट

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