Как высоко вы можете пойти? (Кодирование + алгоритмы вызова)


34

Теперь, когда все разработали свой (часто удивительный) опыт низкоуровневого кодирования для « Как медленно работает Python?» (Или как быстро ваш язык?) И как медленно работает Python (часть II)?пришло время для испытания, которое также расширит вашу способность улучшать алгоритм.

Следующий код вычисляет список длины 9. Позиция iв списке подсчитывает количество раз, iкогда были найдены хотя бы последовательные нули при вычислении внутренних произведений между Fи S. Чтобы сделать это точно, он перебирает все возможные списки Fдлины nи списки Sдлины n+m-1.

#!/usr/bin/python
import itertools
import operator

n=8
m=n+1
leadingzerocounts = [0]*m
for S in itertools.product([-1,1], repeat = n+m-1):
    for F in itertools.product([-1,1], repeat = n):
        i = 0
        while (i<m and sum(map(operator.mul, F, S[i:i+n])) == 0):
            leadingzerocounts[i] +=1
            i+=1
print leadingzerocounts

Выход

[4587520, 1254400, 347648, 95488, 27264, 9536, 4512, 2128, 1064]

Если вы увеличите n до 10,12,14,16,18,20 с помощью этого кода, он очень быстро станет слишком медленным.

правила

  • Задача состоит в том, чтобы дать правильный вывод для максимально большого числа n. Только четные значения n актуальны.
  • Если есть ничья, выигрыш переходит к самому быстрому коду на моей машине для наибольшего n.
  • Я оставляю за собой право не тестировать код, который занимает более 10 минут.
  • Вы можете изменить алгоритм любым удобным вам способом, если он дает правильный результат. На самом деле вам придется изменить алгоритм, чтобы добиться приличного прогресса на пути к победе.
  • Победитель будет награжден через неделю после того, как был задан вопрос.
  • Награда будет присуждена в установленный срок, то есть немного позже, когда победитель будет награжден.

Моя машина Время будет запущено на моей машине. Это стандартная установка Ubuntu на восьмиъядерный процессор AMD FX-8350. Это также означает, что мне нужно иметь возможность запускать ваш код. Как следствие, используйте только легкодоступное бесплатное программное обеспечение и, пожалуйста, включите подробные инструкции по компиляции и запуску вашего кода.

Статус .

  • C . n = 12 за 49 секунд @Fors
  • Java . n = 16 в 3:07 от @PeterTaylor
  • С ++ . n = 16 в 2:21 @ilmale
  • Rpython . n = 22 в 3:11 @primo
  • Java . n = 22 в 6:56 @PeterTaylor
  • Нимрод . n = 24 за 9:28 секунд от @ReimerBehrends

Победителем стал Реймер Берендс с записью в Nimrod!

В качестве проверки вывод для 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].


Конкурс завершен, но ... Я буду предлагать 200 баллов за каждую заявку, которая увеличивается n на 2 (в течение 10 минут на моем компьютере), пока у меня не кончатся баллы. Это предложение открыто навсегда .


1
«Я оставляю за собой право не тестировать код, который занимает больше нескольких минут». > Вы должны указать точное время на вашей машине, иначе у этого вопроса не будет объективного критерия выигрыша.
pastebin.com косая черта 0mr8spkT

14
Я люблю эти вызовы "увеличить скорость". Если вы создаете коммерческий продукт с этим, у вас будет чертовски быстрый продукт, и вы также злой гений .
Rainbolt

1
Возможно, более информативное название привлечет внимание к этому?
TheDoctor

8
Если мы будем продолжать выполнять такого рода задачи, я думаю, что мы должны, по крайней мере, попытаться решить другую проблему, чтобы она была интересной (а не вариацией той же проблемы с дополнительными спецификациями).
grovesNL

2
@Claudiu его процессор имеет 8 физических ядер, но единицы выборки / декодирования и FPU распределяются между ядрами. Поэтому, когда узкое место находится в одной из этих областей, оно ведет себя больше как квадрокол. Злоупотребляйте целочисленной логикой и избегайте больших размеров кода, и это больше похоже на 8-ядерный.
Стефан

Ответы:


20

