2013-07-22 21 views
5

Python'a nispeten yeniyim ve iç içe geçmiş bir döngüye sahibim. Döngülerin çalışması için biraz zaman aldığından, bu kodu daha hızlı çalışabilmesi için bu kodu değiştirmenin bir yolunu bulmaya çalışıyorum.Döngüler için vektörlendirme NumPy

Bu durumda, kord koordinatının [x, 0, 0] ve koordinatının [x, 0, 1] tamsayı ve koordinatın [x, 0, 2] 0 veya 1 olduğu 3 boyutlu bir dizidir. H, bir SciPy seyrek matrisidir ve x_dist, y_dist, z_dist ve a'nın hepsi yüzer. çok daha hızlı, geçici dizilerden bellek kullanımı büyük miktarda yol açabilir ederken

# x_dist, y_dist, and z_dist are floats 
# coord is a num x 1 x 3 numpy array where num can go into the hundreds of thousands 
num = coord.shape[0]  
H = sparse.lil_matrix((num, num)) 
for i in xrange(num): 
    for j in xrange(num): 
     if (np.absolute(coord[i, 0, 0] - coord[j, 0, 0]) <= 2 and 
       (np.absolute(coord[i, 0, 1] - coord[j, 0, 1]) <= 1)): 

      x = ((coord[i, 0, 0] * x_dist + coord[i, 0, 2] * z_dist) - 
       (coord[j, 0, 0] * x_dist + coord[j, 0, 2] * z_dist)) 

      y = (coord[i, 0, 1] * y_dist) - (coord[j, 0, 1] * y_dist) 

      if a - 0.5 <= np.sqrt(x ** 2 + y ** 2) <= a + 0.5: 
       H[i, j] = -2.7 

Ben de numpy ile bu yayın okudum. Vektörizasyon rotasına gitmek veya Cython gibi bir şeyi kullanmak daha iyi olur mu?

cevap

2

Hesaplamanın niteliği, aşina olduğum sayısız yöntemlerle vektörü zorlaştırır. Hız ve hafıza kullanımı açısından en iyi çözümün cython olacağını düşünüyorum. Ancak, numba'u kullanarak biraz hız kazanabilirsiniz. Bu çıktıyı almak

import numpy as np 
from scipy import sparse 
import time 
from numba.decorators import autojit 
x_dist=.5 
y_dist = .5 
z_dist = .4 
a = .6 
coord = np.random.normal(size=(1000,1000,1000)) 

def run(coord, x_dist,y_dist, z_dist, a): 
    num = coord.shape[0]  
    H = sparse.lil_matrix((num, num)) 
    for i in xrange(num): 
     for j in xrange(num): 
      if (np.absolute(coord[i, 0, 0] - coord[j, 0, 0]) <= 2 and 
        (np.absolute(coord[i, 0, 1] - coord[j, 0, 1]) <= 1)): 

       x = ((coord[i, 0, 0] * x_dist + coord[i, 0, 2] * z_dist) - 
        (coord[j, 0, 0] * x_dist + coord[j, 0, 2] * z_dist)) 

       y = (coord[i, 0, 1] * y_dist) - (coord[j, 0, 1] * y_dist) 

       if a - 0.5 <= np.sqrt(x ** 2 + y ** 2) <= a + 0.5: 
        H[i, j] = -2.7 
    return H 

runaj = autojit(run) 

t0 = time.time() 
run(coord,x_dist,y_dist, z_dist, a) 
t1 = time.time() 
print 'First Original Runtime:', t1 - t0 

t0 = time.time() 
run(coord,x_dist,y_dist, z_dist, a) 
t1 = time.time() 
print 'Second Original Runtime:', t1 - t0 

t0 = time.time() 
run(coord,x_dist,y_dist, z_dist, a) 
t1 = time.time() 
print 'Third Original Runtime:', t1 - t0 

t0 = time.time() 
runaj(coord,x_dist,y_dist, z_dist, a) 
t1 = time.time() 
print 'First Numba Runtime:', t1 - t0 

t0 = time.time() 
runaj(coord,x_dist,y_dist, z_dist, a) 
t1 = time.time() 
print 'Second Numba Runtime:', t1 - t0 

