जल्दी से पता लगाएं कि क्या सी सरणी में एक मूल्य मौजूद है?


124

मेरे पास एक टाइम-क्रिटिकल ISR के साथ एक एम्बेडेड एप्लिकेशन है, जिसे आकार 256 (अधिमानतः 1024, लेकिन 256 न्यूनतम है) के एक सरणी के माध्यम से पुनरावृत्त करने की आवश्यकता है और चेक करें कि क्या मान ऐरे सामग्री से मेल खाता है। A boolको सही पर सेट किया जाएगा यह मामला है।

माइक्रोकंट्रोलर एक NXP LPC4357, ARM Cortex M4 कोर है, और कंपाइलर GCC है। मेरे पास पहले से ही संयुक्त अनुकूलन स्तर 2 (3 धीमा है) है और फ्लैश के बजाय फ़ंक्शन को रैम में रखता है। मैं सूचक अंकगणितीय और एक forलूप का भी उपयोग करता हूं , जो ऊपर की बजाय डाउन-काउंटिंग करता है (यदि जाँच i!=0से अधिक तेज़ है तो जाँचना i<256)। सभी में, मैं 12.5 µ की अवधि के साथ समाप्त होता हूं, जिसे संभव होने के लिए काफी कम करना पड़ता है। यह (छद्म) कोड है जिसका मैं अभी उपयोग करता हूं:

uint32_t i;
uint32_t *array_ptr = &theArray[0];
uint32_t compareVal = 0x1234ABCD;
bool validFlag = false;

for (i=256; i!=0; i--)
{
    if (compareVal == *array_ptr++)
    {
         validFlag = true;
         break;
     }
}

ऐसा करने का सबसे तेज़ तेज़ तरीका क्या होगा? इनलाइन असेंबली का उपयोग करने की अनुमति है। अन्य 'कम सुरुचिपूर्ण' ट्रिक्स की भी अनुमति है।


28
क्या एरे में मूल्य को अलग तरीके से संग्रहीत करने का कोई तरीका है? यदि आप उन्हें हल कर सकते हैं, तो एक द्विआधारी खोज निश्चित रूप से तेज होगी। यदि संग्रहीत और खोजे जाने वाले डेटा एक निश्चित सीमा के भीतर हैं, तो वे थोड़े नक्शे के साथ प्रतिनिधित्व करने योग्य हो सकते हैं, आदि
Remo.D

20
@BitBank: आपको आश्चर्य होगा कि पिछले तीन दशकों में कंपाइलरों में कितना सुधार हुआ है। एआरएम एक्सक्लूसिवली काफी कंपाइलर-फ्रेंडली है। और मैं इस तथ्य के लिए जानता हूं कि जीसीसी पर एआरएम लोड-मल्टीपल निर्देश जारी कर सकते हैं (2009 से कम से कम)
MSalters

8
भयानक सवाल है, लोग भूल जाते हैं कि वास्तविक दुनिया के मामले हैं जहां प्रदर्शन मायने रखता है। कई बार इस तरह के सवालों का जवाब "सिर्फ स्टाल का उपयोग करें" के साथ दिया जाता है
किक

14
शीर्षक "... एक सरणी के माध्यम से पुनरावृत्ति" वास्तव में आप केवल दिए गए मूल्य के लिए खोज रहे हैं भ्रामक है। एक सरणी पर पुनरावृत्ति करने के लिए प्रत्येक प्रविष्टि पर कुछ करना होता है। छंटनी, अगर लागत कई खोजों से अधिक हो सकती है, तो वास्तव में भाषा कार्यान्वयन मुद्दों से स्वतंत्र एक कुशल दृष्टिकोण है।
हार्डमैथ

8
क्या आप वाकई बाइनरी खोज या हैश टेबल का उपयोग नहीं कर सकते हैं? 256 वस्तुओं के लिए एक द्विआधारी खोज == 8 तुलना। एक हैश तालिका == औसत कूद पर 1 जंप (या यदि आप एक सही हैश है तो 1 जंप अधिकतम )। आपको केवल 1 के बाद असेंबली ऑप्टिमाइज़ेशन का सहारा लेना चाहिए) आपके पास एक सभ्य खोज एल्गोरिथ्म है ( O(1)या O(logN)तुलना में O(N)), और 2) आपने इसे अड़चन होने के लिए प्रोफाइल किया है।
ग्रू

जवाबों:


105

ऐसी परिस्थितियों में जहां प्रदर्शन का अत्यधिक महत्व होता है, सी कंपाइलर संभवत: हैंड ट्यून असेंबली लैंग्वेज के साथ जो कर सकता है उसकी तुलना में सबसे तेज कोड का उत्पादन नहीं करता है। मैं कम से कम प्रतिरोध का रास्ता अपनाता हूं - इस तरह की छोटी दिनचर्या के लिए, मैं सिर्फ asm कोड लिखता हूं और एक अच्छा विचार है कि इसे निष्पादित करने में कितने चक्र लगेंगे। आप सी कोड के साथ फील कर सकते हैं और अच्छे आउटपुट उत्पन्न करने के लिए कंपाइलर प्राप्त कर सकते हैं, लेकिन आप इस तरह से आउटपुट को ट्यून करते हुए बहुत समय बर्बाद कर सकते हैं। पिछले कुछ वर्षों में कंपाइलर्स (विशेष रूप से Microsoft से) ने एक लंबा सफर तय किया है, लेकिन वे अभी भी आपके कानों के बीच कंपाइलर की तरह स्मार्ट नहीं हैं क्योंकि आप अपनी विशिष्ट स्थिति पर काम कर रहे हैं, न कि सिर्फ एक सामान्य स्थिति में। कंपाइलर कुछ निर्देशों (जैसे LDM) का उपयोग नहीं कर सकता है जो इसे गति दे सकते हैं, और यह ' लूप को अनियंत्रित करने के लिए पर्याप्त स्मार्ट होने की संभावना नहीं है। यहां ऐसा करने का एक तरीका है जो मेरी टिप्पणी में उल्लिखित 3 विचारों को शामिल करता है: लूप अनरोल करना, कैश प्रीफ़ेट और कई लोड (एलडीएम) अनुदेश का उपयोग करना। निर्देश चक्र गणना लगभग 3 घड़ियों प्रति सरणी तत्व के लिए निकलती है, लेकिन यह मेमोरी देरी को ध्यान में नहीं रखती है।

