Способ передавать миллионы элементов в Python в C-программу много раз в быстрой последовательности - PullRequest
5 голосов
/ 15 февраля 2011

Я написал скрипт на Python, который должен передавать миллионы элементов в программу на C и многократно получать выходные данные (передавать от 1 до 10 миллионов данных вершин (целочисленный индекс и 2 плавающие координаты) быстро 500 раз, и каждый раз, когда скрипт python вызывает программу на C, мне нужно хранить возвращаемые значения в переменных). Я уже реализовал способ чтения и записи текстовых и / или двоичных файлов, но он медленный и не слишком умный (зачем записывать файлы на жесткий диск, когда вам не нужно хранить данные после завершения сценария python?). Я пытался использовать каналы, но для больших данных они дали мне ошибки ... Итак, сейчас я думаю, что лучшим способом может быть использование способности ctypes загружать функции в .dll Поскольку я никогда не создавал dll, я хотел бы знать, как его настроить (я знаю, что у многих ide есть шаблон для этого, но мой wxdev-c ++ падает, когда я пытаюсь открыть его. Сейчас я загружаю код :: Блоки)

Можете ли вы сказать мне, правильное ли решение, которое я начинаю внедрять, или есть лучшее решение? 2 функции, которые мне нужно вызвать в Python:

void find_vertex(vertex *list, int len, vertex* lower, vertex* highter)
{
    int i;
    *lower=list[0];
    *highter=list[1];
    for(i=0;i<len;i++)
    {
        if ((list[i].x<=lower->x) && (list[i].y<=lower->y))
            *lower=list[i];
        else
        {
            if ((list[i].x>=highter->x) && (list[i].y>=highter->y))
                *highter=list[i];
        }
    }
}

и

vertex *square_list_of_vertex(vertex *list,int len,vertex start, float size)
{
    int i=0,a=0;
    unsigned int *num;
    num=(int*)malloc(sizeof(unsigned int)*len);
    if (num==NULL)
    {
        printf("Can't allocate the memory");
        return 0;
    }
    //controlls which points are in the right position and adds their index from the main list in another list
    for(i=0;i<len;i++)
    {
        if ((list[i].x-start.x)<size && (list[i].y-start.y<size))
        {
            if (list[i].y-start.y>-size/100)
            {
                num[a]=i;
                a++;//len of the list to return
            }
        }
    }

    //create the list with the right vertices
    vertex *retlist;
    retlist=(vertex*)malloc(sizeof(vertex)*(a+1));
    if (retlist==NULL)
    {
        printf("Can't allocate the memory");
        return 0;
    }
    //the first index is used only as an info container
    vertex infos;
    infos.index=a+1;
    retlist[0]=infos;

    //set the value for the return pointer
    for(i=1;i<=a;i++)
    {
        retlist[i]=list[num[i-1]];
    }

    return retlist;
}

EDIT: забыл опубликовать определение типа вершины

typedef struct{
    int index;
    float x,y;
} vertex;

EDIT2: Я буду распространять код, поэтому я предпочитаю не использовать внешние модули в python и внешние программы на C. Также я хочу, чтобы код оставался межплатформенным. Сценарий является аддоном для 3D-приложения, поэтому чем меньше он использует внешних «вещей», тем лучше.

Ответы [ 7 ]

7 голосов
/ 15 февраля 2011

Использование ctypes или Cython для обёртывания ваших функций на C - определенно лучший способ.Таким образом, вам даже не нужно будет копировать данные между кодом C и Python - и C, и часть Python выполняются в одном и том же процессе и имеют доступ к одним и тем же данным.Давайте придерживаться ctypes, так как это то, что вы предложили.Кроме того, использование NumPy сделает это намного удобнее.

Я предполагаю, что ваш тип vertex выглядит следующим образом:

typedef struct
{
    int index;
    float x, y;
} vertex;

Чтобы иметь эти вершины в массиве NumPy, вы можете определитьзапись "dtype" для него:

vertex_dtype = [('index', 'i'), ('x', 'f'), ('y', 'f')]

Также определите этот тип как ctypes структуру:

class Vertex(ctypes.Structure):
    _fields_ = [("index", ctypes.c_int),
                ("x", ctypes.c_float),
                ("y", ctypes.c_float)]

Теперь прототип ctypes для вашей функции find_vertex()будет выглядеть так:

