Как перебрать список в чистом режиме Cython - PullRequest
0 голосов
/ 20 ноября 2019

В попытке ускорить struct.pack() у меня есть следующее, чтобы упаковать int в байты:

import cython as c
from cython import nogil, compile, returns, locals, cfunc, pointer, address

int_bytes_buffer = c.declare(c.char[400], [0] * 400)


@locals(i = c.int, num = c.int)
@returns(c.int)
@cfunc
@nogil
@compile
def int_to_bytes(num):
    i = 0
    while num >0:
        int_bytes_buffer[i] = num%256
        num//=256
        i+=1

    return int_bytes_buffer[0]


int_to_bytes(259)

Я пытаюсь заставить это работать со списком целых, сследующий неправильный код:

@locals(i = c.int, ints_p = pointer(c.int[100]), num = c.int)
@returns(c.int)
@cfunc
@nogil
@compile
def int_to_bytes(num):
    i = 0
    for num in ints_p:
        while num >0:
            int_bytes_buffer[i] = num%256
            num//=256
            i+=1

    return int_bytes_buffer[0]

ints = c.declare(c.int[100],  [259]*100)
int_to_bytes(address(ints))

, который дает мне:

    for num in ints_p:
              ^
----------------------------------------------------------

 Accessing Python global or builtin not allowed without gil

Очевидно, я не должен использовать in или циклически перемещаться по указателю.

Как я могу перебрать массив списков внутри функции?

РЕДАКТИРОВАТЬ :

Я пытаюсь передать указатель намассив целых чисел для функции, и он работает без gil, чтобы его можно было распараллелить.

Параметр функции должен был быть ints_p:

@locals(ints_p = pointer(c.int[100]), i = c.int, num = c.int)
@returns(c.int)
@cfunc
@nogil
@compile
def int_to_bytes(ints_p):
    i = 0
    for num in (*ints_p):
        while num >0:
            int_bytes_buffer[i] = num%256
            num//=256
            i+=1

    return int_bytes_buffer[0]

ints = c.declare(c.int[100],  [259]*100)
int_to_bytes(address(ints))

, и я хочу перебрать действительные целые числа и упаковать их (без gil)

РЕДАКТИРОВАТЬ 2 :

Мне известно о struct.pack. Я хочу сделать распараллеливаемый вариант с Cython и nogil.

Ответы [ 2 ]

2 голосов
/ 25 ноября 2019

Это бессмысленно:

  1. Интервал Python может быть сколь угодно большим. Фактическая вычислительная работа в «упаковке», которую она вырабатывает, если она соответствует заданному размеру, а затем копирует ее в пространство такого размера. Однако вы используете массив C int s. Они имеют фиксированный размер. По сути, нет никакой работы по извлечению их в массив байтов. Все, что вы сделали, написали очень неэффективную версию memcpy. Они буквально уже находятся в памяти как непрерывный набор байтов - все, что вам нужно сделать, это просмотреть их следующим образом:

    # using Numpy (no Cython)
    ints = np.array([1,2,3,4,5,6,7], dtype=np.int) # some numpy array already initialized
    as_bytes = ints.view(dtype=np.byte) # no data is copied - wonderfully efficient
    

    , вы можете заставить аналогичный подход работать с другой библиотекой массивов или с массивами C:

    # slightly pointless use of pure-Python mode since this won't
    # be valid in Python.
    @cython.cfunc
    @cython.returns(cython.p_char)
    @cython.locals(x = cython.p_int)
    def cast_ptr(x):
        return cython.cast(cython.p_char,x)
    
  2. Вы говорите, что хотите nogil, чтобы его можно было распараллелить. Распараллеливание работает хорошо, когда нужно выполнить фактическую вычислительную работу. Это не работает, когда задача ограничена доступом к памяти, так как потоки, как правило, в конечном итоге ждут друг друга для доступа к памяти. Эта задача плохо распараллеливается.

  3. Управление памятью является проблемой. Вы способны записывать только в буферы фиксированного размера. Для выделения массивов переменного размера у вас есть несколько вариантов: вы можете использовать numpy или модуль Python array (или аналогичный), чтобы позволить Python позаботиться об управлении памятью, или вы можете использовать malloc и free разместить массивы на уровне C. Поскольку вы утверждаете, что вам нужно nogil, вы должны использовать подход C. Тем не менее, вы не можете сделать это из режима чистого Python в Cython, так как все также должно работать в Python, и нет эквивалента Python для malloc и free. Если вы настаиваете на том, чтобы попытаться заставить это работать, вам нужно отказаться от режима чистого Python в Cython и использовать стандартный синтаксис Cython, поскольку то, что вы пытаетесь сделать, не может быть совместимо с обоими.

    Обратите внимание, что в настоящее время int_bytes_buffer это глобальный массив. Это означает, что несколько потоков будут совместно использовать его - катастрофа для предполагаемого распараллеливания.


