Как расширить / повторно использовать реализацию Python C Extensions / API? - PullRequest
0 голосов
/ 24 мая 2019

Проблема в том, что теперь мне нужно использовать функцию Posix C getline, чтобы получить строку из файла, только затем преобразовать ее в объект Python Unicode, используя PyUnicode_DecodeUTF8, и кэшировать ее, используя мое кэширование. Алгоритм политики . Этот процесс теряет 23% производительности по сравнению со встроенной реализацией Python for line in file C.

Если я удаляю вызов PyUnicode_DecodeUTF8 из своего кода, моя реализация с использованием Posix C getline становится 5% быстрее, чем встроенная реализация Python for line in file C. Таким образом, если я могу просто заставить Python напрямую предоставлять мне объект Python Unicode String, вместо того, чтобы сначала вызывать функцию Posix C getline (только затем преобразовывать ее результат в объект Python Unicode), производительность моего кода почти улучшится 20% (из максимума 23%), т. Е. Он не станет 100% эквивалентным for line in file производительности, потому что я выполняю небольшую работу по кешированию, однако эти издержки минимальны.

Например, я хотел бы взять функцию _textiowrapper_readline () и использовать ее в своем коде так:

#include <Python.h>
#include <textio.c.h> // C Python file defininig:
                      // _textiowrapper_readline(),
                      // CHECK_ATTACHED(),
                      // PyUnicode_READY(), etc

typedef struct
{
    PyObject_HEAD
}
PyMymoduleExtendingPython;

static PyObject* 
PyMymoduleExtendingPython_iternext(PyMymoduleExtendingPython* self, PyObject* args)
{
    PyObject *line;
    CHECK_ATTACHED(self);
    line = _textiowrapper_readline(self, -1); // <- function from `textio.c`

    if (line == NULL || PyUnicode_READY(line) == -1)
        return NULL;

    if (PyUnicode_GET_LENGTH(line) == 0) {
        /* Reached EOF or would have blocked */
        Py_DECREF(line);
        Py_CLEAR(self->snapshot);
        self->telling = self->seekable;
        return NULL;
    }
    return line;
}

// create my module
PyMODINIT_FUNC PyInit_mymodule_extending_python_api(void)
{
    PyObject* mymodule;
    PyMymoduleExtendingPython.tp_iternext = 
           (iternextfunc) PyMymoduleExtendingPython_iternext;

    Py_INCREF( &PyMymoduleExtendingPython );
    PyModule_AddObject( mymodule, "FastFile", (PyObject*) &PyMymoduleExtendingPython );
    return mymodule;
}

Как я могу включить реализацию textio из C Python и повторно использовать ее код в моем собственном расширении / API Python C?

Как показано в моем последнем вопросе, Как улучшить чтение строк в файле Python C Extensions? , встроенные методы Python для чтения строк быстрее, чем мои собственные с использованием стандартных методов C или C ++ для получения строк из файл.

На этом ответе мне было предложено переопределить алгоритм Python, читая куски по 8 КБ и только затем вызывая PyUnicode_DecodeUTF8 для их декодирования, вместо вызова PyUnicode_DecodeUTF8 в каждой строке, которую я прочитал. ,

Однако вместо того, чтобы переписывать весь код Python на C, уже написанный / готовый / готовый к чтению строк, я мог бы просто вызвать его функцию "getline" _textiowrapper_readline(), чтобы напрямую получить строку как объект Python Unicode, а затем кэшировать ее / используйте, как я уже делаю со строками, полученными из функции Posix C getline (и передайте PyUnicode_DecodeUTF8(), декодируйте их в объекты Python Unicode).

1 Ответ

0 голосов
/ 27 мая 2019

Мне не удалось напрямую импортировать функции C API (расширения), но я использовал Python для импорта модуля io, который имеет ссылку / ссылку на глобальную встроенную функцию open как io.open().

bool hasfinished;
const char* filepath;
long long int linecount;
std::deque<PyObject*> linecache;

PyObject* iomodule;
PyObject* openfile;
PyObject* fileiterator;

FastFile(const char* filepath) : hasfinished(false), filepath(filepath), linecount(0) {
    iomodule = PyImport_ImportModule( "io" );

    if( iomodule == NULL ) {
        std::cerr << "ERROR: FastFile failed to import the io module '"
                << filepath << "')!" << std::endl;
        PyErr_Print();
        return;
    }
    PyObject* openfunction = PyObject_GetAttrString( iomodule, "open" );
    if( openfunction == NULL ) {
        std::cerr << "ERROR: FastFile failed get the io module open function '"
                << filepath << "')!" << std::endl;
        PyErr_Print();
        return;
    }
    openfile = PyObject_CallFunction( openfunction, "s", filepath, 
            "s", "r", "i", -1, "s", "UTF8", "s", "replace" );

    PyObject* iterfunction = PyObject_GetAttrString( openfile, "__iter__" );
    Py_DECREF( openfunction );

    if( iterfunction == NULL ) {
        std::cerr << "ERROR: FastFile failed get the io module iterator function '"
                << filepath << "')!" << std::endl;
        PyErr_Print();
        return;
    }
    PyObject* openfileresult = PyObject_CallObject( iterfunction, NULL );
    Py_DECREF( iterfunction );
    if( openfileresult == NULL ) {
        std::cerr << "ERROR: FastFile failed get the io module iterator object '"
                << filepath << "')!" << std::endl;
        PyErr_Print();
        return;
    }
    fileiterator = PyObject_GetAttrString( openfile, "__next__" );
    Py_DECREF( openfileresult );

    if( fileiterator == NULL ) {
        std::cerr << "ERROR: FastFile failed get the io module iterator object '"
                << filepath << "')!" << std::endl;
        PyErr_Print();
        return;
    }
}