Нимрод (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

(Нимрод можно скачать здесь .)

Это выполняется за отведенное время для n = 20 (и для n = 18, когда используется только один поток, в последнем случае это занимает около 2 минут).

Алгоритм использует рекурсивный поиск, сокращая дерево поиска всякий раз, когда встречается ненулевой внутренний продукт. Мы также сократили пространство поиска пополам, наблюдая, что для любой пары векторов(F, -F) нам нужно рассмотреть только один, потому что другой производит те же самые наборы внутренних произведений (отрицаяS ).

Реализация использует средства метапрограммирования Nimrod, чтобы развернуть / встроить первые несколько уровней рекурсивного поиска. Это экономит немного времени при использовании gcc 4.8 и 4.9 в качестве бэкэнда Nimrod и достаточное количество для clang.

Пространство поиска может быть дополнительно сокращено, если заметить, что нам нужно учитывать только значения 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-ядерном компьютере с gcc 4.4.6 в качестве бэкэнда Nimrod и переполняет 64-битные целые числа leadingZeros[0](возможно, не беззнаковые, не смотрели на это).


Обновление: я нашел место для еще пары улучшений. Во-первых, для заданного значения Fмы можем Sточно перечислить первые 16 записей соответствующих векторов, потому что они должны точно отличаться в разных N/2местах. Таким образом, мы предварительно вычисляем список битовых векторов размера N, в которых N/2установлены биты, и используем их для получения начальной части Sиз F.

Во-вторых, мы можем улучшить рекурсивный поиск, наблюдая, что мы всегда знаем значение F[N](поскольку MSB равен нулю в битовом представлении). Это позволяет нам точно предсказать, в какую ветвь мы перейдем из внутреннего продукта. Хотя это на самом деле позволило бы нам превратить весь поиск в рекурсивный цикл, на самом деле это совсем немного мешает предсказанию ветвлений, поэтому мы сохраняем верхние уровни в первоначальном виде. Мы все еще экономим некоторое время, прежде всего, за счет уменьшения количества ветвлений, которые мы делаем.

Для некоторой очистки код теперь использует целые числа без знака и фиксирует их на 64-битном (на тот случай, если кто-то захочет запустить это на 32-битной архитектуре).

Общее ускорение составляет от х3 до х4. Для N = 22 по-прежнему требуется более восьми ядер для работы менее чем за 10 минут, но на 64-ядерном компьютере это теперь сокращается примерно до четырех минут (с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 на моей четырехъядерной машине.

Окончательное обновление (я думаю). Классы лучшей эквивалентности для выбора F, сокращая время выполнения для N = 22 до 3:19 минут 57 секунд (правка: я случайно запустил это только с одним потоком) на моей машине.

Это изменение использует тот факт, что пара векторов производит одинаковые ведущие нули, если один можно преобразовать в другой, вращая его. К сожалению, довольно критичная низкоуровневая оптимизация требует, чтобы верхний бит F в битовом представлении всегда был одинаковым, и при использовании этой эквивалентности значительно сократил пространство поиска и сократил время выполнения примерно на четверть по сравнению с использованием другого пространства состояний сокращение на F, издержки от устранения низкоуровневой оптимизации более чем компенсируют ее. Однако оказывается, что эту проблему можно устранить, если учесть тот факт, что F, обратные друг другу, также эквивалентны. Хотя это немного усложнило вычисление классов эквивалентности, оно также позволило мне сохранить вышеупомянутую низкоуровневую оптимизацию, что привело к ускорению примерно в 3 раза.

Еще одно обновление для поддержки 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)

Результат с N = 22 равен 12410090985684467712, который занимает 63,42 бита и, таким образом, помещается в 64-разрядный бит без знака.
Стефан

2
Вы определенно подняли планку очень впечатляюще.

1
Рад видеть кого-то, кто использует Nimrod. :)
cjfaure

@ Stefan Может быть, ваш мастер кодирования может получить этот метод менее 10 минут для N = 22?

Я попытался N = 22, который закончился через несколько часов. Однако это дает мне [-6036653088025083904, 2087229562810269696, 351473149499408384, 59178309967151104, 9975110458933248, 1682628717576192, 284866824372224, 48558946385920, 8416739196928, 1518499004416, 301448822784, 71620493312, 22100246528, 8676573184, 3897278464, 1860960256, 911646720, 451520512, 224785920, 112198656, 56062720, 28031360, 14015680], которая кажется ошибкой переполнения. Я не знаю nimrod, но возможно ли использовать неподписанные целые числа для решения этой проблемы?

11

Java ( n=22?)

Я думаю, что большинство ответов лучше, чем n=16использование аналогичного подхода к этому, хотя они отличаются по симметрии, которую они используют, и по тому, как они делят задачу между потоками.

Векторы, определенные в вопросе, могут быть заменены битовыми строками, а внутренний продукт - XOR, перекрывающим перекрывающее окно и проверяющим, установлены ли именно n/2биты (и, следовательно, n/2биты очищены). Существуют n! / ((n/2)!)цепочки nбитов (центральный биномиальный коэффициент) с n/2установленными битами (которые я называю сбалансированными строками), поэтому для любого заданного числа Fсуществует множество окон, Sкоторые дают нулевой внутренний продукт. Более того, действие скольжения Sвдоль единицы и проверки, можем ли мы все еще найти входящий бит, который дает нулевой внутренний продукт, соответствует поиску ребра в графе, узлами которого являются окна, а ребра которого связывают узел uс узлом, vчьи первые n-1биты последниеn-1биты u.

Например, с n=6и F=001001мы получаем этот график:

График для F = 001001

и для F=001011мы получаем этот график:

График для F = 001011

Затем нам нужно рассчитывать для каждого iиз 0к nсколько путей длины iесть, суммируя по графикам на каждый F. Я думаю, что большинство из нас используют поиск в глубину.

Обратите внимание, что графики разрежены: легко доказать, что каждый узел имеет степень не более 1, а степень не более 1. Это также означает, что единственными возможными структурами являются простые цепочки и простые петли. Это немного упрощает DFS.

