コンピューター:あなたは数学をする


13

この課題は、一部はアルゴリズムの課題であり、いくつかの数学が関与し、一部は単に最速のコードの課題です。

何らかの正の整数のために、長さのsとs のn一様にランダムなストリングを考えて、それを呼び出してください。また、値がまたはである2番目の一様に選ばれた長さのランダムストリングを考えて、それを呼び出してください。今聞かせても+ 。それはそれ自体に連結されます。10nAn-10,1B_preBB_preB_preB_pre

今の内積を考えるAB[j,...,j+n-1]、それを呼び出すZ_jから、インデックスを1

仕事

出力は、n+1分数のリストである必要があります。i出力のth番目の項は、すべての最初の項が等しいという正確な確率でなければなりません。iZ_jj <= i0

スコア

n私のマシンでは、コードが10分以内に正しい出力を提供する最大のもの。

タイ・ブレーカー

2つの回答のスコアが同じ場合、最初に提出された方が勝ちです。

誰かが無制限のスコアを取得する方法を見つけるという(非常に)まれなイベントでは、そのようなソリューションの最初の有効な証拠が受け入れられます。

ヒント

この問題を数学的に解決しようとしないでください。難しすぎます。私の考えでは、高校からの確率の基本的な定義に戻って、可能性を徹底的に列挙するコードを取得するスマートな方法を見つけることが最善の方法です。

言語とライブラリ

自由に利用できるコンパイラ/インタープリター/などを備えた任意の言語を使用できます。LinuxおよびLinuxでも自由に利用できるライブラリ用。

私のマシン タイミングは私のマシンで実行されます。これは、AMD FX-8350 8コアプロセッサへの標準のUbuntuインストールです。これは、コードを実行できる必要があることも意味します。結果として、簡単に入手できる無料のソフトウェアのみを使用し、コードをコンパイルして実行する方法の完全な指示を含めてください。


いくつかのテスト出力。それぞれの最初の出力だけを考えてくださいn。そのときi=1です。以下のためにn1から13まで、彼らはする必要があります。

 1: 4/6
 2: 18/36
 3: 88/216
 4: 454/1296
 5: 2424/7776
 6: 13236/46656
 7: 73392/279936
 8: 411462/1679616
 9: 2325976/10077696
10: 13233628/60466176
11: 75682512/362797056
12: 434662684/2176782336
13: 2505229744/13060694016

あなたはまたのための一般的な式を見つけることができるi=1http://oeis.org/A081671

リーダーボード(言語で分割)

  • n = 15。Python+並列python + Jakubeによる1min49sのpypy
  • n = 17. Keith Randallによる3分37秒のC ++
  • n = 16. 黒井猫による2min38sのC ++

1
@Knerdノーと言えますか。Linuxでコードを実行する方法を考えてみますが、どんな助けでも大歓迎です。

コメントを削除してすみません。読んでいないすべてのことについては、F#またはC#が許可されている場合
でした

もう1つの質問、有効な入力出力の例はありますか?
ケナー14

あなたのグラフィックカードは何ですか?GPUの仕事のように見えます。
マイケルM. 14

1
@Knerd代わりに、確率の表を質問に追加しました。お役に立てば幸いです。

回答:


5

C ++、8スレッドで9分でn = 18

(お使いのマシンで10分未満になるかどうかを教えてください。)

B配列のいくつかの形式の対称性を利用しています。それらは、サイクリック(1つの位置だけシフト)、反転(要素の順序を逆にする)、および符号(各要素の負を取ります)です。最初に、試してみる必要があるBとその重みのリストを計算します。次に、各Bは、Aのすべての2 ^ n値に対して高速ルーチン(ビットカウント命令を使用)で実行されます。

n == 18の結果は次のとおりです。

> time ./a.out 18
 1: 16547996212044 / 101559956668416
 2:  3120508430672 / 101559956668416
 3:   620923097438 / 101559956668416
 4:   129930911672 / 101559956668416
 5:    28197139994 / 101559956668416
 6:     6609438092 / 101559956668416
 7:     1873841888 / 101559956668416
 8:      813806426 / 101559956668416
 9:      569051084 / 101559956668416
10:      510821156 / 101559956668416
11:      496652384 / 101559956668416
12:      493092812 / 101559956668416
13:      492186008 / 101559956668416
14:      491947940 / 101559956668416
15:      491889008 / 101559956668416
16:      449710584 / 101559956668416
17:      418254922 / 101559956668416
18:      409373626 / 101559956668416

