Ceci est un suivi de la lenteur de Python. (Ou à quelle vitesse votre langue est-elle?) .
Il s’est avéré qu’il était un peu trop facile d’obtenir une accélération x100 pour ma dernière question. Pour la partie qui a aimé le défi mais qui veut quelque chose de plus difficile où ils peuvent vraiment utiliser leurs compétences de bas niveau, voici la partie II. Le défi consiste à obtenir une accélération x100 pour le code python suivant, testé sur mon ordinateur.
Pour rendre les choses plus difficiles, j'utilise pypy cette fois-ci. Pour moi, le timing actuel est de 1 minute et 7 secondes avec Pypy 2.2.1.
Règles
- La première personne à soumettre du code que je peux exécuter est correcte et x100 fois plus rapide sur ma machine se verra attribuer une prime de 50 points.
- Je vais attribuer la victoire au code le plus rapide après une semaine.
import itertools
import operator
import random
n = 8
m = 8
iters = 1000
# creates an array of 0s with length m
# [0, 0, 0, 0, 0, 0, 0, 0]
leadingzerocounts = [0]*m
# itertools.product creates an array of all possible combinations of the
# args passed to it.
#
# Ex:
# itertools.product("ABCD", "xy") --> Ax Ay Bx By Cx Cy Dx Dy
# itertools.product("AB", repeat=5) --> [
# ('A', 'A', 'A', 'A', 'A'),
# ('A', 'A', 'A', 'A', 'B'),
# ('A', 'A', 'A', 'B', 'A'),
# ('A', 'A', 'A', 'B', 'B'),
# etc.
# ]
for S in itertools.product([-1,1], repeat = n+m-1):
for i in xrange(iters):
F = [random.choice([-1,0,0,1]) for j in xrange(n)]
# if the array is made up of only zeros keep recreating it until
# there is at least one nonzero value.
while not any(F):
F = [random.choice([-1,0,0,1]) for j in xrange(n)]
j = 0
while (j < m and sum(map(operator.mul, F, S[j:j+n])) == 0):
leadingzerocounts[j] +=1
j += 1
print leadingzerocounts
La sortie doit être similaire à
[6335185, 2526840, 1041967, 439735, 193391, 87083, 40635, 19694]
Vous devez utiliser une graine aléatoire dans votre code et tout générateur de nombre aléatoire suffisant pour donner des réponses proches de ce qui précède sera accepté.
Ma machine Les timings seront exécutés sur ma machine. Il s’agit d’une installation ubuntu standard sur un processeur à huit cœurs AMD FX-8350. Cela signifie également que je dois pouvoir exécuter votre code.
Explication du code
Ce code itère sur tous les tableaux S de longueur n + m-1 constitués de -1 et de 1. Pour chaque tableau S, il échantillonne 1000 tableaux aléatoires non nuls F de longueur n constituée de -1,0 ou 1 avec une probabilité de 1/4, 1/2, / 14 de la prise de chaque valeur. Il calcule ensuite les produits internes entre F et chaque fenêtre de S de longueur n jusqu'à trouver un produit interne non nul. Il ajoute 1 leadingzerocounts
à chaque position où il a trouvé un produit intérieur nul.
Statut
Perl . Ralentissement de 2,7 fois par @tobyink. (Comparé à pypy pas cpython.)
J . 39 fois plus rapide par @Eelvex.
- C . 59 fois plus rapide par @ace.
- Julia . 197 fois plus rapide sans compter le temps de démarrage de @ une minute de plus. 8,5 fois plus rapide, temps de démarrage compris (l'utilisation de 4 processeurs est plus rapide que 8).
- Fortran . 438 fois plus rapide en @ semi-extrinsèque.
- Rpython . 258 fois plus rapide par @primo.
- C ++ . 508 fois plus rapide par @ilmale.
(J'ai arrêté de chronométrer les nouvelles améliorations car elles sont trop rapides et trop petites.)
Il a été souligné que les durées inférieures à une seconde ne sont pas fiables et que certaines langues ont un coût de démarrage. L'argument est que si vous voulez inclure cela, vous devez également inclure le temps de compilation de C / C ++, etc. Voici les timings pour le code le plus rapide avec le nombre d'itérations porté à 100 000.
- Julia . 42 secondes par @ une minute de plus.
- C ++ . 14 secondes par @GuySirton.
- Fortran . 14s par @ semi-extrinsiques.
- C ++ . 12s par @ilmale.
- Rpython . 18s par @primo.
- C ++ . 5s de @Stefan.
Le gagnant est .. Stefan!
Défi de suivi affiché. À quelle hauteur pouvez-vous aller? (Un défi de codage + algorithmes) . Celui-ci est plus difficile.
la source
Réponses:
C ++ bit magic
~ 16ms en multithread, 56ms en simple. ~ 4000 accélération.
(L'accélération est basée sur le code multithread sur mon i7-2820QM et les 1 min 9 secondes mentionnées dans la question. Étant donné que le système de l'OP a des performances plus médiocres que mon processeur, mais une meilleure perfiormance multi-thread, je m'attends à ce que ce nombre soit exact)
La partie multithread est assez inefficace en raison de la génération de threads. Je pourrais probablement faire mieux en exploitant ma bibliothèque de tâches personnalisée, mais celle-ci présente des bogues sous les systèmes unix. Pour une explication et un code presque identique sans thread, reportez-vous à la page https://codegolf.stackexchange.com/a/26485/20965 .
modifier
J'ai donné à chaque thread son propre RNG et ai réduit la longueur de bit à 32, ce qui a réduit le temps d'exécution de quelques ms.
Exemple de sortie:
la source
C ++
x150x450x530Au lieu de tableau, j'ai utilisé des bits (et de la magie noire).
Merci @ace pour la fonction aléatoire plus rapide.
Comment ça marche: les 15 premiers bits de l'entier
s
représentent le tableauS[15]
; les zéros représentent -1, ceux-ci représentent +1. Le tableauF
est construit de manière similaire. Mais avec deux bits pour chaque symbole.Causer
S
etF
avoir une représentation différente je dois entrelacerS
avec lui-même pour être comparableF
.F
)F
)Maintenant, nous pouvons simplement utiliser Carnot pour calculer le produit intérieur. Rappelez-vous qu'une variable ne peut prendre que la valeur 00 ou 11
0 00 = 11 (-1 * -1 = +1)
0. 01 = 10 (-1 * 0 = 0)
0. 10 = 01 (-1 * 0 = 0)
0. 11 = 00 (-1 * +1 = -1)
1. 00 = 00 (+1 * -1 = -1)
1. 10 = 10 (+1 * 0 = 0)
1. 01 = 01 (+1 * 0 = 0)
1. 11 = 11 (+1 * +1 = +1)
On dirait un pas xor pour moi. :)
Somme les uns est juste un jeu de décalage et de masque, rien de vraiment complexe.
Voici un exemple de sortie:
Le programme a été compilé avec:
sur Fedora 20 avec gcc 4.8.2 Le Cpu est un i7 8core.
Je peux probablement gagner quelques ms en peaufinant les paramètres du compilateur.
Bien que ce soit le temps de solution OP sur ma machine:
Modifier:
Il suffit d’ajouter openmp et de modifier l’ordre des résultats car j’ai un gain x3, ce qui conduit à une amélioration des performances x450 par rapport au code OP. : D Dans ce cas, le
leadingZero
tableau doit être atomique. Les aléatoires globaux ... sont aléatoires, ils seront plus aléatoires.besoin d'ajouter
-fopenmp
à l'indicateur du compilateurEdit: 2 Comme suggéré par user71404, j’ai modifié les fonctions sumOnes et sumArray et c’est maintenant très rapide.
Avec openmp, c'est plus lent, car les atomics ajoutent trop de temps système.
Sans atomics, c'est encore plus rapide, mais je me trompe de résultat.
2137992 1147218 619297 321243 155815 70946 32919 15579
Pour comprendre sumArray, considérons que 16 bits représentent et un tableau de 8 nombres.
00 n'a pas 1 et représente -1
01 et 10 ont un 1 et représentent 0
11 ont deux 1 et représentent 1
Pour que le nombre de bits intégré soit compté à 1 [ http://en.wikipedia.org/wiki/ Hamming_weight] et à chaque groupe, nous supprimons 1. Cool.
sumOnes est juste de la magie noire.
Voici les derniers drapeaux de compilation et le code.
gcc -std = c ++ 11 -mfpmath = sse -O3 -flto -march = native -funroll-loops -Wall -lstdc ++
la source
inline int32_t sumOnes(int32_t v) { /* 0xAAAA == 0b1010 1010 1010 1010 */ return !! (0xAAAA & (v ^ ~(v << 1))); } inline int32_t sumArray(int32_t v) { return __builtin_popcount(v) - 8; }
cela a été suggéré par @ user71404Julia: 0.7s, 120x plus rapide
Comme le montre user20768, un portage simple du code vers Julia est environ deux fois plus rapide que PyPy. Mais nous pouvons faire beaucoup mieux que cela.
Vous pouvez exécuter ceci en utilisant
julia -p 8 -e 'require("golf.jl");main()'
(le 8 étant le nombre de processus, vous pouvez jouer avec). Sur la dernière version préliminaire de Julia, cela prend 0,7s contre 1m22s pour PyPy.Si vous avez suffisamment de cœurs sur votre ordinateur et que vous pouvez éventuellement créer quelques instances AWS, vous devriez pouvoir en réduire encore plus :)
la source
C, 1.210s
Avec le code de l'OP qui exécute 1m45.729s sur ma machine.
Compilation:
Un merci tout spécial: @dyp pour les drapeaux de compilation et les idées d'optimisation.
Exemple de sortie:
la source
-march=native -fwhole-program -fstrict-aliasing -ftree-vectorize
Btw. Je suis descendu à moins de 4 secondes en utilisant du C ++ 11, dont un MT19937 plus ununiform_int_distribution
.F
.n
est égal à8
, vous pouvez probablement utiliser AVX (ou 2 * SSE) pour calculer le produit dot avec unS
stockage approprié .smmintrin.h
)Perl
C'est loin d'être aussi rapide que la solution C, mais c'est assez rapide pour un langage interprété de haut niveau, je pense. Cela réduit d'environ 40% le temps d'exécution de l'implémentation Python.
Algorithm :: Combinatorics est disponible dans Ubuntu (
sudo apt-get install libalgorithm-combinatorics-perl
). Les autres modules utilisés sont des modules Perl de base, ils devraient donc déjà être installés dans le cadre de l'installation de base d'Ubuntu.la source
0..N-1
portée est la dernièremap
, non? Vous avez oublié deuse warnings
? :-) Bien que la logique dans OP soit déroutante, la fenêtre glissante n’atteint jamais le dernier élément deS
.warnings
ce qui permettait de traiter les éléments manquants comme nuls.N-1
améliore cela. Et cela améliore réellement très légèrement la vitesse - il est maintenant environ 40% plus rapide que l’implémentation Python.any
On peut également trouver List :: MoreUtils, qui, bien qu’il ne s’agisse pas d’un module de base, est l’un des modules CPAN les plus couramment utilisés.Julia: 4,66x plus lente!
Je commence vraiment à douter des statistiques sur leur site web ...
Notez que le code Julia suivant est en réalité une transcription directe du code Python de l'OP, sans aucune optimisation. J'utilise cette
time()
fonction pour exclure le temps de démarrage lent de Julia ...Julia: 5 m 32,912 s
Code de l'OP dans PyPy: 1 m 11,506 s
Julia sortie:
la source
RPython 0.187s (258x plus rapide)
Source originale avec PyPy2.2.1: 1m 6.718s
Maintenant, avec le threading, le support de base pour Python standard a été supprimé. Le nombre de threads de travail peut être spécifié en tant que paramètre de ligne de commande. La valeur par défaut est deux.
RPython est un sous-ensemble restreint de Python, qui peut être traduit en C puis compilé à l'aide de RPython Toolchain . Son objectif est d'aider à la création d'interprètes de langue, mais il peut également être utilisé pour compiler des programmes simples comme celui ci-dessus. La plupart des fonctionnalités "sophistiquées" de Python, telles que
itertools
ou mêmemap
ne sont pas disponibles.Pour compiler, créez un clone local du référentiel pypy actuel et exécutez les opérations suivantes:
L'exécutable résultant sera nommé
convolution-c
ou similaire dans le répertoire de travail actuel.J'ai paramétré les variables d'entrée, le programme doit donc être exécuté comme suit:
pour correspondre à l'exemple de code.
Notes d'implémentation
S in itertools.product([-1,1], repeat = n+m-1)
devientS in xrange(1<<n+m-1)
, interprétantS
comme un bitmap: [0
,1
] → [-1
,1
]De la même manière,
F
est également une carte de bits, avec chaque fois deux bits représentant une valeur unique:[
00
,01
,10
,11
] → [-1
,0
,0
,1
]Une table de vérité est utilisée pour rechercher le produit plutôt que pour effectuer une réplication multiple.
Etant donné que les entiers signés 32 bits sont utilisés, ils
n
ne doivent pas dépasser 15 etn+m
31. Un support arbitraire entier peut être obtenu avec lerpython.rlib.rbigint
module, si nécessaire.La première itération de la boucle de produit scalaire est déroulée et associée au test de nullité de
F
.Un PRNG homebrew est utilisé, la liste des sources. L'auteur de l'article présente une période de 2 32 -1 et affirme qu'il réussit tous les tests de Diehard sauf un, bien que je ne l'aie pas personnellement confirmé.
La graine aléatoire change toutes les millisecondes, ce qui est aussi bon d'utiliser un horodatage. De plus, chaque thread de
xor
travail identifie son identifiant de processus avec cette valeur, afin de s'assurer qu'ils ont chacun une graine différente.Exemples de timings
2 fils de travail:
4 fils de travail:
8 fils de travail:
Source d'origine de l'OP:
Calendrier pour 100 000 itérations:
la source
Julia: 1 min 21.4s (2.2x plus rapide) (modification du code d'Arman)
Code Op dans PyPy: 3 min 1,4 s
Les deux sont effectués dans le REPL, sans compter le temps nécessaire pour charger les packages.
Le code d'Arman pose quelques problèmes en le rendant très lent: il utilise beaucoup de fonctions anonymes et de fonctions supérieures inutilement. Pour tester si tout le vecteur F est nul, pourquoi ne pas simplement écrire tout (F == 0) au lieu de tout (x-> x == 0, F)? Il est plus court et littéralement mille fois plus rapide.
Il utilise également sum (map (*, x, y)) comme produit scalaire au lieu de simplement dot (x, y). La première version 650 fois plus lente pour un vecteur de 10k double. Et la fonction de produit scalaire est implémentée comme une boucle for dans Julia pure.
En outre, la compréhension des tableaux est lente. Il vaut mieux écrire [0,1,0, -1] [rand (1: 4, n)] au lieu de [[-1 -1 0 0 1] [rand (1: 4)] pour j = 1: n] .
Enfin, les variables globales sont mauvaises pour Julia. Julia n'est rapide que si vous écrivez du code de manière à ce que le JIT et l'inférence de type fonctionnent. Une grande partie de ceci est la stabilité du type: Le compilateur doit être capable de s'assurer que le type d'une variable ne changera pas pendant qu'il est dans une boucle, par exemple.
la source
Nimrod
Exemple de sortie:
Nimrod compile en C, donc le choix du compilateur C pour les questions d’arrière-plan aussi.
En utilisant clang, compilez avec:
En utilisant gcc, compilez avec:
Omettez
--passc:-flto
si vous avez un ancien compilateur C qui ne prend pas en charge LTO. Omettez l'--cc=...
option si le choix par défaut du compilateur C vous convient. Le code nécessite Nimrod 0.9.4 ou 0.9.5 .Sur mon quadcore iMac (2,65 GHz core i5), le code tourne en environ 0,15 seconde avec gcc 4,9, 0,16 seconde avec clang, comparé à 88 secondes pour PyPy 2.2.1 (soit une accélération de plus de 500 fois). Malheureusement, je n'ai pas accès à une machine avec plus de quatre cœurs sur laquelle PyPy est également installé ou sur laquelle je pourrais facilement installer PyPy, bien que je dispose d'environ 0,1 seconde (avec beaucoup de bruit de mesure) sur un processeur AMD 64 cœurs Opteron 6376 1,4 GHz (selon / proc / cpuinfo) avec gcc 4.4.6.
L'implémentation essaie d'être fidèle à l'original plutôt que d'optimiser le code au détriment de la lisibilité, sans renoncer à des optimisations évidentes. Chose intéressante, la récursion de la queue
initVecRand()
est un peu plus rapide qu'une boucle avec une instruction break avec à la fois gcc et clang. Le déroulement manuel d'une itération de laconvolve
boucle de test à l'intérieur de la boucle principale a également entraîné une accélération, probablement due à une meilleure prédiction de branche.la source
Java
J'ai traduit la solution C ++ ci-dessus en Java:
Sur ma machine, je reçois la sortie suivante pour le programme java:
Le programme OPs dure environ 53 secondes sur ma machine:
Le programme c ++ n'a exécuté que 0,15 seconde environ:
C'est environ 2,5 fois plus rapide que la solution Java correspondante (je n'ai pas exclu le démarrage de la machine virtuelle). Cette solution Java est environ 142 fois plus rapide que le programme exécuté avec PyPy.
Étant donné que j'étais personnellement intéressé, je suis
iters
passé à 100_000 pour Java et C ++, mais le facteur de 2,5 n'a pas diminué en faveur de Java si quelque chose devenait plus grand.EDIT: J'ai exécuté les programmes sur un PC Linux Linux 64 bits.
EDIT2: Je veux ajouter que j'ai commencé par une traduction brute du code python:
Ce programme a duré environ 3,6 secondes:
Ce qui est environ 14 fois plus rapide que la solution PyPy. (Le choix de la fonction aléatoire standard sur la fonction fastRandom entraîne un temps d'exécution de 5 secondes)
la source
Python 3.5 + numpy 1.10.1, 3,76 secondes
Les tests ont été exécutés sur mon Macbook Pro. Le code d'OP a pris environ 6 minutes sur la même machine.
La raison pour laquelle je réponds à cette question est en fait parce que je n'ai pas 10 réputations et que je ne peux pas répondre à la partie I :-p
Ces derniers jours, j'ai essayé de comprendre comment effectuer des convolutions massives de manière efficace avec numpy (sans faire appel à un package tiers, ni même scipy). Lorsque je suis tombé sur cette série de défis au cours de mes recherches, j'ai décidé de l'essayer. Je suis peut-être arrivé trop tard à ce jeu, mais voici ma tentative d'utilisation de Python 3.5 et de numpy 1.10.1.
J'ai pré-calculé les tableaux S et F, et aplati le tableau S tout en effectuant la convolution, ce qui (selon mes expériences) pourrait tirer parti de la vitesse de np.convolve. En d'autres termes, n'ayant pas trouvé de routine de convolution vectorisée, j'ai simulé le code en aplatissant le tableau entier et espéré que np.convolved ferait la vectorisation sous le capot pour moi, ce qui semblait fonctionner. Notez que j'ai utilisé mode = 'same' et coupé les éléments de début et de fin qui étaient inutiles.
Sur mon Macbook Pro, les résultats du test donnent 3,76 secondes . Quand j'ai exécuté le code d'OP (modifié en Python 3.5), j'ai eu environ 6 minutes . L'accélération est environ 100 fois.
Un inconvénient est que, comme les tableaux S et F doivent être stockés, la mémoire requise peut poser problème si les tailles sont trop grandes.
J'ai utilisé la même méthode pour la partie I et j'ai eu une accélération d'environ 60-100x sur mon ordinateur portable.
Comme je faisais tout sur mon Macbook Pro, si quelqu'un pouvait tester mon code et me faire savoir comment ça se passe sur votre machine, je l'apprécierais beaucoup!
la source
J,
130x~ 50x accélération?Fois sur un debian aléatoire:
Je pense qu'il y a place à l'amélioration.
la source
pypy
, nonpython
, ce qui explique pourquoi votre script semble accélérer 130 fois.C ++: x200 (i7 à 4 cœurs, devrait passer à x400 sur 8 cœurs)
Essayer une solution plus simple C ++ 11 (testée avec VS 2012, gcc et clang) avec parallélisation.
Pour que cela soit compilé et exécuté sous Linux avec gcc 4.8.1:
Sous Linux, nous devons également
std::launch::async
forcer plusieurs threads. Cela me manquait dans une version antérieure.Dans Visual Studio (2012+), cela devrait fonctionner mais créer une version de compilation pour le timing ...
Sur mon i3 dual core à l'ancienne, cela s'exécute en ~ 0,9 seconde. Sur mon quad core i7, il est 0.319s vs Pypy 66 secondes.
Sur un i7 à 8 cœurs, cela devrait être dans la plage d'accélération x400. Le passage à des tableaux de style C pourrait accélérer les choses, mais je souhaitais rester avec des conteneurs C ++. Pour moi, il est intéressant de voir l'accélération que vous pouvez obtenir tout en restant relativement proche du domaine du problème et à un niveau relativement élevé, quelque chose que je pense que le C ++ est vraiment bon. Il convient également de noter la relative facilité de mise en parallèle utilisant des constructions C ++ 11.
La solution de bit de @ ilmale est très cool et fonctionne pour -1/1/0. On pourrait aussi jeter le SSE sur cela et peut-être obtenir une accélération significative.
Au-delà de la parallélisation, il existe un autre "truc" qui consiste à réduire le nombre de sommations. Exemple de résultats: 6332947 2525357 1041957 438353 193024 87331 40902 19649
la source
Fortran: 316x
Fortran: Je l’ai jusqu’à
106x155x160x316x d’ accélération lorsqu’on utilise un RNG Xorshift et OpenMP sur un processeur i7 à 4 cœurs. Autre que cela, il n'y a pas de gros trucs. Pour que l'itérateur construise S, j'utilise simplement la représentation binaire de l'entier 16 bits i. Vous remarquerez que, mis à part le générateur de ressources en ligne et le "itérateur" / mappage de i à S, le code est aussi puissant que le code Python.Edit: suppression du "if" dans le Xorshift, en utilisant maintenant "r = abs (w / ...)" au lieu de "r = w / ...". Va de 106x à 155x.
Edit2: Ceci génère 15x autant de nombres aléatoires que la solution C ++. Si quelqu'un a une solution zéro-overhead pour convertir un entier aléatoire en un tableau de 0 et de 1 en Fortran, je suis tout ouïe. Ensuite, nous pourrions battre C ++ :)
Edit3: La première édition a introduit un bogue, comme l'a souligné Lembik. Ceci est corrigé maintenant, avec une amélioration minime de l'accélération. J'essaierai d'utiliser la suggestion d'Eelvex pour obtenir plus de rapidité.
Edit4: Profiling a indiqué que la conversion en réel et en entier avec nint () était lente. J'ai remplacé ceci par une division entière effectuant à la fois la mise à l'échelle et l'arrondi, passant de 160x à une accélération de 316x.
Compiler avec:
Exemple de sortie:
Code de l'OP:
la source