निमरोड (एन = 22)
import math, locks
const
N = 20
M = N + 1
FSize = (1 shl N)
FMax = FSize - 1
SStep = 1 shl (N-1)
numThreads = 16
type
ZeroCounter = array[0..M-1, int]
ComputeThread = TThread[int]
var
leadingZeros: ZeroCounter
lock: TLock
innerProductTable: array[0..FMax, int8]
proc initInnerProductTable =
for i in 0..FMax:
innerProductTable[i] = int8(countBits32(int32(i)) - N div 2)
initInnerProductTable()
proc zeroInnerProduct(i: int): bool =
innerProductTable[i] == 0
proc search2(lz: var ZeroCounter, s, f, i: int) =
if zeroInnerProduct(s xor f) and i < M:
lz[i] += 1 shl (M - i - 1)
search2(lz, (s shr 1) + 0, f, i+1)
search2(lz, (s shr 1) + SStep, f, i+1)
when defined(gcc):
const
unrollDepth = 1
else:
const
unrollDepth = 4
template search(lz: var ZeroCounter, s, f, i: int) =
when i < unrollDepth:
if zeroInnerProduct(s xor f) and i < M:
lz[i] += 1 shl (M - i - 1)
search(lz, (s shr 1) + 0, f, i+1)
search(lz, (s shr 1) + SStep, f, i+1)
else:
search2(lz, s, f, i)
proc worker(base: int) {.thread.} =
var lz: ZeroCounter
for f in countup(base, FMax div 2, numThreads):
for s in 0..FMax:
search(lz, s, f, 0)
acquire(lock)
for i in 0..M-1:
leadingZeros[i] += lz[i]*2
release(lock)
proc main =
var threads: array[numThreads, ComputeThread]
for i in 0 .. numThreads-1:
createThread(threads[i], worker, i)
for i in 0 .. numThreads-1:
joinThread(threads[i])
initLock(lock)
main()
echo(@leadingZeros)
संकलन
nimrod cc --threads:on -d:release count.nim
(निम्रोद को डाउनलोड किया जा सकता है यहां है ।)
यह n = 20 के लिए आवंटित समय में चलता है (और n = 18 के लिए जब केवल एक ही धागे का उपयोग करते हुए, उत्तरार्द्ध में लगभग 2 मिनट लगते हैं)।
एल्गोरिथ्म एक पुनरावर्ती खोज का उपयोग करता है, जब भी एक गैर-शून्य आंतरिक उत्पाद का सामना किया जाता है, तो खोज पेड़ को काट देता है। हम किसी भी जोड़ीदार वैक्टर के लिए यह देख कर खोज स्थान को आधे में काट देते हैं(F, -F)
हमें केवल एक पर विचार करने की आवश्यकता है क्योंकि दूसरा आंतरिक उत्पादों के सटीक सेटों ( S
भी नकार कर ) का उत्पादन करता है ।
कार्यान्वयन निम्रोद की मेटाप्रोग्रामिंग सुविधाओं का उपयोग पुनरावर्ती खोज के पहले कुछ स्तरों को अनियंत्रित / इनलाइन करने के लिए करता है। यह थोड़ा समय बचाता है जब gcc 4.8 और 4.9 निम्रोड के बैकेंड के रूप में और क्लैंग के लिए उचित राशि का उपयोग करता है।
खोज स्थान को यह देखते हुए आगे बढ़ाया जा सकता है कि हमें केवल एस के मूल्यों पर विचार करने की आवश्यकता है जो एफ की हमारी पसंद से पहले एन पदों की एक समान संख्या में भिन्न है। हालांकि, उस की जटिलता या स्मृति की जरूरत बड़े मूल्यों के लिए पैमाने पर नहीं है। N का, यह देखते हुए कि उन मामलों में लूप बॉडी पूरी तरह से छोड़ दी जाती है।
टैबिंग जहां आंतरिक उत्पाद शून्य है, लूप में किसी भी बिट काउंटिंग कार्यक्षमता का उपयोग करने की तुलना में तेज प्रतीत होता है। जाहिरा तौर पर मेज तक पहुँचने के लिए बहुत अच्छा इलाका है।
ऐसा लगता है कि समस्या को गतिशील प्रोग्रामिंग के लिए उत्तरदायी होना चाहिए, यह देखते हुए कि पुनरावर्ती खोज कैसे काम करती है, लेकिन उचित मात्रा में स्मृति के साथ ऐसा करने का कोई स्पष्ट तरीका नहीं है।
उदाहरण आउटपुट:
एन = 16:
@[55276229099520, 10855179878400, 2137070108672, 420578918400, 83074121728, 16540581888, 3394347008, 739659776, 183838720, 57447424, 23398912, 10749184, 5223040, 2584896, 1291424, 645200, 322600]
एन = 18:
@[3341140958904320, 619683355033600, 115151552380928, 21392898654208, 3982886961152, 744128512000, 141108051968, 27588886528, 5800263680, 1408761856, 438001664, 174358528, 78848000, 38050816, 18762752, 9346816, 4666496, 2333248, 1166624]
एन = 20:
@[203141370301382656, 35792910586740736, 6316057966936064, 1114358247587840, 196906665902080, 34848574013440, 6211866460160, 1125329141760, 213330821120, 44175523840, 11014471680, 3520839680, 1431592960, 655872000, 317675520, 156820480, 78077440, 39005440, 19501440, 9750080, 4875040]
अन्य कार्यान्वयन के साथ एल्गोरिथ्म की तुलना करने के प्रयोजनों के लिए, N = 16 को एक मशीन का उपयोग करते समय मेरी मशीन पर लगभग 7.9 सेकंड और चार सेकंड का उपयोग करते समय 2.3 सेकंड लगते हैं।
N = 22 को 64-कोर मशीन पर gcc 4.4.6 के साथ लगभग 15 मिनट लगते हैं क्योंकि Nimrod का बैकएंड और 64-बिट पूर्णांक को ओवरलैप करता है leadingZeros[0]
(संभवतः अहस्ताक्षरित लोगों ने इसे नहीं देखा है)।
अपडेट: मुझे कुछ सुधारों के लिए जगह मिली है। सबसे पहले, दिए गए मूल्य के लिए F
, हम संबंधित S
वैक्टर की पहली 16 प्रविष्टियों को ठीक से गणना कर सकते हैं , क्योंकि वे बिल्कुल N/2
स्थानों में भिन्न होना चाहिए । इसलिए हम आकार की बिट वैक्टर की एक सूची precompute N
है कि N/2
बिट्स सेट और के प्रारंभिक भाग प्राप्त करने के लिए इन का उपयोग S
से F
।
दूसरा, हम यह देख कर पुनरावर्ती खोज में सुधार कर सकते हैं कि हम हमेशा मूल्य जानते हैं F[N]
(जैसा कि एमएसबी बिट प्रतिनिधित्व में शून्य है)। यह हमें ठीक से भविष्यवाणी करने की अनुमति देता है कि हम किस शाखा में आंतरिक उत्पाद से पुनरावृत्ति करते हैं। जबकि यह वास्तव में हमें पूरी खोज को पुनरावर्ती लूप में बदलने की अनुमति देगा, जो वास्तव में शाखा भविष्यवाणी को थोड़ा पेंच करने के लिए होता है, इसलिए हम शीर्ष स्तरों को इसके मूल रूप में रखते हैं। हम अभी भी कुछ समय बचाते हैं, मुख्य रूप से हम कर रहे शाखाओं की मात्रा को कम करके।
कुछ सफाई के लिए, कोड अब अहस्ताक्षरित पूर्णांक का उपयोग कर रहा है और उन्हें 64-बिट पर ठीक कर रहा है (बस अगर कोई इसे 32-बिट आर्किटेक्चर पर चलाना चाहता है)।
कुल मिलाकर स्पीडअप x3 और x4 के कारक के बीच है। N = 22 को अभी भी 10 मिनट से कम समय में चलने के लिए आठ से अधिक कोर की आवश्यकता है, लेकिन 64-कोर मशीन पर यह अब लगभग चार मिनट ( numThreads
तदनुसार टकराकर) नीचे है। मुझे नहीं लगता कि एक अलग एल्गोरिथ्म के बिना सुधार के लिए बहुत अधिक जगह है, हालांकि।
एन = 22:
@[12410090985684467712, 2087229562810269696, 351473149499408384, 59178309967151104, 9975110458933248, 1682628717576192, 284866824372224, 48558946385920, 8416739196928, 1518499004416, 301448822784, 71620493312, 22100246528, 8676573184, 3897278464, 1860960256, 911646720, 451520512, 224785920, 112198656, 56062720, 28031360, 14015680]
फिर से अद्यतन, खोज स्थान में आगे संभव कटौती का उपयोग कर। मेरी क्वाडकोर मशीन पर N = 22 के लिए लगभग 9:49 मिनट में चलता है।
अंतिम अद्यतन (मुझे लगता है)। F के विकल्पों के लिए बेहतर तुल्यता वर्ग, N = 22 के लिए रनटाइम को घटाकर 3:19 मिनट 57 सेकंड (संपादित करें: मैंने गलती से केवल एक धागे से चलाया था)।
यह परिवर्तन इस तथ्य का उपयोग करता है कि वैक्टर की एक जोड़ी एक ही अग्रणी शून्य उत्पन्न करती है यदि एक को घुमाकर दूसरे में परिवर्तित किया जा सकता है। दुर्भाग्य से, एक काफी महत्वपूर्ण निम्न-स्तरीय अनुकूलन की आवश्यकता है कि बिट प्रतिनिधित्व में एफ का शीर्ष बिट हमेशा समान होता है, और इस समानता का उपयोग करते हुए खोज स्थान को काफी कम कर दिया और एक अलग राज्य स्थान का उपयोग करके लगभग एक चौथाई से कम रनटाइम किया F पर कमी, ओवर-लेड ऑप्टिमाइज़ेशन को समाप्त करने से अधिक ओवरहेड इसे ऑफसेट करने से अधिक है। हालांकि, यह पता चला है कि इस समस्या को इस तथ्य से भी समाप्त किया जा सकता है कि एफ एक दूसरे के व्युत्क्रम भी बराबर हैं। जबकि इसने समतुल्यता वर्गों की गणना की जटिलता को थोड़ा जोड़ा, इसने मुझे पूर्वोक्त निम्न-स्तरीय अनुकूलन को बनाए रखने की भी अनुमति दी, जिससे x3 की गति बढ़ गई।
संचित डेटा के लिए 128-बिट पूर्णांक का समर्थन करने के लिए एक और अपडेट। 128 बिट पूर्णांकों के साथ संकलन करने के लिए, आप की आवश्यकता होगी longint.nim
से यहाँ और साथ संकलित करने के लिए -d:use128bit
। एन = 24 में अभी भी 10 मिनट से अधिक समय लगता है, लेकिन मैंने नीचे रुचि रखने वालों के लिए परिणाम शामिल किया है।
एन = 24:
@[761152247121980686336, 122682715414070296576, 19793870419291799552, 3193295704340561920, 515628872377565184, 83289931274780672, 13484616786640896, 2191103969198080, 359662314586112, 60521536552960, 10893677035520, 2293940617216, 631498735616, 230983794688, 102068682752, 48748969984, 23993655296, 11932487680, 5955725312, 2975736832, 1487591936, 743737600, 371864192, 185931328, 92965664]
import math, locks, unsigned
when defined(use128bit):
import longint
else:
type int128 = uint64 # Fallback on unsupported architectures
template toInt128(x: expr): expr = uint64(x)
const
N = 22
M = N + 1
FSize = (1 shl N)
FMax = FSize - 1
SStep = 1 shl (N-1)
numThreads = 16
type
ZeroCounter = array[0..M-1, uint64]
ZeroCounterLong = array[0..M-1, int128]
ComputeThread = TThread[int]
Pair = tuple[value, weight: int32]
var
leadingZeros: ZeroCounterLong
lock: TLock
innerProductTable: array[0..FMax, int8]
zeroInnerProductList = newSeq[int32]()
equiv: array[0..FMax, int32]
fTable = newSeq[Pair]()
proc initInnerProductTables =
for i in 0..FMax:
innerProductTable[i] = int8(countBits32(int32(i)) - N div 2)
if innerProductTable[i] == 0:
if (i and 1) == 0:
add(zeroInnerProductList, int32(i))
initInnerProductTables()
proc ror1(x: int): int {.inline.} =
((x shr 1) or (x shl (N-1))) and FMax
proc initEquivClasses =
add(fTable, (0'i32, 1'i32))
for i in 1..FMax:
var r = i
var found = false
block loop:
for j in 0..N-1:
for m in [0, FMax]:
if equiv[r xor m] != 0:
fTable[equiv[r xor m]-1].weight += 1
found = true
break loop
r = ror1(r)
if not found:
equiv[i] = int32(len(fTable)+1)
add(fTable, (int32(i), 1'i32))
initEquivClasses()
when defined(gcc):
const unrollDepth = 4
else:
const unrollDepth = 4
proc search2(lz: var ZeroCounter, s0, f, w: int) =
var s = s0
for i in unrollDepth..M-1:
lz[i] = lz[i] + uint64(w)
s = s shr 1
case innerProductTable[s xor f]
of 0:
# s = s + 0
of -1:
s = s + SStep
else:
return
template search(lz: var ZeroCounter, s, f, w, i: int) =
when i < unrollDepth:
lz[i] = lz[i] + uint64(w)
if i < M-1:
let s2 = s shr 1
case innerProductTable[s2 xor f]
of 0:
search(lz, s2 + 0, f, w, i+1)
of -1:
search(lz, s2 + SStep, f, w, i+1)
else:
discard
else:
search2(lz, s, f, w)
proc worker(base: int) {.thread.} =
var lz: ZeroCounter
for fi in countup(base, len(fTable)-1, numThreads):
let (fp, w) = fTable[fi]
let f = if (fp and (FSize div 2)) == 0: fp else: fp xor FMax
for sp in zeroInnerProductList:
let s = f xor sp
search(lz, s, f, w, 0)
acquire(lock)
for i in 0..M-1:
let t = lz[i].toInt128 shl (M-i).toInt128
leadingZeros[i] = leadingZeros[i] + t
release(lock)
proc main =
var threads: array[numThreads, ComputeThread]
for i in 0 .. numThreads-1:
createThread(threads[i], worker, i)
for i in 0 .. numThreads-1:
joinThread(threads[i])
initLock(lock)
main()
echo(@leadingZeros)