real    8m55.854s
user    67m58.336s
sys 0m5.607s

以下のプログラムをコンパイルします g++ --std=c++11 -O3 -mpopcnt dot.cc

#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <thread>
#include <mutex>
#include <chrono>

using namespace std;

typedef long long word;

word n;

void inner(word bpos, word bneg, word w, word *cnt) {
    word maxi = n-1;
    for(word a = (1<<n)-1; a >= 0; a--) {
        word m = a;
        for(word i = maxi; i >= 0; i--, m <<= 1) {
            if(__builtin_popcount(m&bpos) != __builtin_popcount(m&bneg))
                break;
            cnt[i]+=w;
        }
    }
}

word pow(word n, word e) {
    word r = 1;
    for(word i = 0; i < e; i++) r *= n;
    return r;
}

typedef struct {
    word b;
    word weight;
} Bentry;

mutex block;
Bentry *bqueue;
word bhead;
word btail;
word done = -1;

word maxb;

// compute -1*b
word bneg(word b) {
    word w = 1;
    for(word i = 0; i < n; i++, w *= 3) {
        word d = b / w % 3;
        if(d == 1)
            b += w;
        if(d == 2)
            b -= w;
    }
    return b;
}

// rotate b one position
word brot(word b) {
    b *= 3;
    b += b / maxb;
    b %= maxb;
    return b;
}

// reverse b
word brev(word b) {
    word r = 0;
    for(word i = 0; i < n; i++) {
        r *= 3;
        r += b % 3;
        b /= 3;
    }
    return r;
}

// individual thread's work routine
void work(word *cnt) {
    while(true) {
        // get a queue entry to work on
        block.lock();
        if(btail == done) {
            block.unlock();
            return;
        }
        if(bhead == btail) {
            block.unlock();
            this_thread::sleep_for(chrono::microseconds(10));
            continue;
        }
        word i = btail++;
        block.unlock();

        // thread now owns bqueue[i], work on it
        word b = bqueue[i].b;
        word w = 1;
        word bpos = 0;
        word bneg = 0;
        for(word j = 0; j < n; j++, b /= 3) {
            word d = b % 3;
            if(d == 1)
                bpos |= 1 << j;
            if(d == 2)
                bneg |= 1 << j;
        }
        bpos |= bpos << n;
        bneg |= bneg << n;
        inner(bpos, bneg, bqueue[i].weight, cnt);
    }
}

int main(int argc, char *argv[]) {
    n = atoi(argv[1]);

    // allocate work queue
    maxb = pow(3, n);
    bqueue = (Bentry*)(malloc(maxb*sizeof(Bentry)));

    // start worker threads
    word procs = thread::hardware_concurrency();
    vector<thread> threads;
    vector<word*> counts;
    for(word p = 0; p < procs; p++) {
        word *cnt = (word*)calloc(64+n*sizeof(word), 1);
        threads.push_back(thread(work, cnt));
        counts.push_back(cnt);
    }

    // figure out which Bs we actually want to test, and with which weights
    bool *bmark = (bool*)calloc(maxb, 1);
    for(word i = 0; i < maxb; i++) {
        if(bmark[i]) continue;
        word b = i;
        word w = 0;
        for(word j = 0; j < 2; j++) {
            for(word k = 0; k < 2; k++) {
                for(word l = 0; l < n; l++) {
                    if(!bmark[b]) {
                        bmark[b] = true;
                        w++;
                    }
                    b = brot(b);
                }
                b = bneg(b);
            }
            b = brev(b);
        }
        bqueue[bhead].b = i;
        bqueue[bhead].weight = w;
        block.lock();
        bhead++;
        block.unlock();
    }
    block.lock();
    done = bhead;
    block.unlock();

    // add up results from threads
    word *cnt = (word*)calloc(n,sizeof(word));
    for(word p = 0; p < procs; p++) {
        threads[p].join();
        for(int i = 0; i < n; i++) cnt[i] += counts[p][i];
    }
    for(word i = 0; i < n; i++)
        printf("%2lld: %14lld / %14lld\n", i+1, cnt[n-1-i], maxb<<n);
    return 0;
}

自分のペットのモンスターの上に、さらに作業の良い、その分注私を...

これをありがとう。現在の受賞作品があります。-pthreadもう一度覚えておく必要があります。私はに取得する n=17私のマシン上で。

おっと..あなたは完全な賞金を手に入れたはずです。締め切りに間に合いませんでした。

@Lembik:問題ありません。
キースランドール14

6

pypyとppを使用したPython 2:3分でn = 15

