Подтвердить что ты не робот

Никакой прирост скорости от Cython

Я пытаюсь определить функцию, содержащую внутренний цикл для моделирования интеграла.

Проблема - это скорость. Оценка функции однажды может занять до 30 секунд на моей машине. Поскольку моя конечная цель - свести к минимуму эту функцию, какая-то дополнительная скорость будет приятной.

Таким образом, я пытался заставить Cython работать, но я должен совершить серьезную ошибку (вероятно, многие из них!). Следуя документации Cython, я попытался ввести мои переменные. После этого код работает так же медленно, как чистый Python. Это кажется странным.

Вот мой код:

import numpy as np 
cimport cython
cimport numpy as np
import minuit

data = np.genfromtxt('q6data.csv', usecols = np.arange(1, 24, 1), delimiter = ',')  

cdef int ns    = 1000                 # Number of simulation draws
cdef int K     = 5                    # Number of observed characteristics, including            constant
cdef int J     = len(data[:,1])       # Number of products, including outside
cdef double tol   = 0.0001            # Inner GMM loop tolerance
nu = np.random.normal(0, 1, (6, ns))  # ns random deviates

@cython.boundscheck(False)
@cython.wraparound(False)


def S(np.ndarray[double, ndim=1] delta, double s1, double s2, double s3, double s4,  double s5, double a):
    """Computes the simulated integrals, one for each good.
    Parameters: delta is an array of length J containing mean product specific utility levels
    Returns: Numpy array with length J."""
    cdef np.ndarray[double, ndim=2] mu_ij = np.dot((data[:,2:7]*np.array([s1, s2, s3, s4, s5])), nu[1:K+1,:])
    cdef np.ndarray[double, ndim=2] mu_y  = a * np.log(np.exp(data[:,21].reshape(J,1) +  data[:,22].reshape(J,1)*nu[0,:].reshape(1, ns)) - data[:,7].reshape(J,1))
    cdef np.ndarray[double, ndim=2] V = delta.reshape(J,1) + mu_ij + mu_y
    cdef np.ndarray[double, ndim=2] exp_vi = np.exp(V)
    cdef np.ndarray[double, ndim=2] P_i = (1.0 / np.sum(exp_vi[np.where(data[:,1] == 71)], 0)) * exp_vi[np.where(data[:,1] == 71)] 
    cdef int yrs = 19
    cdef int yr
    for yr in xrange(yrs):
        P_yr = (1.0 / np.sum(exp_vi[np.where(data[:,1]== (yr + 72))], 0)) *   exp_vi[np.where(data[:,1] == (yr + 72))]
        P_i  = np.concatenate((P_i, P_yr)) 
    cdef np.ndarray[double, ndim=1] S = np.zeros(dtype = "d", shape = J)
    cdef int j
    for j in xrange(ns):
        S += P_i[:,j]
    return (1.0 / ns) * S

def d_infty(np.ndarray[double, ndim=1] x, np.ndarray[double, ndim=1] y):
    """Sup norm."""
    return np.max(np.abs(x - y)) 

def T(np.ndarray[double, ndim=1] delta_exp, double s1, double s2, double s3, double s4,  double s5, double a):
    """The contraction operator.  This function takes the parameters and the exponential
    of the starting value of delta and returns the fixed point.""" 
    cdef int iter = 0
    cdef int maxiter = 200
    cdef int i
    for i in xrange(maxiter): 
        delta1_exp = delta_exp * (data[:, 8] / S(np.log(delta_exp), s1, s2, s3, s4, s5, a))                    
        print i
        if d_infty(delta_exp, delta1_exp) < tol:                                       
            break
        delta_exp = delta1_exp
    return np.log(delta1_exp)


def Q(double s1, double s2, double s3, double s4, double s5, double a):
    """GMM objective function."""  
    cdef np.ndarray[double, ndim=1] delta0_exp = np.exp(data[:,10])                                                     
    cdef np.ndarray[double, ndim=1] delta1 = T(delta0_exp, s1, s2, s3, s4, s5, a)
    delta1[np.where(data[:,10]==0)] = np.zeros(len(np.where(data[:,10]==0)))            
    cdef np.ndarray[double, ndim=1] xi =  delta1 - (np.dot(data[:,2:7],   np.linalg.lstsq(data[:,2:7], delta1)[0]))   
    cdef np.ndarray[double, ndim=2] g_J = xi.reshape(J,1) * data[:,11:21]
    cdef np.ndarray[double, ndim=1] G_J = (1.0 / J) * np.sum(g_J, 0) 
    return np.sqrt(np.dot(G_J, G_J))

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

Кто-нибудь видит вопиющую ошибку в коде Cython, которая может привести к такому результату?

О, и поскольку я очень новичок в программировании, наверняка много плохого стиля и вещей, замедляющих код. Если у вас есть время, не стесняйтесь устанавливать меня прямо в этих точках.

4b9b3361

Ответ 1

Cython может создать html файл, чтобы помочь с этим:

cython -a MODULE.py

Это показывает каждую строку исходного кода, окрашенную в белый цвет, с различными оттенками желтого. Чем темнее желтый цвет, тем более динамичное поведение Python все еще выполняется на этой линии. Для каждой строки, содержащей желтый, вам нужно добавить более статические объявления для ввода текста.

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