ऑपरेशन का सिद्धांत: एआरएम का सीपीयू डिजाइन एक घड़ी चक्र में अधिकांश निर्देशों को निष्पादित करता है, लेकिन निर्देशों को एक पाइपलाइन में निष्पादित किया जाता है। सी कंपाइलर बीच में अन्य निर्देशों को इंटरलेय करके पाइपलाइन की देरी को खत्म करने की कोशिश करेंगे। जब मूल सी कोड की तरह एक तंग लूप के साथ प्रस्तुत किया जाता है, तो संकलक को देरी को छिपाने में एक कठिन समय होगा क्योंकि स्मृति से पढ़े गए मूल्य की तुरंत तुलना की जानी चाहिए। 4 रजिस्टरों के 2 सेट के बीच वैकल्पिक रूप से मेरा कोड मेमोरी की देरी और डेटा को प्राप्त करने वाली पाइपलाइन को काफी कम करने के लिए है। सामान्य तौर पर, जब बड़े डेटा सेट के साथ काम करते हैं और आपका कोड उपलब्ध रजिस्टरों में से अधिकांश या सभी का उपयोग नहीं करता है, तो आपको अधिकतम प्रदर्शन नहीं मिल रहा है।

; r0 = count, r1 = source ptr, r2 = comparison value

   stmfd sp!,{r4-r11}   ; save non-volatile registers
   mov r3,r0,LSR #3     ; loop count = total count / 8
   pld [r1,#128]
   ldmia r1!,{r4-r7}    ; pre load first set
loop_top:
   pld [r1,#128]
   ldmia r1!,{r8-r11}   ; pre load second set
   cmp r4,r2            ; search for match
   cmpne r5,r2          ; use conditional execution to avoid extra branch instructions
   cmpne r6,r2
   cmpne r7,r2
   beq found_it
   ldmia r1!,{r4-r7}    ; use 2 sets of registers to hide load delays
   cmp r8,r2
   cmpne r9,r2
   cmpne r10,r2
   cmpne r11,r2
   beq found_it
   subs r3,r3,#1        ; decrement loop count
   bne loop_top
   mov r0,#0            ; return value = false (not found)
   ldmia sp!,{r4-r11}   ; restore non-volatile registers
   bx lr                ; return
found_it:
   mov r0,#1            ; return true
   ldmia sp!,{r4-r11}
   bx lr

अपडेट: टिप्पणियों में बहुत सारे संदेह हैं जो सोचते हैं कि मेरा अनुभव उपाख्यान / बेकार है और प्रमाण की आवश्यकता है। मैंने अनुकूलन ( -2 ) के साथ निम्न आउटपुट उत्पन्न करने के लिए (एंड्रॉइड एनडीके 9 सी से) जीसीसी 4.8 का उपयोग किया (सभी अनुकूलन लूप अन्रॉलिंग सहित चालू हो गए )। मैंने उपरोक्त प्रश्न में प्रस्तुत मूल C कोड संकलित किया। यहां GCC द्वारा निर्मित क्या है:

.L9: cmp r3, r0
     beq .L8
.L3: ldr r2, [r3, #4]!
     cmp r2, r1
     bne .L9
     mov r0, #1
.L2: add sp, sp, #1024
     bx  lr
.L8: mov r0, #0
     b .L2

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

अद्यतन 2: मैंने Microsoft के विज़ुअल स्टूडियो 2013 SP2 को कोड के साथ बेहतर करने का मौका दिया। यह मेरी सरणी आरंभीकरण को वेक्टर करने के लिए नीयन निर्देशों का उपयोग करने में सक्षम था, लेकिन ओपी द्वारा लिखित रैखिक मान खोज जीसीसी द्वारा उत्पन्न (मैं इसे और अधिक पठनीय बनाने के लिए लेबल का नाम बदला) के समान था:

loop_top:
   ldr  r3,[r1],#4  
   cmp  r3,r2  
   beq  true_exit
   subs r0,r0,#1 
   bne  loop_top
false_exit: xxx
   bx   lr
true_exit: xxx
   bx   lr

जैसा कि मैंने कहा, मैं ओपी के सटीक हार्डवेयर का मालिक नहीं हूं, लेकिन मैं 3 अलग-अलग संस्करणों के एनवीडिया टेग्रा 3 और टेग्रा 4 पर प्रदर्शन का परीक्षण करूंगा और जल्द ही यहां परिणाम पोस्ट करूंगा।

अद्यतन 3: मैंने अपना कोड और Microsoft का संकलन एआरएम कोड एक टेग्रा 3 और टेग्रा 4 (सरफेस आरटी, सर्फेस आरटी 2) पर चलाया। मैंने एक लूप की 1000000 पुनरावृत्तियों को चलाया जो एक मैच खोजने में विफल रहता है ताकि सब कुछ कैश में हो और मापना आसान हो।

             My Code       MS Code
Surface RT    297ns         562ns
Surface RT 2  172ns         296ns  

दोनों ही मामलों में मेरा कोड लगभग दोगुना तेज चलता है। अधिकांश आधुनिक एआरएम सीपीयू शायद इसी तरह के परिणाम देंगे।


13
@ LưuV LnhPhúc - यह आम तौर पर सच है, लेकिन तंग ISR सबसे बड़े अपवादों में से एक है, जिसमें आप अक्सर कंपाइलर की तुलना में बहुत अधिक जानते हैं।
सपि

47
डेविल के वकील: क्या कोई मात्रात्मक सबूत है कि यह कोड तेज है?
ओलिवर चार्ल्सवर्थ

11
@ बिटबैंक: यह काफी अच्छा नहीं है। आपको सबूतों के साथ अपने दावों का समर्थन करना होगा ।
ऑर्बिट में

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

14
"टिप्पणी में संदेह करने वाले जो सोचते हैं कि मेरा अनुभव किस्सा / बेकार है और प्रमाण की आवश्यकता है।" उनकी टिप्पणियों को नकारात्मक रूप से न लें। सबूत दिखाने से बस आपका शानदार जवाब मिलता है।
कोड़ी ग्रे

87

इसे अनुकूलित करने के लिए एक चाल है (मुझे एक बार नौकरी-साक्षात्कार पर यह पूछा गया था):

  • यदि सरणी में अंतिम प्रविष्टि वह मान रखती है जिसे आप खोज रहे हैं, तो सही लौटें
  • उस मान को लिखें जो आप सरणी में अंतिम प्रविष्टि के लिए देख रहे हैं
  • जब तक आप उस मान का सामना न कर लें, जिसे आप ढूंढ रहे हैं, तब तक इरेट करें
  • यदि आपने इसे सरणी में अंतिम प्रविष्टि से पहले सामना किया है, तो सही लौटें
  • विवरण झूठा है

bool check(uint32_t theArray[], uint32_t compareVal)
{
    uint32_t i;
    uint32_t x = theArray[SIZE-1];
    if (x == compareVal)
        return true;
    theArray[SIZE-1] = compareVal;
    for (i = 0; theArray[i] != compareVal; i++);
    theArray[SIZE-1] = x;
    return i != SIZE-1;
}

यह प्रति शाखा दो शाखाओं के बजाय प्रति पुनरावृत्ति प्रति एक शाखा प्राप्त करता है।


अपडेट करें:

यदि आपको सरणी आवंटित करने की अनुमति है SIZE+1, तो आप "अंतिम प्रविष्टि स्वैपिंग" भाग से छुटकारा पा सकते हैं:

bool check(uint32_t theArray[], uint32_t compareVal)
{
    uint32_t i;
    theArray[SIZE] = compareVal;
    for (i = 0; theArray[i] != compareVal; i++);
    return i != SIZE;
}

आप theArray[i]इसके बजाय निम्नलिखित का उपयोग करके, अतिरिक्त अंकगणित से छुटकारा पा सकते हैं :

bool check(uint32_t theArray[], uint32_t compareVal)
{
    uint32_t *arrayPtr;
    theArray[SIZE] = compareVal;
    for (arrayPtr = theArray; *arrayPtr != compareVal; arrayPtr++);
    return arrayPtr != theArray+SIZE;
}

यदि संकलक पहले से ही इसे लागू नहीं करता है, तो यह फ़ंक्शन सुनिश्चित करने के लिए ऐसा करेगा। दूसरी ओर, यह लूप को अनियंत्रित करने के लिए ऑप्टिमाइज़र पर कठिन बना सकता है, इसलिए आपको यह सत्यापित करना होगा कि उत्पन्न विधानसभा में ...


2
@ratchetfreak: ओपी यह विवरण नहीं देता है कि यह सरणी कैसे, कहाँ और कब आवंटित की गई है और आरंभिक है, इसलिए मैंने एक उत्तर दिया जो उस पर निर्भर नहीं करता है।
बराक मंस

3
ऐरे रैम में है, हालांकि लिखने की अनुमति नहीं है।
बजे

1
अच्छा है, लेकिन सरणी अब नहीं है const, जो इसे थ्रेड-सुरक्षित नहीं बनाता है। भुगतान करने के लिए एक उच्च कीमत की तरह लगता है।
EOF

2
@ ईओएफ: constप्रश्न में कभी भी कहां उल्लेख किया गया था ?
बराक मानोस

4
@barakmanos: यदि मैं आपके लिए एक सरणी और एक मान पास करता हूं, और आपसे पूछता हूं कि क्या मान सरणी में है, तो मैं आमतौर पर यह नहीं मानता कि आप सरणी को संशोधित करेंगे। मूल प्रश्न में न तो constधागे का उल्लेख है , लेकिन मुझे लगता है कि इस चेतावनी का उल्लेख करना उचित है।
EOF

62

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

परफेक्ट हैश फंक्शन

यदि आपके 256 "मान्य" मान स्थिर हैं और संकलन समय पर ज्ञात हैं, तो आप एक सही हैश फ़ंक्शन का उपयोग कर सकते हैं । आपको एक हैश फ़ंक्शन खोजने की ज़रूरत है जो आपके इनपुट मान को श्रेणी 0 .. n में मान के लिए मैप करता है , जहां आपके द्वारा परवाह किए गए सभी मान्य मानों के लिए कोई टकराव नहीं हैं । यही है, कोई दो "मान्य" मान समान आउटपुट मान के लिए हैश नहीं है। एक अच्छे हैश फ़ंक्शन की खोज करते समय, आप निम्न का लक्ष्य रखते हैं:

  • हैश फ़ंक्शन को यथोचित रूप से तेज़ रखें।
  • कम से कम n । सबसे छोटा आप 256 प्राप्त कर सकते हैं (न्यूनतम सही हैश फ़ंक्शन), लेकिन डेटा के आधार पर इसे प्राप्त करना मुश्किल है।

कुशल हैश कार्यों के लिए ध्यान दें, n अक्सर 2 की शक्ति है, जो कम बिट्स (और ऑपरेशन) के बिटवाइज़ मास्क के बराबर है। उदाहरण हैश कार्य:

  • इनपुट बाइट्स के सीआरसी, मोडुलो एन
  • ((x << i) ^ (x >> j) ^ (x << k) ^ ...) % n(कई के रूप में उठा i, j, k, ... जरूरत के रूप में, बाएं या दाएं बदलाव के साथ)

फिर आप n प्रविष्टियों की एक निश्चित तालिका बनाते हैं, जहां हैश इनपुट मानों को तालिका I में एक सूचकांक में मैप करता है । मान्य मान के लिए, तालिका प्रविष्टि मैं वैध मान है। अन्य सभी तालिका प्रविष्टियों के लिए, यह सुनिश्चित करें कि सूचकांक के प्रत्येक प्रविष्टि मैं कुछ अन्य अमान्य मान जो करने के लिए हैश नहीं होता है मैं

फिर इनपुट एक्स के साथ आपकी रुकावट की दिनचर्या में :

  1. हैश x को I में अनुक्रमणित करें (जो कि सीमा 0. में है)
  2. तालिका में प्रविष्टि i देखें और देखें कि इसमें मान x है या नहीं

यह 256 या 1024 मानों की रैखिक खोज की तुलना में बहुत तेज होगा।

मैंने उचित हैश फ़ंक्शन खोजने के लिए कुछ पायथन कोड लिखा है।

द्विआधारी खोज

यदि आप अपने सरणी को "मान्य" मान के अनुसार क्रमबद्ध करते हैं, तो आप रैखिक खोज के बजाय एक द्विआधारी खोज कर सकते हैं । इसका मतलब है कि आप केवल 8 चरणों ( log2(256)), या 10 चरणों में 1024-प्रवेश तालिका में 256-प्रवेश तालिका खोज सकते हैं । फिर, यह 256 या 1024 मानों की एक रेखीय खोज की तुलना में बहुत तेज होगा।


उसके लिए धन्यवाद। द्विआधारी खोज विकल्प वह है जिसे मैंने चुना है। पहले पोस्ट में एक टिप्पणी भी देखें। यह असेंबली का उपयोग किए बिना चाल को बहुत अच्छी तरह से करता है।
wlamers

11
दरअसल, अपने कोड (जैसे असेंबली या अन्य ट्रिक्स का उपयोग) को अनुकूलित करने की कोशिश करने से पहले आपको यह देखना चाहिए कि क्या आप एल्गोरिथम जटिलता को कम कर सकते हैं। आमतौर पर अल्गोरिदमिक जटिलता को कम करना कुछ चक्रों को स्कैप करने की कोशिश से अधिक कुशल होगा लेकिन समान एल्गोरिथम जटिलता को बनाए रखना।
ysdx

3
बाइनरी सर्च के लिए +1। एल्गोरिथम पुनः डिजाइन अनुकूलन का सबसे अच्छा तरीका है।
रॉकेटमैग्नेट

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

60

तालिका को क्रमबद्ध क्रम में रखें, और बेंटले की अनियंत्रित बाइनरी खोज का उपयोग करें:

i = 0;
if (key >= a[i+512]) i += 512;
if (key >= a[i+256]) i += 256;
if (key >= a[i+128]) i += 128;
if (key >= a[i+ 64]) i +=  64;
if (key >= a[i+ 32]) i +=  32;
if (key >= a[i+ 16]) i +=  16;
if (key >= a[i+  8]) i +=   8;
if (key >= a[i+  4]) i +=   4;
if (key >= a[i+  2]) i +=   2;
if (key >= a[i+  1]) i +=   1;
return (key == a[i]);

मुद्दा ये है,

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

** यदि आप संभावनाओं के संदर्भ में सोचने के अभ्यस्त नहीं हैं, तो प्रत्येक निर्णय बिंदु में एक एन्ट्रॉपी है , जिसे आप इसे निष्पादित करके सीखी जाने वाली औसत जानकारी है। के लिए >=परीक्षण, प्रत्येक शाखा की संभावना, 0.5, और -log2 (0.5) 1 है के बारे में है, तो इसका मतलब है कि यदि आप एक शाखा ले आप 1 बिट जानने, और यदि आप अन्य शाखा ले आप एक बिट, और औसत सीखते हैं कि उस शाखा की संभावना की तुलना में आप प्रत्येक शाखा पर जो कुछ भी सीखते हैं उसका योग है। तो 1*0.5 + 1*0.5 = 1, इसलिए >=परीक्षण की एन्ट्रॉपी 1 है। चूंकि आपके पास सीखने के लिए 10 बिट्स हैं, इसलिए 10 शाखाएं लगती हैं। इसलिए यह तेज है!

दूसरी ओर, क्या होगा यदि आपका पहला परीक्षण है if (key == a[i+512)? सच होने की संभावना 1/1024 है, जबकि झूठ की संभावना 1023/1024 है। तो अगर यह सच है कि आप सभी 10 बिट्स सीखते हैं! लेकिन अगर यह गलत है तो आप जानें -log2 (1023/1024) = .00141 बिट्स, व्यावहारिक रूप से कुछ भी नहीं! तो उस परीक्षण से जो औसत राशि आप सीखते हैं वह 10/1024 + .00141*1023/1024 = .0098 + .00141 = .0112बिट्स है। करीब एक सैकड़ा। वह परीक्षण अपना वजन नहीं उठा रहा है!


4
मुझे वास्तव में यह समाधान पसंद है। यदि मूल्य का स्थान संवेदनशील जानकारी है तो समय-आधारित फोरेंसिक से बचने के लिए इसे निश्चित संख्या में चक्रों में चलाने के लिए संशोधित किया जा सकता है।
ओरेगॉनट्रिल

1
@OregonTrail: समय-आधारित फोरेंसिक? मजेदार समस्या, लेकिन दुखद टिप्पणी।
माइक डनलैवी

16
आप समय पर हमलों को रोकने के लिए क्रिप्टो पुस्तकालयों में इस तरह अनियंत्रित छोरों को देखते हैं । en.wikipedia.org/wiki/Timing_attack । यहाँ एक अच्छा उदाहरण है github.com/jedisct1/libsodium/blob/… इस मामले में हम एक हमलावर को एक स्ट्रिंग की लंबाई का अनुमान लगाने से रोक रहे हैं। आमतौर पर हमलावर एक समय पर हमला करने के लिए एक फ़ंक्शन आह्वान के कई मिलियन नमूने ले जाएगा।
ओरेगॉनट्रिल

3
+1 महान! अच्छी छोटी अनियंत्रित खोज। मैंने पहले नहीं देखा था। मैं इसका इस्तेमाल कर सकता हूं।
रॉकेटमैग्नेट

1
@OregonTrail: मैं आपकी टाइमिंग-आधारित टिप्पणी को दूसरा कहता हूं। मेरे पास एक से अधिक बार क्रिप्टोग्राफ़िक कोड लिखना पड़ता है जो निश्चित संख्या में चक्रों में निष्पादित होता है, ताकि समय-आधारित हमलों की जानकारी लीक न हो सके।
टोनीके

16

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

आमतौर पर, कुंजी के विशिष्ट सेट के लिए सही हैश फ़ंक्शन गणना करने के लिए अपेक्षाकृत आसान है; आप नहीं चाहते हैं कि लंबे और जटिल हों क्योंकि समय के लिए प्रतिस्पर्धा संभवत: बेहतर तरीके से कई जांच कर रही है।

परफेक्ट हैशिंग एक "1-जांच अधिकतम" योजना है। एक विचार को सामान्य कर सकता है, इस विचार के साथ कि किसी को k जांच करने में लगने वाले समय के साथ हैश कोड कंप्यूटिंग की सादगी का व्यापार करना चाहिए। सब के बाद, लक्ष्य "कम से कम कुल समय है", सबसे कम जांच या सरल हैश फ़ंक्शन नहीं। हालाँकि, मैंने कभी किसी को k-probes-max हैशिंग एल्गोरिथ्म का निर्माण करते नहीं देखा। मुझे संदेह है कि कोई भी ऐसा कर सकता है, लेकिन इसकी संभावना अनुसंधान है।

एक अन्य विचार: यदि आपका प्रोसेसर बेहद तेज है, तो एक सही हैश से मेमोरी की जांच संभवतः निष्पादन समय पर हावी हो जाती है। यदि प्रोसेसर बहुत तेज नहीं है, तो k> 1 जांच व्यावहारिक हो सकती है।


1
एक कोर्टेक्स-एम कहीं भी बहुत तेज नहीं है
MSalters

2
वास्तव में इस मामले में उन्हें किसी भी हैश तालिका की आवश्यकता नहीं है। वह केवल यह जानना चाहता है कि क्या एक निश्चित कुंजी सेट में है, वह इसे एक मूल्य पर मैप नहीं करना चाहता है। इसलिए यह पर्याप्त है अगर सही हैश फ़ंक्शन प्रत्येक 32 बिट मान को 0 या 1 में मैप करता है जहां "1" को "सेट में" के रूप में परिभाषित किया जा सकता है।
डेविड ओंगारो

1
अच्छा बिंदु, अगर वह इस तरह के मानचित्रण का उत्पादन करने के लिए एक सही हैश जनरेटर प्राप्त कर सकता है। लेकिन, यह "एक बेहद घना सेट" होगा; मुझे लगता है कि वह एक सही हैश जनरेटर है कि ऐसा करता है पा सकते हैं। वह एक सही हैश पाने की कोशिश में बेहतर हो सकता है जो सेट में कुछ निरंतर K पैदा करता है, और किसी भी मूल्य पर K यदि सेट में नहीं है। मुझे संदेह है कि बाद के लिए भी एक आदर्श हैश प्राप्त करना कठिन है।
इरा बैक्सटर

@DavidOngaro table[PerfectHash(value)] == value1 का उत्पादन करता है यदि मान सेट में है और 0 यदि यह नहीं है, और PerfectHash फ़ंक्शन का उत्पादन करने के लिए अच्छी तरह से ज्ञात तरीके हैं (देखें, उदाहरण के लिए, burtleburtle.net/bob/hash/perm.html )। एक हैश फ़ंक्शन को खोजने की कोशिश करना जो सेट में सभी मूल्यों को सीधे 1 में मैप करता है और सभी मानों को 0 में सेट नहीं करना एक मूर्खतापूर्ण कार्य है।
जिम बाल्टर

@DavidOngaro: एक सही हैश फ़ंक्शन में कई "झूठी सकारात्मक" हैं, जो यह कहना है कि मान सेट में नहीं है, सेट में मान के समान हैश होगा। तो आपके पास हैश मान द्वारा अनुक्रमित एक तालिका होनी चाहिए, जिसमें "इन-द-सेट" इनपुट मूल्य शामिल है। तो किसी भी दिए गए इनपुट मान को मान्य करने के लिए आप (ए) हैश; (बी) टेबल लुक-अप करने के लिए हैश मान का उपयोग करें; (c) जाँचें कि तालिका में प्रविष्टि इनपुट मान से मेल खाती है या नहीं।
क्रेग मैकक्वीन

14

हैश सेट का उपयोग करें। यह O (1) लुकअप टाइम देगा।

निम्न कोड मानता है कि आप मूल्य 0को 'खाली' मान के रूप में आरक्षित कर सकते हैं , अर्थात वास्तविक डेटा में नहीं। समाधान को ऐसी स्थिति के लिए विस्तारित किया जा सकता है जहां यह मामला नहीं है।

#define HASH(x) (((x >> 16) ^ x) & 1023)
#define HASH_LEN 1024
uint32_t my_hash[HASH_LEN];

int lookup(uint32_t value)
{
    int i = HASH(value);
    while (my_hash[i] != 0 && my_hash[i] != value) i = (i + 1) % HASH_LEN;
    return i;
}

void store(uint32_t value)
{
    int i = lookup(value);
    if (my_hash[i] == 0)
       my_hash[i] = value;
}

bool contains(uint32_t value)
{
    return (my_hash[lookup(value)] == value);
}

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


3
यह इस बात पर निर्भर करता है कि प्रभावी होने के लिए यह लुकअप कितनी बार किया जाना है।
मैक्सविब

1
एर, लुकअप सरणी के अंत तक चल सकता है। और इस तरह के रैखिक हैशिंग में उच्च टक्कर दर हैं - कोई भी तरीका आपको O (1) नहीं मिलेगा। अच्छे हैश सेट इस तरह लागू नहीं होते हैं।
जिम बेल्टर

@JimBalter ट्रू, परफेक्ट कोड नहीं। सामान्य विचार की तरह अधिक; बस मौजूदा हैश सेट कोड को इंगित कर सकता था। लेकिन यह देखते हुए कि यह एक बाधा सेवा दिनचर्या है, यह प्रदर्शित करने के लिए उपयोगी हो सकता है कि लुकअप बहुत जटिल कोड नहीं है।
जेपा

आपको बस इसे ठीक करना चाहिए ताकि यह मेरे चारों ओर लपेटे।
जिम बैटर

एक परिपूर्ण हैश फ़ंक्शन की बात यह है कि यह एक जांच करता है। अवधि।
इरा बैक्सटर

10

इस मामले में, यह ब्लूम फ़िल्टर की सार्थक जांच हो सकती है । वे जल्दी से यह स्थापित करने में सक्षम हैं कि एक मूल्य मौजूद नहीं है, जो कि 2 ^ 32 के अधिकांश के बाद से एक अच्छी बात है, जो कि संभव नहीं है 1024 x सरणी में। हालांकि, कुछ गलत सकारात्मक हैं जिन्हें अतिरिक्त जांच की आवश्यकता होगी।

चूंकि आपकी तालिका स्पष्ट रूप से स्थिर है, आप यह निर्धारित कर सकते हैं कि आपके ब्लूम फ़िल्टर के लिए कौन सी झूठी सकारात्मक मौजूद हैं और उन लोगों को एक आदर्श हैश में डाल दिया।


1
दिलचस्प है, मैंने पहले ब्लूम फ़िल्टर नहीं देखा था।
रॉकेटमेग्नेट

8

मान लें कि आपका प्रोसेसर 204 मेगाहर्ट्ज पर चलता है जो LPC4357 के लिए अधिकतम लगता है, और यह भी मानकर कि आपका समय परिणाम औसत मामले को दर्शाता है (सरणी का आधा भाग), हम प्राप्त करते हैं:

  • सीपीयू आवृत्ति: 204 मेगाहर्ट्ज
  • चक्र अवधि: 4.9 एन.एस.
  • चक्रों में अवधि: 12.5 :s / 4.9 ns = 2551 चक्र
  • प्रति चक्कर चक्र: 2551/128 = 19.9

तो, आपका खोज लूप प्रति प्रवाह लगभग 20 चक्र खर्च करता है। यह भयानक नहीं लगता है, लेकिन मुझे लगता है कि इसे तेज करने के लिए आपको विधानसभा को देखने की जरूरत है।

मैं सूचकांक को छोड़ने और इसके बजाय एक सूचक तुलना का उपयोग करने और सभी बिंदुओं को बनाने की सलाह दूंगा const

bool arrayContains(const uint32_t *array, size_t length)
{
  const uint32_t * const end = array + length;
  while(array != end)
  {
    if(*array++ == 0x1234ABCD)
      return true;
  }
  return false;
}

यह कम से कम परीक्षण के लायक है।


1
-1, ARM में इंडेक्सेड एड्रेस मोड है इसलिए यह बेकार है। पॉइंटर बनाने के लिए const, जीसीसी पहले से ही स्पॉट करता है कि यह नहीं बदलता है। constDoesnt't ऐड कुछ भी या तो।
MSALERS

11
@MSalters ठीक है, मैं उत्पन्न कोड के साथ सत्यापित नहीं किया था, बिंदु कुछ है कि यह सी स्तर पर आसान बनाता है व्यक्त करने के लिए था, और मैं सिर्फ एक सूचक के बजाय संकेत प्रबंध लगता है और एक सूचकांक है सरल। मैं बस असहमत हूं कि " constकुछ भी नहीं जोड़ता है": यह बहुत स्पष्ट रूप से पाठक को बताता है कि मूल्य नहीं बदलेगा। यह शानदार जानकारी है।
खोलना

9
यह गहराई से एम्बेडेड कोड है; अब तक के अनुकूलन में फ्लैश से रैम तक कोड को शामिल करना शामिल है। और फिर भी इसे अभी और तेज करने की जरूरत है। इस बिंदु पर, पठनीयता लक्ष्य नहीं है।
एमएसल्टर्स

1
@MSalters "ARM में एक इंडेक्सड एड्रेस मोड है इसलिए यह व्यर्थ है" - ठीक है, यदि आप पूरी तरह से बिंदु को याद करते हैं ... ओपी ने लिखा है "मैं सूचक अंकगणित और एक लूप के लिए भी उपयोग करता हूं"। अंकन ने इंडेक्सिंग को पॉइंटर्स से प्रतिस्थापित नहीं किया, उसने इंडेक्स वेरिएबल को समाप्त कर दिया और इस तरह हर लूप पुनरावृत्ति पर एक अतिरिक्त घटाव। लेकिन ओपी बुद्धिमान था (जवाब देने और टिप्पणी करने वाले कई लोगों के विपरीत) और एक द्विआधारी खोज करने के लिए समाप्त हो गया।
जिम बेल्टर

6

अन्य लोगों ने आपकी तालिका को पुनर्गठित करने, अंत में एक प्रहरी मूल्य जोड़ने या इसे बाइनरी खोज प्रदान करने के लिए क्रमबद्ध करने का सुझाव दिया है।

आप कहते हैं, "मैं सूचक अंकगणित और एक लूप के लिए भी उपयोग करता हूं, जो ऊपर की बजाय डाउन-काउंटिंग करता है (अगर i != 0जाँच की तुलना में तेज है i < 256)।"

मेरी पहली सलाह है: पॉइंटर अंकगणित और डाउनकाउंटिंग से छुटकारा पाएं। सामान की तरह

for (i=0; i<256; i++)
{
    if (compareVal == the_array[i])
    {
       [...]
    }
}

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

उदाहरण के लिए, उपरोक्त कोड को लूप में -256या -255शून्य से संकलित करके अनुक्रमणित किया जा सकता है &the_array[256]। संभवतः सामान जो मान्य सी में भी व्यक्त नहीं है, लेकिन उस मशीन की वास्तुकला से मेल खाता है जिसे आप पैदा कर रहे हैं।

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

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


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

3

वैश्वीकरण का उपयोग यहां किया जा सकता है, क्योंकि यह अक्सर मेमचर के कार्यान्वयन में होता है। आप निम्न एल्गोरिथम का उपयोग करें:

  1. अपनी क्वेरी दोहराते हुए एक मुखौटा बनाएं, जो आपके OS'S बिट काउंट (64-बिट, 32-बिट, आदि) की लंबाई के बराबर हो। 64-बिट सिस्टम पर आप 32-बिट क्वेरी को दो बार दोहराएंगे।

  2. सूची को एक बार में डेटा के कई टुकड़ों की सूची के रूप में संसाधित करें, बस सूची को बड़े डेटा प्रकार की सूची में डालकर और मानों को बाहर खींचकर। प्रत्येक चंक के लिए, XOR को मास्क के साथ, फिर XOR के साथ 0b0111 ... 1, फिर 1 को जोड़ें, और फिर 0b1000 के मास्क के साथ ... 0 को दोहराएं। यदि परिणाम 0 है, तो निश्चित रूप से एक मैच नहीं है। अन्यथा, आमतौर पर बहुत अधिक संभावना के साथ मैच हो सकता है, इसलिए सामान्य रूप से चंक को खोजें।

उदाहरण कार्यान्वयन: https://sourceware.org/cgi-bin/cvsweb.cgi/src/newlib/libc/string/memchr.c?rev=1.3&content-type=text-x-cvsweb-markup&cvsroot=src


3

यदि आप अपने मूल्यों की डोमेन को आपके आवेदन के लिए उपलब्ध स्मृति की मात्रा के साथ समायोजित कर सकते हैं , तो, सबसे तेज़ समाधान बिट्स के एक सरणी के रूप में आपके सरणी का प्रतिनिधित्व करना होगा:

bool theArray[MAX_VALUE]; // of which 1024 values are true, the rest false
uint32_t compareVal = 0x1234ABCD;
bool validFlag = theArray[compareVal];

संपादित करें

मैं आलोचकों की संख्या से चकित हूं। इस धागे का शीर्षक है "मैं जल्दी से कैसे पता लगा सकता हूं कि क्या कोई मूल्य सी सरणी में मौजूद है?" जिसके लिए मैं अपने उत्तर के साथ खड़ा रहूंगा क्योंकि यह सटीक उत्तर देता है। मैं तर्क दे सकता हूं कि इसमें सबसे अधिक गति कुशल हैश फ़ंक्शन (पता === मान के बाद से) है। मैंने टिप्पणियाँ पढ़ी हैं और मुझे स्पष्ट कैविटीज़ के बारे में पता है। निस्संदेह उन गुहाओं को उन समस्याओं की सीमा को सीमित किया जाता है जिन्हें हल करने के लिए इसका उपयोग किया जा सकता है, लेकिन, उन समस्याओं के लिए जो इसे हल करती हैं, यह बहुत कुशलता से हल करती है।

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


8
यह 4 उत्थान कैसे प्राप्त करता है? प्रश्न में कहा गया है कि यह एक कोर्टेक्स एम 4 है। बात 136 केबी रैम की है, न कि 262.144 केबी की।
एमएसलटर्स

1
यह आश्चर्यजनक है कि प्रकट रूप से गलत उत्तरों के लिए कितने उत्थान दिए गए क्योंकि उत्तर देने वाले ने पेड़ों के लिए जंगल को याद किया। ओपी के सबसे बड़े मामले के लिए ओ (लॉग एन) << ओ (एन)।
msw

3
मुझे प्रोग्रामर पर बहुत क्रोध आता है जो स्मृति की हास्यास्पद मात्रा को जला देते हैं, जब कहीं बेहतर समाधान उपलब्ध होते हैं। हर 5 साल में ऐसा लगता है कि मेरा पीसी मेमोरी से बाहर चल रहा है, जहां 5 साल पहले यह राशि काफी थी।
क्रेग मैकक्यून

1
@CraigMcQueen किड्स इन दिनों। व्यर्थ की स्मृति। अपमानजनक! वापस मेरे दिनों में, हमारे पास 1 MiB मेमोरी और 16-बिट्स का एक शब्द आकार था। / एस
कोल जॉनसन

2
कठोर आलोचकों के साथ क्या है? ओपी स्पष्ट रूप से बताता है कि कोड के इस हिस्से के लिए गति बिल्कुल महत्वपूर्ण है, और स्टीफनकुआन ने पहले ही "स्मृति की हास्यास्पद मात्रा" का उल्लेख किया है।
बोगदान अलेक्जेंड्रू

1

सुनिश्चित करें कि निर्देश ("छद्म कोड") और डेटा ("एयर") अलग-अलग (RAM) यादों में हैं इसलिए CM4 हार्वर्ड आर्किटेक्चर अपनी पूरी क्षमता के लिए उपयोग किया जाता है। उपयोगकर्ता पुस्तिका से:

यहाँ छवि विवरण दर्ज करें

CPU प्रदर्शन को अनुकूलित करने के लिए, ARM Cortex-M4 में इंस्ट्रक्शन (कोड) (I) एक्सेस, डेटा (D) एक्सेस, और सिस्टम (S) एक्सेस के लिए तीन बसें हैं। जब निर्देश और डेटा को अलग-अलग यादों में रखा जाता है, तो कोड और डेटा एक्सेस समानांतर में एक चक्र में किया जा सकता है। जब कोड और डेटा को एक ही मेमोरी में रखा जाता है, तो डेटा को लोड या स्टोर करने के निर्देश में दो चक्र लग सकते हैं।


दिलचस्प है, कॉर्टेक्स-एम 7 में वैकल्पिक निर्देश / डेटा कैश हैं, लेकिन इससे पहले निश्चित रूप से नहीं। en.wikipedia.org/wiki/ARM_Cortex-M#Silicon_customization
पीटर कॉर्डेस

0

मुझे खेद है अगर मेरा जवाब पहले से ही उत्तर दिया गया था - बस मैं एक आलसी पाठक हूं। लगता है कि आप तब तक नीचे जाने के लिए स्वतंत्र हैं))

1) आप काउंटर 'i' को बिल्कुल हटा सकते हैं - बस पॉइंटर्स की तुलना करें, अर्थात

for (ptr = &the_array[0]; ptr < the_array+1024; ptr++)
{
    if (compareVal == *ptr)
    {
       break;
    }
}
... compare ptr and the_array+1024 here - you do not need validFlag at all.

सभी जो कि कोई महत्वपूर्ण सुधार नहीं देंगे, लेकिन इस तरह के अनुकूलन को कंपाइलर द्वारा ही प्राप्त किया जा सकता है।

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

if (compareVal == the_array[0]) { validFlag = true; goto end_of_compare; }
if (compareVal == the_array[1]) { validFlag = true; goto end_of_compare; }
...and so on...
end_of_compare:

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

इस ऑप्टिमाइज़ेशन का दूसरा भाग यह है कि सरणी आइटम को डायरेक्ट एड्रेस (कंपाइलिंग स्टेज पर गणना, सुनिश्चित करें कि आप एक स्टैटिक एरे का उपयोग करते हैं) द्वारा लिया गया है, और एरे के आधार पते से पॉइंटर की गणना के लिए अतिरिक्त ADD सेशन की आवश्यकता नहीं है। इस अनुकूलन का महत्वपूर्ण प्रभाव नहीं हो सकता है, क्योंकि AFAIK ARM आर्किटेक्चर में सरणियों को संबोधित करने के लिए विशेष सुविधाएँ हैं। लेकिन वैसे भी यह जानना हमेशा बेहतर होता है कि आपने सीधे सी कोड में ही सबसे अच्छा किया, है ना?

ROM की बर्बादी के कारण साइकिल रोलबैक अजीब लग सकता है (हां, आपने इसे सही तरीके से रैम के तेज हिस्से पर रखा है, यदि आपका बोर्ड इस सुविधा का समर्थन करता है), लेकिन वास्तव में यह RISC अवधारणा पर आधारित होने के नाते, गति के लिए एक उचित भुगतान है। यह गणना अनुकूलन का सिर्फ एक सामान्य बिंदु है - आप अपनी आवश्यकताओं के आधार पर गति के लिए स्थान का त्याग करते हैं, और इसके विपरीत।

अगर आपको लगता है कि 1024 तत्वों की सरणी के लिए रोलबैक आपके मामले के लिए बहुत बड़ा त्याग है, तो आप 'आंशिक रोलबैक' पर विचार कर सकते हैं, उदाहरण के लिए सरणी को 512 आइटम प्रत्येक के 2 भागों में विभाजित करना, या 4x256, और इसी तरह।

3) आधुनिक सीपीयू अक्सर SIMD ऑप्स का समर्थन करते हैं, उदाहरण के लिए ARM NEON निर्देश सेट - यह समानांतर में समान ऑप्स निष्पादित करने की अनुमति देता है। सच कहूं तो मुझे याद नहीं है कि यह तुलनात्मक ऑप्स के लिए उपयुक्त है, लेकिन मुझे लगता है कि यह हो सकता है, आपको इसकी जांच करनी चाहिए। Googling से पता चलता है कि अधिकतम गति प्राप्त करने के लिए कुछ चालें भी हो सकती हैं, https://stackoverflow.com/a/5734019/1028256 देखें

