নিমরোড (এন = 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 মিনিট সময় নেয়) time
অ্যালগরিদম একটি পুনরাবৃত্ত অনুসন্ধান অনুসন্ধান করে, যখনই কোনও শূন্য-অভ্যন্তরীণ পণ্যটির মুখোমুখি হয় তখন অনুসন্ধান গাছকে ছাঁটাই করে। যে কোনও ভেক্টর জোড়ের জন্য পর্যবেক্ষণ করে আমরা অনুসন্ধানের স্থানটি অর্ধেকে কেটে ফেলেছি(F, -F)
আমাদের কেবল একটি বিবেচনা করা দরকার কারণ অন্যটি অভ্যন্তরীণ পণ্যের সঠিক একই সেট তৈরি করে (উপেক্ষা করে)S
এছাড়াও )।
পুনরাবৃত্ত অনুসন্ধানের প্রথম কয়েক স্তরের তালিকাটি আনرول / ইনলাইন করতে নিম্রোডের রূপক সুবিধাগুলি প্রয়োগ করা হয়। নিম্রোডের ব্যাকএন্ড এবং ঝাঁকুনির জন্য মোটামুটি পরিমাণ হিসাবে জিসিসি 4.8 এবং 4.9 ব্যবহার করার সময় এটি অল্প সময় সাশ্রয় করে।
অনুসন্ধানের স্থানটি পর্যবেক্ষণ করে আরও ছাঁটাই করা যেতে পারে যে আমাদের কেবলমাত্র এস এর মানগুলি বিবেচনা করা দরকার যা আমাদের পছন্দ হিসাবে আমাদের প্রথম পছন্দ হিসাবে এন এর প্রথম সংখ্যাগুলির মধ্যে পৃথক হয়। তবে, এর জটিলতা বা মেমরির প্রয়োজনগুলি বড় মানগুলির জন্য স্কেল করে না এন এর, প্রদত্ত যে লুপ বডি পুরোপুরি এই ক্ষেত্রে বাদ দেওয়া হয়।
অভ্যন্তরীণ পণ্যটি শূন্য যেখানে সারণীটি লুপটিতে কোনও বিট গণনা কার্যকারিতা ব্যবহারের চেয়ে দ্রুত বলে মনে হয়। স্পষ্টত টেবিল অ্যাক্সেস বেশ ভাল লোকেশন আছে।
দেখে মনে হচ্ছে সমস্যাটি ডাইনামিক প্রোগ্রামিংয়ের জন্য উপযুক্ত হতে হবে, এটি পুনরাবৃত্ত অনুসন্ধানগুলি কীভাবে কাজ করে তা বিবেচনা করে, তবে যুক্তিসঙ্গত পরিমাণ মেমরির সাথে এটি করার কোনও আপাত উপায় নেই।
উদাহরণ ফলাফল:
এন = 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]
অন্যান্য বাস্তবায়নের সাথে অ্যালগরিদমের তুলনা করার উদ্দেশ্যে, এন সিটেন 16 মেশিনে প্রায় 7.9 সেকেন্ড সময় নেয় যখন একটি একক থ্রেড ব্যবহার করে এবং 2.4 সেকেন্ড চারটি কোর ব্যবহার করার সময়।
নিম = 224-তে জি.সি.সি. ৪.৪. with সহ একটি -৪-কোর মেশিনে প্রায় 15 মিনিট সময় নেয় এবং inte৪-বিট পূর্ণসংখ্যায় ওভারফ্লো হয় leadingZeros[0]
(সম্ভবত স্বাক্ষরযুক্ত নয়, এটির দিকে নজর দেয়নি)।
আপডেট: আমি আরও কয়েকটি উন্নতির জন্য জায়গা পেয়েছি। প্রথমত, প্রদত্ত মানের জন্য F
, আমরা সংশ্লিষ্ট S
ভেক্টরগুলির প্রথম 16 টি এন্ট্রিগুলিকে যথাযথভাবে গণনা করতে পারি , কারণ তাদের অবশ্যই ঠিক N/2
জায়গায় পৃথক হতে হবে । সুতরাং আমরা আকারের বিট ভেক্টর একটি তালিকা precompute N
আছে N/2
বিট সেট এবং প্রারম্ভিক অংশ আহরণ করতে এগুলি ব্যবহার S
থেকেF
।
দ্বিতীয়ত, আমরা আমরা সর্বদা এর মান জানি F[N]
(এবং এমএসবি বিট উপস্থাপনায় শূন্য হয়) পর্যবেক্ষণ করে পুনরাবৃত্ত অনুসন্ধানের উপর উন্নতি করতে পারি । এটি আমাদের অভ্যন্তরীণ পণ্য থেকে কোন শাখায় পুনরাবৃত্তি করে তা সুনির্দিষ্টভাবে অনুমান করতে দেয়। যদিও এটি আসলে আমাদের পুরো অনুসন্ধানটিকে একটি পুনরাবৃত্ত লুপে রূপান্তর করতে দেয়, যা শাখার পূর্বাভাসটি বেশ খানিকটা স্ক্রুতে ঘটেছিল, তাই আমরা শীর্ষ স্তরের মূল আকারে রাখি। আমরা এখনও কিছু সময় সাশ্রয় করি, প্রাথমিকভাবে আমরা যে শাখাগুলি করছি তার পরিমাণ হ্রাস করে।
কিছু পরিষ্কার করার জন্য, কোডটি এখন স্বাক্ষরবিহীন পূর্ণসংখ্যা ব্যবহার করছে এবং সেগুলি 64৪-বিটে স্থির করছে (কেবলমাত্র কেউ যদি 32-বিট আর্কিটেকচারে এটি চালাতে চান) wants
সামগ্রিক স্পিডআপ x3 এবং x4 এর একটি ফ্যাক্টরের মধ্যে is এন = 22 টি 10 মিনিটের অধীনে চলতে আট কোরেরও বেশি প্রয়োজন, তবে একটি 64৪-কোর মেশিনে এটি এখন প্রায় চার মিনিটের মধ্যে নেমে গেছে ( numThreads
সেই অনুযায়ী ধাক্কা দিয়ে) with যদিও আমি আলাদা আলাদা অ্যালগরিদম ছাড়া উন্নতির আরও অনেক জায়গা আছে বলে মনে করি না।
এন = 22:
@[12410090985684467712, 2087229562810269696, 351473149499408384, 59178309967151104, 9975110458933248, 1682628717576192, 284866824372224, 48558946385920, 8416739196928, 1518499004416, 301448822784, 71620493312, 22100246528, 8676573184, 3897278464, 1860960256, 911646720, 451520512, 224785920, 112198656, 56062720, 28031360, 14015680]
অনুসন্ধানের জায়গায় আরও সম্ভাব্য হ্রাস ব্যবহার করে আবার আপডেট হয়েছে। আমার কোয়াডকোয়ার মেশিনে এন = 22 এর জন্য প্রায় 9:49 মিনিটের মধ্যে চলে।
চূড়ান্ত আপডেট (আমার মনে হয়)। আমার পছন্দসই এফ এর জন্য আরও সমতুল্য ক্লাস, এন = 22 এর জন্য রানটাইম কেটে 3:19 মিনিট 57 সেকেন্ডে (সম্পাদনা করুন: আমি দুর্ঘটনাক্রমে কেবলমাত্র একটি থ্রেড সহ এটি চালিয়েছি) আমার মেশিনে।
এই পরিবর্তনটি এই সত্যটি ব্যবহার করে যে এক জোড়া ভেক্টর একই লিডিং জিরো তৈরি করে যদি একজনকে অন্যকে ঘোরানোর মাধ্যমে অন্যটিতে রূপান্তর করা যায়। দুর্ভাগ্যক্রমে, মোটামুটি সমালোচিত নিম্ন-স্তরের অপ্টিমাইজেশনের জন্য বিট উপস্থাপনায় এফের শীর্ষ বিটটি সর্বদা একই থাকে এবং এই সমতা ব্যবহার করার সময় অনুসন্ধানের স্থানটি বেশ খানিকটা কমে যায় এবং রানটাইমকে আলাদা স্টেট স্পেস ব্যবহার করে প্রায় এক চতুর্থাংশ কমে যায় reduced F এ হ্রাস, ওপেনহেড এটি অফসেটের চেয়ে কম স্তরের অপ্টিমাইজেশনকে বাদ দেওয়া থেকে। তবে দেখা যাচ্ছে যে এফ যে একে অপরের বিপরীতমুখী সেগুলিও সমান considering এটি সমতা শ্রেণির গণনার জটিলতায় কিছুটা যোগ করার পরে, এটি আমাকে উপরোক্ত নিম্ন-স্তরের অপ্টিমাইজেশন ধরে রাখতেও অনুমতি দেয়, যার ফলে প্রায় 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)