Я использую пару симметрий: уравновешенные строки закрываются под инвертированными битами ( ~операция на многих языках из семейства ALGOL) и при ротации битов, поэтому мы можем сгруппировать значения, Fкоторые связаны этими операциями, и выполнять только DFS один раз.

public class CodeGolf26459v8D implements Runnable {
    private static final int NUM_THREADS = 8;

    public static void main(String[] args) {
        v8D(22);
    }

    private static void v8D(int n) {
        int[] bk = new int[1 << n];
        int off = 0;
        for (int i = 0; i < bk.length; i++) {
            bk[i] = Integer.bitCount(i) == n/2 ? off++ : -1;
        }

        int[] fwd = new int[off];
        for (int i = 0; i < bk.length; i++) {
            if (bk[i] >= 0) fwd[bk[i]] = i;
        }

        CodeGolf26459v8D[] runners = new CodeGolf26459v8D[NUM_THREADS];
        Thread[] threads = new Thread[runners.length];
        for (int i = 0; i < runners.length; i++) {
            runners[i] = new CodeGolf26459v8D(n, i, runners.length, bk, fwd);
            threads[i] = new Thread(runners[i]);
            threads[i].start();
        }

        try {
            for (int i = 0; i < threads.length; i++) threads[i].join();
        }
        catch (InterruptedException ie) {
            throw new RuntimeException("This shouldn't be reachable", ie);
        }

        long surviving = ((long)fwd.length) << (n - 1);
        for (int i = 0; i <= n; i++) {
            for (CodeGolf26459v8D runner : runners) surviving -= runner.survival[i];
            System.out.print(i == 0 ? "[" : ", ");
            java.math.BigInteger result = new java.math.BigInteger(Long.toString(surviving));
            System.out.print(result.shiftLeft(n + 1 - i));
        }
        System.out.println("]");
    }

    public final int n;
    protected final int id;
    protected final int numRunners;
    private final int[] bk;
    private final int[] fwd;

    public long[] survival;

    public CodeGolf26459v8D(int n, int id, int numRunners, int[] bk, int[] fwd) {
        this.n = n;
        this.id = id;
        this.numRunners = numRunners;

        this.bk = bk;
        this.fwd = fwd;
    }

    private int dfs2(int[] graphShape, int flip, int i) {
        if (graphShape[i] != 0) return graphShape[i];

        int succ = flip ^ (fwd[i] << 1);
        if (succ >= bk.length) succ ^= bk.length + 1;

        int j = bk[succ];
        if (j == -1) return graphShape[i] = 1;

        graphShape[i] = n + 1; // To detect cycles
        return graphShape[i] = dfs2(graphShape, flip, j) + 1;
    }

    @Override
    public void run() {
        int n = this.n;
        int[] bk = this.bk;
        int[] fwd = this.fwd;

        // NB The initial count is approx 2^(2n - 1.33 - 0.5 lg n)
        // For n=18 we overflow 32-bit
        // 64-bit is good up to n=32.
        long[] survival = new long[n + 1];
        boolean[] visited = new boolean[1 << (n - 1)];
        int th = 0;
        for (int f = 0; f < visited.length; f++) {
            if (visited[f]) continue;

            int m = 1, g = f;
            while (true) {
                visited[g] = true;
                int ng = g << 1;
                if ((ng >> (n - 1)) != 0) ng ^= (1 << n) - 1;
                if (ng == f) break;
                m++;
                g = ng;
            }

            if (th++ % numRunners != id) continue;

            int[] graphShape = new int[fwd.length];
            int flip = (f << 1) ^ f;
            for (int i = 0; i < graphShape.length; i++) {
                int life = dfs2(graphShape, flip, i);
                if (life <= n) survival[life] += m;
            }
        }

        this.survival = survival;
    }
}

На моем 2.5 ГГц Core 2 я получаю

# n=18
$ javac CodeGolf26459v8D.java && time java CodeGolf26459v8D
[3341140958904320, 619683355033600, 115151552380928, 21392898654208, 3982886961152, 744128512000, 141108051968, 27588886528, 5800263680, 1408761856, 438001664, 174358528, 78848000, 38050816, 18762752, 9346816, 4666496, 2333248, 1166624]

real    0m3.131s
user    0m10.133s
sys     0m0.380s

# n=20
$ javac CodeGolf26459v8D.java && time java CodeGolf26459v8D
[203141370301382656, 35792910586740736, 6316057966936064, 1114358247587840, 196906665902080, 34848574013440, 6211866460160, 1125329141760, 213330821120, 44175523840, 11014471680, 3520839680, 1431592960, 655872000, 317675520, 156820480, 78077440, 39005440, 19501440, 9750080, 4875040]

real    1m8.706s
user    4m20.980s
sys     0m0.564s

# n=22
$ javac CodeGolf26459v8D.java && time java CodeGolf26459v8D
[12410090985684467712, 2087229562810269696, 351473149499408384, 59178309967151104, 9975110458933248, 1682628717576192, 284866824372224, 48558946385920, 8416739196928, 1518499004416, 301448822784, 71620493312, 22100246528, 8676573184, 3897278464, 1860960256, 911646720, 451520512, 224785920, 112198656, 56062720, 28031360, 14015680]

