Bitey Magic

Bitey is a tool to import LLVM bitcode directly into Python. Bitey magic is a small IPython extension which adds a %%bitey cell magic for automatically compiling C (or C++) code into LLVM bitcode and loading the bitcode with Bitey.

Requirements

  • Bitey, importable from Python as import bitey
  • clang, executable as clang in the shell
In [17]:
%load_ext biteymagic

Demos

The following demos are taken, with slight modification, from the examples in the Bitey source.

The Basics

Calculate the Fibonacci numbers using a simple recursive formula.

In [18]:
%%bitey
int fib(int n) {
  if (n < 3) {
    return 1;
  } else {
    return fib(n-2) + fib(n-1);
  }
}
In [20]:
map(fib, xrange(1,10))
Out[20]:
[1, 1, 2, 3, 5, 8, 13, 21, 34]

Bitey understands most basic C datatypes including integers, floats, void, pointers, arrays, and structures. Because it builds a ctypes based interface, you would access the code using the same techniques. Here is an example that mutates a value through a pointer.

In [9]:
%%bitey
void mutate_int(int *x) {
  *x *= 2;
}
In [10]:
import ctypes
x = ctypes.c_int(2)
mutate_int(x)
x.value
Out[10]:
4

Structs

An example involing a structure. One subtle issue with structure wrapping is that LLVM bitcode doesn't encode the names of structure fields. So, Bitey simply assigns them to an indexed element variable like this:

>>> p1.e0         # (Returns the .x component)
3
>>> p1.e1         # (Returns the .y component)
4
In [4]:
%%bitey
#include <math.h>

struct Point {
  double x;
  double y;
};

double distance(struct Point *p1, struct Point *p2) {
  return sqrt((p1->x - p2->x)*(p1->x - p2->x) + 
	      (p1->y - p2->y)*(p1->y - p2->y));
}
In [5]:
p1 = Point(3,4)
p2 = Point(6,8)
print distance(p1,p2)
print p1.e0, p1.e1
5.0
3.0 4.0

Performance

The performance profile of Bitey is going to be virtually identical that of using ctypes. LLVM bitcode is translated to native machine code and Bitey builds a ctypes-based interface to it in exactly the same manner as a normal C library.

In [7]:
%%bitey -03
/* A function that determines if an integer is a prime number or not.
   This is just a naive implementation--there are faster ways to do it */

int isprime(int n) {
  int factor = 3;
  /* Special case for 2 */
  if (n == 2) {
    return 1;
  }
  /* Check for even numbers */
  if ((n % 2) == 0) {
    return 0;
  }
  /* Check for everything else */
  while (factor*factor < n) {
    if ((n % factor) == 0) {
      return 0;
    }
    factor += 2;
  }
  return 1;
}
In [8]:
%%timeit
isprime(10143937)
10000 loops, best of 3: 36.8 us per loop

Additional optimization flags given after %%bitey are passed directly to clang. For example, consider a simple function which sums a vector.

In [11]:
%%bitey
double sum1d(double* x, int n)
{
  int i;
  double ret;
  ret = 0.0;
  for(i=0; i<n; i++)
    ret = ret + x[i];
  return ret;
}
In [12]:
%%bitey -O2
double sum1d_O2(double* x, int n)
{
  int i;
  double ret;
  ret = 0.0;
  for(i=0; i<n; i++)
    ret = ret + x[i];
  return ret;
}
In [14]:
import numpy as np
n = 1000
A = np.random.rand(n)
x = np.ctypeslib.as_ctypes(A)
print('sum1d:')
%timeit sum1d(x, n)
print('sum1d_O2:')
%timeit sum1d_O2(x, n)
sum1d:
100000 loops, best of 3: 7.39 us per loop
sum1d_O2:
100000 loops, best of 3: 3.29 us per loop

C++ Support

Bitey does not support C++ functions, but C++ code can be compiled if the function is decorated with extern "C". In addition, you will need to pass -x c++ to clang to specify that the language is C++.

In [15]:
%%bitey -O2 -x c++
#include <numeric>
extern "C" double sum1d_cpp(double* x, int n)
{
  return std::accumulate(x, x+n, 0.0);
}
In [16]:
print('sum1d_cpp')
%timeit sum1d_cpp(x, n)
sum1d_cpp
100000 loops, best of 3: 3.33 us per loop