Почему ускорение кода не работает с Cython? - PullRequest
1 голос
/ 10 апреля 2020

Мне нужно ускорить этот код до 4 миллисекунд.

import numpy as np



def return_call(data):
    num = int(data.shape[0] / 4096)
    buff_spectrum  = np.empty(2048,dtype= np.uint64)
    buff_detect =  np.empty(2048,dtype= np.uint64)
    end_spetrum = np.empty(num*1024,dtype=np.uint64)
    end_detect = np.empty(num*1024,dtype= np.uint64)
    _data = np.reshape(data,(num,4096))

    for _raw_data_spec in _data:
        raw_data_spec = np.reshape(_raw_data_spec,(2048,2))
        for i in range(2048):
            buff_spectrum[i] = (np.int16(raw_data_spec[i][0])<<17)|(np.int16(raw_data_spec[i][1] <<1))>>1
            buff_detect[i] = (np.int16(raw_data_spec[i][0])>>15)
        for i in range (511,-1,-1):
            if buff_spectrum[i+1024] != 0:
                end_spetrum[i]=(np.log10(buff_spectrum[i+1024]))
                end_detect[i]=buff_detect[i+1024]
            else:
                end_spetrum[i] =0
                end_detect[i] = 0
        for i in range(1023, 511, -1):
            if buff_spectrum[i+1024] != 0:
                end_spetrum[i] = (np.log10(buff_spectrum[i + 1024]))
                end_detect[i] = buff_detect[i + 1024]
            else:
                end_spetrum[i] = 0
                end_detect[i] = 0

    return end_spetrum, end_detect

Я решил использовать Cython для этой задачи. Но я не получил никакого ускорения.

import numpy as np
cimport numpy


ctypedef signed short DTYPE_t
cpdef return_call(numpy.ndarray[DTYPE_t, ndim=1] data):
    cdef int i
    cdef int num = data.shape[0]/4096
    cdef numpy.ndarray _data

    cdef numpy.ndarray[unsigned long long, ndim=1] buff_spectrum  = np.empty(2048,dtype= np.uint64)
    cdef numpy.ndarray[ unsigned long long, ndim=1] buff_detect =  np.empty(2048,dtype= np.uint64)
    cdef numpy.ndarray[double , ndim=1] end_spetrum = np.empty(num*1024,dtype= np.double)
    cdef numpy.ndarray[double , ndim=1] end_detect = np.empty(num*1024,dtype= np.double)
    _data = np.reshape(data,(num,4096))

    for _raw_data_spec in _data:
        raw_data_spec = np.reshape(_raw_data_spec,(2048,2))
        for i in range(2048):
            buff_spectrum[i] = (np.uint16(raw_data_spec[i][0])<<17)|(np.uint16(raw_data_spec[i][1] <<1))>>1
            buff_detect[i] = (np.uint16(raw_data_spec[i][0])>>15)
        for i in range (511,-1,-1):
            if buff_spectrum[i+1024] != 0:
                end_spetrum[i]=(np.log10(buff_spectrum[i+1024]))
                end_detect[i]=buff_detect[i+1024]
            else:
                end_spetrum[i] =0
                end_detect[i] = 0
        for i in range(1023, 511, -1):
            if buff_spectrum[i+1024] != 0:
                end_spetrum[i] = (np.log10(buff_spectrum[i + 1024]))
                end_detect[i] = buff_detect[i + 1024]
            else:
                end_spetrum[i] = 0
                end_detect[i] = 0

    return end_spetrum, end_detect

Максимальная скорость, которую я достиг, - 80 миллисекунд, но она мне нужна намного быстрее. Так как вам нужно обрабатывать данные из железа практически в реальном времени, скажите мне причину. И реально ли c добиться желаемых результатов. Я также прилагаю код для тестового файла.


import numpy as np
import example_original
import example_cython
data = np.empty(8192*2, dtype=np.int16)
import time
startpy = time.time()


example_original.return_call(data)
finpy = time.time() -startpy
startcy = time.time()
k,r = example_cython.return_call(data)
fincy = time.time() -startcy
print( fincy, finpy)
print('Cython is {}x faster'.format(finpy/fincy))

Ответы [ 3 ]

1 голос
/ 11 апреля 2020
raw_data_spec = np.reshape(_raw_data_spec,(2048,2))