real    20m10.654s
user    76m53.880s
sys     0m6.852s

Поскольку компьютер Лембика имеет 8 ядер и выполнил мою предыдущую однопоточную программу вдвое быстрее моей, я уверен, что она будет выполнена n=22менее чем за 8 минут.


7:17! Очень хорошо. Не могли бы вы объяснить немного больше вашего метода?

6

С

Это просто слегка оптимизированная реализация алгоритма в вопросе. Это может обойтись n=12в срок.

#include <stdio.h>
#include <inttypes.h>

#define n 12
#define m (n + 1)

int main() {
    int i;
    uint64_t S, F, o[m] = {0};
    for (S = 0; S < (1LLU << (n + m - 1)); S += 2)
        for (F = 0; F < (1 << (n - 1)); F++)
            for (i = 0; i < m; i++)
                if (__builtin_popcount(((S >> i) & ((1 << n) - 1)) ^ F) == n >> 1)
                    o[i] += 4;
                else
                    break;
    for (i = 0; i < m; i++)
        printf("%" PRIu64 " ", o[i]);
    return 0;
}

Тестовый прогон n=12, включая компиляцию:

$ clang -O3 -march=native -fstrict-aliasing -ftree-vectorize -Wall fast.c
$ time ./a.out 
15502147584 3497066496 792854528 179535872 41181184 9826304 2603008 883712 381952 177920 85504 42560 21280 
real    0m53.266s
user    0m53.042s
sys     0m0.068s
$

Комментарий: я просто включил свой мозг и использовал простую комбинаторику, чтобы вычислить, что первое значение всегда будет n! / ((n / 2)!)^2 * 2^(n + m - 1). Мне кажется, что должно быть полностью алгебраическое решение этой проблемы.


Я получаю много предупреждений, когда собираю это. Попробуйте gcc -Wall -Wextra Fors.c -o Fors

Было несколько неиспользованных переменных, забытых с более ранней итерации, но я удалил их, так что по крайней мере пара предупреждений должна была исчезнуть. В данный момент у меня нет доступного GCC (только Clang), и Clang не дает мне никаких предупреждений (после удаления неиспользуемых переменных). И поскольку Clang обычно более строг, когда дело доходит до предупреждений, я должен сказать, что немного удивлен, что вы вообще получили какие-либо предупреждения.
Форс

Он жалуется на Fors.c: 13: 17: предупреждение: предлагать скобки вокруг '-' в операнде '&' [-Wparentheses] (дважды), а также предупреждение: формат "% llu" ожидает аргумент типа "long long unsigned int ', но аргумент 2 имеет тип' uint64_t '[-Wformat =]. На самом деле Clang жалуется на утверждение printf для меня,

С последними изменениями GCC не должен выдавать никаких предупреждающих сообщений.
Форс

Он по-прежнему жалуется на Fors.c: 13: 49: предупреждение: предлагать скобки вокруг арифметики в операнде '^' [-Wparentheses] Но, что еще хуже, это занимает больше 10 минут на моей машине.

5

Джава, n=16

Для любого заданного значения Fсуществуют \binom{n}{n/2}векторы, которые имеют нулевое внутреннее произведение. Таким образом, мы можем построить граф, вершинами которого являются те совпадающие векторы, а ребра которого соответствуют смещению S, и тогда нам просто нужно посчитать пути длины nв графе.

Я не пробовал микрооптимизировать это, заменяя условные выражения побитовыми операциями, но каждое двойное увеличение nувеличивает время выполнения примерно в 16 раз, так что это не будет иметь большого значения, если я не достаточно близок к порогу. На моей машине я нет.

public class CodeGolf26459 {

    public static void main(String[] args) {
        v3(16);
    }

    // Order of 2^(2n-1) * n ops
    private static void v3(int n) {
        long[] counts = new long[n+1];
        int mask = (1 << n) - 1;
        for (int f = 0; f < (1 << (n-1)); f++) {
            // Find adjacencies
            long[] subcounts = new long[1 << n];
            for (int g = 0; g < (1 << n); g++) {
                subcounts[g] = Integer.bitCount(f ^ g) == n/2 ? 2 : -1;
            }

            for (int round = 0; round <= n; round++) {
                long count = 0;
                // Extend one bit.
                long[] next = new long[1 << n];
                for (int i = 0; i < (1 << n); i++) {
                    long s = subcounts[i];
                    if (s == -1) next[i] = -1;
                    else {
                        count += s;
                        int j = (i << 1) & mask;
                        if (subcounts[j] >= 0) next[j] += s;
                        if (subcounts[j + 1] >= 0) next[j + 1] += s;
                    }
                }
                counts[round] += count << (n - round);
                subcounts = next;
            }
        }

        System.out.print("[");
        for (long count : counts) System.out.print(count+", ");
        System.out.println("]");
    }
}

На моем 2.5 ГГц Core 2 я получаю

