5

I am trying to find out why the same code in Python works 25 times slower than C even if I use CDLL, when I try to write into I2C. Below I will describe all the details what I am doing step by step.

The version of Raspberry PI: Raspberry PI 3 Model B

OS: Raspbian Buster Lite Version:July 2019

GCC version: gcc (Raspbian 8.3.0-6+rpi1) 8.3.0

Python version: Python 3.7.3

The device I am working with though I2C is MCP23017. All I do is writing 0 and 1 to the pin B0. Here is my code written in C:

// test1.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
#include <time.h>


int init() {
    int fd = open("/dev/i2c-1", O_RDWR);
    ioctl(fd, I2C_SLAVE, 0x20);
    return fd;
}


void deinit(int fd) {
    close(fd);
}


void makewrite(int fd, int v) {
    char buffer[2] = { 0x13, 0x00 };
    buffer[1] = v;
    write(fd, buffer, 2);
}


void mytest() {
    clock_t tb, te;
    int n = 1000;

    int fd = init();

    tb = clock();
    int v = 1;
    for (int i = 0; i < n; i++) {
        makewrite(fd, v);
        v = 1 - v;
    }
    te = clock();

    printf("Time: %.3lf ms\n", (double)(te - tb) / n / CLOCKS_PER_SEC * 1e3);

    deinit(fd);
}


int main() {
    mytest();
    return 0;
}

I compile and run it with the command:

gcc test1.c -o test1 && ./test1

It gives me the result:

pi@raspberrypi:~/dev/i2c_example $ gcc test1.c -o test1 && ./test1
Time: 0.020 ms

I may conclude that writing to the pin takes 0.02 milliseconds.

After that I create SO-file to be able to access the written functions from my Python script:

gcc -c -fPIC test1.c -o test1.o && gcc test1.o -shared -o test1.so

And my Python script to test:

# test1.py

import ctypes
from time import time

test1so = ctypes.CDLL("/home/pi/dev/i2c_example/test1.so")

test1so.mytest()

n = 1000

fd = test1so.init()

tb = time()
v = 1
for _ in range(n):
    test1so.makewrite(fd, v)
    v = 1 - v
te = time()
print("Time: {:.3f} ms".format((te - tb) / n * 1e3))

test1so.deinit(fd)

This provides me the result:

pi@raspberrypi:~/dev/i2c_example $ python test1.py
Time: 0.021 ms
Time: 0.516 ms

I cannot understand why the call of makewrite is 25 times slower in Python though actually I call the same C-code. I also researched that if I comment write(fd, buffer, 2); in test1.c or change fd to 1, the times given by the Python script are compatible, there is no such huge difference.

// in test1.c
write(fd, buffer, 2); -> write(1, buffer, 2);

Running C-program:

pi@raspberrypi:~/dev/i2c_example $ gcc test1.c -o test1 && ./test1
...Time: 0.012 ms

Running Python-program:

pi@raspberrypi:~/dev/i2c_example $ python3 test1.py 
...Time: 0.009 ms
...Time: 0.021 ms

It confused me a lot. Can anybody tell me why does it happen and how can I improve my performance in Python regarding the access via I2C using C-DLL?

Summary:

Descriptor: 1 (stdout)

Execution time of makewrite in C purely: 0.009 ms

Execution time of makewrite in C called as C-DLL function from Python: 0.021 ms

The result is expectable. This difference is not so high. It can be explained that Python loop and its statements are not as efficient as in C, thus it increases the execution time.

Descriptor: I2C

Execution time of makewrite in C purely: 0.021 ms

Execution time of makewrite in C called as DLL function from Python: 0.516 ms

After switching the file descriptor to I2C the execution time in pure C increased in around 0.012 ms, so I would expect the execution time for calling from Python: 0.021 ms + 0.012 ms = 0.033 ms, because all changes I've done are inside of makewrite, so Python is supposed not to know this internal thing (because it's packed in so-file). But I have 0.516 ms instead of 0.033 ms that confuses me.

Fomalhaut
  • 8,590
  • 8
  • 51
  • 95
  • This usually calls for a profiling session. *mytest* has similar results. Why not using that one? It's a well known thing that "same" code runs significantly slower on *Python* (because even the simplest *Python* statement translates to a big amount of *C* code being executed), so do as much as possible in *C*. Might also want to check https://stackoverflow.com/questions/57044727/python-vs-cpp-why-is-the-difference-in-speed-so-huge. – CristiFati Aug 30 '19 at 07:33
  • The difference because of descriptors is puzzling. One thing that could be done to bring languages difference to a minimum in terms of overhead: split the *C* code in 2 (the *.so* and the *main* func which calls the *.so*). Although I don't think this is the issue. – CristiFati Aug 30 '19 at 08:43

0 Answers0