また、単純な総当たり攻撃です。おもしろいことに、C ++で黒井猫とほぼ同じ速度が得られます。私のコードはn = 12約5分で届きます。そして、1つの仮想コアでのみ実行します。

編集:検索スペースを1分の1に削減 n

を繰り返したときA*に、の循環ベクトルがA元のベクトルと同じ数値(同じ数値)を生成することに気付きAましたB。例えば、ベクターは、(1, 1, 0, 1, 0, 0)ベクターのそれぞれと同じ確率を有し(1, 0, 1, 0, 0, 1)(0, 1, 0, 0, 1, 1)(1, 0, 0, 1, 1, 0)(0, 0, 1, 1, 0, 1)および(0, 1, 1, 0, 1, 0)ランダムを選択するときB。したがって、私は、これらの6つのベクトルの各々を反復処理する必要がありますが、たったの約1と交換しないcount[i] += 1count[i] += cycle_number

これにより、複雑さがからTheta(n) = 6^nに減少しTheta(n) = 6^n / nます。そのためn = 13、以前のバージョンの約13倍の速さです。n = 13約2分20秒で計算されます。n = 14それはまだ少し遅すぎます。約13分かかります。

編集2:マルチコアプログラミング

次の改善にあまり満足していません。また、プログラムを複数のコアで実行しようとすることにしました。2 + 2コアではn = 14、約7分で計算できます。わずか2の改善点。

コードは、このgithubリポジトリ:Linkで入手できます。マルチコアプログラミングは少しTheいものです。

編集3:AベクトルおよびBベクトルの検索スペースを削減する

Aクロイネコと同じように、ベクトルのミラー対称性に気付きました。まだわからない、なぜこれが機能するのか(そして、それがそれぞれに機能するかどうかn)。

Bベクトルの検索スペースの削減は少し賢いです。ベクトルの生成(itertools.product)を独自の関数に置き換えました。基本的には、空のリストから始めて、スタックに配置します。スタックが空になるまで、リストを削除します。リストと同じ長さでない場合は、n他の3つのリストを生成し(-1、0、1を追加して)、スタックにプッシュします。リストの長さはと同じでn、合計を評価できます。

ベクトルを自分で生成したので、合計= 0に到達できるかどうかに応じてベクトルをフィルタリングできます。たとえば、ベクターA(1, 1, 1, 0, 0)で、ベクターBが見える場合、値を(1, 1, ?, ?, ?)埋めることができないことがわかり?ますA*B = 0。そのBため、フォームのこれらの6つのベクトルすべてを反復処理する必要はありません(1, 1, ?, ?, ?)

1の値を無視すると、これを改善できます。質問で述べたように、の値i = 1はシーケンスA081671です。それらを計算する方法はたくさんあります。単純な繰り返しを選択します:a(n) = (4*(2*n-1)*a(n-1) - 12*(n-1)*a(n-2)) / ni = 1基本的に短時間で計算できるため、のベクトルをさらにフィルタリングできますB。例えばA = (0, 1, 0, 1, 1)B = (1, -1, ?, ?, ?)。最初のが? = 1であるため、A * cycled(B) > 0これらすべてのベクトルについて、ベクトルを無視できます。フォローしていただければ幸いです。それはおそらく最良の例ではありません。

これによりn = 15、6分で計算できます。

編集4:

すぐに黒井猫の素晴らしいアイデアを実装し、それは言う、それは同じ結果B-B生み出します。スピードアップx2。ただし、実装は簡単なハックにすぎません。n = 153分で。

コード:

完全なコードについては、Githubにアクセスしてください。次のコードは、主な機能の単なる表現です。インポート、マルチコアプログラミング、結果の印刷は省略しました...

count = [0] * n
count[0] = oeis_A081671(n)

#generating all important vector A
visited = set(); todo = dict()
for A in product((0, 1), repeat=n):
    if A not in visited:
        # generate all vectors, which have the same probability
        # mirrored and cycled vectors
        same_probability_set = set()
        for i in range(n):
            tmp = [A[(i+j) % n] for j in range(n)]
            same_probability_set.add(tuple(tmp))
            same_probability_set.add(tuple(tmp[::-1]))
        visited.update(same_probability_set)
        todo[A] = len(same_probability_set)