Без этого легко упускать некоторые статические объявления типов переменных, вызовов функций или операторов. (например, оператор индексирования x [y] по-прежнему является полностью динамической операцией Python, если вы не заявляете иначе)

Ответ 2

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

В частности, если вы хотите улучшить производительность петель, вам нужно избегать вызова функций Python в них, что в этом случае вам предстоит сделать много (все вызовы np. - это вызовы Python, нарезка и, возможно, другие вещи).

См. эту страницу для общих рекомендаций по оптимизации производительности с Cython (ключ -a действительно удобен при оптимизации) и этот для конкретных особенностей при оптимизации кода numpy.

Ответ 3

Вы можете определенно ускорить свой код, используя больше возможностей Numpy.

Например:

cdef np.ndarray[double, ndim=1] S = np.zeros(dtype = "d", shape = J)
cdef int j
for j in xrange(ns):
    S += P_i[:,j]

будет намного быстрее и четче, чем

S = P_i.sum(axis=1)

Вы также повторяете некоторые вычисления, которые, таким образом, занимают в два раза больше времени, чем необходимо. Например,

np.where(data[:,1]==(yr + 72))

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

Вы также выполняете много изменений и нарезки: это может помочь получить переменные в более удобном формате с самого начала. Если возможно, ваш код будет намного понятнее, и оптимизация может быть гораздо более очевидной.

Ответ 4

Будет ли профилировщик помочь вам определить, какая часть работает медленно? Мне нравится запускать программы, используя стандартный профилировщик библиотеки:

python -O -m cProfile -o profile.out MYAPP.py

а затем просмотрите вывод из этого в GUI "RunSnakeRun":

runsnake profile.out

Здесь можно установить RunSnakeRun: http://www.vrplumber.com/programming/runsnakerun/

RunSnakeRun screenshot

Ответ 5

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

Я немного профилировал код и имею лучшее представление о том, какие части являются самыми медленными. Я дополнительно определил

X = data[:, 2:7]
m_y = data[:, 21].reshape(J,1)
sigma_y = 1.0
price = data[:, 7].reshape(J, 1)
shares_data = data[:,8]

Тогда это следующие строки, которые потребляют большую часть общего времени.

mu_ij = np.dot((X*np.array([s1, s2, s3, s4, s5])), nu[1:K+1,:])
mu_y  = a * np.log(np.exp(m_y + sigma_y*nu[0,:].reshape(1,ns)) - price)
V = delta.reshape(J,1) + mu_ij + mu_y
exp_vi = np.exp(V)
P_i = (1.0 / np.sum(exp_vi[np.where(data[:,1]==71)], 0)) *  exp_vi[np.where(data[:,1]==71)] 
for yr in xarange(19):
    P_yr = (1.0 / np.sum(exp_vi[np.where(data[:,1]==yr)], 0)) * exp_vi[np.where(data[:,1]==yr)]
P_i  = np.concatenate((P_i, P_yr))

У меня создается впечатление, что это слишком громоздкий способ достичь моей цели. Я надеялся, что кто-то сможет дать некоторые советы о том, как ускорить эти линии. Может быть, есть возможности Numpy, которых я не хватает? Если эта проблема недостаточно хорошо для вас, чтобы быть полезной, я был бы рад предоставить более подробную информацию о контексте моей проблемы. Спасибо!

Ответ 6

Только разделение данных, после вашего комментария 28 ноября:

import sys
import time
import numpy as np

def splitdata( data, n, start=1971 ):
    """ split data into n pieces, where col 1 == start .. start + n """
        # not fancy, fast enough for small n
    split = n * [None]
    for j in range(n):
        split[j] = data[ data[:,1] == start + j ]
    return split  # [ arrays: col1 0, col1 1 ... ]

#...........................................................................
N = 2237
ncol = 21
start = 1971
n = 20
seed = 1
exec "\n".join( sys.argv[1:] )  # run this.py N= ...
np.set_printoptions( 2, threshold=100, suppress=True )  # .2f
np.random.seed(seed)

print "N=%d  ncol=%d  n=%d" % (N, ncol, n)
data = np.random.uniform( start, start + n, (N,ncol) )
data[:,1] = data[:,1].round()
t0 = time.time()

split = splitdata( data, n, start )  # n pieces

print "time: %.0f us splitdata" % ((time.time() - t0) * 1e6)
for y, yeardata in enumerate(split):
    print "%d  %d  %g" % (start + y, len(yeardata), yeardata[:,0].sum())

- >

time: 27632 us splitdata  # old mac ppc
1971  69  136638
1972  138  273292
...

Ответ 7

"Основная ошибка" заключается в том, что вы ожидаете хорошей производительности в длинных циклах с Python. Это интерпретируемый язык, и переход между реализациями и ctyping ничего не делает для этого. Существует несколько числовых библиотек Python для быстрых вычислений, написанных на языке C. Например, если вы уже используете numpy для массивов, почему бы вам не пойти дальше и использовать scipy для своей передовой математики? Это увеличит как читаемость, так и скорость.