मुझे उम्मीद है कि यह आपको कुछ नए विचार दे सकता है।


ओपी ने रैखिक छोरों के अनुकूलन पर केंद्रित सभी मूर्खतापूर्ण उत्तरों को दरकिनार कर दिया, और इसके बजाय सरणी को प्रस्तुत किया और बाइनरी खोज की।
जिम बाल्टर

@ जीम, यह स्पष्ट है कि इस तरह का अनुकूलन पहले किया जाना चाहिए। 'मूर्खता' के जवाब कुछ उपयोग मामलों में इतने मूर्ख नहीं लग सकते हैं जब उदाहरण के लिए आपके पास सरणी को सॉर्ट करने के लिए समय नहीं है। या अगर आपको गति मिलती है, तो वैसे भी पर्याप्त नहीं है
मिक्साज़

"यह स्पष्ट है कि इस तरह का अनुकूलन पहले किया जाना चाहिए" - जाहिर है उन लोगों के लिए नहीं जो रैखिक समाधान विकसित करने के लिए महान प्रयास में गए थे। "आपके पास सरणी को सॉर्ट करने का समय नहीं है" - मुझे नहीं पता कि इसका क्या मतलब है। "या अगर आपको गति मिलती है, तो वैसे भी पर्याप्त नहीं है" - उह, यदि द्विआधारी खोज से गति "पर्याप्त नहीं" है, तो एक अनुकूलित रैखिक खोज करने से इसमें सुधार नहीं होगा। अब मैं इस विषय के साथ कर रहा हूँ।
जिम बाल्टर

@JimBalter, अगर मुझे ओपी के रूप में ऐसी समस्या थी, तो मैं निश्चित रूप से अल्गों जैसे कि द्विआधारी खोज या कुछ और का उपयोग करने पर विचार करूंगा। मैं सिर्फ यह नहीं सोच सकता था कि ओपी ने पहले ही इस पर विचार नहीं किया था। "आपके पास सरणी को सॉर्ट करने के लिए समय नहीं है" का अर्थ है कि सॉर्टिंग सरणी को समय लगता है। यदि आपको इसे प्रत्येक इनपुट डेटा सेट के लिए करने की आवश्यकता है, तो लीनियर लूप की तुलना में अधिक समय लग सकता है। "या अगर आपको गति मिलती है, तो वैसे भी पर्याप्त नहीं है" का अर्थ है - ऊपर दिए गए अनुकूलन संकेत द्विआधारी खोज कोड या किसी भी
चीज़

0

मैं हैशिंग का बहुत बड़ा प्रशंसक हूं। पाठ्यक्रम की समस्या एक कुशल एल्गोरिदम को ढूंढना है जो दोनों तेज है और न्यूनतम मात्रा में मेमोरी का उपयोग करता है (विशेषकर एक एम्बेडेड प्रोसेसर पर)।

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