# for each vector A, create all possible vectors B
stack = []
for A, cycled_count in dict_A.iteritems():
    ones = [sum(A[i:]) for i in range(n)] + [0]
    # + [0], so that later ones[n] doesn't throw a exception
    stack.append(([0] * n, 0, 0, 0, False))

    while stack:
        B, index, sum1, sum2, used_negative = stack.pop()
        if index < n:
            # fill vector B[index] in all possible ways,
            # so that it's still possible to reach 0.
            if used_negative:
                for v in (-1, 0, 1):
                    sum1_new = sum1 + v * A[index]
                    sum2_new = sum2 + v * A[index - 1 if index else n - 1]
                    if abs(sum1_new) <= ones[index+1]:
                        if abs(sum2_new) <= ones[index] - A[n-1]:
                            C = B[:]
                            C[index] = v
                            stack.append((C, index + 1, sum1_new, sum2_new, True))
            else:
                for v in (0, 1):
                    sum1_new = sum1 + v * A[index]
                    sum2_new = sum2 + v * A[index - 1 if index else n - 1]
                    if abs(sum1_new) <= ones[index+1]:
                        if abs(sum2_new) <= ones[index] - A[n-1]:
                            C = B[:]
                            C[index] = v
                            stack.append((C, index + 1, sum1_new, sum2_new, v == 1))
        else:
            # B is complete, calculate the sums
            count[1] += cycled_count  # we know that the sum = 0 for i = 1
            for i in range(2, n):
                sum_prod = 0
                for j in range(n-i):
                    sum_prod += A[j] * B[i+j]
                for j in range(i):
                    sum_prod += A[n-i+j] * B[j]
                if sum_prod:
                    break
                else:
                    if used_negative:
                        count[i] += 2*cycled_count
                    else:
                        count[i] += cycled_count

使用法:

pypyをインストールする必要があります(Python 2の場合!!!)。並列pythonモジュールはPython 3用に移植されていません。その後、並列pythonモジュールpp-1.6.4.zipをインストールする必要があります。それをcdフォルダに展開し、を呼び出しますpypy setup.py install

次に、私のプログラムを呼び出すことができます

pypy you-do-the-math.py 15

CPUの数を自動的に決定します。プログラムの終了後にエラーメッセージが表示される場合がありますが、無視してください。n = 16あなたのマシンで可能になるはずです。

出力:

Calculation for n = 15 took 2:50 minutes

 1  83940771168 / 470184984576  17.85%
 2  17379109692 / 470184984576   3.70%
 3   3805906050 / 470184984576   0.81%
 4    887959110 / 470184984576   0.19%
 5    223260870 / 470184984576   0.05%
 6     67664580 / 470184984576   0.01%
 7     30019950 / 470184984576   0.01%
 8     20720730 / 470184984576   0.00%
 9     18352740 / 470184984576   0.00%
10     17730480 / 470184984576   0.00%
11     17566920 / 470184984576   0.00%
12     17521470 / 470184984576   0.00%
13     17510280 / 470184984576   0.00%
14     17507100 / 470184984576   0.00%
15     17506680 / 470184984576   0.00%

メモとアイデア:

  • 2つのコアと4つのスレッドを備えたi7-4600mプロセッサを使用しています。2スレッドまたは4スレッドを使用しても問題ありません。CPU使用率は2スレッドで50%、4スレッドで100%ですが、それでも同じ時間がかかります。理由はわかりません。4つのスレッドがある場合、各スレッドはデータの半分までしか持っていないことを確認し、結果を確認しました...
  • 私は多くのリストを使用します。Pythonは格納するのに非常に効率的ではありません。たくさんのリストをコピーする必要があります。ベクトルAのビット00(0の場合)および11(1の場合)、およびベクトルBのビット10(-1の場合)、00(0の場合)および01(1の場合)を使用できます。 AとBの場合A & B、01ブロックと10ブロックのみを計算してカウントする必要があります。サイクリングは、ベクトルをシフトし、マスクを使用することで実行できます。実際にこれをすべて実装しました。Githubでの古いコミットのいくつかで見つけることができます。しかし、リストよりも遅くなることが判明しました。pypyはリスト操作を本当に最適化していると思います。

私のPCでは、n = 12の実行に7:25かかりますが、C ++のジャンクには約1:23かかり、約5倍高速になります。真のコアが2つしかないため、CPUはモノスレッドアプリケーションと比較して2.5倍のようなものになるので、真の8コアCPUは3倍の速度で動作するはずです。私の老化i3-2100。ただし、これらのC ++フープをすべて実行して、指数関数的に増加する計算時間に取り組むかどうかは、議論の余地があります。

codegolf.stackexchange.com/questions/41021/…の感覚を得ています…... de Bruijnシーケンスは役に立ちますか?
ケニー14