t0 = time.time() 
runaj(coord,x_dist,y_dist, z_dist, a) 
t1 = time.time() 
print 'Third Numba Runtime:', t1 - t0 

: İşte bir örnek (normalde bir dekoratör olarak autojit kullanmak notu) 'dir

First Original Runtime: 21.3574919701 
Second Original Runtime: 15.7615520954 
Third Original Runtime: 15.3634860516 
First Numba Runtime: 9.87108802795 
Second Numba Runtime: 9.32944011688 
Third Numba Runtime: 9.32300305367 
+0

Bahşiş için teşekkürler! Ancak, bunu bir komut dosyasına (@autojit dekoratörünü kullanarak) koymaya ve IPython (% timeit% run Test.py) ile zamanlamaya çalıştığımda, normal Python'dan daha yavaş sonuçlar elde ederim. Bunun neden olduğu hakkında bir fikrin var mı? – sonicxml

+0

@sonicxml Bu ilginç. Örneğimde olduğu gibi aynı verileri mi kullanıyorsunuz? Autojit'in, ona geçirdiğiniz her yeni veri türü için işlevinizi derlemesi gerekiyor ve bunu çalışma zamanında yapıyor. Bu nedenle, küçük örnekler için derleme süresinden dolayı daha yavaş olabilir. Kullanmakta olduğunuz örnekte sorun olabilir mi? – jcrudy

+0

Ahh tamam. Sadece test etmek için daha küçük bir dizi çalıştırıyordum, ama şimdi diziyi daha büyük hale getirdim, numba python'dan çok daha hızlı oluyor. – sonicxml

5

Bu daha sonra, uyarılar üzerine bazı tartışmalar kodunuzu vektörize nasıl olduğunu : Eğer şekil (num, num) ait olacağı gibi konular, ilk idx dizisi ile geleceksin kaydetti, bu nedenle wil gibi

import numpy as np 
import scipy.sparse as sps 

idx = ((np.abs(coord[:, 0, 0] - coord[:, 0, 0, None]) <= 2) & 
     (np.abs(coord[:, 0, 1] - coord[:, 0, 1, None]) <= 1)) 

rows, cols = np.nonzero(idx) 
x = ((coord[rows, 0, 0]-coord[cols, 0, 0]) * x_dist + 
    (coord[rows, 0, 2]-coord[cols, 0, 2]) * z_dist) 
y = (coord[rows, 0, 1]-coord[cols, 0, 1]) * y_dist 
r2 = x*x + y*y 

idx = ((a - 0.5)**2 <= r2) & (r2 <= (a + 0.5)**2) 

rows, cols = rows[idx], cols[idx] 
data = np.repeat(2.7, len(rows)) 

H = sps.coo_matrix((data, (rows, cols)), shape=(num, num)).tolil() 

Muhtemelen num'un "yüz binlerce kişiye" olması durumunda hafızanızı parçalara ayırırım.

Sorununuzu çözmeniz, sorununuzu yönetilebilir parçalara ayırmaktır. 100.000 elemanlık bir diziniz varsa, bunu 1000 öğeden oluşan 100 parçaya bölebilirsiniz ve 10,000 bileşimin her biri için yukarıdaki kodun değiştirilmiş bir sürümünü çalıştırabilirsiniz. Sadece 1.000.000 elemanlık idx dizisine (daha iyi performans için önceden ayırabileceğiniz ve yeniden kullanabileceğiniz) ihtiyacınız olacaktır ve mevcut uygulamanızın 10.000.000.000'inin yerine yalnızca 10.000 yinelemenin bir döngüsüne sahip olursunuz. Bu, çok çekirdekli bir makineye sahipseniz, paralel olarak işlenen bu parçalardan birkaçına sahip olmakla gerçekten iyileştirebileceğiniz fakir bir erkeğin paralelleştirme şemasıdır.

+0

Vay! Bu benim orijinal kodumdan çok daha hızlı. Parçalar ile ilgili olarak: orijinal kodumda, koordinattaki her noktayı, kordinattaki diğer tüm noktalarla karşılaştırıyorum. Belki bunu özlüyorum, ama kodu parçalara böldüğümde, noktaları yığınlar arasında nasıl karşılaştırabilirim? – sonicxml