$ javac CodeGolf26459.java && time java -server CodeGolf26459 
[55276229099520, 10855179878400, 2137070108672, 420578918400, 83074121728, 16540581888, 3394347008, 739659776, 183838720, 57447424, 23398912, 10749184, 5223040, 2584896, 1291424, 645200, 322600, ]

real    6m2.663s
user    6m4.631s
sys     0m1.580s

Спекуляция, так как я не хочу реализовывать собственное решение прямо сейчас. Каждая вершина имеет не более одного преемника, поэтому вам не нужен массив. Для эффективной итерации по комбинациям fи начальным вершинам итерируйте по всем f_xor_gс точно n/2установленными битами. Для каждого из них, переберите все fи возьмите g = f ^ f_xor_g.
Дэвид Эйзенстат

@ Дэвид, я знаю, и моя версия 7 делает n = 18 за одну минуту на моем нетбуке Atom, но я не могу опубликовать его, пока не вернусь из отпуска.
Питер Тейлор

4

RPython, N = 22 ~ 3: 23

Многопоточный, с использованием рекурсивного спуска без стеков. Программа принимает два аргумента командной строки: N и количество рабочих потоков.

from time import sleep

from rpython.rlib.rthread import start_new_thread, allocate_lock
from rpython.rlib.rarithmetic import r_int64, build_int, widen
from rpython.rlib.rbigint import rbigint

r_int8 = build_int('r_char', True, 8)

class ThreadEnv:
  __slots__ = ['n', 'counts', 'num_threads',
               'v_range', 'v_num', 'running', 'lock']

  def __init__(self):
    self.n = 0
    self.counts = [rbigint.fromint(0)]
    self.num_threads = 0
    self.v_range = [0]
    self.v_num = 0
    self.running = 0
    self.lock = None

env = ThreadEnv()

bt_bits = 12
bt_mask = (1<<bt_bits)-1
# computed compile time
bit_table = [r_int8(0)]
for i in xrange(1,1<<bt_bits):
  bit_table += [((i&1)<<1) + bit_table[i>>1]]

def main(argv):
  argc = len(argv)
  if argc < 2 or argc > 3:
    print 'Usage: %s N [NUM_THREADS=2]'%argv[0]
    return 1

  if argc == 3:
    env.num_threads = int(argv[2])
  else:
    env.num_threads = 2

  env.n = int(argv[1])
  env.counts = [rbigint.fromint(0)]*env.n
  env.lock = allocate_lock()

  v_range = []
  v_max = 1<<(env.n-1)
  v_num = 0
  v = (1<<(env.n>>1))-1
  while v < v_max:
    v_num += 1
    v_range += [v]
    if v&1:
      # special case odd v
      s = (v+1)&-v
      v ^= s|(s>>1)
    else:
      s = v&-v
      r = v+s
      # s is at least 2, skip two iterations
      i = 3
      s >>= 2
      while s:
        i += 1
        s >>= 1
      v = r|((v^r)>>i)
  env.v_range = v_range
  env.v_num = v_num

  for i in xrange(env.num_threads-1):
    start_new_thread(run,())

  # use the main process as a worker
  run()

  # wait for any laggers
  while env.running:
    sleep(0.05)

  result = []
  for i in range(env.n):
    result += [env.counts[i].lshift(env.n-i+3).str()]
  result += [env.counts[env.n-1].lshift(3).str()]
  print result
  return 0

def run():
  with env.lock:
    v_start = env.running
    env.running += 1

  n = env.n
  counts = [r_int64(0)]*n
  mask = (1<<n)-1
  v_range = env.v_range
  v_num = env.v_num
  z_count = 1<<(n-2)

  for i in xrange(v_start, v_num, env.num_threads):
    v = v_range[i]
    counts[0] += z_count
    counts[1] += v_num
    r = v^(v<<1)
    for w in v_range:
      # unroll counts[2] for speed
      # ideally, we could loop over x directly,
      # rather than over all v, only to throw the majority away
      # there's a 2x-3x speed improvement to be had here...
      x = w^r
      if widen(bit_table[x>>bt_bits]) + widen(bit_table[x&bt_mask]) == n:
        counts[2] += 1
        x, y = v, x
        o, k = 2, 3
        while k < n:
          # x = F ^ S
          # y = F ^ (S<<1)
          o = k
          z = (((x^y)<<1)^y)&mask
          # z is now F ^ (S<<2), possibly xor 1
          # what S and F actually are is of no consequence

          # the compiler hint `widen` let's the translator know
          # to store the result as a native int, rather than a signed char
          bt_high = widen(bit_table[z>>bt_bits])
          if bt_high + widen(bit_table[z&bt_mask]) == n:
            counts[k] += 1
            x, y = y, z
            k += 1
          elif bt_high + widen(bit_table[(z^1)&bt_mask]) == n:
            counts[k] += 1
            x, y = y, z^1
            k += 1
          else: k = n

  with env.lock:
    for i in xrange(n):
      env.counts[i] = env.counts[i].add(rbigint.fromrarith_int(counts[i]))
    env.running -= 1

def target(*args):
  return main, None

Скомпилировать

Создайте локальный клон PyPy-репозитория, используя mercurial, git или что угодно. Введите следующее заклинание (при условии, что вышеупомянутый скрипт назван convolution-high.py):