マルチスレッドについては、各スレッドを1つにロックすることで、2 + 2コアをもう少し絞ることができます。x2ゲインは、システム内でマッチスティックが移動するたびにスケジューラがスレッドを移動するためです。コアロックを使用すると、代わりに2.5倍のゲインが得られる可能性があります。ただし、Pythonでプロセッサアフィニティを設定できるかどうかはわかりません。

おかげで、私はそれを調べます。しかし、私はマルチスレッドの初心者です。
寂部14

nbviewer.ipython.org/gist/minrk/5500077には、並列処理に別のツールを使用しているにも関わらず、これに関するいくつかの言及があります。

5

羊毛いじめっ子-C ++-遅すぎる

優れたプログラマーがC ++の実装を引き受けたので、私はこの実装の終了を呼び出しています。

#include <cstdlib>
#include <cmath>
#include <vector>
#include <bitset>
#include <future>
#include <iostream>
#include <iomanip>

using namespace std;

/*
6^^n events will be generated, so the absolute max
that can be counted by a b bits integer is
E(b*log(2)/log(6)), i.e. n=24 for a 64 bits counter

To enumerate 3 possible values of a size n vector we need
E(n*log(3)/log(2))+1 bits, i.e. 39 bits
*/
typedef unsigned long long Counter; // counts up to 6^^24

typedef unsigned long long Benumerator; // 39 bits
typedef unsigned long      Aenumerator; // 24 bits

#define log2_over_log6 0.3869

#define A_LENGTH ((size_t)(8*sizeof(Counter)*log2_over_log6))
#define B_LENGTH (2*A_LENGTH)

typedef bitset<B_LENGTH> vectorB;

typedef vector<Counter> OccurenceCounters;

// -----------------------------------------------------------------
// multithreading junk for CPUs detection and allocation
// -----------------------------------------------------------------
int number_of_CPUs(void)
{
    int res = thread::hardware_concurrency();
    return res == 0 ? 8 : res;
}

#ifdef __linux__
#include <sched.h>
void lock_on_CPU(int cpu)
{
    cpu_set_t mask;
    CPU_ZERO(&mask);
    CPU_SET(cpu, &mask);
    sched_setaffinity(0, sizeof(mask), &mask);
}
#elif defined (_WIN32)
#include <Windows.h>
#define lock_on_CPU(cpu) SetThreadAffinityMask(GetCurrentThread(), 1 << cpu)
#else
// #warning is not really standard, so this might still cause compiler errors on some platforms. Sorry about that.
#warning "Thread processor affinity settings not supported. Performances might be improved by providing a suitable alternative for your platform"
#define lock_on_CPU(cpu)
#endif

// -----------------------------------------------------------------
// B values generator
// -----------------------------------------------------------------
struct Bvalue {
    vectorB p1;
    vectorB m1;
};

struct Bgenerator {
    int n;                 // A length
    Aenumerator stop;      // computation limit
    Aenumerator zeroes;    // current zeroes pattern
    Aenumerator plusminus; // current +1/-1 pattern
    Aenumerator pm_limit;  // upper bound of +1/-1 pattern

    Bgenerator(int n, Aenumerator start=0, Aenumerator stop=0) : n(n), stop(stop)
    {
        // initialize generator so that first call to next() will generate first value
        zeroes    = start - 1;
        plusminus = -1;
        pm_limit  = 0;
    }

    // compute current B value
    Bvalue value(void)
    {
        Bvalue res;
        Aenumerator pm = plusminus;
        Aenumerator position = 1;
        int i_pm = 0;
        for (int i = 0; i != n; i++)
        {
            if (zeroes & position)
            {
                if (i_pm == 0)  res.p1 |= position; // first non-zero value fixed to +1
                else         
                {
                    if (pm & 1) res.m1 |= position; // next non-zero values
                    else        res.p1 |= position;
                    pm >>= 1;
                }
                i_pm++;
            }
            position <<= 1;
        }
        res.p1 |= (res.p1 << n); // concatenate 2 Bpre instances
        res.m1 |= (res.m1 << n);
        return res;
    }

    // next value
    bool next(void)
    {
        if (++plusminus == pm_limit)
        {
            if (++zeroes == stop) return false;
            plusminus = 0;
            pm_limit = (1 << vectorB(zeroes).count()) >> 1;
        }
        return true;
    }