~FastFile() {
    this->close();
    Py_XDECREF( iomodule );
    Py_XDECREF( openfile );
    Py_XDECREF( fileiterator );

    for( PyObject* pyobject : linecache ) {
        Py_DECREF( pyobject );
    }
}

void close() {
    PyObject* closefunction = PyObject_GetAttrString( openfile, "close" );
    if( closefunction == NULL ) {
        std::cerr << "ERROR: FastFile failed get the close file function for '"
                << filepath << "')!" << std::endl;
        PyErr_Print();
        return;
    }
    PyObject* closefileresult = PyObject_CallObject( closefunction, NULL );
    Py_DECREF( closefunction );

    if( closefileresult == NULL ) {
        std::cerr << "ERROR: FastFile failed close open file '"
                << filepath << "')!" << std::endl;
        PyErr_Print();
        return;
    }
    Py_DECREF( closefileresult );
}

bool _getline() {
    // Fix StopIteration being raised multiple times because 
    // _getlines is called multiple times
    if( hasfinished ) { return false; }
    PyObject* readline = PyObject_CallObject( fileiterator, NULL );

    if( readline != NULL ) {
        linecount += 1;
        linecache.push_back( readline );
        return true;
    }

    // PyErr_Print();
    PyErr_Clear();
    hasfinished = true;
    return false;
}

При компиляции с помощью Visual Studio Compiler он имеет следующую производительность, используя этот код :

print( 'fastfile_time %.2f%%, python_time %.2f%%' % ( 
        fastfile_time/python_time, python_time/fastfile_time ), flush=True )
$ python3 fastfileperformance.py
Python   timedifference 0:00:00.985254
FastFile timedifference 0:00:01.084283
fastfile_time 1.10%, python_time 0.91% = 0.09%
$ python3 fastfileperformance.py
Python   timedifference 0:00:00.979861
FastFile timedifference 0:00:01.073879
fastfile_time 1.10%, python_time 0.91% = 0.09%
$ python3 fastfileperformance.py
Python   timedifference 0:00:00.990369
FastFile timedifference 0:00:01.086416
fastfile_time 1.10%, python_time 0.91% = 0.09%
$ python3 fastfileperformance.py
Python   timedifference 0:00:00.975223
FastFile timedifference 0:00:01.077857
fastfile_time 1.11%, python_time 0.90% = 0.10%
$ python3 fastfileperformance.py
Python   timedifference 0:00:00.988327
FastFile timedifference 0:00:01.085866
fastfile_time 1.10%, python_time 0.91% = 0.09%
$ python3 fastfileperformance.py
Python   timedifference 0:00:00.971848
FastFile timedifference 0:00:01.087894
fastfile_time 1.12%, python_time 0.89% = 0.11%
$ python3 fastfileperformance.py
Python   timedifference 0:00:00.968116
FastFile timedifference 0:00:01.079976
fastfile_time 1.12%, python_time 0.90% = 0.10%
$ python3 fastfileperformance.py
Python   timedifference 0:00:00.980856
FastFile timedifference 0:00:01.068325
fastfile_time 1.09%, python_time 0.92% = 0.08%

Но при компиляции с g++ он получил следующее исполнение:

$ /bin/python3.6 fastfileperformance.py
Python   timedifference 0:00:00.703964
FastFile timedifference 0:00:00.813478
fastfile_time 1.16%, python_time 0.87% = 0.13%
$ /bin/python3.6 fastfileperformance.py
Python   timedifference 0:00:00.703432
FastFile timedifference 0:00:00.809531
fastfile_time 1.15%, python_time 0.87% = 0.13%
$ /bin/python3.6 fastfileperformance.py
Python   timedifference 0:00:00.705319
FastFile timedifference 0:00:00.814130
fastfile_time 1.15%, python_time 0.87% = 0.13%
$ /bin/python3.6 fastfileperformance.py
Python   timedifference 0:00:00.711852
FastFile timedifference 0:00:00.837132
fastfile_time 1.18%, python_time 0.85% = 0.15%
$ /bin/python3.6 fastfileperformance.py
Python   timedifference 0:00:00.695033
FastFile timedifference 0:00:00.800901
fastfile_time 1.15%, python_time 0.87% = 0.13%
$ /bin/python3.6 fastfileperformance.py
Python   timedifference 0:00:00.694661
FastFile timedifference 0:00:00.796754
fastfile_time 1.15%, python_time 0.87% = 0.13%
$ /bin/python3.6 fastfileperformance.py
Python   timedifference 0:00:00.699377
FastFile timedifference 0:00:00.816715
fastfile_time 1.17%, python_time 0.86% = 0.14%
$ /bin/python3.6 fastfileperformance.py
Python   timedifference 0:00:00.699229
FastFile timedifference 0:00:00.818774
fastfile_time 1.17%, python_time 0.85% = 0.15%
...