AVX Playground¶
This notebook allows interactive experimentation with AVX instructions operating on 256-bit vectors represented in numpy arrays.
Python vector intrinsics: You saw it here first! :)
In [1]:
import numpy as np
import ctypes
In [3]:
def address_from_numpy(obj):
ary_intf = getattr(obj, "__array_interface__", None)
if ary_intf is None:
raise RuntimeError("no array interface")
buf_base, is_read_only = ary_intf["data"]
return buf_base + ary_intf.get("offset", 0)
def cptr_from_numpy(obj):
return ctypes.c_void_p(address_from_numpy(obj))
In [6]:
def make_func(operation):
c_code = """
#include <x86intrin.h>
void f(float *a, float *b, float *out)
{{
__m256 avec = _mm256_loadu_ps(a);
__m256 bvec = _mm256_loadu_ps(b);
__m256 result = {operation};
_mm256_storeu_ps(out, result);
}}
""".format(
operation=operation
)
from os.path import join
from tempfile import mkdtemp
tempdir = mkdtemp()
import subprocess
with open(join(tempdir, "code.c"), "w") as outf:
outf.write(c_code)
cc_proc = subprocess.Popen(
["gcc", "-march=sandybridge", "-shared", join(tempdir, "code.c"), "-o", join(tempdir, "code.so")],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
cc_stdout, cc_stderr = cc_proc.communicate(timeout=4)
if cc_proc.returncode:
raise RuntimeError("C compiler failed. It said:\n<pre>%s</pre>"
% (cc_stdout+cc_stderr).decode())
user_dll = ctypes.CDLL(join(tempdir, "code.so"))
user_func = user_dll.f
user_func.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
user_func.restype = None
def wrapper(a, b=None):
if b is None:
b = np.empty((8,), np.float32)
assert a.dtype == np.float32
assert b.dtype == np.float32
assert a.shape == (8,)
assert b.shape == (8,)
result = np.empty((8,), np.float32)
user_func(cptr_from_numpy(a), cptr_from_numpy(b), cptr_from_numpy(result))
return result
return wrapper
This cell uses the helper above to create the vector "intrinsics". For now, only a few are covered. But it's easy to add more, just by following the pattern.
In [8]:
unpackhi = make_func("_mm256_unpackhi_ps(avec, bvec)")
unpacklo = make_func("_mm256_unpacklo_ps(avec, bvec)")
def make_permute2(sel_lower, sel_upper):
imm = sel_upper << 4 | sel_lower
return make_func(f"_mm256_permute2f128_ps(avec, bvec, {imm})")
permute2_02 = make_permute2(0, 2)
permute2_13 = make_permute2(1, 3)
permute2_12 = make_permute2(1, 2)
permute2_21 = make_permute2(2, 1)
def make_permute(sel_0, sel_1, sel_2, sel_3):
imm = sel_3 << 6 | sel_2 << 4 | sel_1 << 2 | sel_0
return make_func(f"_mm256_permute_ps(avec, {imm})")
permute_3210 = make_permute(3, 2, 1, 0)
permute_2301 = make_permute(2, 3, 0, 1)
def make_shuffle(sel_0, sel_1, sel_2, sel_3):
imm = sel_3 << 6 | sel_2 << 4 | sel_1 << 2 | sel_0
return make_func(f"_mm256_shuffle_ps(avec, bvec, {imm})")
shuffle_0123 = make_shuffle(0, 1, 2, 3)
shuffle_1032 = make_shuffle(1, 0, 3, 2)
shuffle_2301 = make_shuffle(2, 3, 0, 1)
shuffle_0101 = make_shuffle(0, 1, 0, 1)
shuffle_2323 = make_shuffle(2, 3, 2, 3)
The examples below demonstrate how to use these functions:
In [9]:
A = np.arange(8)[:, np.newaxis]*10 + np.arange(6)
A = A.astype(np.float32)
In [10]:
A
Out[10]:
array([[ 0., 1., 2., 3., 4., 5.], [10., 11., 12., 13., 14., 15.], [20., 21., 22., 23., 24., 25.], [30., 31., 32., 33., 34., 35.], [40., 41., 42., 43., 44., 45.], [50., 51., 52., 53., 54., 55.], [60., 61., 62., 63., 64., 65.], [70., 71., 72., 73., 74., 75.]], dtype=float32)
In [11]:
Avec = A.reshape(-1, 8)
Avec
Out[11]:
array([[ 0., 1., 2., 3., 4., 5., 10., 11.], [12., 13., 14., 15., 20., 21., 22., 23.], [24., 25., 30., 31., 32., 33., 34., 35.], [40., 41., 42., 43., 44., 45., 50., 51.], [52., 53., 54., 55., 60., 61., 62., 63.], [64., 65., 70., 71., 72., 73., 74., 75.]], dtype=float32)
In [12]:
permute2_12(Avec[0], Avec[1])
Out[12]:
array([ 4., 5., 10., 11., 12., 13., 14., 15.], dtype=float32)
In [13]:
v1 = np.empty(8, np.float32)
v1[:] = np.arange(8)
v2 = np.empty(8, np.float32)
v2[:] = np.arange(8)+10
shuffle_2301(v1, v2)
Out[13]:
array([ 2., 3., 10., 11., 6., 7., 14., 15.], dtype=float32)
In [15]:
A2 = np.empty(Avec.shape, A.dtype)
A2.fill(0)
A2[0] = unpacklo(Avec[0], Avec[1])
A2[1] = unpackhi(Avec[0], Avec[1])
A2
Out[15]:
array([[ 0., 12., 1., 13., 4., 20., 5., 21.], [ 2., 14., 3., 15., 10., 22., 11., 23.], [ 0., 0., 0., 0., 0., 0., 0., 0.], [ 0., 0., 0., 0., 0., 0., 0., 0.], [ 0., 0., 0., 0., 0., 0., 0., 0.], [ 0., 0., 0., 0., 0., 0., 0., 0.]], dtype=float32)
In [ ]: