C ++ bit magic
0.84ms avec RNG simple, 1.67ms avec c ++ 11 std :: knuth
0,16 ms avec une légère modification algorithmique (voir édition ci-dessous)
L'implémentation en python s'exécute en 7,97 secondes sur ma plate-forme. Donc, ceci est 9488 à 4772 fois plus rapide en fonction du choix de RNG que vous avez choisi.
#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;
}
Compiler en 64 bits pour des registres supplémentaires. Lorsque vous utilisez le générateur aléatoire simple, les boucles de convolve () s'exécutent sans accès mémoire, toutes les variables sont stockées dans les registres.
Fonctionnement: plutôt que de stocker S
et en F
tant que tableaux en mémoire, il est stocké sous forme de bits dans un uint32_t.
En effet S
, les n
bits les moins significatifs sont utilisés lorsqu'un bit défini désigne un +1 et un bit non défini désigne un -1.
F
nécessite au moins 2 bits pour créer une distribution de [-1, 0, 0, 1]. Cela se fait en générant des bits aléatoires et en examinant les 16 bits les moins significatifs (appelés r
) et les 16 bits les plus significatifs (appelés l
). Si l & ~r
nous supposons que F est +1, si ~l & r
nous supposons que F
c'est -1. Sinon, la valeur F
est 0. Ceci génère la distribution que nous recherchons.
Nous avons maintenant S
, posBits
avec un bit défini à chaque emplacement où F == 1 et negBits
un bit défini à chaque emplacement où F == -1.
Nous pouvons prouver que F * S
(où * désigne la multiplication) est évalué à +1 dans la condition (S & posBits) | (~S & negBits)
. Nous pouvons également générer une logique similaire pour tous les cas où F * S
évalue à -1. Et enfin, nous savons quesum(F * S)
s’évalue à 0 si et seulement s’il existe une quantité égale de -1 et de +1 dans le résultat. Ceci est très facile à calculer en comparant simplement le nombre de +1 bits et -1 bits.
Cette implémentation utilise des bits 32 bits, et le maximum n
accepté est de 16. Il est possible d’échelonner l’implémentation à 31 bits en modifiant le code de génération aléatoire et à 63 bits en utilisant uint64_t au lieu de uint32_t.
modifier
La fonction de convolution suivante:
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);
}
coupe le temps d'exécution à 0.160-0.161ms. Le déroulement manuel de la boucle (non illustré ci-dessus) correspond à 0,150. Le moins trivial n = 10, iter = 100000 cas s’exécute en dessous de 250 ms. Je suis sûr que je peux obtenir moins de 50 ms en exploitant des cœurs supplémentaires, mais c'est trop facile.
Ceci est fait en libérant la branche de la boucle interne et en permutant les boucles F et S.
Si ce bothZero
n’est pas nécessaire, je peux réduire le temps d’exécution à 0,02 ms en effectuant une boucle parcimonieuse sur tous les tableaux S possibles.
-std=c++0x -mpopcnt -O2
et prend 1.01ms pour fonctionner en mode 32 bits (je n'ai pas de version GCC 64 bits à portée de main).Python2.7 + Numpy 1.8.1: 10.242 s
Fortran 90+:
0.029 s0.003 s0.022 s0.010 sBon sang, vous avez perdu votre pari! Pas une goutte de parallélisation ici aussi, juste le Fortran 90+.
EDIT J'ai pris l'algorithme de Guy Sirton pour permuter le tableau
S
(bonne découverte: D). J'avais apparemment aussi les-g -traceback
indicateurs de compilation actifs qui ralentissaient ce code à environ 0,017s. Actuellement, je compile ceci en tant quePour ceux qui n'ont pas
ifort
, vous pouvez utiliserEDIT 2 : La diminution du temps d’exécution est due au fait que j’avais fait quelque chose de mal auparavant et que j’ai eu une réponse incorrecte. Le faire de la bonne manière est apparemment plus lent. Je n'arrive toujours pas à croire que le C ++ est plus rapide que le mien, je vais donc probablement passer un peu de temps cette semaine à essayer de régler le problème pour accélérer le processus.
EDIT 3 : En changeant simplement la section RNG à l'aide d'une section basée sur le RNG de BSD (comme suggéré par Sampo Smolander) et en éliminant la division constante par
m1
, j'ai réduit le temps d'exécution au même résultat que la réponse C ++ de Guy Sirton . L'utilisation de tableaux statiques (comme suggéré par Sharpie) laisse le temps d'exécution passer à celui d'exécution C ++! Yay Fortran! :RÉEDIT 4 Apparemment, cela ne compile pas (avec gfortran) et ne fonctionne pas correctement (valeurs incorrectes) car les entiers outrepassent leurs limites. J'ai apporté des corrections pour que cela fonctionne, mais cela nécessite d'avoir ifort 11+ ou gfortran 4.7+ (ou un autre compilateur qui le permet
iso_fortran_env
et duint64
type F2008 ).Voici le code:
Je suppose que la question qui se pose maintenant est la suivante: allez-vous cesser d'utiliser Python à la mélasse lente et utiliser le Fortran rapide comme électrons-peut-déplacer;).
la source
integer(int64) :: b = 3141592653_int64
pour tout int64. Cela fait partie du standard fortran et est attendu par le programmeur dans un langage de programmation déclaré. (notez que les paramètres par défaut peuvent bien sûr remplacer cette option)Python 2.7 -
0.882s0.283s(OP de l'original: 6.404s)
Edit: l'optimisation de Steven Rumbalski en précalculant les valeurs F. Avec cette optimisation, cpython bat 0.365s.
Le code original de l'OP utilise de tels tableaux minuscules; l'utilisation de Numpy ne présente aucun avantage, comme le montre cette implémentation en python pur. Mais voyez aussi cette implémentation numpy qui est trois fois plus rapide que mon code.
J'optimise également en ignorant le reste de la convolution si le premier résultat n'est pas nul.
la source
F
car elles ne sont que 4032. Définir enchoicesF = filter(any, itertools.product([-1, 0, 0, 1], repeat=n))
dehors des boucles. Puis, dans la boucle intérieure, définissezF = random.choice(choicesF)
. Je reçois une accélération 3x avec une telle approche.range(iters)
hors de la boucle. Au total, je reçois une accélération d’environ 7% par rapport à votre très belle réponse.Rouille: 0.011s
Python d'origine: 8.3
Une traduction directe de l'original Python.
--opt-level=3
rustc 0.11-pre-nightly (eea4909 2014-04-24 23:41:15 -0700)
pour être précis)la source
a
s et mesb
dans la convolution; corrigé (ne change pas le temps d'exécution de manière notable).C ++ (VS 2012) -
0.026s0.015sPython 2.7.6 / Numpy 1.8.1 - 12s
Accélération ~ x 800.
L'écart serait beaucoup plus petit si les tableaux convolués étaient très grands ...
Quelques notes:
S[0]
le chiffre "le moins significatif".Ajoutez cette fonction principale pour un exemple autonome:
la source
advance
fonction. Mon code est donc plus rapide que le vôtre: P (mais très bonne concurrence!)C
Prend 0.015s sur ma machine, avec le code original de l'OP prenant ~ 7.7s. J'ai essayé d'optimiser en générant le tableau aléatoire et en convolution dans la même boucle, mais cela ne semble pas faire beaucoup de différence.
Le premier tableau est généré en prenant un entier, en l'écrivant en binaire et en changeant tous les 1 en -1 et tous les 0 en 1. Le reste doit être très simple.
Éditer: au lieu d’avoir
n
unint
, nous avons maintenantn
une constante définie par une macro, donc nous pouvons utiliserint arr[n];
au lieu demalloc
.Edit2: Au lieu de la
rand()
fonction intégrée, cela implémente maintenant un PRNG xorshift. En outre, de nombreuses instructions conditionnelles sont supprimées lors de la génération du tableau aléatoire.Instructions de compilation:
Code:
la source
do{}while(!flag)
ou quelque chose de similaire. Je ne pense pas que cela changera beaucoup le temps d'exécution (peut le rendre plus rapide).continue;
déclaration que j'attribue-1
àk
, donck
en boucle de 0 à nouveau.-=
plutôt que=-
:-) Une boucle while serait plus lisible.J
Je ne m'attends pas à battre tous les langages compilés, et quelque chose me dit que cela prendrait une machine miraculeuse pour obtenir moins de 0,09 seconde avec cela, mais j'aimerais quand même soumettre ce J, parce que c'est plutôt lisse.
Cela prend environ 0,5 s sur un ordinateur portable de la décennie précédente, seulement environ 20 fois plus vite que le Python dans la réponse. La plupart du temps est passé
conv
dedans parce que nous l'écrivons paresseusement (nous calculons la convolution entière) et en toute généralité.Puisque nous savons des choses sur
S
etF
, nous pouvons accélérer les choses en faisant des optimisations spécifiques pour ce programme. Le mieux que j'ai pu trouver estconv =: ((num, num+1) { +//.)@:(*/)"1
—sélectionnez précisément les deux nombres qui correspondent des sommes diagonales aux éléments les plus longs de la convolution — ce qui réduit approximativement le temps de moitié.la source
Perl - 9.3X plus rapide ... amélioration de 830%
Sur mon ancien netbook, le code de l'OP prend 53 secondes; La version d'Alistair Buxton prend environ 6,5 secondes et la version suivante de Perl, environ 5,7 secondes.
la source
Python 2.7 - numpy 1.8.1 avec des liaisons mkl - 0.086s
(OP de l'original: 6.404s) (Le python pur de Buxton: 0.270s)
Comme le souligne Buxton, le code d'origine de l'OP utilise de tels tableaux minuscules; l'utilisation de Numpy ne présente aucun avantage. Cette implémentation exploite numpy en effectuant tous les cas F et S à la fois de manière orientée tableau. Cette combinaison avec les liaisons mkl pour python conduit à une implémentation très rapide.
Notez également que le simple chargement des bibliothèques et le démarrage de l'interpréteur prend 0,076 seconde, donc le calcul prend environ 0,01 seconde, comme pour la solution C ++.
la source
python -c "import numpy; numpy.show_config()"
vous montrera si votre version de numpy est compilée contre blas / atlas / mkl, etc. ATLAS est un progiciel mathématique accéléré gratuit avec lequel numpy peut être lié , Intel MKL pour lequel vous devez habituellement payer (sauf si vous êtes universitaire). et peut être lié à numpy / scipy .MATLAB 0.024s
Ordinateur 1
Ordinateur 2
J'ai décidé d'essayer le si lent Matlab. Si vous savez comment faire, vous pouvez vous débarrasser de la plupart des boucles (en Matlab), ce qui le rend assez rapide. Cependant, les besoins en mémoire sont plus importants que pour les solutions en boucle, mais cela ne sera pas un problème si vous ne disposez pas de très grands tableaux ...
Voici ce que je fais:
Je suppose que vous n'avez pas matlab, ce qui est bien dommage car j'aurais vraiment aimé voir comment il se compare ...
(La fonction peut être plus lente la première fois que vous l'exécutez.)
la source
Julia: 0.30 s
Op Python: 21.36 s (Core2 duo)
71 fois plus rapide
Julia apporta quelques modifications à la réponse d'Arman: Tout d'abord, je l'ai enveloppée dans une fonction, car les variables globales rendent difficile l'inférence de type de Julia et JIT: une variable globale peut changer de type à tout moment et doit être vérifiée à chaque opération. . Ensuite, je me suis débarrassé des fonctions anonymes et des compréhensions de tableaux. Ils ne sont pas vraiment nécessaires et sont encore assez lents. Julia est plus rapide avec les abstractions de niveau inférieur en ce moment.
Il y a beaucoup plus de moyens d'accélérer les choses, mais cela fait un travail décent.
la source
Ok, je poste ceci simplement parce que j'estime que Java doit être représenté ici. Je suis terrible avec d’autres langues et j’avoue ne pas comprendre exactement le problème. Il me faut donc de l’aide pour corriger ce code. J'ai volé la plupart de l'exemple C de l'as de code, puis emprunté des extraits à d'autres. J'espère que ce n'est pas un faux pas ...
Une chose que je voudrais souligner est que les langages optimisés au moment de l'exécution doivent être exécutés plusieurs fois pour atteindre leur vitesse maximale. Je pense qu'il est justifié de prendre la vitesse entièrement optimisée (ou du moins la vitesse moyenne) car la plupart des choses qui vous intéressent de courir vite le seront plusieurs fois.
Le code doit encore être corrigé, mais je l’ai couru quand même pour voir le nombre de fois que j’aurais.
Voici les résultats obtenus sur un processeur Intel (R) Xeon (E) E3-1270 V2 à 3.50 GHz sous Ubuntu exécuté 1 000 fois:
serveur: / tmp # time java8 -cp. Testeur
firstzero 40000
bothzero 20000
premier temps d'exécution: 41 ms dernier temps d'exécution: 4 ms
réel 0m5.014s utilisateur 0m4.664 sys 0m0.268s
Voici mon code merdique:
Et j'ai essayé d'exécuter le code python après la mise à niveau de python et l'installation de python-numpy mais je comprends ceci:
la source
currentTimeMillis
pour le benchmarking (utiliser la version nano dans System) et les exécutions de 1k risquent de ne pas suffire à impliquer le JIT (1.5k pour le client et 10k pour le serveur seraient les valeurs par défaut, bien que vous appeliez souvent myRand pour qu'il le soit JITed, ce qui devrait entraîner la compilation de certaines fonctions dans le callstack, qui peut fonctionner ici). Enfin, le PNRG faible triche, mais la solution C ++ et d’autres aussi, de même que je suppose que ce n’est pas trop injuste.gettimeofday(&time, NULL)
d’archiver mon arbre de dev HotSpot et Linux utilise pour milliSeconds ce qui n’est pas monotone et ne donne aucune garantie de précision (de sorte que sur certaines plates-formes / noyaux, les mêmes des problèmes comme l’implémentation actuelle de Windows TimeMime - de sorte que cela ne pose pas de problème non plus. D'autre part, nanoTime utiliseclock_gettime(CLOCK_MONOTONIC, &tp)
clairement ce qui est également la bonne chose à utiliser lors de l'analyse comparative sur Linux.Golang version 45X de python sur ma machine en dessous des codes Golang:
et les codes python ci-dessous copiés d'en haut:
et l'heure ci-dessous:
la source
"github.com/yanatan16/itertools"
? aussi diriez-vous que cela fonctionnerait bien dans plusieurs goroutines?C # 0.135s
C # basé sur le python brut d' Alistair Buxton : 0.278s
C parallélisé: 0.135s
Python tiré de la question: 5.907s
Le python ordinaire d'Aristair: 0.853s
En réalité, je ne suis pas certain que cette implémentation est correcte - sa sortie est différente, si vous regardez les résultats plus bas.
Il y a certainement plus d'algorithmes optimaux. J'ai juste décidé d'utiliser un algorithme très similaire à celui de Python.
C simple filetage
Parallèle C #:
Test de sortie:
Windows (.NET)
Le C # est beaucoup plus rapide sous Windows. Probablement parce que .NET est plus rapide que mono.
Le timing utilisateur et système ne semble pas fonctionner (utilisé
git bash
pour le timing).Linux (mono)
la source
Haskell: ~ 2000x accélération par coeur
Compilez avec 'ghc -O3 -funbox-strict-fields -threaded -fllvm' et exécutez-le avec '+ RTS -Nk' où k est le nombre de cœurs de votre machine.
la source
Rubis
Ruby (2.1.0) 0.277s
Ruby (2.1.1) 0.281s
Python (Alistair Buxton) 0.330s
Python (normal) 0.097s
la source
le fil ne serait pas complet sans PHP
6.6x plus rapide
PHP v5.5.9 -
1.2230.646 sec;contre
Python v2.7.6 - 8.072 sec
convolve
fonction simplifiée un peu pour être plus rapide$F
et$FS
contrôles).Les sorties:
Modifier. La deuxième version du script fonctionne pour seulement
0.646 sec
:la source
Solution F #
Le temps d’exécution est de 0.030s lorsque compilé en x86 sur le CLR Core i7 4 (8) à 3.4 Ghz
Je n'ai aucune idée si le code est correct.
la source
Q, 0.296 seg
Q est un langage orienté collection (kx.com)
Code réécrit pour expliquer Q idiomatique, mais pas d'autres optimisations intelligentes
Les langages de script optimisent le temps du programmeur, pas le temps d'exécution
Première tentative de codage = pas un gagnant, mais un temps raisonnable (environ 30 fois plus rapide)
REMARQUES.-
\S seed
\t sentence
mesure le temps consommé par cette phrasela source
Julia:
12.1496.929 sMalgré leurs prétentions de rapidité , le temps de compilation JIT initial nous retient!
Notez que le code Julia suivant est en réalité une traduction directe du code Python original (aucune optimisation n’est apportée) car il montre que vous pouvez facilement transférer votre expérience de programmation dans un langage plus rapide;)
Modifier
Courir avec
n = 8
prend 32.935 s. Considérant que la complexité de cet algorithme estO(2^n)
, alors4 * (12.149 - C) = (32.935 - C)
, oùC
est une constante représentant le temps de compilation JIT. Résoudre pourC
Nous trouvons cela enC = 5.2203
, ce qui donne à penser que le temps d'exécution réeln = 6
est de 6,929 s.la source
Rouille, 6,6 ms, accélération 1950x
Une bonne partie de la traduction directe du code d’ Alistair Buxton dans Rust. J'ai envisagé d'utiliser plusieurs cœurs avec de la rayonne (concurrence sans peur!), Mais cela n'a pas amélioré les performances, probablement parce que c'est déjà très rapide.
Et Cargo.toml, car j'utilise des dépendances externes:
Comparaison de vitesse:
6625608 ns est d'environ 6,6 ms. Cela signifie 1950 fois plus rapide. Il y a beaucoup d'optimisations possibles ici, mais je visais la lisibilité plutôt que la performance. Une optimisation possible consisterait à utiliser des tableaux au lieu de vecteurs pour stocker les choix, car ils auront toujours des
n
éléments. Il est également possible d’utiliser un autre groupe que XorShift, car si Xorshift est plus rapide que le CSPRNG HC-128 par défaut, il est plus lent que la plus naïve des algorithmes PRNG.la source