6.19. Performans¶
numpy kütüphanesini kamerada hızlı kılan tasarım kararları – tüm dizi üzerinde çalışan kütüphane çağrıları, sıkıştırılmış tipli arabellekler, kaynağıyla veri paylaşan görünümler – aynı zamanda bilinmesi faydalı bir dizi alışkanlığı da ortaya çıkarır. Şekil ve adımlar sayfası son eksen yerleşim kuralını zaten ele almıştı; bu sayfa ise akışlı (streaming) bir döngüde en çok önem taşıyan ayırma ve dtype alışkanlıklarını listeler.
6.19.1. Makul bir dtype seçin¶
Her kurucunun varsayılan dtype değeri float türüdür. Doğal olarak 8 bit veya 16 bit olan veriler için – ADC örnekleri, görüntü pikselleri, sensör okumaları – dtype= parametresini açıkça tamsayı türlerinden birine ayarlayın:
adc = np.array(adc_samples, dtype=np.uint16)
RAM tasarrufu, 4 baytlık varsayılan float türüne kıyasla uint16 için 2 kat, uint8 için 4 kattır. Ayrıca işlemler daha hızlı çalışır, çünkü numpy içindeki tamsayı kod yolları genel float yollarına göre daha sıkıdır. Dtype’lar sayfasında ele alınan tamsayı taşması kuralı geçerlidir – taşma olasılığı olan aritmetikten önce daha geniş bir türe dönüştürün.
6.19.2. Yinelenebilir yerine ndarray tercih edin¶
Çoğu indirgeme ve evrensel fonksiyon, ya bir yinelenebilir nesneyi ya da bir ndarray nesnesini kabul eder:
np.sum([1, 2, 3, 4, 5]) # works, but slow
np.sum(np.array([1, 2, 3, 4, 5])) # ~3x faster
Yinelenebilir biçim, numpy kütüphanesini girişte her seferinde tek bir Python nesnesi üzerinden ilerlemeye ve kullanmadan önce her birini bir sayıya dönüştürmeye zorlar. Bir ndarray karşısında bu dönüşüm zaten yapılmıştır ve çağrı, sıkıştırılmış arabellek üzerinden doğrudan ilerler.
Aynı veri birden fazla kez kullanıldığında, ndarray nesnesini bir kez oluşturun ve dolaştırın. Veri yalnızca bir Python listesi olarak var olduğunda ve bir kez tüketildiğinde, dönüşüm maliyeti hız kazancından ağır basabilir – array() kurucusunun kendisi de listeyi dolaşmak ve bellek ayırmak zorundadır.
6.19.3. Kopya yerine görünüm tercih edin¶
Dilimleme, daha yüksek dereceli bir dizinin tek eksenli indekslenmesi, reshape(), transpose() ve frombuffer() işlemlerinin tümü, kaynakla veri paylaşan görünümler döndürür. Bunlar esasen bedavadır.
copy(), flatten(), mantıksal indeksleme (a[mask]) ve herhangi bir aritmetik ifade bir kopya ayırır. Bunlara yalnızca gerçekten bağımsız bir arabelleğe ihtiyaç duyulduğunda başvurun.
Şüpheye düştüğünüzde, ndinfo() alttaki arabelleğin konumunu yazdırır; aynı adresi bildiren iki dizi verisini paylaşır. Tam görünüm-kopya tablosu Görünümler ve kopyalar sayfasındadır.
6.19.4. Bir kez ayırın, sonra yazın¶
Kameradaki en büyük tek performans tuzağı, saniyede birçok kez çalışan bir döngünün içinde her seferinde yeni diziler ayırmaktır. Her yeni ndarray kameradan RAM ister ve sık sık yeni ayırmalar yapmak bunu boşa harcar.
Çoğu evrensel fonksiyon out= parametresini kabul eder, böylece sonuç zaten var olan bir diziye yazılabilir:
x = np.linspace(0, 2 * np.pi, num=512)
y = np.zeros(512) # allocate once
while True:
np.sin(x, out=y)
# use y ...
image.Image.to_ndarray() aynı nedenle buffer= parametresini kabul eder; spectrogram() ve from_int32_buffer() tarzı dönüştürücüler hem out= hem de scratchpad= parametrelerini kabul eder. Her şeyi bir kez ayırın ve yeniden kullanın.
6.19.5. Yerinde operatörler kullanın¶
b = b + 1 ifadesi b boyutunda geçici bir alan ayırır, kopyalar ve yeniden atar. b += 1 ise b nesnesini doğrudan değiştirir:
# makes a temporary
b = b + 1
# no temporary
b += 1
Aynı fikir bileşik ifadeler için de geçerlidir. a + b * c ifadesi b * c için geçici bir alan ayırır. İfadeyi, önceden ayrılmış bir arabelleğe yazan basit alt atamalara bölmek bu geçici alanları ortadan kaldırır:
# one temporary for (a + b), another for the ``* 2``
out = (a + b) * 2
# zero temporaries
out[:] = a
out += b
out *= 2
6.19.6. Sonucu oluşturun, ona ekleme yapmayın¶
ndarray nesnesinin bilinçli olarak bir append yöntemi yoktur. Bir diziyi büyütmek, yeni ve daha büyük bir arabellek ayırmak ve eski içeriği ona kopyalamak anlamına gelirdi. Bir mikrodenetleyicide, son boyutu önceden ayırın ve onu doldurun
out = np.zeros(N, dtype=np.float)
for i in range(N):
out[i] = some_calculation(i)
N gerçekten önceden bilinmediğinde, bir Python list nesnesine yazın ve sonunda array() ile bir kez dönüştürün.
6.19.7. Yeni diziler yerine dilim ataması¶
“Başkalarının parçalarından yeni bir dizi oluştur” desenlerinin çoğu, her çağrıda yeni bir ayırma yerine önceden ayrılmış bir arabelleğe dilim ataması olarak ifade edilebilir.
Bir örnek akışı üzerindeki kayan pencere – bir hareketli ortalama filtresinin temeli – bu durumun klasik örneğidir. Arabellek son N örneği tutar; her yineleme en eskisini düşürür ve en yenisini ekler. Bariz biçim her yinelemede arabelleği yeniden oluşturur:
while True:
sample = read_sample()
buf = np.concatenate((buf[1:], # new buffer every loop
np.array([sample])))
avg = np.mean(buf)
Bu, örnek başına yeni bir ayırma – ve N - 1 öğenin bir kopyası – demektir. Dilim atama biçimi ise yerinde kaydırma yapar:
N = 16
buf = np.zeros(N, dtype=np.float) # allocate once
while True:
sample = read_sample()
buf[:-1] = buf[1:] # shift left by one
buf[-1] = sample # append at the end
avg = np.mean(buf)
buf[:-1] = buf[1:] ilginç olan satırdır: aynı arabelleğe iki örtüşen görünüm, sağ taraftaki dilim bir uçtan okunup diğer uca yazılır. numpy, alttaki belleği yerinde kaydırmayı güvenli kılacak sırada dolaşır. Döngünün içinde hiçbir zaman yeni bir dizi ayrılmaz.
6.19.8. Akışlı döngülerde mantıksal maskelere dikkat edin¶
Mantıksal indeksleme ve where(), her çağrıda yeni bir dizi üretir – sonucun boyutu veriye bağlı olduğundan, önceden ayrılmış hiçbir arabellek bu ayırmayı emecek nitelikte değildir. Akışlı bir döngüde tekrar tekrar maske oluşturmak RAM’i atılacak dizilerle doldurur. Periyodik bir gc.collect() çağrısı bu alanı geri kazanır:
import gc
for i in range(1000):
mask = a < threshold
_ = a[mask]
if i % 100 == 0:
gc.collect()
Aynı uyarı, (a > lo) & (a < hi) gibi bileşik mantıksal ifadeler için de geçerlidir – her operatör yeni bir bool dizisi ayırır. Bir maske yeniden kullanıldığında, onu bir kez oluşturun ve saklayın:
mask = a < threshold
foo[mask] = 0
bar[mask] = 1