컴퓨터 : 당신은 수학을


13

이 과제는 부분적으로 알고리즘 과제이며 일부 수학과 관련이 있으며 부분적으로 가장 빠른 코드 과제입니다.

양의 정수의 경우 n, 길이 가 일정한 임의의 1s 및 0s 문자열을 고려하여 n호출하십시오 A. 이제도 길이의 제 균일 선택된 임의의 문자열을 고려 n하는 값이다를 -1, 0,또는 1이를 호출 B_pre. 지금 할 BB_pre+ B_pre. 그것은 그 B_pre자체 로 연결되어 있습니다.

지금의 내적을 고려 A하고 B[j,...,j+n-1]있으며 전화 Z_j에서 인덱스를 1.

직무

출력은 n+1분수 목록이어야합니다 . i출력의 번째 항은되어야 정확한 확률이 모든 제의 i조건 Z_jj <= i동일 0.

점수

n내 컴퓨터에서 10 분 안에 코드가 올바른 출력을 제공하는 최대 크기 입니다.

타이 브레이커

두 답변의 점수가 같으면 제출 된 답변이 먼저 이깁니다.

누군가가 무제한 점수를 얻는 방법을 찾은 경우는 매우 드물지만 그러한 해결책에 대한 첫 번째 유효한 증거가 인정됩니다.

힌트

이 문제를 수학적으로 해결하려고하지 마십시오. 너무 어렵습니다. 내 견해에서 가장 좋은 방법은 고등학교의 확률에 대한 기본 정의로 돌아가 코드가 가능성을 철저히 열거하는 현명한 방법을 찾는 것입니다.

언어와 라이브러리

자유롭게 사용할 수있는 컴파일러 / 인터프리터 등이있는 모든 언어를 사용할 수 있습니다. Linux 및 Linux 용으로 무료로 제공되는 모든 라이브러리 용.

내 컴퓨터 타이밍이 내 컴퓨터에서 실행됩니다. 이것은 AMD FX-8350 8 코어 프로세서에 표준 우분투 설치입니다. 이것은 또한 코드를 실행할 수 있어야 함을 의미합니다. 따라서 쉽게 구할 수있는 무료 소프트웨어 만 사용하고 코드를 컴파일하고 실행하는 방법에 대한 전체 지침을 포함하십시오.


일부 테스트 출력. 각각에 대한 첫 번째 출력 만 고려하십시오 n. 그때입니다 i=1. 들어 n그들이해야 1 ~ 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

http://oeis.org/A081671i=1 에서 일반 공식을 찾을 수도 있습니다 .

리더 보드 (언어별로 분리)

  • N = 15 파이썬 + 평행 파이썬 + pypy Jakube에 의해 1min49s
  • n = 17. Keith Randall의 3min37s C ++
  • n = 16. kuroi neko의 2min38s C ++

1
@Knerd 어떻게 거절 할 수 있습니까? 리눅스에서 코드를 실행하는 방법을 알아 내려고 노력하지만 많은 도움을 주시면 감사하겠습니다.

좋아요, 댓글을 삭제하여 죄송합니다. 읽지 않은 모든 내용은 F # 또는 C #이 허용되는 경우입니다. :
Knerd

또 다른 질문은 유효한 입력 출력의 예를 가지고 있습니까?
Knerd

당신의 그래픽 카드는 무엇입니까? GPU 작업처럼 보입니다.
Michael M.

1
@ Knerd 대신 질문에 확률 표를 추가했습니다. 도움이 되길 바랍니다.

답변:


5

C ++, 8 개 스레드에서 9 분 동안 n = 18

(귀하의 컴퓨터에서 10 분 이내에 작동하는지 알려주십시오.)

B 배열에서 여러 형태의 대칭을 활용합니다. 그것들은 주기적 (한 위치 씩 이동), 반전 (요소의 순서를 반대로) 및 부호 (각 요소의 음수를 취)입니다. 먼저 시도해야 할 B 목록과 가중치를 계산합니다. 그런 다음 각 2 ^ n 값 A에 대해 각 B를 빠른 루틴 (비트 카운트 명령 사용)을 통해 실행합니다.

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 : 문제 없습니다.
Keith Randall

6

pypy 및 pp를 사용하는 Python 2 : 3 분 안에 n = 15

또한 단순한 무차별 대입. 흥미롭게도 C ++에서 쿠 로이 네코와 거의 같은 속도를 얻습니다. 내 코드는 n = 12약 5 분 안에 도달 할 수 있습니다 . 그리고 하나의 가상 코어에서만 실행합니다.

