यह निर्धारित करने का सबसे तेज़ तरीका है कि मानों के ज्ञात सेट के साथ एक पूर्णांक दो पूर्णांकों (समावेशी) के बीच है या नहीं


389

x >= start && x <= endयदि पूर्णांक दो पूर्णांकों के बीच है, तो C या C ++ से अधिक तेज़ तरीका है ?

अद्यतन : मेरा विशिष्ट मंच iOS है। यह एक बॉक्स ब्लर फ़ंक्शन का हिस्सा है जो किसी दिए गए वर्ग में एक सर्कल में पिक्सल को प्रतिबंधित करता है।

अद्यतन : स्वीकृत उत्तर का प्रयास करने के बाद , मुझे इसे सामान्य x >= start && x <= endतरीके से करने के लिए कोड की एक पंक्ति पर परिमाण स्पीडअप का एक आदेश मिला ।

अद्यतन : यहाँ XCode से कोडांतरक के साथ कोड के बाद और पहले है:

नया रास्ता

// diff = (end - start) + 1
#define POINT_IN_RANGE_AND_INCREMENT(p, range) ((p++ - range.start) < range.diff)

Ltmp1313:
 ldr    r0, [sp, #176] @ 4-byte Reload
 ldr    r1, [sp, #164] @ 4-byte Reload
 ldr    r0, [r0]
 ldr    r1, [r1]
 sub.w  r0, r9, r0
 cmp    r0, r1
 blo    LBB44_30

पुराना तरीका

#define POINT_IN_RANGE_AND_INCREMENT(p, range) (p <= range.end && p++ >= range.start)

Ltmp1301:
 ldr    r1, [sp, #172] @ 4-byte Reload
 ldr    r1, [r1]
 cmp    r0, r1
 bls    LBB44_32
 mov    r6, r0
 b      LBB44_33
LBB44_32:
 ldr    r1, [sp, #188] @ 4-byte Reload
 adds   r6, r0, #1
Ltmp1302:
 ldr    r1, [r1]
 cmp    r0, r1
 bhs    LBB44_36

बहुत आश्चर्यजनक है कि कैसे ब्रांचिंग को कम करना या समाप्त करना ऐसी नाटकीय गति प्रदान कर सकता है।


28
आप क्यों चिंतित हैं कि यह आपके लिए पर्याप्त उपवास नहीं है?
मैट बॉल

90
कौन परवाह करता है, इसका एक दिलचस्प सवाल है। यह सिर्फ एक चुनौती के लिए एक चुनौती है।
डेविड का कहना है कि मोनिका

46
@SLaks तो हमें ऐसे सभी सवालों को आँख बंद करके अनदेखा कर देना चाहिए और बस यही कहना चाहिए कि "आशावादी को ऐसा करने दो?"
डेविड का कहना है कि मोनिका

87
इससे कोई फर्क नहीं पड़ता कि प्रश्न क्यों पूछा जा रहा है। यह एक वैध सवाल है, भले ही जवाब नहीं है
tay10r

41
यह मेरे एक ऐप में एक फंक्शन में अड़चन है
jjxtra

जवाबों:


527

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

// use a < for an inclusive lower bound and exclusive upper bound
// use <= for an inclusive lower bound and inclusive upper bound
// alternatively, if the upper bound is inclusive and you can pre-calculate
//  upper-lower, simply add + 1 to upper-lower and use the < operator.
    if ((unsigned)(number-lower) <= (upper-lower))
        in_range(number);

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

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

जैसा कि यह कैसे काम करता है, मूल विचार बहुत सरल है: एक ऋणात्मक संख्या, जब एक अहस्ताक्षरित संख्या के रूप में देखा जाता है, तो सकारात्मक संख्या के रूप में शुरू की गई किसी भी चीज़ से बड़ा होगा।

व्यवहार में, यह विधि अनुवाद करती है numberऔर अंतराल की उत्पत्ति के बिंदु तक पहुंचती है और जांचती numberहै कि अंतराल में [0, D], कहाँ है D = upper - lower। यदि numberनीचे की ओर बाउंड: नेगेटिव , और अगर ऊपरी बाउंड से ऊपर: से बड़ाD


8
@ टोमसैबदान: वे दोनों किसी भी उचित मशीन पर एक चक्र होंगे। क्या महंगी है शाखा।
ओलिवर चार्ल्सवर्थ

3
अतिरिक्त-शाखाकरण शॉर्ट-सर्किटिंग के कारण किया जाता है? यदि ऐसा है, तो lower <= x & x <= upper(के बजाय lower <= x && x <= upper) बेहतर प्रदर्शन के परिणामस्वरूप होगा ?
मार्कस मेयर

6
@ AK4749, jxh: जैसा कि इस डली के रूप में अच्छा है, मैं अपवित्र करने में संकोच कर रहा हूं, क्योंकि दुर्भाग्य से यह सुझाव देने के लिए कुछ भी नहीं है कि यह किसी भी अभ्यास में तेज है (जब तक कोई परिणामी कोडांतरक और प्रोफाइलिंग जानकारी की तुलना नहीं करता है)। हम सभी जानते हैं, ओपी के संकलक एक ही शाखा के साथ ओपी के कोड को प्रस्तुत कर सकते हैं ...
ओलिवर चार्ल्सवर्थ

152
वाह!!! इस कोड की विशिष्ट लाइन के लिए मेरे ऐप में परिमाण में सुधार का एक परिणाम हुआ। ऊपरी-निचले हिस्से को precomputing करके मेरी रूपरेखा इस फ़ंक्शन के 25% समय से 2% से कम हो गई! अड़चन अब इसके अलावा और घटाव संचालन है, लेकिन मुझे लगता है कि यह अब काफी अच्छा हो सकता है :)
jjxtra

28
आह, अब @PychoDad ने सवाल अपडेट किया है, यह स्पष्ट है कि यह तेज क्यों है। वास्तविक कोड तुलना, जिसके कारण संकलक दूर शॉर्ट सर्किट का अनुकूलन नहीं कर सकता है में एक पक्ष प्रभाव है।
ओलिवर चार्ल्सवर्थ

17

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


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

17

यह इस बात पर निर्भर करता है कि आप एक ही डेटा पर कितनी बार परीक्षण करना चाहते हैं।

यदि आप एक बार परीक्षण कर रहे हैं, तो एल्गोरिथ्म को गति देने का कोई सार्थक तरीका नहीं है।

यदि आप मूल्यों के बहुत सीमित सेट के लिए ऐसा कर रहे हैं, तो आप एक लुकअप तालिका बना सकते हैं। अनुक्रमण करना अधिक महंगा हो सकता है, लेकिन यदि आप पूरी तालिका को कैश में फिट कर सकते हैं, तो आप कोड से सभी शाखाओं को हटा सकते हैं, जिससे चीजों को गति मिलनी चाहिए।

आपके डेटा के लिए लुकअप टेबल 128 ^ 3 = 2,097,152 होगी। यदि आप तीन चर में से एक को नियंत्रित कर सकते हैं, तो आप सभी उदाहरणों start = Nपर विचार करते हैं जहां एक समय में, फिर काम करने वाले सेट का आकार 128^2 = 16432बाइट्स तक गिर जाता है , जो कि अधिकांश आधुनिक कैश में फिट होना चाहिए।

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


तो आप किसी प्रकार के लुकअप को एक वैल्यू, स्टार्ट और एंड को स्टोर करेंगे और इसमें एक BOOL होगा जो आपको बताएगा कि यह बीच में था?
जजक्स्ट्रा

सही बात। यह एक 3 डी लुकअप टेबल होगी bool between[start][end][x]:। यदि आप जानते हैं कि आपका एक्सेस पैटर्न कैसा दिखने वाला है (उदाहरण के लिए x एकरस रूप से बढ़ रहा है) तो आप टेबल को स्थानीयता को संरक्षित करने के लिए डिज़ाइन कर सकते हैं भले ही पूरी टेबल मेमोरी में फिट न हो।
एंड्रयू प्रोक

मैं देखूंगा कि क्या मैं इस तरीके को आजमा सकता हूं और देख सकता हूं कि यह कैसे होता है। मैं इसे प्रति पंक्ति एक बिट वेक्टर के साथ करने की योजना बना रहा हूं जहां बिंदु सर्कल में होने पर बिट सेट हो जाएगा। लगता है कि एक बाइट या int32 बनाम बिट मास्किंग से तेज हो जाएगा?
jjxtra

2

यह उत्तर स्वीकृत उत्तर के साथ किए गए परीक्षण पर रिपोर्ट करना है। मैंने क्रमबद्ध यादृच्छिक पूर्णांक के एक बड़े वेक्टर पर एक बंद रेंज परीक्षण किया और मेरे आश्चर्य की मूल विधि (निम्न <= num && संख्या <= उच्च) वास्तव में ऊपर दिए गए उत्तर की तुलना में तेज़ है! एचपी पवेलियन g6 (AMD A6-3400APU पर 6GB RAM के साथ टेस्ट किया गया था। यहां कोर कोड का इस्तेमाल किया गया है:

int num = rand();  // num to compare in consecutive ranges.
chrono::time_point<chrono::system_clock> start, end;
auto start = chrono::system_clock::now();

int inBetween1{ 0 };
for (int i = 1; i < MaxNum; ++i)
{
    if (randVec[i - 1] <= num && num <= randVec[i])
        ++inBetween1;
}
auto end = chrono::system_clock::now();
chrono::duration<double> elapsed_s1 = end - start;

निम्नलिखित के साथ तुलना में जो ऊपर स्वीकार किए जाते हैं उत्तर:

int inBetween2{ 0 };
for (int i = 1; i < MaxNum; ++i)
{
    if (static_cast<unsigned>(num - randVec[i - 1]) <= (randVec[i] - randVec[i - 1]))
        ++inBetween2;
}

ध्यान दें कि randVec एक सॉर्ट किया गया वेक्टर है। MaxNum के किसी भी आकार के लिए पहला तरीका मेरी मशीन पर दूसरा है!


1
मेरा डेटा सॉर्ट नहीं किया गया है और मेरे परीक्षण iPhone आर्म सीपीयू पर हैं। विभिन्न डेटा और CPU के साथ आपके परिणाम भिन्न हो सकते हैं।
jjxtra

मेरी परीक्षा में छांटना केवल यह सुनिश्चित करने के लिए था कि ऊपरी सीमा निचली सीमा से छोटी नहीं है।
रेजेली

1
सॉर्ट किए गए संख्याओं का मतलब है कि शाखा की भविष्यवाणी बहुत विश्वसनीय होगी और स्विचओवर बिंदुओं पर कुछ को छोड़कर सभी शाखाओं को सही मिलेगा। ब्रांचलेस कोड का फायदा यह है कि इससे अप्रत्याशित डेटा पर इस तरह के मिसप्रिंट्स से छुटकारा मिल जाएगा।
एंड्रियास क्लेबिंगर

0

किसी भी चर रेंज की जाँच के लिए:

if (x >= minx && x <= maxx) ...

बिट ऑपरेशन का उपयोग करना तेज़ है:

if ( ((x - minx) | (maxx - x)) >= 0) ...

यह दो शाखाओं को एक में कम कर देगा।

यदि आप सुरक्षित प्रकार की परवाह करते हैं:

if ((int32_t)(((uint32_t)x - (uint32_t)minx) | ((uint32_t)maxx - (uint32_t)x)) > = 0) ...

आप अधिक परिवर्तनीय श्रेणी की जाँच को एक साथ जोड़ सकते हैं:

if (( (x - minx) | (maxx - x) | (y - miny) | (maxy - y) ) >= 0) ...

यह 1 में 4 शाखाओं को कम करेगा।

यह gcc में पुराने की तुलना में 3.4 गुना तेज है:

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


-4

क्या पूर्णांक पर एक बिटवाइज़ ऑपरेशन करना संभव नहीं है?

चूंकि यह 0 और 128 के बीच होना है, अगर 8 वां बिट सेट है (2 ^ 7) यह 128 या अधिक है। किनारे का मामला एक दर्द होगा, हालांकि, जब से आप एक समावेशी तुलना चाहते हैं।


3
वह जानना चाहता है कि x <= end, कहाँ end <= 128। नहीं है x <= 128
बेन वोइगट

1
यह कथन " चूंकि यह 0 और 128 के बीच होना है, अगर 8 वां बिट सेट है (2 ^ 7) यह 128 या अधिक है " गलत है। 256 पर विचार करें।
हैप्पी ग्रीन किड नप्स

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