├── main.exe ├── create_fractal32.dll ├── create_fractal64.dll ├── cffiAPI.cp36-win32.pyd ├── .gitignore ├── create_fractal.h ├── README.md ├── mandel.py ├── mandel.c ├── create_fractal.c ├── LICENSE ├── create_fractal.py ├── main.c └── fractal_ctypes.py /main.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattip/c_from_python/HEAD/main.exe -------------------------------------------------------------------------------- /create_fractal32.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattip/c_from_python/HEAD/create_fractal32.dll -------------------------------------------------------------------------------- /create_fractal64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattip/c_from_python/HEAD/create_fractal64.dll -------------------------------------------------------------------------------- /cffiAPI.cp36-win32.pyd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattip/c_from_python/HEAD/cffiAPI.cp36-win32.pyd -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | .ipynb_checkpoints/ 3 | *.png 4 | *.pgm 5 | *.pyc 6 | main 7 | *.so 8 | *.o 9 | *.obj 10 | .gdb_history 11 | build/ 12 | c.raw 13 | pure_c.txt 14 | -------------------------------------------------------------------------------- /create_fractal.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | typedef struct _Img{ 3 | int width; 4 | int height; 5 | unsigned char * data; 6 | } Img; 7 | 8 | 9 | #ifdef _MSC_VER 10 | #define EXPORT __declspec( dllexport ) 11 | #else 12 | #define EXPORT 13 | #endif 14 | EXPORT 15 | int create_fractal(Img img, int iters); 16 | EXPORT 17 | int mandel(float real, float imag, int max_iters, unsigned char * val); 18 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Calling C from Python 2 | Comparing CFFI, Ctypes, Cython, CPPYY 3 | 4 | A comparison of four ways to call from python into a c function. I show the 5 | advantages and disadvantages of all 4: boilerplate required, maintenance, speed. 6 | 7 | If you read all the way through, there is a suprise at the end. No fair peeking. 8 | 9 | An earlier version of this notebook was part of [this talk](https://youtu.be/tqx9VW7V3Lc) 10 | at PyCon Israel 2017 11 | -------------------------------------------------------------------------------- /mandel.py: -------------------------------------------------------------------------------- 1 | def mandel(x, y, max_iters, value): 2 | """ 3 | Given the real and imaginary parts of a complex number, 4 | determine if it is a candidate for membership in the Mandelbrot 5 | set given a fixed number of iterations. 6 | """ 7 | i = 0 8 | c = complex(x,y) 9 | z = 0.0j 10 | for i in range(max_iters): 11 | z = z*z + c 12 | if (z.real*z.real + z.imag*z.imag) >= 4: 13 | value[0] = i 14 | return 0 15 | value[i] = 255 16 | return 1 -------------------------------------------------------------------------------- /mandel.c: -------------------------------------------------------------------------------- 1 | #include "create_fractal.h" 2 | int mandel(float x, float y, int max_iters, unsigned char * val) 3 | { 4 | int i = 0; 5 | float cR = x; 6 | float cI = y; 7 | float zR = 0; 8 | float zI = 0; 9 | for (i = 0; i < max_iters; i++) 10 | { 11 | /* in complex notation, z * z + c */ 12 | float prev_zR = zR; 13 | zR = zR * zR - zI * zI + cR; 14 | zI = 2 * prev_zR * zI + cI; 15 | if ((zR * zR + zI * zI) >= 4) 16 | { 17 | *val = i; 18 | return 0; 19 | } 20 | } 21 | *val = max_iters; 22 | return 1; 23 | } 24 | -------------------------------------------------------------------------------- /create_fractal.c: -------------------------------------------------------------------------------- 1 | #include "create_fractal.h" 2 | 3 | int create_fractal(Img img, int iters) { 4 | float pixel_size_x = 3.0 / img.width; 5 | float pixel_size_y = 2.0 / img.height; 6 | int x, y, ret=0; 7 | for (y=0; y < img.height; y++) { 8 | float imag = y * pixel_size_y - 1; 9 | int yy = y * img.width; 10 | for (x=0; x < img.width; x++) { 11 | float real = x * pixel_size_x - 2; 12 | unsigned char color; 13 | ret += mandel(real, imag, iters, &color); 14 | img.data[yy + x] = color; 15 | } 16 | } 17 | return ret; 18 | } 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This work is licensed under the Creative Commons Attribution-NonCommercial 3.0 2 | Unported License. To view a copy of this license, visit 3 | http://creativecommons.org/licenses/by-nc/3.0/ or send a letter to Creative 4 | Commons, PO Box 1866, Mountain View, CA 94042, USA. 5 | 6 | Under the terms of this license you are free to 7 | Share — copy and redistribute the material in any medium or format 8 | Adapt — remix, transform, and build upon the material 9 | 10 | Under the following terms: 11 | Attribution — You must give appropriate credit, provide a link to the license, 12 | and indicate if changes were made. You may do so in any reasonable manner, but 13 | not in any way that suggests the licensor endorses you or your use. 14 | NonCommercial — You may not use the material for commercial purposes. 15 | -------------------------------------------------------------------------------- /create_fractal.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function, division 2 | 3 | class Img(object): 4 | def __init__(self, width, height): 5 | self.width = width 6 | self.height = height 7 | self.data = bytearray(width*height) 8 | 9 | def create_fractal(image, iters, func, oneval): 10 | ''' Call a function for each pixel in the image, where 11 | -2 < real < 1 over the columns and 12 | -1 < imag < 1 over the rows 13 | ''' 14 | pixel_size_x = 3.0 / image.width 15 | pixel_size_y = 2.0 / image.height 16 | for y in range(image.height): 17 | imag = y * pixel_size_y - 1 18 | yy = y * image.width 19 | for x in range(image.width): 20 | real = x * pixel_size_x - 2 21 | func(real, imag, iters, oneval) 22 | image.data[yy + x] = oneval[0] 23 | 24 | def mandel(x, y, max_iters, value): 25 | """ 26 | Given the real and imaginary parts of a complex number, 27 | determine if it is a candidate for membership in the Mandelbrot 28 | set given a fixed number of iterations. 29 | """ 30 | i = 0 31 | c = complex(x,y) 32 | z = 0.0j 33 | for i in range(max_iters): 34 | z = z*z + c 35 | if (z.real*z.real + z.imag*z.imag) >= 4: 36 | value[0] = i 37 | return 0 38 | value[0] = max_iters 39 | return max_iters 40 | 41 | if __name__ == '__main__': 42 | from timeit import default_timer as timer 43 | from PIL import Image 44 | # Pure python 45 | width = 1500 46 | height = 1000 47 | image = Img(width, height) 48 | s = timer() 49 | oneval = bytearray(1) 50 | create_fractal(image, 20, mandel, oneval) 51 | e = timer() 52 | elapsed = e - s 53 | import platform 54 | imp = platform.python_implementation().lower() 55 | print('pure {} required {:.2f} millisecs'.format(imp, 1000*elapsed)) 56 | im = Image.frombuffer("L", (width, height), image.data, "raw", "L", 0, 1) 57 | im.save('{}.png'.format(imp)) 58 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "create_fractal.h" 5 | 6 | #ifdef CLOCK_PROCESS_CPUTIME_ID 7 | // call this function to start a nanosecond-resolution timer 8 | struct timespec timer_start(){ 9 | struct timespec start_time; 10 | clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &start_time); 11 | return start_time; 12 | } 13 | 14 | // call this function to end a timer, returning nanoseconds elapsed as a long 15 | long timer_end(struct timespec start_time){ 16 | struct timespec end_time; 17 | clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &end_time); 18 | long diffInNanos = end_time.tv_nsec - start_time.tv_nsec; 19 | return diffInNanos; 20 | } 21 | #else 22 | #include 23 | #endif 24 | 25 | int main(int argc, const char *argv[], const char * env[]) 26 | { 27 | int width = 1500; 28 | int height = 1000; 29 | int iters = 20; 30 | FILE * fid = NULL; 31 | Img img; 32 | size_t written; 33 | #ifdef CLOCK_PROCESS_CPUTIME_ID 34 | struct timespec vartime; 35 | #else 36 | struct timeb start, stop; 37 | #endif 38 | long time_elapsed_nanos; 39 | img.width = width; 40 | img.height = height; 41 | img.data = (unsigned char*)malloc(width * height * sizeof(unsigned char)); 42 | if (NULL == img.data) 43 | return -1; 44 | 45 | #ifdef CLOCK_PROCESS_CPUTIME_ID 46 | vartime = timer_start(); 47 | #else 48 | ftime(&start); 49 | #endif 50 | 51 | create_fractal(img, iters); 52 | 53 | #ifdef CLOCK_PROCESS_CPUTIME_ID 54 | time_elapsed_nanos = timer_end(vartime); 55 | #else 56 | ftime(&stop); 57 | time_elapsed_nanos = 1000000L * ((int) (1000.0 * (stop.time - start.time) 58 | + (stop.millitm - start.millitm))); 59 | #endif 60 | fprintf(stdout, "create_fractal required %ld millisecs\n", time_elapsed_nanos / 1000000); 61 | 62 | fid = fopen("c.raw", "wb"); 63 | if (NULL == fid) 64 | return -2; 65 | written = fwrite(img.data, sizeof(unsigned char), width * height, fid); 66 | fclose(fid); 67 | return 0; 68 | } 69 | -------------------------------------------------------------------------------- /fractal_ctypes.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function, division 2 | import subprocess 3 | import os 4 | 5 | width = 1500 6 | height = 1000 7 | #ctypes 8 | # First all the declarations. Each function and struct must be redefined ... 9 | import ctypes 10 | 11 | class CtypesImg(ctypes.Structure): 12 | _fields_ = [('width', ctypes.c_int), 13 | ('height', ctypes.c_int), 14 | ('data', ctypes.POINTER(ctypes.c_uint8)), # HUH? 15 | ] 16 | array_cache = {} 17 | def __init__(self, width, height): 18 | self.width = width 19 | self.height = height 20 | # Create a class type to hold the data. 21 | # Since this creates a type, cache it for reuse rather 22 | # than create a new one each time 23 | if width*height not in self.array_cache: 24 | self.array_cache[width*height] = ctypes.c_uint8 * (width * height) 25 | # Note this keeps the img.data alive in the interpreter 26 | self.data = self.array_cache[width*height]() # !!!!!! 27 | 28 | def asmemoryview(self): 29 | # There must be a better way, but this code will not 30 | # be timed, so explicit trumps implicit 31 | ret = self.array_cache[width*height]() 32 | for i in range(width*height): 33 | ret[i] = self.data[i] 34 | return ret 35 | 36 | ctypesimg = CtypesImg(width, height) 37 | 38 | 39 | # Load the DLL 40 | cdll = ctypes.cdll.LoadLibrary('./libcreate_fractal.so') 41 | 42 | #Fish the function pointers from the DLL and define the interfaces 43 | create_fractal_ctypes = cdll.create_fractal 44 | create_fractal_ctypes.argtypes = [CtypesImg, ctypes.c_int] 45 | 46 | mandel_ctypes = cdll.mandel 47 | mandel_ctypes.argtypes = [ctypes.c_float, ctypes.c_float, ctypes.c_int, 48 | ctypes.POINTER(ctypes.c_uint8)] 49 | 50 | 51 | if __name__ == "__main__": 52 | from timeit import default_timer as timer 53 | from PIL import Image 54 | from create_fractal import create_fractal 55 | s = timer() 56 | create_fractal_ctypes(ctypesimg, 20) 57 | e = timer() 58 | ctypes_onecall = e - s 59 | print('ctypes calling create_fractal required {:.2f} millisecs'.format(1000*ctypes_onecall)) 60 | data = ctypesimg.asmemoryview() 61 | print(max(data)) 62 | im = Image.frombuffer("L", (width, height), data, 'raw', 'L', 0, 1) 63 | im.save('ctypes_fractal.png') 64 | 65 | value = (ctypes.c_uint8*1)() 66 | s = timer() 67 | create_fractal(ctypesimg, 20, mandel_ctypes, value) 68 | e = timer() 69 | ctypes_createfractal = e - s 70 | data = ctypesimg.asmemoryview() 71 | print(max(data)) 72 | print('ctypes calling mandel required {:.2f} millisecs'.format(1000*ctypes_createfractal)) 73 | im = Image.frombuffer("L", (width, height), data, 'raw', 'L', 0, 1) 74 | im.save('ctypes_mandel.png') 75 | --------------------------------------------------------------------------------