    // calibration: produces ranges that will yield the approximate same number of B values
    vector<Aenumerator> calibrate(int segments)
    {
        // setup generator for the whole B range
        zeroes = 0;
        stop = 1 << n;
        plusminus = -1;
        pm_limit = 0;

        // divide range into (nearly) equal chunks
        Aenumerator chunk_size = ((Aenumerator)pow (3,n)-1) / 2 / segments;

        // generate bounds for zeroes values
        vector<Aenumerator> res(segments + 1);
        int bound = 0;
        res[bound] = 1;
        Aenumerator count = 0;
        while (next()) if (++count % chunk_size == 0) res[++bound] = zeroes;
        res[bound] = stop;
        return res;
    }
};

// -----------------------------------------------------------------
// equiprobable A values merging
// -----------------------------------------------------------------
static char A_weight[1 << A_LENGTH];
struct Agroup {
    vectorB value;
    int     count;
    Agroup(Aenumerator a = 0, int length = 0) : value(a), count(length) {}
};
static vector<Agroup> A_groups;

Aenumerator reverse(Aenumerator n) // this works on N-1 bits for a N bits word
{
    Aenumerator res = 0;
    if (n != 0) // must have at least one bit set for the rest to work
    {
        // construct left-padded reverse value
        for (int i = 0; i != 8 * sizeof(n)-1; i++)
        {
            res |= (n & 1);
            res <<= 1;
            n >>= 1;
        }

        // shift right to elimitate trailing zeroes
        while (!(res & 1)) res >>= 1;
    }
    return res;
}

void generate_A_groups(int n)
{
    static bitset<1 << A_LENGTH> lookup(0);
    Aenumerator limit_A = (Aenumerator)pow(2, n);
    Aenumerator overflow = 1 << n;
    for (char & w : A_weight) w = 0;

    // gather rotation cycles
    for (Aenumerator a = 0; a != limit_A; a++)
    {
        Aenumerator rotated = a;
        int cycle_length = 0;
        for (int i = 0; i != n; i++)
        {
            // check for new cycles
            if (!lookup[rotated])
            {
                cycle_length++;
                lookup[rotated] = 1;
            }

            // rotate current value
            rotated <<= 1;
            if (rotated & overflow) rotated |= 1;
            rotated &= (overflow - 1);
        }

        // store new cycle
        if (cycle_length > 0) A_weight[a] = cycle_length;
    }

    // merge symetric groups
    for (Aenumerator a = 0; a != limit_A; a++)
    {
        // skip already grouped values
        if (A_weight[a] == 0) continue;

        // regroup a symetric pair
        Aenumerator r = reverse(a);
        if (r != a)
        {
            A_weight[a] += A_weight[r];
            A_weight[r] = 0;
        }  
    }

    // generate groups
    for (Aenumerator a = 0; a != limit_A; a++)
    {
        if (A_weight[a] != 0) A_groups.push_back(Agroup(a, A_weight[a]));
    }
}

// -----------------------------------------------------------------
// worker thread
// -----------------------------------------------------------------
OccurenceCounters solve(int n, int index, Aenumerator Bstart, Aenumerator Bstop)
{
    OccurenceCounters consecutive_zero_Z(n, 0);  // counts occurences of the first i terms of Z being 0

    // lock on assigned CPU
    lock_on_CPU(index);

    // enumerate B vectors
    Bgenerator Bgen(n, Bstart, Bstop);
    while (Bgen.next())
    {
        // get next B value
        Bvalue B = Bgen.value();

        // enumerate A vector groups
        for (const auto & group : A_groups)
        {
            // count consecutive occurences of inner product equal to zero
            vectorB sliding_A(group.value);
            for (int i = 0; i != n; i++)
            {
                if ((sliding_A & B.p1).count() != (sliding_A & B.m1).count()) break;
                consecutive_zero_Z[i] += group.count;
                sliding_A <<= 1;
            }
        }
    }
    return consecutive_zero_Z;
}

// -----------------------------------------------------------------
// main
// -----------------------------------------------------------------
#define die(msg) { cout << msg << endl; exit (-1); }