편집 : 요인으로 검색 공간을 줄입니다 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] += 1와 함께 count[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 repo : Link 에서 사용 가능 합니다. 멀티 코어 프로그래밍은 약간 추악합니다.

편집 3 : A벡터 및 B벡터의 검색 공간 줄이기

Akuroi neko와 같은 벡터에 대해 동일한 대칭 대칭을 발견했습니다 . 아직도 이것이 왜 작동하는지 (그리고 그것이 각각 작동하는 경우) 확실하지 않습니다 n.

B벡터 의 검색 공간을 줄이는 것이 약간 더 영리합니다. 벡터 생성 ( itertools.product)을 자체 함수 로 대체했습니다 . 기본적으로 빈 목록으로 시작하여 스택에 넣습니다. 스택이 비어있을 때까지 목록과 동일한 길이가 아닌 경우 목록을 제거하고 n(-1, 0, 1을 추가하여) 3 개의 다른 목록을 생성하고 스택에 밀어 넣습니다. 목록의 길이가과 (와 n) 같 으며 합계를 평가할 수 있습니다.

이제 벡터를 직접 생성 했으므로 sum = 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)) / n. i = 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 개의 스레드가있을 때 각 스레드가 절반의 데이터 양만 가지고 있는지 확인하고 결과를 확인했습니다 ...
  • 나는 많은 목록을 사용합니다. 파이썬은 저장하는 데 효율적이지 않으므로 많은 목록을 복사해야합니다 ... 그래서 정수를 대신 사용하려고 생각했습니다. 벡터 A에서 비트 00 (0의 경우) 및 11 (1의 경우)을 사용하고 벡터 B의 비트 10 (-1의 경우), 00 (0의 경우) 및 01 (1의 경우)을 사용할 수 있습니다. A와 B의 경우, A & B01과 10 블록 만 계산 하고 계산하면됩니다. 사이클링은 벡터를 이동하고 마스크를 사용하여 수행 할 수 있습니다 ... 실제로이 모든 것을 구현했습니다 .Github의 이전 커밋 중 일부에서 찾을 수 있습니다. 그러나 목록보다 느립니다. pypy는 실제로 목록 작업을 최적화합니다.

내 PC에서 n = 12 실행에는 7:25가 걸리고 C ++ 정크는 약 1:23이 걸리므로 약 5 배 빠릅니다. 실제 코어가 두 개이면 내 CPU는 모노 스레드 응용 프로그램과 비교하여 2.5 배 정도의 성능을 얻을 수 있으므로 실제 8 코어 CPU는 3 배 더 빠르게 실행되어야하며 기본 모노 코어 속도 향상으로 계산되지 않습니다. 내 노화 i3-2100. 그러나 기하 급수적으로 증가하는 계산 시간을 해결하기 위해 이러한 모든 C ++ 후프를 통과하는지 여부는 노력의 가치가 있습니다.

codegolf.stackexchange.com/questions/41021/… 의 느낌이 들었습니다 ... ... Bruijn 시퀀스가 ​​유용합니까?
kennytm

멀티 스레딩에 대해서는 각 스레드를 하나로 고정하여 2 + 2 코어를 조금 더 짜낼 수 있습니다. x2 게인은 시스템에서 성냥개비를 움직일 때마다 스케줄러가 스레드 주위를 이동하기 때문입니다. 코어 잠금을 사용하면 아마도 x2.5 이득을 얻게 될 것입니다. 그러나 파이썬이 프로세서 선호도를 설정할 수 있는지는 알 수 없습니다.

고마워, 내가 조사 할게 그러나 나는 멀티 스레딩의 초보자입니다.
Jakube

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
  • 우분투 및 g ++ 4.8 (CPU 2 개가 할당 된 VirtualBox VM에서)

g ++로 컴파일하는 경우 다음을 사용하십시오. g ++ -O3 -pthread -std = c ++ 11
잊어 -pthread버리면 멋지고 친근한 코어 덤프가 생성됩니다.

최적화

  1. 마지막 Z 항은 첫 번째 항과 같으므로 (두 경우 모두 Bpre x A) 마지막 두 결과는 항상 동일하므로 마지막 Z 값을 계산하지 않아도됩니다.
    게인은 무시할 수 없지만 코딩하는 데 비용이 들지 않으므로 사용할 수도 있습니다.

  2. Jakube에서 알 수 있듯이 주어진 A 벡터의 모든 주기적 값은 동일한 확률을 생성합니다.
    A의 단일 인스턴스로이를 계산하고 결과에 가능한 회전 수를 곱할 수 있습니다. 무시할 수있는 시간 안에 회전 그룹을 쉽게 사전 계산할 수 있으므로 이는 순 속도가 크게 향상됩니다.
    n 길이 벡터의 순열 수는 n-1이므로 복잡도는 o (6 n )에서 o (6 n / (n-1))으로 떨어지며 기본적으로 동일한 계산 시간 동안 한 단계 더 나아갑니다.

  3. 대칭 패턴 쌍도 동일한 확률을 생성합니다. 예를 들어 100101과 101001입니다. 그에
    대한 수학적 증거는 없지만 가능한 모든 B 패턴을 제시 할 때 직관적으로 각 대칭 A 값은 동일한 전역 결과에 대해 해당 대칭 B 값 과 뒤얽 힙니다 .
    이를 통해 A 그룹 수를 약 30 % 줄이려면 더 많은 A 벡터를 다시 그룹화 할 수 있습니다.

  4. 잘못된 약간의 신비한 이유로, 하나 또는 두 개의 비트 세트 만있는 모든 패턴은 동일한 결과를 생성합니다. 이것은 많은 별개의 그룹을 나타내지는 않지만 여전히 사실상 무료로 병합 될 수 있습니다.

  5. 벡터 B와 -B (모든 성분에 -1을 곱한 B)는 동일한 확률을 생성합니다.
    (예 : [1, 0, -1, 1] 및 [-1, 0, 1, -1]).
    널 벡터 (모든 성분이 0과 같음)를 제외하고 B와 -B는 한 쌍의 별개의 벡터를 형성합니다 .
    이를 통해 각 쌍 중 하나만 고려하고 기여도에 2를 곱하여 알려진 확률로 널 B의 전역 기여도를 각 확률에 한 번만 추가하여 B 값 수를 절반으로 줄일 수 있습니다.

