Должен ли я динамически выделять память для массива numpy, который может быть создан во время выполнения в cython?

#python #cython

#python #cython

Вопрос:

Это работает плавно и быстро:

 solexa_scores = '!"#$%amp;'   "'()* ,-./0123456789:;<=>?@ABCDEFGHI"

cdef np.ndarray[np.uint32_t, ndim=2] sums = np.zeros(shape=(length, len(solexa_scores) 33), dtype=np.uint32) 

cdef bytes line
cdef str decoded_line
cdef int counter=0 # Useful to know if it's the 3rd or 4th line of the current sequence in fastq.
with gzip.open(file_in, "rb") as f:
    for line in f:
    
        if counter%4==0: # first line of the sequence (obtain tile info)
            counter=0
    
        elif counter%3==0: # 3rd line of the sequence (obtain the qualities)
            decoded_line = line.decode('utf-8')
            for n in range(len(decoded_line)): #     enumerate(line.decode('utf-8')):
                sums[n, ord(decoded_line[n])]  =1
                
        counter =1
  

Здесь суммы numpy ndarray содержат результаты.

Однако вместо одного массива numpy мне нужно неизвестное количество массивов в словаре (с именем tiles), и это код, который должен достичь моей цели:

 solexa_scores = '!"#$%amp;'   "'()* ,-./0123456789:;<=>?@ABCDEFGHI"

cdef dict tiles = {} # each tile will have it's own 'sums' numpy array

cdef bytes line
cdef str decoded_line
cdef str tile

cdef int counter=0 # Useful to know if it's the 3rd or 4th line of the current sequence in fastq.
with gzip.open(file_in, "rb") as f:
    for line in f:

        if counter%4==0: # first line of the sequence (obtain tail info)
            decoded_line = line.decode('utf-8')
            tile = decoded_line.split(':')[4]
            if tile != tile_specific and tile not in tiles.keys(): # tile_specific is mentiones elsewhere. 
                tiles[tile] = np.zeros(shape=(length, len(solexa_scores) 33), dtype=np.uint32)

            counter=0

        elif counter%3==0: # 3rd line of the sequence (obtain the qualities)
            decoded_line = line.decode('utf-8')
            for n in range(len(decoded_line)): #     enumerate(line.decode('utf-8')):
                tiles[tile][n, ord(decoded_line[n])]  =1
                
        counter =1
  

В этом втором примере я априори не знаю количество ключей в плитках словаря, и поэтому массивы numpy будут объявлены и инициализированы во время выполнения (пожалуйста, поправьте меня, если я ошибаюсь или использую неправильные термины).
Cython не переводил / компилировал при использовании cython-объявления массивов numpy, и, следовательно, я оставил его как tiles[tile] = np.zeros(shape=(length, len(solexa_scores) 33), dtype=np.uint32) .
Поскольку все другие оптимизации cython для кода, который является общим для двух фрагментов, в порядке, я считаю, что это объявление массива numpy является проблемой.

Как я должен это исправить? Здесь в руководстве указаны способы динамического выделения памяти, но я не знаю, как это работает с массивами numpy и должен ли я вообще это делать.

Спасибо!

Ответ №1:

Я бы проигнорировал документацию о динамическом распределении памяти. Это не то, что вы хотите сделать — это очень много на уровне C, и вы обрабатываете объекты Python.

Вы можете легко переназначить переменную, введенную как массив Numpy (или, в равной степени, более новый типизированный memoryview) несколько раз, чтобы она ссылалась на другой массив Numpy. Я подозреваю, что вы хотите что-то вроде

 # start of function
cdef np.ndarray[np.uint32_t, ndim=2] tile_array

# in "if counter%4==0":
if tile != tile_specific and tile not in tiles.keys(): # tile_specific is mentiones elsewhere. 
    tiles[tile] = np.zeros(shape=(length, len(solexa_scores) 33), dtype=np.uint32)
tile_array = tiles[tile]  # not a copy! Just two references to exactly the same object

# in "if counter%3==0"
tile_array[n, ord(decoded_line[n])]  =1
  

tile_array = tiles[tile] Просто для проверки типов требуется небольшая стоимость, поэтому, вероятно, это будет полезно, только если вы используете tile_array несколько раз между каждым назначением (трудно точно угадать, каков порог, но сопоставьте его с вашей текущей версией).

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

1. Я не совсем понимаю, почему это быстрее, но из-за того, что он длится вечно, он теперь заканчивается через 17 секунд для тех же входных данных. Спасибо! Я надеюсь, что скоро смогу понять концепцию memoryview.

2. Больше всего Cython ускоряет индексацию отдельных элементов массивов: например tile_array[n, ord(decoded_line[n])] =1 . Это изменяется с двух довольно медленных вызовов Python (один для получения и один для установки элемента) на одну быструю операцию C. Для этого Cython должен знать тип массива. Вот почему это значительно быстрее.