$ pypy %PYPY_REPO%/rpython/bin/rpython --thread convolution-high.py

где %PYPY_REPO%представляет переменную среды, указывающую на репозиторий, который вы только что клонировали. Компиляция занимает около минуты.


Пример времени

N = 16, 4 темы:

$ timeit convolution-high-c 16 4
[55276229099520, 10855179878400, 2137070108672, 420578918400, 83074121728, 16540581888, 3394347008, 739659776, 183838720, 57447424, 23398912, 10749184, 5223040, 2584896, 1291424, 645200, 322600]
Elapsed Time:     0:00:00.109
Process Time:     0:00:00.390

N = 18, 4 темы:

$ timeit convolution-high-c 18 4
[3341140958904320, 619683355033600, 115151552380928, 21392898654208, 3982886961152, 744128512000, 141108051968, 27588886528, 5800263680, 1408761856, 438001664, 174358528, 78848000, 38050816, 18762752, 9346816, 4666496, 2333248, 1166624]
Elapsed Time:     0:00:01.250
Process Time:     0:00:04.937

N = 20, 4 темы:

$ timeit convolution-high-c 20 4
[203141370301382656, 35792910586740736, 6316057966936064, 1114358247587840, 196906665902080, 34848574013440, 6211866460160, 1125329141760, 213330821120, 44175523840, 11014471680, 3520839680, 1431592960, 655872000, 317675520, 156820480, 78077440, 39005440, 19501440, 9750080, 4875040]
Elapsed Time:     0:00:15.531
Process Time:     0:01:01.328

N = 22, 4 темы:

$ timeit convolution-high-c 22 4
[12410090985684467712, 2087229562810269696, 351473149499408384, 59178309967151104, 9975110458933248, 1682628717576192, 284866824372224, 48558946385920, 8416739196928, 1518499004416, 301448822784, 71620493312, 22100246528, 8676573184, 3897278464, 1860960256, 911646720, 451520512, 224785920, 112198656, 56062720, 28031360, 14015680]
Elapsed Time:     0:03:23.156
Process Time:     0:13:25.437

9:26. Добро пожаловать в 22 экипажа :)

Я не уверен, почему, но ваша новая версия не быстрее для меня. Еще около 9:30, когда я делаю время ./primo-c 22 8.

@Lembik, который имел бы смысл, если бы деление было в среднем примерно таким же быстрым, как 3 сдвига вправо (3 = сумма {(n + 1) / (2 ^ n)}, n = 1..infty). Я полагаю, что в случае с архитектурой Certian дело обстоит именно так, но в моем подразделении это заметно медленнее. Спасибо, что нашли время, чтобы проверить это :)
primo

3

Python 3.3, N = 20, 3,5 мин

Отказ от ответственности: мое намерение НЕ размещать это как мой собственный ответ, так как алгоритм я использую только бесстыдный порт из раствора RPython Primo в . Моя цель здесь - показать, что вы можете сделать в Python, если сочетаете магию Numpy и модулей Numba .

Нумба объяснил вкратце:

Numba - это специализированный компилятор, работающий точно в срок, который компилирует аннотированный код Python и NumPy в LLVM (через декораторы). http://numba.pydata.org/

Обновление 1 : я заметил после бросания чисел, что мы можем просто полностью пропустить некоторые числа. Так что теперь MaxF становится (1 << n) // 2, а maxs становится maxf 2 **. Это немного ускорит процесс. n = 16 теперь занимает всего ~ 48 секунд (вместо 4,5 минут). У меня также есть другая идея, которую я собираюсь попробовать и посмотреть, смогу ли я сделать это немного быстрее.

Обновление 2: изменен алгоритм (решение primo). Хотя мой порт еще не поддерживает многопоточность, добавить его довольно просто. Можно даже выпустить CPython GIL, используя Numba и ctypes. Однако это решение работает очень быстро и на одном ядре!

import numpy as np
import numba as nb

bt_bits = 11
bt_mask = (1 << bt_bits) - 1
bit_table = np.zeros(1 << bt_bits, np.int32)

for i in range(0, 1 << bt_bits):
    bit_table[i] = ((i & 1) << 1) + bit_table[i >> 1]

@nb.njit("void(int32, int32, int32, int32, int64[:], int64[:])")
def run(n, m, start, re, counts, result):
    mask = (1 << n) - 1

    v_max = (1 << n) // 2
    rr = v_max // 2

    v = (1 << (n >> 1)) - 1
    while v < v_max:
        s = start

        while s < rr:
            f = v ^ s
            counts[0] += 8
            t = s << 1
            o, j = 0, 1

            while o < j and j < m:
                o = j
                w = (t ^ f) & mask
                bt_high = bit_table[w >> bt_bits]

                if bt_high + bit_table[w & bt_mask] == n:
                    counts[j] += 8
                    t <<= 1
                    j += 1
                elif bt_high + bit_table[(w ^ 1) & bt_mask] == n:
                    counts[j] += 8
                    t = (t | 1) << 1
                    j += 1
                    s += re

            s = v & -v
            r = v + s
            o = v ^ r
            o = (o >> 2) // s
            v = r | o

    for e in range(m):
        result[e] += counts[e] << (n - e)

