นิมรอด (N = 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
(Nimrod สามารถดาวน์โหลดได้ที่นี่ )
สิ่งนี้จะทำงานในเวลาที่กำหนดสำหรับ n = 20 (และสำหรับ n = 18 เมื่อใช้เพียงเธรดเดียวโดยใช้เวลาประมาณ 2 นาทีในกรณีหลัง)
อัลกอริทึมใช้การค้นหาแบบเรียกซ้ำการตัดทอนการค้นหาเมื่อใดก็ตามที่พบผลิตภัณฑ์ภายในที่ไม่เป็นศูนย์ นอกจากนี้เรายังลดพื้นที่การค้นหาลงครึ่งหนึ่งด้วยการสังเกตว่าสำหรับเวกเตอร์คู่ใด ๆ(F, -F)เราเพียงแค่ต้องพิจารณาอันใดอันหนึ่งเพราะอีกอันหนึ่งผลิตผลิตภัณฑ์ภายในชุดเดียวกันแน่นอน (โดยปฏิเสธSด้วย)
การติดตั้งใช้สิ่งอำนวยความสะดวก metaprogramming ของ Nimrod ในการเปิด / ปิดอินไลน์ระดับแรก ๆ ของการค้นหาแบบเรียกซ้ำ สิ่งนี้ช่วยประหยัดเวลาเล็กน้อยเมื่อใช้ gcc 4.8 และ 4.9 เป็นแบ็กเอนด์ของนิมโรดและจำนวนที่เหมาะสมสำหรับเสียงดังกราว
พื้นที่การค้นหาอาจถูกตัดทิ้งเพิ่มเติมโดยการสังเกตว่าเราเพียงแค่ต้องพิจารณาค่าของ S ที่แตกต่างกันในตำแหน่ง N แรกจำนวนเท่ากันจากทางเลือกของ F อย่างไรก็ตามความซับซ้อนหรือความต้องการหน่วยความจำของที่ไม่ขยายสำหรับค่าขนาดใหญ่ ของ N เนื่องจากว่าลูปบอดี้ถูกข้ามอย่างสมบูรณ์ในกรณีเหล่านั้น
การสร้างตารางที่ผลิตภัณฑ์ด้านในเป็นศูนย์ดูเหมือนจะเร็วกว่าการใช้ฟังก์ชันการนับบิตใด ๆ ในลูป เห็นได้ชัดว่าการเข้าถึงตารางมีสถานที่ที่ดีพอสมควร
ดูเหมือนว่าปัญหาควรคล้อยตามการเขียนโปรแกรมแบบไดนามิกโดยพิจารณาว่าการค้นหาแบบเรียกซ้ำทำงานอย่างไร แต่ไม่มีวิธีที่ชัดเจนในการทำเช่นนั้นด้วยหน่วยความจำในปริมาณที่เหมาะสม
ตัวอย่างผลลัพธ์:
N = 16:
@[55276229099520, 10855179878400, 2137070108672, 420578918400, 83074121728, 16540581888, 3394347008, 739659776, 183838720, 57447424, 23398912, 10749184, 5223040, 2584896, 1291424, 645200, 322600]
N = 18:
@[3341140958904320, 619683355033600, 115151552380928, 21392898654208, 3982886961152, 744128512000, 141108051968, 27588886528, 5800263680, 1408761856, 438001664, 174358528, 78848000, 38050816, 18762752, 9346816, 4666496, 2333248, 1166624]
N = 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 ใช้เวลาประมาณ 15 นาทีบนเครื่อง 64-core กับ GCC 4.4.6 เป็นแบ็กเอนด์นิมและล้นจำนวนเต็ม 64 บิตในleadingZeros[0](อาจจะไม่ได้คนที่ไม่ได้ลงชื่อไม่ได้มองไปที่มัน)
อัปเดต: ฉันพบที่ว่างสำหรับการปรับปรุงเพิ่มเติมอีกสองสามข้อ อันดับแรกสำหรับค่าที่กำหนดFเราสามารถระบุ 16 รายการแรกของSเวกเตอร์ที่เกี่ยวข้องได้อย่างแม่นยำเพราะพวกมันต้องแตกต่างกันในN/2สถานที่ ดังนั้นเราจึง precompute รายการเวกเตอร์บิตของขนาดNที่มีN/2บิตชุดและใช้เหล่านี้จะได้รับส่วนเริ่มต้นของจากSF
ประการที่สองเราสามารถปรับปรุงการค้นหาแบบเรียกซ้ำโดยการสังเกตว่าเรารู้คุณค่าของเสมอF[N](เนื่องจาก MSB เป็นศูนย์ในการแทนค่าบิต) สิ่งนี้ช่วยให้เราสามารถทำนายได้อย่างแม่นยำว่าสาขาใดที่เรารับเงินคืนจากผลิตภัณฑ์ภายใน ในขณะที่จริงจะช่วยให้เราเปลี่ยนการค้นหาทั้งหมดเป็นวนซ้ำแบบซ้ำ ๆ ที่เกิดขึ้นจริงจะทำให้การคาดคะเนสาขาแตกหักเล็กน้อยดังนั้นเราจึงรักษาระดับสูงสุดไว้ในรูปแบบดั้งเดิม เรายังคงประหยัดเวลาส่วนใหญ่โดยการลดจำนวนสาขาที่เราทำ
สำหรับการล้างข้อมูลรหัสตอนนี้ใช้จำนวนเต็มไม่ได้ลงนามและแก้ไขได้ที่ 64- บิต (ในกรณีที่มีคนต้องการเรียกใช้บนสถาปัตยกรรม 32 บิต)
การเร่งความเร็วโดยรวมอยู่ระหว่างปัจจัย x3 และ x4 N = 22 ยังคงต้องการมากกว่าแปดแกนที่จะทำงานในอายุต่ำกว่า 10 นาที แต่บนเครื่อง 64-core ก็ตอนนี้ลงไปประมาณสี่นาที (ด้วยการnumThreadsชนขึ้นตามลำดับ) ฉันไม่คิดว่าจะมีพื้นที่สำหรับการปรับปรุงมากขึ้นหากไม่มีอัลกอริทึมที่แตกต่างกัน
N = 22:
@[12410090985684467712, 2087229562810269696, 351473149499408384, 59178309967151104, 9975110458933248, 1682628717576192, 284866824372224, 48558946385920, 8416739196928, 1518499004416, 301448822784, 71620493312, 22100246528, 8676573184, 3897278464, 1860960256, 911646720, 451520512, 224785920, 112198656, 56062720, 28031360, 14015680]
อัปเดตอีกครั้งใช้ประโยชน์จากการลดพื้นที่การค้นหาที่เป็นไปได้เพิ่มเติม ทำงานในเวลาประมาณ 9:49 นาทีสำหรับ N = 22 บนเครื่อง quadcore ของฉัน
การปรับปรุงครั้งสุดท้าย (ฉันคิดว่า) คลาสความเท่าเทียมที่ดีกว่าสำหรับตัวเลือกของ F ตัด runtime สำหรับ N = 22 ลงไปที่3:19 นาที 57 วินาที (แก้ไข: ฉันรันโดยไม่ตั้งใจด้วยด้ายเพียงอันเดียว) บนเครื่องของฉัน
การเปลี่ยนแปลงนี้ใช้ประโยชน์จากความจริงที่ว่าเวกเตอร์คู่หนึ่งสร้างศูนย์นำหน้าเดียวกันหากสามารถแปลงเป็นเวกเตอร์อื่นได้ด้วยการหมุน น่าเสียดายที่การเพิ่มประสิทธิภาพในระดับต่ำที่สำคัญอย่างยิ่งต้องการให้บิตบนสุดของ F ในการแทนบิทนั้นเหมือนกันเสมอและในขณะที่ใช้ความสมดุลนี้เฉือนพื้นที่การค้นหาค่อนข้างน้อยและลดเวลาทำงานประมาณหนึ่งในสี่ ลดค่า F, ค่าใช้จ่ายจากการกำจัดการเพิ่มประสิทธิภาพในระดับต่ำมากกว่าชดเชย อย่างไรก็ตามปรากฎว่าปัญหานี้สามารถถูกกำจัดได้ด้วยการพิจารณาข้อเท็จจริงที่ว่า F ที่ผู้รุกรานของกันและกันนั้นเทียบเท่ากัน ในขณะที่สิ่งนี้ได้เพิ่มความซับซ้อนของการคำนวณคลาสความเท่าเทียมกันเล็กน้อย แต่มันก็ช่วยให้ฉันสามารถคงการเพิ่มประสิทธิภาพระดับต่ำดังกล่าวซึ่งนำไปสู่การเร่งความเร็วประมาณ x3
อีกหนึ่งการปรับปรุงเพื่อรองรับจำนวนเต็ม 128- บิตสำหรับข้อมูลที่สะสม เพื่อรวบรวมกับ 128 จำนวนเต็มบิตคุณจะต้องlongint.nimจากที่นี่-d:use128bitและจะรวบรวมกับ N = 24 ยังคงใช้เวลานานกว่า 10 นาที แต่ฉันได้รวมผลลัพธ์ด้านล่างสำหรับผู้ที่สนใจ
N = 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)