2014-12-19 12 views
8

Bawiłem się z Cython w ramach przygotowań do innych prac. Wypróbowałem prosty przypadek testowy i zauważyłem coś dziwnego w sposobie, w jaki mój kod radzi sobie z większymi problemami. Stworzyłem prostą funkcję min/max, która oblicza min i maksimum tablicy 2D float32 i porównała ją do działania numpy.min(a), numpy.max(a). Dla tablicy 10000 elementów wydajność była podobna. Dla tablicy 1000000 elementów, cyton działał znacznie gorzej. Oto mój kod Cython:Cython vs numpy wydajność skalowania

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 

A oto mój prosty rozrządu wykonane w 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 

Czy ktoś ma pomysły dlaczego tak Cython trwa znacznie dłużej dla większego wkładu? I to było po prostu coś, z czym grałem, ale jeśli masz jakieś wskazówki lub sztuczki, chciałbym je usłyszeć. Z góry dziękuję.

Edit: Pobiegłem te testy na macbook 10.10 z 8 GB pamięci. Skompilowałem cyton z gcc z macports z flagami wymienionymi w ich tutorialach -shared -pthread -fPIC -fwrapv -O2 -Wall -fno-strict-aliasing.

+0

Co się stanie, jeśli odwrócisz wewnętrzne i zewnętrzne pętle? – mtrw

+0

Dobre pytanie, trwa do ~ 7 ms. Wszystko, co zrobiłem, to przełączenie dwóch linii 'for'. – daveydave400

+0

Zgaduję, że kompilator stara się prawidłowo autovectorize - patrz [to] (https://groups.google.com/d/msg/cython-users/LfBH6M7gNTc/B19uFB5YbYYJ). –

Odpowiedz

2

Wygląda na to, że NumPy używa instrukcji SSE tam, gdzie są dostępne dla min i max, co oznacza, że ​​mogą one znacznie lepiej wykorzystać twój sprzęt w znacznie większym stopniu niż może to osiągnąć Cython.

Oto kod źródłowy implementacji redukcji NumPy w min i max w SSE: https://github.com/numpy/numpy/blob/master/numpy/core/src/umath/simd.inc.src#L696. Zauważ, że używają preprocesora do automatycznego generowania kodu dla wielu typów danych i operacji jednocześnie.

+0

Przeszukałem odręczne źródło, ale w końcu znalazłem 'loops.c.src', który nie ma żadnych optymalizacji. Wygląda na to, że może to coś zmienić. – daveydave400

1

pierwsze, aby uniknąć nieporozumień nigdy nie jest dobry pomysł, aby skorzystać z wbudowanych w nazwach funkcji MIN i MAX jako nazwy zmiennych tak nazwać fmin i fmax.

Zasadniczo warto pamiętać, że numpy jest wysoce zoptymalizowany, można także spróbować w zmieniających 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] 

do:

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

i dodając definicję: cdef DTYPE_t val

w ten sposób zmniejsza się liczbę operacji indeksu tablicy 4 do 1.

Możesz także spróbować użyć:

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

ponieważ może to pokazać pewną poprawę.

Można również dokonać X, Y, row_max i row_min do niepodpisanych wskazówki i wyłączyć sprawdzanie granic dodając dekorator funkcji @cython.boundscheck(False) # turn of bounds-checking for entire function

Ten tutorial jest warta przeczytania.

+0

Próbowałem twoich sugestii, ale nic się nie stało z czasem wykonania. Sprawdziłem już granice i sprawdziłem ten samouczek (chociaż wygląda na to, że został skopiowany do ich instrukcji obsługi, a to właśnie śledziłem). Nie zrobiłem 'cdef DTYPE_t max', ponieważ nie jestem naprawdę pewien co masz na myśli. Co by to zrobiło? – daveydave400

+0

Niestety, typedef miał być val. Edytowane powyżej. Zapobiegnie to tworzeniu nowego czasu za każdym razem. –