मैंने ऐसा प्रोग्राम बनाया, जिसके बारे में आप इस पोस्ट में पढ़ सकते हैं और कुछ बहुत तेजी से परिणाम प्राप्त । बाइनरी खोज का उपयोग करके मूल्य खोजने के लिए 16000 प्रविष्टियाँ लगभग 2 ^ 14 या औसतन 14 तुलनाओं का अनुवाद करती हैं। मैंने स्पष्ट रूप से बहुत तेज़ लुकअप के लिए लक्ष्य किया है - औसत में मान को खोजने में <= 1.5 लुकअप - जिसके परिणामस्वरूप अधिक रैम आवश्यकताएं थीं। मेरा मानना ​​है कि अधिक रूढ़िवादी औसत मूल्य (<= 3) के साथ बहुत सारी मेमोरी को बचाया जा सकता है। आपके 256 या 1024 प्रविष्टियों पर एक द्विआधारी खोज के लिए औसत मामले की तुलना करने पर क्रमशः 8 और 10 की तुलना की औसत संख्या होगी।

जेनेरिक एल्गोरिथ्म (एक चर द्वारा एक विभाजन का उपयोग) और एक विशेष (शायद एक गुणा का उपयोग) के साथ 40-45 चक्र के साथ लगभग 60 चक्र (एक इंटेल i5 के साथ एक लैपटॉप पर) के लिए मेरे औसत लुक की आवश्यकता है। यह आपके MCU पर उप-माइक्रोसेकंड लुकअप समय में अनुवाद करना चाहिए, यह उस समय की आवृत्ति पर निर्भर करता है जिस पर यह निष्पादित होता है।