from numpy.ctypeslib import ndpointer
lib = ctypes.CDLL(...)
lib.find_vertex.argtypes = [ndpointer(dtype=vertex_dtype, flags="C_CONTIGUOUS"),
                            ctypes.c_int,
                            ctypes.POINTER(Vertex),
                            ctypes.POINTER(Vertex)]
lib.find_vertex.restypes = None

Чтобы вызвать эту функцию, создайте массив вершин NumPy

vertices = numpy.empty(1000, dtype=vertex_dtype)

и две структуры для возвращаемых значений

lower = Vertex()
higher = Vertex()

и, наконец, вызовите вашу функцию:

lib.find_vertex(vertices, len(vertices), lower, higher)

NumPy и ctypes позаботятся о передаче указателя на начало данных vertices в вашу функцию C - копирование не требуется.

Возможно, вам придется прочитать немного документации по ctypes и NumPy, но я надеюсь, что этот ответ поможет вам начать работу с ним.

1 голос
/ 15 февраля 2011

Кажется, что вы действительно хотите, чтобы превратить вашу C-программу в модуль Python. Здесь - учебник, с которого вы можете начать.

0 голосов
/ 17 февраля 2011

Вот вариант, который использует Cython для написания модуля расширения для CPython.

C объявления для использования в Cython:

# file: cvertex.pxd
cdef extern from "vertex.h":
    ctypedef struct vertex:
        int index
        float x,y
    void find_vertex(vertex *list, int len, vertex* lower, vertex* highter)

Где vertex.h:

typedef struct{
    int index;
    float x,y;
} vertex;

void find_vertex(vertex *list, int len, vertex* lower, vertex* highter);

Реализация Cython для использования в Python:

# file: pyvertex.pyx
cimport numpy
cimport cvertex # use declarations from cvertex.pxd

def find_vertex(numpy.ndarray[cvertex.vertex,ndim=1,mode="c"] vertices):
    if len(vertices) < 2:
        raise ValueError('provide at least 2 vertices')

    cdef cvertex.vertex lower, highter
    cvertex.find_vertex(<cvertex.vertex*>vertices.data, len(vertices),
                        &lower, &highter)
    return lower, highter # implicitly convert to dicts

Чтобы скомпилировать расширение, запустите:

$ python setup.py build_ext -i

Где setup.py:

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext

setup(
    cmdclass = {'build_ext': build_ext},
    ext_modules = [Extension("vertex", ["pyvertex.pyx", "vertex.c"])]
)

Теперь расширение можно использовать из Python:

import numpy
import vertex # import the extension

n = 10000000
vertex_list = numpy.zeros(n, dtype=[('index', 'i'), ('x', 'f'), ('y', 'f')])
i = n//2
vertex_list[i] = i, 1, 1
v1, v2 = vertex.find_vertex(vertex_list)
print(v2['index'])
print(v1, v2)
Выход
5000000
{'y': 0.0, 'index': 0, 'x': 0.0} {'y': 1.0, 'index': 5000000, 'x': 1.0}
0 голосов
/ 15 февраля 2011

Если вы можете, заставьте программу Python буферизовать отправляемые данные, чтобы она не отправляла каждую вершину одну за другой. Сохраняйте их до тех пор, пока их не станет 100, 500 или 1000, и таким образом вы будете делать меньше звонков. Проведите несколько временных тестов, чтобы определить оптимальный размер буфера.

0 голосов
/ 15 февраля 2011

Итерации по миллионам элементов - это наихудшая возможная операция, которую вы могли бы сделать в Python ... Если вообще возможно написать эту часть программы на C или C ++, она будет в 100 раз быстрее и использовать в 100 раз меньше памяти ...

Я люблю python, но это не лучшее решение для этого типа операций.

0 голосов
/ 15 февраля 2011

Я думаю, что для этой работы я бы использовал библиотеку типа sysv ipc и просто отобразил бы данные в сегмент общей памяти.

0 голосов
/ 15 февраля 2011

Если вы хотите передавать данные между двумя программами, и у вас уже есть код для использования файла, почему бы просто не использовать RAM-диск?Для Windows вы можете использовать что-то вроде http://www.ltr -data.se / opencode.html / # ImDisk для создания RAM-диска, и вы можете использовать команды, перечисленные здесь для Linux.Для небольших объемов данных (все, что умещается в ОЗУ без необходимости постоянного извлечения), это должно превзойти дисковые операции на пару порядков.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...