작동 원리

B 값의 개수는 엄청 나기 때문에 (3 n ) 미리 계산하기 위해서는 메모리 용량이 부족하여 계산 속도가 느려지고 사용 가능한 RAM이 소진됩니다.
불행히도, 최적화 된 B 값의 절반 세트를 열거하는 간단한 방법을 찾을 수 없으므로 전용 발전기를 코딩했습니다.

수율 메커니즘을 지원하는 언어가 훨씬 더 우아한 방식으로 프로그래밍 할 수 있었지만 강력한 B 생성기는 코딩하기가 매우 재미있었습니다.
간단히 말해 Bpre 벡터의 "골격"은 1이 실제 -1 또는 +1 값을 나타내는 이진 벡터로 간주됩니다.
이러한 모든 + 1 / -1 전위 값 중에서 첫 번째 값은 +1로 고정되므로 (가능한 B / B 벡터 중 하나를 선택) 나머지 가능한 모든 + 1 / -1 조합이 열거됩니다.
마지막으로, 간단한 교정 시스템은 각 작업자 스레드가 대략 동일한 크기의 값 범위를 처리하도록합니다.

등가 청크로 다시 그룹화하기 위해 값이 크게 필터링됩니다.
이것은 무차별 대입이 가능한 모든 값을 검사하는 사전 계산 단계에서 수행됩니다.
이 부분은 무시할 수있는 O (2 n ) 실행 시간을 가지며 최적화 할 필요가 없습니다 (코드는 이미 읽을 수없는 상태입니다).

내부 제품 (0에 대해서만 테스트해야 함)을 평가하기 위해 B의 -1 및 1 구성 요소가 이진 벡터로 다시 그룹화됩니다.
0이 아닌 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 컴파일러가 Microsoft보다 성능이 좋지 않다고 생각했습니다.

좀 더 철저한 테스트를 거친 후 g ++이 또 다시 승리하여 약 30 % 더 빠른 코드를 생성합니다 (2 개의 다른 계산이 많은 프로젝트에서 알아 낸 것과 동일한 비율).

특히이 std::bitset라이브러리는 g ++ 4.8의 전용 비트 카운트 명령으로 구현되는 반면 MSVC 2013은 기존의 비트 시프트 루프 만 사용합니다.

예상했듯이 32 비트 또는 64 비트로 컴파일해도 차이가 없습니다.

추가 개선

모든 축소 작업 후에 동일한 확률을 생성하는 일부 A 그룹을 발견했지만 다시 그룹화 할 수있는 패턴을 식별 할 수 없었습니다.

n = 11에 대해 주목 한 쌍은 다음과 같습니다.

  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

마지막 두 확률은 항상 같아야한다고 생각합니다. 이는 n + 1 번째 내부 제품이 실제로 첫 번째 제품과 동일하기 때문입니다.

내가 의미하는 바는 첫 번째 n + 1 인 경우에만 첫 번째 n 개의 내부 제품이 0이라는 것입니다. 가장 최근의 내부 제품은 이전에 이미했던 것처럼 새로운 정보를 제공하지 않습니다. 따라서 n 제로 곱을 제공하는 문자열의 수는 n + 1 제로 곱을 제공하는 수와 정확히 동일합니다.

관심이 없다면, 정확히 무엇을 계산 했습니까?

업데이트 해 주셔서 감사하지만 "0 2160009216 2176782336"줄을 이해하지 못합니다. 이 경우 정확히 무엇을 세고 있습니까? 첫 번째 내부 곱이 0 일 확률은 그보다 훨씬 작습니다.

이것을 컴파일하고 실행하는 방법에 대한 조언을 줄 수 있습니까? 나는 g ++ -Wall -std = c ++ 11 kuroineko.cpp -o kuroineko and ./kuroineko 12를 시도했지만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.