Essayez D'Abord
À l'aide de scipy.weave
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:
import numpy
import time
from os import urandom
from scipy import weave
SIZE = 2**20
def faster_slow_xor(aa,bb):
b = numpy.fromstring(bb, dtype=numpy.uint64)
numpy.bitwise_xor(numpy.frombuffer(aa,dtype=numpy.uint64), b, b)
return b.tostring()
code = """
const __m128i* pa = (__m128i*)a;
const __m128i* pend = (__m128i*)(a + arr_size);
__m128i* pb = (__m128i*)b;
__m128i xmm1, xmm2;
while (pa < pend) {
xmm1 = _mm_loadu_si128(pa); // must use unaligned access
xmm2 = _mm_load_si128(pb); // numpy will align at 16 byte boundaries
_mm_store_si128(pb, _mm_xor_si128(xmm1, xmm2));
++pa;
++pb;
}
"""
def inline_xor(aa, bb):
a = numpy.frombuffer(aa, dtype=numpy.uint64)
b = numpy.fromstring(bb, dtype=numpy.uint64)
arr_size = a.shape[0]
weave.inline(code, ["a", "b", "arr_size"], headers = ['"emmintrin.h"'])
return b.tostring()
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:
support = """
#define ALIGNMENT 16
static void memxor(const char* in1, const char* in2, char* out, ssize_t n) {
const char* end = in1 + n;
while (in1 < end) {
*out = *in1 ^ *in2;
++in1;
++in2;
++out;
}
}
"""
code2 = """
PyObject* res = PyString_FromStringAndSize(NULL, real_size);
const ssize_t tail = (ssize_t)PyString_AS_STRING(res) % ALIGNMENT;
const ssize_t head = (ALIGNMENT - tail) % ALIGNMENT;
memxor((const char*)a, (const char*)b, PyString_AS_STRING(res), head);
const __m128i* pa = (__m128i*)((char*)a + head);
const __m128i* pend = (__m128i*)((char*)a + real_size - tail);
const __m128i* pb = (__m128i*)((char*)b + head);
__m128i xmm1, xmm2;
__m128i* pc = (__m128i*)(PyString_AS_STRING(res) + head);
while (pa < pend) {
xmm1 = _mm_loadu_si128(pa);
xmm2 = _mm_loadu_si128(pb);
_mm_stream_si128(pc, _mm_xor_si128(xmm1, xmm2));
++pa;
++pb;
++pc;
}
memxor((const char*)pa, (const char*)pb, (char*)pc, tail);
return_val = res;
Py_DECREF(res);
"""
def inline_xor_nocopy(aa, bb):
real_size = len(aa)
a = numpy.frombuffer(aa, dtype=numpy.uint64)
b = numpy.frombuffer(bb, dtype=numpy.uint64)
return weave.inline(code2, ["a", "b", "real_size"],
headers = ['"emmintrin.h"'],
support_code = support)
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 qu' weave
insiste sur la copie Python str
objets d' 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
, ce qui fera en sorte 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, l' slow_xor
entrée dans la première édition visée à ma version améliorée (inline xor au niveau du bit, en 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.