raw_data_spec не набрано. В начале функции добавьте определение для нее. Я рекомендую более новый синтаксис памяти (но используйте старый синтаксис numpy, если хотите):

cdef DTYPE_t[:,:] raw_data_spec

Эта строка (которую вы определили как bottle -neck) - беспорядок :

buff_spectrum[i] = (np.int16(raw_data_spec[i][0])<<17)|(np.int16(raw_data_spec[i][1] <<1))>>1
  1. Выполняйте индексацию за один шаг, а не за два: raw_data_spec[i, 0] (обратите внимание на одну партию скобок и запятую).

  2. Пересмотрите приведение к 16-битному целому числу. Действительно ли имеет смысл сдвигать 16-битное целое число на 17 бит ?

  3. Возможно, вам вообще не нужно приведение, поскольку данные будут известны будет DTYPE_t, но если вы действительно хотите использовать приведение, используйте угловые скобки: <numpy.uint16_t>(raw_data_spec[i, 0])


Рассмотрите возможность отключения boundscheck и wraparound. Убедитесь, что это безопасно и что вы не полагаетесь на исключения, чтобы сообщить, когда индексируете за пределами массива или используете отрицательную индексацию. Делайте это только после размышления, а не автоматически «автомобилем go культа».

cimport cython    

@cython.boundscheck(False)
@cython.wraparound(False)
cpdef return_call(numpy.ndarray[DTYPE_t, ndim=1] data):

Отбросьте вызовы на np.log10. Это целый вызов Python для одного элемента, который оказывается неэффективным. Вместо этого вы можете использовать математические функции стандартной библиотеки C:

from libc.math cimport log10

, а затем заменить np.log10 на log10.

1 голос
/ 10 апреля 2020

Я думаю, что основной причиной этого может быть то, что в вашем python коде почти не было python операций, и все это было numpy операций. Большая часть кода numpy написана в C. Часть из них написана на Фортране. Многое написано в Python. Хорошо написанный код numpy сопоставим по скорости с кодом C.

0 голосов
/ 10 апреля 2020

У меня не так много опыта работы с Cython, так что это всего лишь пример того, что время должно быть возможно и с Cython.

Пример

import numpy as np
import numba as nb

@nb.njit(cache=True)
def return_call(data_in):
    #If the input is not contigous the reshape will fail
    #-> make a c-contigous copy if the array isn't c-contigous
    data=np.ascontiguousarray(data_in)

    num = int(data.shape[0] / 4096)
    buff_spectrum  = np.zeros(2048,dtype= np.uint64)
    buff_detect =  np.zeros(2048,dtype= np.uint64)
    end_spetrum = np.zeros(num*1024,dtype=np.float64)
    end_detect = np.zeros(num*1024,dtype= np.float64)
    _data = np.reshape(data,(num,4096))

    #for _raw_data_spec in _data: is not supported
    #but the followin works
    for x in range(_data.shape[0]):
        raw_data_spec = np.reshape(_data[x],(2048,2))
        for i in range(2048):
            buff_spectrum[i] = (np.int16(raw_data_spec[i][0])<<17)|(np.int16(raw_data_spec[i][1] <<1))>>1
            buff_detect[i] = (np.int16(raw_data_spec[i][0])>>15)
        for i in range (511,-1,-1):
            if buff_spectrum[i+1024] != 0:
                end_spetrum[i]=(np.log10(buff_spectrum[i+1024]))
                end_detect[i]=buff_detect[i+1024]
            else:
                end_spetrum[i] =0
                end_detect[i] = 0
        for i in range(1023, 511, -1):
            if buff_spectrum[i+1024] != 0:
                end_spetrum[i] = (np.log10(buff_spectrum[i + 1024]))
                end_detect[i] = buff_detect[i + 1024]
            else:
                end_spetrum[i] = 0
                end_detect[i] = 0

    return end_spetrum, end_detect

Сроки

data = np.random.rand(8192*2)*20
data=data.astype(np.int16)

#with compilation
%timeit end_spetrum, end_detect=return_call(data)
#32.7 µs ± 5.61 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)

#without compilation
%timeit end_spetrum, end_detect=return_call_orig(data)
#106 ms ± 448 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
...