int main(int argc, char * argv[])
{
    int n = argc == 2 ? atoi(argv[1]) : 16; // arbitray value for debugging
    if (n < 1 || n > 24) die("vectors of lenght between 1 and 24 is all I can (try to) compute, guv");

    auto begin = time(NULL);

    // one worker thread per CPU
    int num_workers = number_of_CPUs();

    // regroup equiprobable A values
    generate_A_groups(n);

    // compute B generation ranges for proper load balancing
    vector<Aenumerator> ranges = Bgenerator(n).calibrate(num_workers);

    // set workers to work
    vector<future<OccurenceCounters>> workers(num_workers);
    for (int i = 0; i != num_workers; i++)
    {
        workers[i] = async(
            launch::async, // without this parameter, C++ will decide whether execution shall be sequential or asynchronous (isn't C++ fun?).
            solve, n, i, ranges[i], ranges[i+1]); 
    }

    // collect results
    OccurenceCounters result(n + 1, 0);
    for (auto& worker : workers)
    {
        OccurenceCounters partial = worker.get();
        for (size_t i = 0; i != partial.size(); i++) result[i] += partial[i]*2; // each result counts for a symetric B pair
    }
    for (Counter & res : result) res += (Counter)1 << n; // add null B vector contribution
    result[n] = result[n - 1];                           // the last two probabilities are equal by construction

    auto duration = time(NULL) - begin;

    // output
    cout << "done in " << duration / 60 << ":" << setw(2) << setfill('0') << duration % 60 << setfill(' ')
        << " by " << num_workers << " worker thread" << ((num_workers > 1) ? "s" : "") << endl;
    Counter events = (Counter)pow(6, n);
    int width = (int)log10(events) + 2;
    cout.precision(5);
    for (int i = 0; i <= n; i++) cout << setw(2) << i << setw(width) << result[i] << " / " << events << " " << fixed << (float)result[i] / events << endl;

    return 0;
}

実行可能ファイルの構築

警告なしでコンパイルされ、以下でスムーズに実行されるスタンドアロンのC ++ 11ソースです。

  • Win7およびMSVC2013
  • Win7およびMinGW-g ++ 4.7
  • Ubuntuおよびg ++ 4.8(2つのCPUが割り当てられたVirtualBox VM内)

g ++でコンパイルする場合は、次を使用します。g ++ -O3 -pthread -std = c ++ 11を
忘れる-pthreadと、素晴らしくフレンドリーなコアダンプが生成されます。

最適化

  1. 最後のZ項は最初の項(両方の場合でBpre x A)に等しいため、最後の2つの結果は常に等しく、最後のZ値の計算は不要です。
    ゲインは無視できますが、コーディングに費用はかかりません。

  2. ジャクベが発見したように、与えられたAベクトルのすべての巡回値は同じ確率を生成します。
    これらをAの単一インスタンスで計算し、結果に可能な回転数を掛けることができます。ローテーショングループは、無視できる時間内で簡単に事前計算できるため、これは非常に大きな正味速度の向上です。
    長さnのベクトルの順列の数はn-1であるため、複雑さはo(6 n)からo(6 n /(n-1))に低下し、基本的に同じ計算時間でさらに一歩進みます。

  3. 対称パターンのペアも同じ確率を生成するようです。たとえば、100101および101001。
    その数学的証明はありませんが、すべての可能なBパターンが提示されると、各対称A値は同じグローバル結果の対応する対称B値と畳み込まれます。
    これにより、Aグループ数を約30%削減するために、さらにいくつかのAベクトルを再グループ化できます。

  4. 誤った 理由がいくつかあるため、1ビットまたは2ビットのみが設定されているすべてのパターンで同じ結果が得られます。これは、多くの個別のグループを表すものではありませんが、それでも実質的に無料でマージできます。

  5. ベクトルBおよび-B(すべてのコンポーネントに-1を掛けたB)は、同じ確率を生成します。
    (たとえば、[1、0、-1、1]および[-1、0、1、-1])。
    ヌルベクトル(すべてのコンポーネントが0に等しい)を除き、Bと-Bは別個のベクトルのペアを形成します。
    これにより、各ペアの1つだけを考慮し、その寄与に2を掛けることでB値の数を半分に削減できます。nullBの既知のグローバル寄与を各確率に1回だけ追加します。

使い方

B値の数は膨大(3 n)であるため、それらを事前に計算するには不当な量のメモリが必要になり、計算が遅くなり、最終的に使用可能なRAMを使い果たします。
残念ながら、最適化されたB値の半分のセットを列挙する簡単な方法を見つけることができなかったため、専用のジェネレーターのコーディングに頼りました。

強力なBジェネレーターは、コードを作成するのがとても楽しかったのですが、yieldメカニズムをサポートする言語であれば、はるかに洗練された方法でプログラミングできます。
簡単に言うと、Bpreベクトルの「スケルトン」は、1が実際の-1または+1値を表すバイナリベクトルと見なされます。
これらすべての+ 1 / -1ポテンシャル値のうち、最初の値は+1に固定され(したがって、可能なB / -Bベクトルの1つが選択されます)、残りのすべての可能な+ 1 / -1の組み合わせが列挙されます。
最後に、シンプルなキャリブレーションシステムにより、各ワーカースレッドがほぼ同じサイズの値の範囲を処理することが保証されます。

