निरंतर द्वारा तेजी से पूर्णांक विभाजन

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

यह आगे वर्णित किया जाएगा कि कारक की गणना कैसे की जाती है।


असली संख्या के लिए विचार और मामला

उदाहरण के लिए, यदि कोड x x = a / 2.5 है , तो इसे x = a * (1.0 / 2.5) या x = a * 0.4 द्वारा प्रतिस्थापित किया जा सकता है। कोड तेज़ होगा, लेकिन कभी-कभी कम सटीक होता है। पूर्णांक के लिए, माथे में ऐसी चाल काम नहीं करती है, हो सकता है। कारक एक (उन शून्य) से कम होगा। आप फिक्स्ड-पॉइंट नंबरों (mantissa N ) का उपयोग कर सकते हैं: int x = a / b = a * B >> N, जहाँ B = (1 << N) / b । समस्या यह है कि कभी-कभी गणना की गई x सही उत्तर से 1 भिन्न होगी, जो पूर्णांकों के लिए अस्वीकार्य है।

एल्गोरिथ्म

सरल बनाने के लिए, हम केवल गैर-ऋणात्मक संख्याओं के साथ काम करेंगे। गणितीय सूत्रीकरण:

किसी ज्ञात पूर्णांक b के लिए, पूर्णांक M, B की एक जोड़ी को खोजें , जो सभी पूर्णांकों के लिए a : 0 ≤ a <(1 << N ) x = [a / b] = [a * B >> M] , जहां [] पूर्णांक है भाग, और >> M को M (और << गुणा) की शक्ति से 2 से भाग देना है।

हम बाइनरी फॉर्म में 1.0 / b लिखते हैं (कभी-कभी एक असीम रूप से लंबी संख्या), जिनमें से पहले M बिट्स को पूर्णांक C , शेष बिट्स (टेल-शेष) के रूप में D , उन 1.0 / b = C >> M + D, D <(1> ) के रूप में दर्शाया जाता है। > म)

आइए देखें कि क्या होता है अगर बी = सी और एक * डी those 1 (उन एन : एम ):

x = [a / b] = [a * (C + M + D)] = [a * C >> M + a * D] D [a * C >> M] + [a * D] = [ एक * सी >> एम]

यह पता चला है कि यदि बी नंबर 1 / बी के पहले एम बिट्स हैं, और शेष बिट्स को (शून्य द्वारा प्रतिस्थापित) छोड़ दिया जाता है, तो हमें कभी-कभी सही मान मिलेगा, और कभी-कभी 1 से कम एक्स । ऐसा इसलिए होता है क्योंकि * D को छोड़ दिया जाता है, जो कि एकता से कम है, फिर भी पूरे हिस्से को बदल सकता है।

आइए एक करके बी को बढ़ाने की कोशिश करें (उन बी = सी + 1 ), फिर:

[a * (C + 1) >> M] = [a * C >> M + a * (1 >> M)] C [a * C >> M + a * D] = [a * (C>) > एम + डी)] = [ए / बी] = एक्स

अब यह पता चला है कि कभी-कभी हमें सही उत्तर x मिलता है, और कभी-कभी 1 और।

ऐसा प्रतीत हो सकता है कि गुणा के माध्यम से x की सही गणना करना असंभव है क्योंकि एक सन्निकटन संख्या कभी-कभी एक और 1 के साथ 1 से भी कम हो जाएगी। लेकिन ऐसा है नहीं। वास्तव में, एक पूर्णांक है, और अधिकतम भिन्न भाग a / b वास्तव में (b-1) / b है , और x की गणना में यह एक * ((1 >> M) - D) से बढ़ता है। हाँ, इसका मतलब है कि अगर एक * ((1 >> M) - D) <1 / b तो असमानता समानता में बदल जाती है !!! बता दें कि L की संख्या ऐसी है (1 << L) then b , तो (1 >> M) ≤ (1 >> (L + N)) या M + L + N समानता के लिए पर्याप्त है।

जावा उदाहरण

उदाहरण में, भाजक 127 ( L = 7) है। JVM द्वारा गुणा का परिणाम 32 बिट में फिट होना चाहिए, N = (32 - 7) / 2 = 12. का चयन करें: उदाहरण:

final static int N = 12; private static int divideBy127(int a) { final int b = 127; final int L = 7; final int B = ((1 << (N + L)) / b) + 1; return (a * B) >>> (N + L); } public static void main(String[] args) { final int size = 20000; final int times = 10001; final int[] data = new int[size]; for (int i = 0; i < size; ++i) data[i] = (i * i) & ((1 << N) - 1); // fill by any random data for (int iteration = 0; iteration < 10; ++iteration) { long time0 = System.currentTimeMillis(); // Test 1: Using div int v0 = 0; for (int j = 0; j < times; ++j) for (int i = 0; i < size; ++i) v0 ^= data[i] / 127; // keep result long time1 = System.currentTimeMillis(); // Test 2: Using mul int v1 = 0; for (int j = 0; j < times; ++j) for (int i = 0; i < size; ++i) v1 ^= divideBy127(data[i]); // keep result long time2 = System.currentTimeMillis(); System.out.println((time1 - time0) + " vs " + (time2 - time1) + " " + (v0 - v1)); } } 


मेरे Intel Core-i5 2500, JVM 1.7.0u5 के साथ JIT और विकल्प -XX: + AggressiveOpts, विभाजन परीक्षण गुणा के साथ 2 गुना धीमा काम करता है।

इसके अलावा


इस लेख में शामिल विषय पर बाकी बातें:

साहित्य

"गुणन का उपयोग करते हुए इनवेरियंट इंटेगर द्वारा विभाजन", टोरबॉर्न ग्रैनलुंड, पीटर एल। मॉन्टगोमरी - औपचारिक प्रमाण और विषय पर कुछ और एल्गोरिदम।

Source: https://habr.com/ru/post/In147096/


All Articles