C ++ bit magic
0.84ms dengan RNG sederhana, 1.67ms dengan c ++ 11 std :: knuth
0.16ms dengan sedikit modifikasi algoritmik (lihat edit di bawah)
Implementasi python berjalan dalam 7,97 detik di rig saya. Jadi ini 9488 hingga 4772 kali lebih cepat tergantung pada RNG apa yang Anda pilih.
#include <iostream>
#include <bitset>
#include <random>
#include <chrono>
#include <stdint.h>
#include <cassert>
#include <tuple>
#if 0
// C++11 random
std::random_device rd;
std::knuth_b gen(rd());
uint32_t genRandom()
{
return gen();
}
#else
// bad, fast, random.
uint32_t genRandom()
{
static uint32_t seed = std::random_device()();
auto oldSeed = seed;
seed = seed*1664525UL + 1013904223UL; // numerical recipes, 32 bit
return oldSeed;
}
#endif
#ifdef _MSC_VER
uint32_t popcnt( uint32_t x ){ return _mm_popcnt_u32(x); }
#else
uint32_t popcnt( uint32_t x ){ return __builtin_popcount(x); }
#endif
std::pair<unsigned, unsigned> convolve()
{
const uint32_t n = 6;
const uint32_t iters = 1000;
unsigned firstZero = 0;
unsigned bothZero = 0;
uint32_t S = (1 << (n+1));
// generate all possible N+1 bit strings
// 1 = +1
// 0 = -1
while ( S-- )
{
uint32_t s1 = S % ( 1 << n );
uint32_t s2 = (S >> 1) % ( 1 << n );
uint32_t fmask = (1 << n) -1; fmask |= fmask << 16;
static_assert( n < 16, "packing of F fails when n > 16.");
for( unsigned i = 0; i < iters; i++ )
{
// generate random bit mess
uint32_t F;
do {
F = genRandom() & fmask;
} while ( 0 == ((F % (1 << n)) ^ (F >> 16 )) );
// Assume F is an array with interleaved elements such that F[0] || F[16] is one element
// here MSB(F) & ~LSB(F) returns 1 for all elements that are positive
// and ~MSB(F) & LSB(F) returns 1 for all elements that are negative
// this results in the distribution ( -1, 0, 0, 1 )
// to ease calculations we generate r = LSB(F) and l = MSB(F)
uint32_t r = F % ( 1 << n );
// modulo is required because the behaviour of the leftmost bit is implementation defined
uint32_t l = ( F >> 16 ) % ( 1 << n );
uint32_t posBits = l & ~r;
uint32_t negBits = ~l & r;
assert( (posBits & negBits) == 0 );
// calculate which bits in the expression S * F evaluate to +1
unsigned firstPosBits = ((s1 & posBits) | (~s1 & negBits));
// idem for -1
unsigned firstNegBits = ((~s1 & posBits) | (s1 & negBits));
if ( popcnt( firstPosBits ) == popcnt( firstNegBits ) )
{
firstZero++;
unsigned secondPosBits = ((s2 & posBits) | (~s2 & negBits));
unsigned secondNegBits = ((~s2 & posBits) | (s2 & negBits));
if ( popcnt( secondPosBits ) == popcnt( secondNegBits ) )
{
bothZero++;
}
}
}
}
return std::make_pair(firstZero, bothZero);
}
int main()
{
typedef std::chrono::high_resolution_clock clock;
int rounds = 1000;
std::vector< std::pair<unsigned, unsigned> > out(rounds);
// do 100 rounds to get the cpu up to speed..
for( int i = 0; i < 10000; i++ )
{
convolve();
}
auto start = clock::now();
for( int i = 0; i < rounds; i++ )
{
out[i] = convolve();
}
auto end = clock::now();
double seconds = std::chrono::duration_cast< std::chrono::microseconds >( end - start ).count() / 1000000.0;
#if 0
for( auto pair : out )
std::cout << pair.first << ", " << pair.second << std::endl;
#endif
std::cout << seconds/rounds*1000 << " msec/round" << std::endl;
return 0;
}
Kompilasi dalam 64-bit untuk register tambahan. Saat menggunakan generator acak sederhana, loop di convolve () berjalan tanpa akses memori apa pun, semua variabel disimpan dalam register.
Cara kerjanya: alih-alih menyimpan S
dan F
sebagai array dalam memori, ia disimpan sebagai bit dalam uint32_t.
Untuk S
, n
bit paling signifikan digunakan di mana bit set menunjukkan +1 dan bit unset menunjukkan -1.
F
membutuhkan setidaknya 2 bit untuk membuat distribusi [-1, 0, 0, 1]. Ini dilakukan dengan menghasilkan bit acak dan memeriksa 16 bit paling signifikan (disebut r
) dan 16 bit paling signifikan (disebut l
). Jika l & ~r
kita menganggap bahwa F adalah +1, jika ~l & r
kita menganggap itu F
-1. Kalau F
tidak 0. Ini menghasilkan distribusi yang kita cari.
Sekarang kita miliki S
, posBits
dengan set bit pada setiap lokasi di mana F == 1 dan negBits
dengan bit set pada setiap lokasi di mana F == -1.
Kami dapat membuktikan bahwa F * S
(di mana * menunjukkan perkalian) mengevaluasi ke +1 dalam kondisi tersebut (S & posBits) | (~S & negBits)
. Kami juga dapat membuat logika yang sama untuk semua kasus yang F * S
dievaluasi menjadi -1. Dan akhirnya, kita tahu bahwa sum(F * S)
mengevaluasi ke 0 jika dan hanya jika ada jumlah yang sama dengan -1 dan +1 di hasilnya. Ini sangat mudah untuk dihitung hanya dengan membandingkan jumlah +1 bit dan -1 bit.
Implementasi ini menggunakan 32 bit int, dan maksimum yang n
diterima adalah 16. Dimungkinkan untuk menskalakan implementasi hingga 31 bit dengan memodifikasi kode menghasilkan acak, dan menjadi 63 bit dengan menggunakan uint64_t alih-alih uint32_t.
sunting
Fungsi berbelit-belit berikut:
std::pair<unsigned, unsigned> convolve()
{
const uint32_t n = 6;
const uint32_t iters = 1000;
unsigned firstZero = 0;
unsigned bothZero = 0;
uint32_t fmask = (1 << n) -1; fmask |= fmask << 16;
static_assert( n < 16, "packing of F fails when n > 16.");
for( unsigned i = 0; i < iters; i++ )
{
// generate random bit mess
uint32_t F;
do {
F = genRandom() & fmask;
} while ( 0 == ((F % (1 << n)) ^ (F >> 16 )) );
// Assume F is an array with interleaved elements such that F[0] || F[16] is one element
// here MSB(F) & ~LSB(F) returns 1 for all elements that are positive
// and ~MSB(F) & LSB(F) returns 1 for all elements that are negative
// this results in the distribution ( -1, 0, 0, 1 )
// to ease calculations we generate r = LSB(F) and l = MSB(F)
uint32_t r = F % ( 1 << n );
// modulo is required because the behaviour of the leftmost bit is implementation defined
uint32_t l = ( F >> 16 ) % ( 1 << n );
uint32_t posBits = l & ~r;
uint32_t negBits = ~l & r;
assert( (posBits & negBits) == 0 );
uint32_t mask = posBits | negBits;
uint32_t totalBits = popcnt( mask );
// if the amount of -1 and +1's is uneven, sum(S*F) cannot possibly evaluate to 0
if ( totalBits & 1 )
continue;
uint32_t adjF = posBits & ~negBits;
uint32_t desiredBits = totalBits / 2;
uint32_t S = (1 << (n+1));
// generate all possible N+1 bit strings
// 1 = +1
// 0 = -1
while ( S-- )
{
// calculate which bits in the expression S * F evaluate to +1
auto firstBits = (S & mask) ^ adjF;
auto secondBits = (S & ( mask << 1 ) ) ^ ( adjF << 1 );
bool a = desiredBits == popcnt( firstBits );
bool b = desiredBits == popcnt( secondBits );
firstZero += a;
bothZero += a & b;
}
}
return std::make_pair(firstZero, bothZero);
}
memotong runtime menjadi 0,160-0,161ms. Buka gulungan manual (tidak digambarkan di atas) membuat 0,150. Semakin sedikit sepele n = 10, iter = 100000 case berjalan di bawah 250ms. Saya yakin saya bisa mendapatkannya di bawah 50 ms dengan memanfaatkan core tambahan tapi itu terlalu mudah.
Ini dilakukan dengan membuat cabang loop dalam bebas dan menukar loop F dan S.
Jika bothZero
tidak diperlukan saya dapat mengurangi waktu berjalan ke 0,02 ms dengan jarang perulangan semua kemungkinan array S