И наконец:

if __name__ == "__main__":
    n = 20
    m = n + 1

    result = np.zeros(m, np.int64)
    counts = np.zeros(m, np.int64)

    s1 = time.time() * 1000
    run(n, m, 0, 1, counts, result)
    s2 = time.time() * 1000

    print(result)
    print("{0}ms".format(s2 - s1))

Это работает на моей машине в 212688 мс или ~ 3,5 мин.


Спасибо. Теперь как насчет n = 18? :)

Прошло почти 20 минут с тех пор, как я запустил программу, используя n = 18. Я думаю, можно с уверенностью сказать, что Python не может решить эту проблему даже с Numba вовремя, используя этот конкретный алгоритм.
Анна Джокела

Я оптимистичен, что существует лучший алгоритм.

Я попытался установить pumb numba, но он говорит, что не может найти llvmpy. Я пытался установить sudo pip llvmpy, но он говорит, что не может найти versioneer. Я попробовал sudo pip установить versioneer, но там написано, что он у меня уже есть.

Хотя у меня еще нет numba на работу (думаю, мне придется в конце концов установить anaconda), я впечатлен этим. Вопрос в том, можете ли вы решить проблему N = 22, используя метод, аналогичный nimrod?

2

C ++ N = 16

Я тестирую EEEPC с атомом ... мое время не имеет большого смысла. : D
Атом решает n = 14 за 34 секунды. И n = 16 через 20 минут. Я хочу проверить n = 16 на ПК. Я настроен оптимистично

Идея состоит в том, что каждый раз, когда мы находим решение для данного F, мы находим 2 ^ i решение, потому что мы можем изменить нижнюю часть S, приводя к тому же результату.

#include <stdio.h>
#include <cinttypes>
#include <cstring>

int main()
{
   const int n = 16;
   const int m = n + 1;
   const uint64_t maxS = 1ULL << (2*n);
   const uint64_t maxF = 1ULL << n;
   const uint64_t mask = (1ULL << n)-1;
   uint64_t out[m]={0};
   uint64_t temp[m] = {0};
   for( uint64_t F = 0; F < maxF; ++F )
   {
      for( uint64_t S = 0; S < maxS; ++S )
      {
         int numSolution = 1;
         for( int i = n; i >= 0; --i )
         {
            const uint64_t window = S >> i;
            if( __builtin_popcount( mask & (window ^ F) ) == (n / 2) )
            {
               temp[i] += 1;
            } else {
               numSolution = 1 << i;
               S += numSolution - 1;
               break;
            }
         }
         for( int i = n; i >= 0; --i )
         {
            out[i] += temp[i]*numSolution;
            temp[i] = 0;
         }
      }
   }
   for( int i = n; i >= 0; --i )
   {
      uint64_t x = out[i];
      printf( "%lu ", x );
   }
   return 0;
}

Скомпилировать:

gcc 26459.cpp -std = c ++ 11 -O3 -march = собственный -fstrict-aliasing -ftree-vectorize -Wall -pedantic -o 26459


1
Это здорово. У меня есть несколько недоделанных идей о том, как решить эту проблему для больших n. Хотели бы вы услышать их или это испортило бы конкуренцию?

2

JAVASCRIPT n: 12

В моем компьютере это заняло 231,242 секунды. В демоверсии я использую веб-работников для предотвращения зависания браузера. Это наверняка может быть улучшено с параллельными работниками. Я знаю, что у JS нет шансов в этом испытании, но я сделал это для удовольствия!

Нажмите, чтобы запустить онлайн демо

var n = 8;        
var m = n + 1;
var o = [];
var popCount = function(bits) {
  var SK5  = 0x55555555,
      SK3  = 0x33333333,
      SKF0 = 0x0f0f0f0f,
      SKFF = 0xff00ff;

  bits -= (bits >> 1) & SK5;
  bits  = (bits & SK3) + ((bits >> 2) & SK3);
  bits  = (bits & SKF0) + ((bits >> 4) & SKF0);
  bits += bits >> 8;

  return (bits + (bits >> 15)) & 63;
};
for(var S = 0; S < (1 << n + m - 1); S += 2){
  for(var F = 0; F < (1 << n - 1); F += 1){
    for (var i = 0; i < m; i++){
      var c = popCount(((S >> i) & ((1 << n) - 1)) ^ F);
      if(c == n >> 1){
        if(!o[i]) o[i] = 0;
        o[i] += 4;
      } else break;
    }
  }
}
return o;

Как насчет одного из этих новых (быстрых) движков JavaScript? Могут ли они быть использованы?

Вы имеете в виду что-то вроде дартс ?
rafaelcastrocouto

1
На самом деле я не прав. Вы могли бы также просто попробовать и firefox и хром. Если, конечно, вы не хотите писать это в asm.js :)

1
вызов принят ... собираюсь сделать это!
rafaelcastrocouto

