воскресенье, 30 июня 2013 г.

Python: примеры и тесты, часть 2 - модули C/C++

Написание модулей Python на языках C/C++.


Этот предмет в меру детально описан в оригинальной документации на сайте Python. Тем не менее, хотелось это проверить и проделать автономно этот путь (хотя бы для того, чтобы оценить его трудоёмкость).

Интерес представляла реализация измерителя временных интервалов наносекундного диапазона, построенного на чтении аппаратного счётчика процессорных циклов процессоров x86 (такой счётчик появился начиная с Pentium II, на других архитектурах Linux это не работает). Функция (файл rdtsc.c), читающая значение этого счётчика, на языке C (или, если точнее, на инлайн ассемблерной вставке, допускаемой как компилятором GCC, так и компилятором Clang):

unsigned long long rdtsc( void ) {
    unsigned long long int x;
    asm volatile ( "rdtsc" : "=A" (x) );
    return x;
}


Для более старых версий GCC, не распознающих мнемонику ассемблерной команды rdtsc из расширенного набора команд (для операционных систем Solaris, Minix 3), этот код может выглядеть так:

unsigned long long rdtsc( void ) {
    unsigned long long int x;
    asm volatile ( ".byte 0x0f, 0x31" : "=A" (x) );
    return x;
}


Хотелось бы получить интерфейс для вызова rdtsc() из среды Python. Этот пример крайне прост, чем хорош для экспериментирования, и обладает некоторой практической полезностью. Пишем, пользуясь документацией с сайта Python, интерфейсный модуль (файл rdtsc_wrap.c)

// это будет модуль Python "rdtsc":
#include <Python.h>
extern unsigned long long rdtsc( void );
  
PyObject* rdtsc_wrap( PyObject* self, PyObject* args ) {
    if( self != NULL ) return NULL; // обработка ошибки вызова
    return Py_BuildValue( "L", rdtsc() );

  
// таблица методов
static PyMethodDef rdtscmethods[] = {
    { "rdtsc", rdtsc_wrap, METH_NOARGS },
    { NULL, NULL }
};
  
// функция инициализации модуля
void initrdtsc() {
    Py_InitModule( "rdtsc", rdtscmethods );
}

Собираем всё это командами:

$ gcc -c -fpic rdtsc_wrap.c rdtsc.c -I/usr/include/python2.7
$ ld -shared rdtsc_wrap.o rdtsc.o -lc -o rdtscmodule.so


После чего получим реализующую модуль динамическую библиотеку:

$ ls -l *.so
-rwxrwxr-x 1 olej olej 2616 июня 26 23:23 rdtscmodule.so

Для проверки результата создадим два (для сравнения) тестовых приложения: одно на языке C и одно - на Pyton. Попутно реализуем ещё одну полезную функцию того же сорта (что и rdtsc()) - calibr(), интервал времени (в циклах частоты процессора) для вычисления двух последовательных, следующих друг за другом, вызовов rdtsc() (это можно считать временем выполнения самого вызова rdtsc() при измерении протяжённости очень коротких операций).

Тест на C (файл ctest.c):

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
extern unsigned long long rdtsc( void );
  
#define NUMB 10
static unsigned calibr( int rep ) {
    uint32_t n, m, sum = 0;
    n = m = ( rep <= 0 ? NUMB : rep );
    while( n-- ) {
        uint64_t cf, cs;
        cf = rdtsc();
        cs = rdtsc();
        sum += (uint32_t)( cs - cf );
    }
    return sum / m;

  
int main( int argc, char **argv, char **envp ) {
    printf( "число процессорных тактов = %llu\n", rdtsc() );
    printf( "калибровка последовательных вызовов:" );
    printf( " %lu(0)", calibr( 0 ) );
    int n;
    for( n = 10; n <= 100000; n*=10 )
        printf( " %lu(%d)", calibr( n ), n );
        printf( "\n");
    exit( EXIT_SUCCESS );
};

Выполнение такого теста:

$ ./ctest
число процессорных тактов = 79971042747980
калибровка последовательных вызовов: 117(0) 115(10) 115(100) 147(1000) 139(10000) 115(100000)


Подобный тест, но на этот раз написанный на Python:

  • файл модуля (calibr.py), реализующего calibr():
#!/usr/bin/python
# -*- coding: utf-8 -*-
from rdtsc import rdtsc
 
def calibr( args = 10 ):
    sum = 0L
    if int( args ) <= 0: n = 10
    else: n = int( args )
    m = n
    while n > 0 :
        cf = -( rdtsc() - rdtsc() )
        sum = sum + cf
        n = n - 1
    return sum / m

  • файл (ptest.py) вызывающего теста: 

#!/usr/bin/python -O
# -*- coding: utf-8 -*-
from rdtsc import rdtsc
from calibr import calibr
 
counter = []
for i in range( 5 ):
counter.append( rdtsc() )
print "счётчик процессорных циклов:\n", counter
 
arg = [ 0, 10, 100, 1000, 10000, 100000 ]
msg = "калибровка последовательных вызовов:"
for i in arg:
s = " %s(%i)" % ( str( calibr( i ) ), i )
msg = msg + s
print msg


Выполнение такого теста:

$ python -O ptest.py
счётчик процессорных циклов:
[79971137334370L, 79971137337650L, 79971137338900L, 79971137340040L, 79971137341090L]
калибровка последовательных вызовов: 548(0) 515(10) 947(100) 500(1000) 487(10000) 484(100000)


Всё достаточно предсказуемо!

Этот источник (rdtsc()) временных меток высокого разрешения я буду неоднократно использовать в тестах параллельного выполнение (потоков, процессов) для наблюдения за последовательностью операций.

Комментариев нет: