Просмотр памяти Cython может быть медленным? - PullRequest
2 голосов
/ 01 апреля 2019

У меня есть некоторый код, который должен выполнять вычисления только для подмножества строк двумерного массива.Например, если массив скажет:

[[0.5, 0.5, 0.5]
[0.9, 0.2, 0.8]
[0.9, 0.2, 0.8],
[0.9, 0.2, 0.2]]

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

Cython может ускорить итерацию, но некоторые эксперименты, похоже, предполагают, что использование более новых результатов API "memoryviews" APIв большом количестве дополнительных выделений (я предполагаю объекты-оболочки представления памяти?), что делает его значительно медленнее, чем старый API np.ndarray, и в некоторых случаях медленнее, чем ванильный Python.

Вот минимальный примерэто демонстрирует это:

в файле my_cython.pyx:

# cython: profile=True

import numpy as np
cimport numpy as np

def cython_test1(
    np.ndarray vectors,
    np.ndarray target_vector):

    cdef Py_ssize_t i

    for i in range(1000000):
        x = np.dot(target_vector, vectors[i])

def cython_test2(
    np.float32_t[:,:] vectors,
    np.float32_t[:] target_vector):

    cdef Py_ssize_t i

    for i in range(1000000):
        x = np.dot(target_vector, vectors[i])

Сравнительный анализ с ipython REPL:

from my_cython import cython_test1, cython_test2
import numpy as np 
vectors = np.random.random((1000000, 100)).astype(np.float32)
target_vector = np.random.random(100).astype(np.float32)
%timeit cython_test1(vectors, target_vector)
%timeit cython_test2(vectors, target_vector)

cython_test1: 462 ms ± 2.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

cython_test2: 1.23 s ± 8.82 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

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

Запуск с подключенным профилировщиком:

import pstats, cProfile 
cProfile.runctx("cython_test2(vectors, target_vector)", globals(), locals(), "Profile.prof") 
s = pstats.Stats("Profile.prof") 
s.strip_dirs().sort_stats("time").print_stats() 

Результат:

1    2.070    2.070    3.263    3.263 speedy.pyx:52(cython_test2)
2000000    0.705    0.000    0.867    0.000 stringsource:995(memoryview_fromslice)
4000000    0.155    0.000    0.155    0.000 stringsource:514(__getbuffer__)
2000002    0.090    0.000    0.090    0.000 stringsource:372(__dealloc__)
2000002    0.082    0.000    0.082    0.000 stringsource:345(__cinit__)
2000000    0.081    0.000    0.081    0.000 stringsource:972(__dealloc__)
2000000    0.080    0.000    0.080    0.000 stringsource:555(__get__)
...

Может быть, создается новый вид среза для каждого 1D-вектора, доступ к которому осуществляется из 2D-матрицы?Если так, возможно, ответ «не используйте представления памяти в этом конкретном случае».Но это, кажется, немного расходится с документацией, в которой говорится, что новый API быстрее и предполагает, что это всегда предпочтительнее.

...