1
Попробовал это и занял у моего компьютера 5,4 секунды, чтобы сделать n=22 [235388928,86292480,19031048,5020640,1657928,783920,545408,481256,463832,460256,459744,459744,459744,459744,459744,459744,459744,459744,459744,459744,459744,459744] i.imgur.com/FIJa2Ch.png
Spedwards

1

Фортран: n = 12

Я только что сделал быструю и грязную версию на Фортране, без оптимизаций, кроме OpenMP. Он должен выжать чуть меньше 10 минут для n = 12 на машине OP, это займет 10:39 на моей машине, что немного медленнее.

64-битные целые действительно оказывают негативное влияние на производительность; думаю, мне придется переосмыслить весь алгоритм, чтобы это было намного быстрее. Не знаю, буду ли я беспокоиться, думаю, я скорее потрачу некоторое время на то, чтобы придумать хороший вызов, который больше соответствует моим собственным вкусам. Если кто-то еще хочет взять это и бежать с этим, продолжайте :)

program golf
use iso_fortran_env
implicit none
integer, parameter ::  n=12
integer :: F(n), S(2*n)
integer(int64) :: leadingzerocounts(n+1)
integer :: k
integer(int64) :: i,j,bindec,enc

leadingzerocounts=0

!$OMP parallel do private(i,enc,j,bindec,S,F,k) reduction(+:leadingzerocounts) schedule(dynamic)
do i=0,2**(2*n)-1
  enc=i
  ! Short loop to convert i into the array S with -1s and 1s
  do j=2*n,1,-1
    bindec=2**(j-1)
    if (enc-bindec .ge. 0) then
      S(j)=1
      enc=enc-bindec
    else
      S(j)=-1
    endif
  end do
  do j=0,2**(n)-1
    ! Convert j into the array F with -1s and 1s
    enc=j
    do k=n,1,-1
      bindec=2**(k-1)
      if (enc-bindec .ge. 0) then
        F(k)=1
        enc=enc-bindec
      else
        F(k)=-1
      endif
    end do
    ! Compute dot product   
    do k=1,n+1
      if (dot_product(F,S(k:k+n-1)) /= 0) exit
      leadingzerocounts(k)=leadingzerocounts(k)+1
    end do
  end do
end do
!$OMP end parallel do

print *, leadingzerocounts

end

1

Луа: n = 16

Отказ от ответственности: я не собираюсь публиковать это как мой собственный ответ, поскольку алгоритм, который я использую, бесстыдно украден из умного ответа Анны Джокелы . который был бесстыдно украден из умного ответа Ильмале .

Кроме того, он даже недействителен - в нем есть неточности, вызванные числами с плавающей запятой (было бы лучше, если бы Lua поддерживал 64-битные целые числа). Тем не менее, я все еще загружаю его, просто чтобы показать, насколько быстро это решение. Это динамический язык программирования, и все же я могу вычислить n = 16 за разумное время (1 минута на 800 МГц CPU).

Запустите с LuaJIT, стандартный интерпретатор работает слишком медленно.

local bit = require "bit"
local band = bit.band
local bor = bit.bor
local bxor = bit.bxor
local lshift = bit.lshift
local rshift = bit.rshift

-- http://stackoverflow.com/a/11283689/736054
local function pop_count(w)
    local b1 = 1431655765
    local b2 = 858993459
    local b3 = 252645135
    local b7 = 63

    w = band(rshift(w, 1), b1) + band(w, b1)
    w = band(rshift(w, 2), b2) + band(w, b2)
    w = band(w + rshift(w, 4), b3)
    return band(rshift(w, 24) + rshift(w, 16) + rshift(w, 8) + w, b7)
end

local function gen_array(n, value)
    value = value or 0
    array = {}
    for i = 1, n do
        array[i] = value
    end
    return array
end

local n = 16
local u = math.floor(n / 2)
local m = n + 1
local maxf = math.floor(lshift(1, n) / 2)
local maxs = maxf ^ 2
local mask = lshift(1, n) - 1

local out = gen_array(m, 0)
local temp = gen_array(m, 0)


for f = 0, maxf do
    local s = 0
    while s <= maxs do
        local num_solution = 1

        for i = n, 0, -1 do
            if pop_count(band(mask, bxor(rshift(s, i), f))) == u then
                temp[i + 1] = temp[i + 1] + 8
            else
                num_solution = lshift(1, i)
                s = s + num_solution - 1
                break
            end
        end

        for i = 1, m do
            out[i] = out[i] + temp[i] * num_solution
            temp[i] = 0
        end

        s = s + 1
    end
end

for i = m, 1, -1 do
    print(out[i])
end

Спасибо. Я думаю, что последние версии lua используют long long int, который должен быть 64-битным в 64-битной системе. Смотрите "lua_integer" на lua.org/work/doc/manual.html .

@Lembik: интересно. В любом случае, это стандартный Lua (который уже поддерживается long longвместо doubleнастройки компиляции), а не LuaJIT.
Конрад Боровски

Я думаю, что я был просто неправ в любом случае. Нужно было бы 5.3, который не существует. Лучший совет, который могли дать люди, которые были у Луи, - «попробуй 5.3-workx».
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.