2014-12-19 2 views
8

Я играл с Cython в рамках подготовки к другой работе. Я попробовал простой тестовый пример и заметил что-то странное в том, как мой код работает для больших размеров проблем. Я создал простую функцию min/max, которая вычисляет min и max массива 2D float32 и сравнивает его с запуском numpy.min(a), numpy.max(a). Для массива из 10000 элементов производительность была одинаковой. Для массива из 1000000 элементов cython выполнялся намного хуже. Вот мой Cython код:Cython vs numpy performance scaling

import numpy 
cimport cython 
cimport numpy 

DTYPE = numpy.float32 
ctypedef numpy.float32_t DTYPE_t 

@cython.boundscheck(False) 
@cython.wraparound(False) 
def minmax_float32(numpy.ndarray[DTYPE_t, ndim=2] arr): 
    cdef DTYPE_t min = arr[0, 0] 
    cdef DTYPE_t max = arr[0, 0] 
    cdef int row_max = arr.shape[0] 
    cdef int col_max = arr.shape[1] 
    cdef int x, y 
    for y in range(row_max): 
     for x in range(col_max): 
      if arr[y, x] < min: 
       min = arr[y, x] 
      if arr[y, x] > max: 
       max = arr[y, x] 

    return min, max 

А вот мой простой выбор времени сделано в IPython:

a = numpy.random.random(10000).reshape((100, 100)).astype(numpy.float32) 
%timeit -r3 -n50 (numpy.min(a), numpy.max(a)) 
# 50 loops, best of 3: 22.2 µs per loop 

%timeit -r3 -n50 minmax_float32(a) 
# 50 loops, best of 3: 23.8 µs per loop 

a = numpy.random.random(1000000).reshape((1000, 1000)).astype(numpy.float32) 
%timeit -r3 -n50 (numpy.min(a), numpy.max(a)) 
# 50 loops, best of 3: 307 µs per loop 

%timeit -r3 -n50 minmax_float32(a) 
# 50 loops, best of 3: 1.22 ms per loop 

307/22.2 
# 13.82882882882883 

1220/23.8 
# 51.26050420168067 

Кто-нибудь есть идеи, почему Cython занимает так много больше времени для увеличения ввода? И это было то, с чем я играл, но если у вас есть какие-то подсказки или уловки, мне интересно их услышать. Заранее спасибо.

Редактирование: Я провел эти тесты на macbook 10.10 с 8 ГБ памяти. Скомпилировал cython с gcc из macports с флагами, указанными в их учебниках -shared -pthread -fPIC -fwrapv -O2 -Wall -fno-strict-aliasing.

+0

Что произойдет, если вы перевернете свои внутренние и внешние петли? – mtrw

+0

Хороший вопрос, он поднимается до ~ 7 мс. Все, что я сделал, это переключить две строки 'for'. – daveydave400

+0

Я предполагаю, что компилятор пытается правильно autovectorize - см. [This] (https://groups.google.com/d/msg/cython-users/LfBH6M7gNTc/B19uFB5YbYYJ). –

ответ

2

Похоже, что NumPy использует инструкции SSE, где доступно для min и max, что означает, что они, вероятно, могут использовать ваше оборудование в гораздо большей степени, чем Cython.

Вот исходный код для уменьшенных реализаций NumPy min и max в SSE: https://github.com/numpy/numpy/blob/master/numpy/core/src/umath/simd.inc.src#L696. Обратите внимание, что они используют препроцессор для автоматического создания кода для нескольких типов данных и операций одновременно.

+0

Я просмотрел источник numpy, но в итоге обнаружил 'loops.c.src', который не имеет никаких оптимизаций. Это определенно похоже, что это может изменить ситуацию. – daveydave400

1

Прежде всего, чтобы избежать путаницы, никогда не рекомендуется использовать встроенные имена функций min и max в качестве имен переменных, поэтому вызывайте fmin и fmax.

В основном это стоит помнить, что NumPy высоко оптимизирован, вы можете попробовать в вашем изменении Cython:

for x in range(col_max): 
     if arr[y, x] < min: 
      min = arr[y, x] 
     if arr[y, x] > max: 
      max = arr[y, x] 

к:

for x in range(col_max): 
     val = arr[y, x] 
     if val < fmin: 
      fmin = val 
     if val > fmax: 
      fmax = val 

и добавив определение: cdef DTYPE_t val

Это уменьшит количество операций индекса массива от 4 до 1.

Вы также можете попробовать использовать:

(fmin, fmax) = (min(fmin, val), max(fmax, val)) 

как это может показать некоторое улучшение.

Вы также можете сделать х, у, row_max и row_min в неподписанных Интс и отключить проверку границ путем добавления функции декоратора @cython.boundscheck(False) # turn of bounds-checking for entire function

Это tutorial стоит читать.

+0

Я пробовал ваши предложения, но ничего не случилось с временем выполнения. Я уже проверил проверку границ и посмотрел на этот учебник (хотя похоже, что он был скопирован в их руководство пользователя, что я и следовал). Я не делал 'cdef DTYPE_t max', потому что я не совсем уверен, что вы имели в виду. Что это будет? – daveydave400

+0

Извините, что typedef должен был быть вал. Отредактировано выше. Это предотвратило бы создание нового вала раз за разом. –