Вам необходимо четко подумать, какими будут ваши входные данные. Если это список типов Python, то вы не можете заставить это работать с nogil (так как вы манипулируете объектами Python, а для этого требуется GIL). Если это какой-то массив уровня C (будь то Numpy, модуль array или объявленный Cython массив C), тогда ваши данные уже в нужном вам формате, и вам просто нужно просмотреть их в таком виде.


Редактировать: Из комментариев это явно проблема XY (вы спрашиваете об исправлении этого синтаксиса Cython, потому что вы хотите упаковать список целых чисел) Я добавил быстрый способ упаковкисписок Python, использующих Cython. Это в 7 раз быстрее, чем struct pack, и в 5 раз быстрее, чем передача списка в array.array. В основном это быстрее, потому что специализируется только на одном.

Я использовал bytearray в качестве удобного хранилища данных для записи и Python memoryview класс (не совсем то же самое, что исинтаксис просмотра памяти Cython ...) как способ приведения типов данных. Никаких реальных усилий не было потрачено на его оптимизацию, чтобы вы могли улучшить его. Обратите внимание, что копирование в bytes в конце не изменяет измеряемое время, иллюстрируя, насколько не имеет значения копирование памяти для общей скорости.

@cython.boundscheck(False)
@cython.wraparound(False)
def packlist(a):
    out = bytearray(4*len(a))
    cdef int[::1] outview = memoryview(out).cast('i')
    cdef int i
    for i in range(len(a)):
        outview[i] = a[i]
    return bytes(out)
0 голосов
/ 24 ноября 2019

В вашем коде есть несколько ошибок.

  1. В ошибке Accessing Python global or builtin not allowed without gil, поэтому необходимо удалить тег @nogil. После удаления это не покажет ошибку. Проверено в моем коде. Но есть и другие ошибки.

  2. У вашей функции есть несколько проблем. def int_to_bytes(num): Вы не должны передавать num в функцию, так как значение num будет присвоено в цикле for. Я удаляю его как def int_to_bytes(): и функция работает. Но есть еще ошибка.

    @locals(i = c.int, ints_p = c.int(5), num = c.int)
    @returns(c.int)
    @cfunc
    @compile

    def int_to_bytes():
        ints_p = [1,2,3,4,5]
        i = 0
        for num in ints_p:
            while num >0:
                int_bytes_buffer[i] = num%256
                num//=256
                i+=1

        return int_bytes_buffer[1]

    a = int_to_bytes()
    print(a)
Наконец, я не понимаю, почему вы передаете адрес функции, поскольку функция не должна ничего брать.

Код работает для меня:

import cython as c
from cython import nogil, compile, returns, locals, cfunc, pointer, address

int_bytes_buffer = c.declare(c.char[400], [0] * 400)

ints = c.declare(c.int[100],  [259]*100)
# for i in list(*address(ints)):
#   print(i)
@locals(i = c.int, num = c.int)
@returns(c.int)
@cfunc
@compile

def int_to_bytes(values):
    i = 0
    for num in list(*address(values)):
        while num >0:
            int_bytes_buffer[i] = num%256
            num//=256
            i+=1

    return int_bytes_buffer

a = int_to_bytes(ints)
print([i for i in a])

Надеюсь, это поможет.

...