Simple Python Défi: le plus Rapide XOR au niveau du Bit sur les Tampons de Données
Défi:
Effectuer un XOR au niveau du bit sur l'égalité des deux tampons de taille. Les tampons seront nécessaires pour être le python str
type puisque c'est traditionnellement le type de tampons de données en python. De retour de la valeur résultante comme un str
. Le faire aussi vite que possible.
Les entrées sont deux 1 mégaoctet (2**20 octets) les chaînes de caractères.
Le défi est de sensiblement battre mon inefficace de l'algorithme à l'aide de python ou de tiers existants modules python (a assoupli les règles: ou créer votre propre module.) Des augmentations marginales sont inutiles.
from os import urandom
from numpy import frombuffer,bitwise_xor,byte
def slow_xor(aa,bb):
a=frombuffer(aa,dtype=byte)
b=frombuffer(bb,dtype=byte)
c=bitwise_xor(a,b)
r=c.tostring()
return r
aa=urandom(2**20)
bb=urandom(2**20)
def test_it():
for x in xrange(1000):
slow_xor(aa,bb)
- Il sonne comme le Python n'est peut-être pas le meilleur langage pour tout problème que vous essayez de résoudre.
- Je peux vous assurer qu'il est. Python me fait maudire le moins.
- Si vous voulez de la vitesse dans les opérations bit à bit, moins vous allez être le meilleur il est. Vous pouvez faire un XOR sur un tableau en C, en quelques lignes, et il va battre tout Python de mise en œuvre.
- Avez-vous le code de prise en charge pour faire de ce module?
- S. Quel est le niveau de l'utilisation de NumPy, à votre avis?
- Pourquoi n'êtes-vous pas le faire en C, assemblée, ou GPGPU?
- Je ne peux pas croire que ce n'est pas tout près (Pas une question) s'...
- S. naïve C mise en œuvre va faire très mal si le compilateur n'est pas auto-vectorisation, comme nous l'avons vu dans plusieurs exemples ici.
- Il pourrait être utile de vérifier ce que gcc 4.4 lorsque le forçage de la boucle de dérouler et -O3 (qui comprend la vectorisation), ou de la cpi, ou clang pour cette question. L'optimisation d'un "normal" de la boucle à un vectorisé est non triviale si, en raison à la fois des défauts d'alignement et de fuite des éléments (c'est à dire impossible à remplir la dernière 128bits) doivent être traités correctement, et pour les petits tableaux de la surcharge de qui va l'emporter sur les avantages. Optimisations comme l'utilisation de MOVNTDQ au lieu de MOVDQA est encore plus difficile (voire impossible, dans le cas général).
- Je vais voter pour fermer cette question hors-sujet parce que c'est un codage défi et pas une vraie question.
Vous devez vous connecter pour publier un commentaire.
D'Abord Essayer De
À l'aide de
scipy.tissage
et SSE2 intrinsèques donne une amélioration marginale. La première invocation est un peu plus lent étant donné que le code doit être chargé à partir du disque et de la mise en cache, à la suite d'invocations sont plus rapides:Deuxième Essai
En prenant en compte les commentaires, j'ai revisité le code pour savoir si la copie pourrait être évité. S'avère que j'ai lu la documentation de l'objet string de mal, alors, voici mon deuxième essai:
La différence est que la chaîne est allouée à l'intérieur de la C code. Il est impossible d'avoir aligné à 16 octets-frontière comme requis par les instructions SSE2, donc la non alignés régions de la mémoire au début et à la fin sont copiés à l'aide de byte-sage d'accès.
Les données d'entrée est remis à l'aide de tableaux numpy de toute façon, parce que
weave
insiste sur la copie Pythonstr
objets àstd::string
s.frombuffer
ne copie pas, donc c'est très bien, mais la mémoire n'est pas aligné à 16 octets, donc nous avons besoin d'utiliser_mm_loadu_si128
au lieu de la plus rapide_mm_load_si128
.Au lieu d'utiliser
_mm_store_si128
, nous utilisons_mm_stream_si128
, qui sera assurez-vous que toutes les écritures sont diffusées en direct à la mémoire principale dès que possible---de cette façon, la sortie de la matrice de ne pas utiliser de précieuses lignes de cache.Timings
Comme pour les timings, la
slow_xor
entrée dans la première édition visée à ma version améliorée (inline xor au niveau du bit,uint64
), j'ai supprimé cette confusion.slow_xor
fait référence au code de l'origine des questions. Tous les horaires sont fait pour 1000 pistes.slow_xor
: 1.85 s (1x)faster_slow_xor
: 1.25 s (1.48 x)inline_xor
: 0.95 s (1,95 x)inline_xor_nocopy
: 0.32 s (5.78 x)Le code a été compilé avec gcc 4.4.3 et j'ai vérifié que le compilateur utilise le jeu d'instructions SSE.
11
(2020
usec vs172
usec par itération). Le Ratio slow_xor/inline_xor est1.6
; slow_xor/faster_slow_xor est1.5
(Python 2.6.4 x86_64 GNU/Linux)Comparaison des performances: numpy vs Cython vs C ou Fortran vs coup de pouce.Python (pyublas)
Reproduire les résultats, télécharger http://gist.github.com/353005 et le type
make
(pour installer les dépendances, type:sudo apt-get install build-essential python-numpy python-scipy cython gfortran
, dépendances pourBoost.Python
,pyublas
ne sont pas inclus en raison de qu'ils nécessitent une intervention manuelle pour le travail)Où:
slow_xor()
est de l'OP questionfaster_slow_xor()
,inline_xor()
,inline_xor_nocopy()
sont de @Torsten Marek répondrecython_xor()
etcython_vectorised()
sont de @gnibbler réponseEt
xor_$type_sig()
sont:Il est utilisé à partir de Python comme suit:
xorcpp_inplace()
(Boost.Python, pyublas):xor.cpp:
Il est utilisé à partir de Python comme suit:
Voici mes résultats pour cython
Vectorising en cython rasages environ 25% de rabais pour la boucle sur mon ordinateur, Cependant, plus de la moitié du temps est consacré à la construction de la chaîne python (le
return
déclaration) - je ne pense pas que la copie supplémentaire peut être évité (légalement) le tableau peut contenir des octets nuls.La manière illégale, serait de passer une chaîne Python et muter en place et permettrait de doubler la vitesse de la fonction.
xor.py
xor_.custode
6.2
(2020 usec vs 325 usec pour les 2**20 taille). Le Ratio slow_xor/cython_xor est1.6
(Python 2.6.4 x86_64 GNU/Linux)Un easy speedup est d'utiliser une plus grande 'chunk':
avec
uint64
également importé denumpy
de cours. Jetimeit
ce à 4 millisecondes, contre 6 millisecondes pour l'byte
version.Votre problème n'est pas la vitesse de NumPy du xOr méthode, mais plutôt avec l'ensemble de la mise en mémoire tampon/conversions de types de données. Personnellement, je soupçonne que le point de ce post peut ont vraiment été pour se vanter de Python, parce que ce que vous faites ici, c'est le traitement de TROIS GIGAOCTETS de données dans les délais, à égalité avec les langages interprétés, qui sont intrinsèquement plus rapide.
Le code ci-dessous montre que, même à mon humble ordinateur Python peut xOr "aa" (1 MO) et "bb" (1 MO) en "c" (1 MO) d'un millier de fois (total de 3 go) en vertu de deux secondes. Sérieusement, combien plus l'amélioration voulez-vous? En particulier à partir d'un langage interprété! 80% du temps a été passé en appelant "frombuffer" et "tostring". La réelle xOr-ing est réalisé dans les autres 20% du temps. À 3 GO en 2 secondes, vous serait difficile à améliorer que sensiblement même juste en utilisant memcpy dans c.
Dans le cas où c'était une vraie question, et pas seulement secrète se vanter de Python, la réponse est pour le code, de façon à minimiser le nombre, le montant et la fréquence de vos conversions de types tels que "frombuffer" et "tostring". Le réel qui utilise xOr est rapide comme l'éclair déjà.
De toute façon, le "test_it2" ci-dessus accomplit exactement le même montant de xOr-ing "test_it", mais à 1/5 du temps. 5x amélioration de la vitesse devrait être considéré comme "important", non?
test_it2
s'exécutec.tostring
une fois, touttest_it
des milliers de fois?La manière la plus rapide XOR au niveau du bit est "^". Je peux taper que beaucoup plus rapide que "bitwise_xor" 😉
Python3 a
int.from_bytes
etint.to_bytes
, donc:Il est plus rapide que d'IO, un peu dur à tester juste la façon dont il est rapide, ressemble de 0,018 .. 0.020 s sur ma machine. Étrangement
"little"
-endian conversion est un peu plus rapide.Disponible 2.x a la fonction sous-jacente
_PyLong_FromByteArray
, il n'est pas exporté, mais accessible par l'ctypes:Python 2 les détails sont laissé comme exercice au lecteur.
Si vous voulez faire des opérations rapides sur le tableau des types de données, alors vous devriez essayer Cython (cython.org). Si vous lui donnez le droit déclarations, il doit être capable de compiler le bas à de purs code c.
À quel point avez-vous besoin d'une réponse comme une chaîne de caractères? Notez que le
c.tostring()
méthode a pour copie les données dansc
à une nouvelle chaîne, comme Python chaînes sont immuables (etc
est mutable). Python 2.6 et 3.1 ont unbytearray
type, qui agit commestr
(bytes
en Python 3.x) sinon pour être mutable.Une autre optimisation est à l'aide de la
out
paramètrebitwise_xor
pour spécifier l'emplacement où stocker le résultat.Sur ma machine je obtenir
avec le code à la fin de ce post. Notez en particulier que la méthode à l'aide d'un préaffectés tampon est deux fois plus rapide que la création d'un nouvel objet (sur 4 octets (
uint64
) morceaux). Ceci est cohérent avec la méthode plus lente à effectuer deux opérations par morceau (xor + copie) à l'accélération de l'1 (juste xor).Aussi, FWIW,
a ^ b
est équivalent àbitwise_xor(a,b)
, eta ^= b
est équivalent àbitwise_xor(a, b, a)
.Donc, 5x speedup sans écrire de modules externes 🙂
Vous pouvez essayer de la différence symétrique de la bitsets de sauge.
http://www.sagemath.org/doc/reference/sage/misc/bitset.html
Le moyen le plus rapide (speedwise) feront ce qu'Max. S recommandé. Mettre en œuvre dans C.
Le code de prise en charge pour cette tâche devrait être assez simple à écrire. C'est juste une fonction dans un module de création d'une nouvelle chaîne et de faire le xor. C'est tout. Lorsque vous avez mis en place un module comme ça, il est simple de prendre le code en tant que modèle. Ou même de prendre un module de mise en œuvre de quelqu'un d'autre qui implémente une amélioration simple module de Python et de les jeter tout ce qui n'est pas nécessaire pour votre tâche.
Le réel complexe, ce qui est juste, de faire le RefCounter-des Trucs à droite. Mais une fois compris comment ça fonctionne, c'est gérable, c'est aussi la tâche à portée de main est vraiment simple (allouer de la mémoire, et de la retourner -- params sont de ne pas être touché (Ref-sage)).