यदि प्रवेश सरणी कितनी बार प्रवेश की गई, इसका ट्रैक रखता है, तो यह वास्तविक जीवन को और भी नया बनाया जा सकता है। अगर प्रवेश सरणी को कम से कम एक्सेस किया जाता है इससे पहले कि मल की गणना की जाती है, तो यह एक एकल तुलना के साथ सबसे आम तौर पर उत्पन्न होने वाले मानों को ढूंढेगा।


0

यह उत्तर की तुलना में अधिक परिशिष्ट की तरह है।

मेरा अतीत में भी ऐसा ही मामला रहा है , लेकिन मेरी सरणी काफी खोजों पर स्थिर थी।

उनमें से आधे में, खोज मूल्य सरणी में मौजूद नहीं था। तब मुझे एहसास हुआ कि मैं कोई भी खोज करने से पहले "फ़िल्टर" लगा सकता हूं।

यह "फ़िल्टर" एक सरल पूर्णांक संख्या है, जो प्रत्येक खोज में ONCE और गणना की जाती है ।

यह जावा में है, लेकिन यह बहुत सरल है:

binaryfilter = 0;
for (int i = 0; i < array.length; i++)
{
    // just apply "Binary OR Operator" over values.
    binaryfilter = binaryfilter | array[i];
}

तो, बाइनरी खोज करने से पहले, मैं बाइनरीफिल्टर की जांच करता हूं:

// Check binaryfilter vs value with a "Binary AND Operator"
if ((binaryfilter & valuetosearch) != valuetosearch)
{
    // valuetosearch is not in the array!
    return false;
}
else
{
    // valuetosearch MAYBE in the array, so let's check it out
    // ... do binary search stuff ...

}

आप एक 'बेहतर' हैश एल्गोरिथ्म का उपयोग कर सकते हैं, लेकिन यह बहुत तेज़ हो सकता है, विशेष रूप से बड़ी संख्या के लिए। हो सकता है कि यह आपको और भी चक्र बचा सके।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.