等確率のチャンクに再グループ化するために、値が大きくフィルタリングされます。
これは、ブルートフォースがすべての可能な値を調べる事前計算フェーズで行われます。
この部分には無視できるほどのO(2 n)実行時間があり、最適化する必要はありません(コードは既に十分に読み込めない!)。

内積(ゼロに対してのみテストする必要がある)を評価するために、Bの-1および1成分はバイナリベクトルに再グループ化されます。
非ゼロのA値に対応するB値の間に等しい数の+1と-1がある場合(およびその場合のみ)、内積はNULLです。
これは、単純なマスキングとビットカウント操作で計算できます。これによりstd::bitset、い組み込み命令に頼ることなく、合理的に効率的なビットカウントコードが生成されます。

作業は、CPUアフィニティを強制的に使用してコア間で均等に分割されます(少しでも役立つ、またはそう言われます)。

結果の例

C:\Dev\PHP\_StackOverflow\C++\VectorCrunch>release\VectorCrunch.exe 16
done in 8:19 by 4 worker threads
 0  487610895942 / 2821109907456 0.17284
 1   97652126058 / 2821109907456 0.03461
 2   20659337010 / 2821109907456 0.00732
 3    4631534490 / 2821109907456 0.00164
 4    1099762394 / 2821109907456 0.00039
 5     302001914 / 2821109907456 0.00011
 6     115084858 / 2821109907456 0.00004
 7      70235786 / 2821109907456 0.00002
 8      59121706 / 2821109907456 0.00002
 9      56384426 / 2821109907456 0.00002
10      55686922 / 2821109907456 0.00002
11      55508202 / 2821109907456 0.00002
12      55461994 / 2821109907456 0.00002
13      55451146 / 2821109907456 0.00002
14      55449098 / 2821109907456 0.00002
15      55449002 / 2821109907456 0.00002
16      55449002 / 2821109907456 0.00002

公演

マルチスレッドはこれで完全に動作するはずですが、「真の」コアのみが計算速度に完全に貢献します。私のCPUには4つのCPUに対して2つのコアしかなく、シングルスレッドバージョンを超えるゲインは約3.5だけです。

コンパイラー

マルチスレッドの最初の問題から、GNUコンパイラのパフォーマンスはマイクロソフトよりも劣っていると思いました。

さらに徹底的なテストを行った結果、g ++が再び勝利を収め、約30%高速なコードが生成されたようです(他の2つの計算量の多いプロジェクトで気付いたのと同じ比率)。

特に、std::bitsetライブラリはg ++ 4.8によって専用のビットカウント命令で実装されますが、MSVC 2013は従来のビットシフトのループのみを使用します。

予想どおり、32ビットまたは64ビットでコンパイルしても違いはありません。

さらなる改良

すべてのリダクション操作の後に同じ確率を生成するいくつかのAグループに気付きましたが、それらを再グループ化できるパターンを特定できませんでした。

以下は、n = 111で気づいたペアです。

  10001011 and 10001101
 100101011 and 100110101
 100101111 and 100111101
 100110111 and 100111011
 101001011 and 101001101
 101011011 and 101101011
 101100111 and 110100111
1010110111 and 1010111011
1011011111 and 1011111011
1011101111 and 1011110111

最後の2つの確率は常に同じであると思います。これは、n + 1番目の内積が実際には最初のものと同じだからです。

つまり、最初のn + 1がそうである場合にのみ、最初のn個の内積がゼロになるということです。これまでに行ったように、最後の内積は新しい情報を提供しません。したがって、n個のゼロ積を与える文字列の数は、n + 1個のゼロ積を与える数とまったく同じです。

興味深いことに、代わりに正確に何を計算していましたか?

更新いただきありがとうございますが、「0 2160009216 2176782336」という行がわかりません。この場合、正確に何を数えていますか?最初の内積がゼロになる確率は、それよりはるかに小さくなります。

これをコンパイルして実行する方法についてアドバイスをいただけますか?Iは、G ++ -Wall -std = C ++ 11 kuroineko.cpp -o kuroinekoを試み、12 ./kuroinekoそれが与えるterminate called after throwing an instance of 'std::system_error' what(): Unknown error -1 Aborted (core dumped)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.