6.19. Performa¶
Keputusan desain yang sama yang membuat numpy cepat di kamera -- panggilan library seluruh-array, buffer bertipe padat, tampilan yang berbagi data dengan sumbernya -- juga mengungkap sekumpulan kebiasaan yang perlu diketahui. Halaman Shape dan strides sudah membahas aturan tata letak sumbu terakhir; halaman ini mendaftar kebiasaan alokasi dan dtype yang paling penting dalam loop streaming.
6.19.1. Pilih dtype yang tepat¶
Dtype default setiap konstruktor adalah float. Untuk data yang secara alami 8-bit atau 16-bit -- sampel ADC, piksel citra, pembacaan sensor -- berikan dtype= secara eksplisit ke salah satu tipe integer:
adc = np.array(adc_samples, dtype=np.uint16)
Penghematan RAM adalah 2x untuk uint16 dan 4x untuk uint8 dibanding float 4-byte default. Operasi matematika juga berjalan lebih cepat karena jalur kode integer di dalam numpy lebih ringkas daripada jalur float generik. Aturan overflow integer yang dibahas di Dtype berlaku -- cast ke tipe yang lebih lebar sebelum aritmetika yang mungkin overflow.
6.19.2. Lebih baik gunakan ndarray daripada iterable¶
Sebagian besar reduksi dan fungsi universal menerima iterable maupun ndarray
np.sum([1, 2, 3, 4, 5]) # works, but slow
np.sum(np.array([1, 2, 3, 4, 5])) # ~3x faster
Bentuk iterable memaksa numpy untuk melangkah melalui input satu objek Python pada satu waktu, mengonversi setiap elemen ke angka sebelum dapat menggunakannya. Dengan ndarray, konversi sudah selesai dan panggilan langsung berjalan melalui buffer padat.
Ketika data yang sama digunakan lebih dari sekali, bangun ndarray sekali dan teruskan. Ketika data hanya ada sebagai Python list dan dikonsumsi sekali, biaya konversi bisa melebihi percepatan -- konstruktor array() sendiri harus menelusuri list dan mengalokasikan.
6.19.3. Lebih baik gunakan view daripada copy¶
Slicing, pengindeksan sumbu tunggal dari array berperingkat lebih tinggi, reshape(), transpose(), dan frombuffer() semuanya mengembalikan view yang berbagi data dengan sumbernya. Semuanya pada dasarnya gratis.
copy(), flatten(), pengindeksan boolean (a[mask]), dan ekspresi aritmetika apa pun mengalokasikan copy. Gunakan hanya saat buffer independen benar-benar diperlukan.
Jika ragu, ndinfo() mencetak lokasi buffer yang mendasari; dua array yang melaporkan alamat yang sama berbagi datanya. Tabel lengkap view-vs-copy ada di View dan copy.
6.19.4. Alokasi sekali, lalu tulis¶
Jebakan performa terbesar pada kamera adalah mengalokasikan array baru di dalam loop yang berjalan berkali-kali per detik. Setiap ndarray baru meminta RAM dari kamera, dan alokasi baru yang sering memboroskannya.
Sebagian besar fungsi universal menerima out= sehingga hasilnya dapat ditulis ke dalam array yang sudah ada:
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() menerima buffer= untuk alasan yang sama; spectrogram() dan konverter gaya from_int32_buffer() menerima baik out= maupun scratchpad=. Alokasikan semuanya sekali dan gunakan kembali.
6.19.5. Gunakan operator in-place¶
b = b + 1 mengalokasikan sementara sebesar b, menyalin, dan menetapkan ulang. b += 1 memodifikasi b secara langsung:
# makes a temporary
b = b + 1
# no temporary
b += 1
Ide yang sama berlaku untuk ekspresi majemuk. a + b * c mengalokasikan sementara untuk b * c. Memecah ekspresi menjadi sub-penugasan sederhana yang menulis ke buffer yang sudah dialokasikan menghilangkan sementara-sementara:
# one temporary for (a + b), another for the ``* 2``
out = (a + b) * 2
# zero temporaries
out[:] = a
out += b
out *= 2
6.19.6. Bangun hasilnya, jangan ditambahkan¶
ndarray tidak memiliki append -- dengan sengaja. Memperbesar array berarti mengalokasikan buffer yang lebih besar dan baru, lalu menyalin isi lama ke dalamnya. Pada mikrokontroler, pre-alokasikan ukuran akhir dan isi itu:
out = np.zeros(N, dtype=np.float)
for i in range(N):
out[i] = some_calculation(i)
Ketika N memang tidak diketahui terlebih dahulu, tulis ke Python list dan konversi sekali di akhir dengan array().
6.19.7. Penugasan slice daripada array baru¶
Banyak pola "bangun array baru dari potongan array lain" dapat diekspresikan sebagai penugasan slice ke dalam buffer yang sudah dialokasikan daripada alokasi baru setiap kali.
Rolling window atas aliran sampel -- fondasi filter rata-rata bergerak -- adalah kasus kanonik. Buffer menyimpan N sampel terakhir; setiap iterasi membuang yang terlama dan menambahkan yang terbaru. Bentuk yang jelas membangun ulang buffer setiap iterasi:
while True:
sample = read_sample()
buf = np.concatenate((buf[1:], # new buffer every loop
np.array([sample])))
avg = np.mean(buf)
Itu adalah alokasi baru -- dan salinan N - 1 elemen -- per sampel. Bentuk penugasan slice menggeser di tempat:
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:] adalah baris yang menarik: dua view yang tumpang tindih ke buffer yang sama, slice kanan dibaca dari satu ujung dan ditulis ke ujung lainnya. numpy menelusuri memori yang mendasari dalam urutan yang membuat pergeseran in-place aman. Tidak ada array baru yang pernah dialokasikan di dalam loop.
6.19.8. Waspadai boolean mask dalam loop streaming¶
Pengindeksan boolean dan where() menghasilkan array baru pada setiap panggilan -- ukuran hasilnya bergantung pada data, sehingga tidak ada buffer yang sudah dialokasikan yang dapat menyerap alokasi tersebut. Pembangunan mask berulang dalam loop streaming memenuhi RAM dengan array sekali pakai. gc.collect() berkala mengklaim kembali ruang:
import gc
for i in range(1000):
mask = a < threshold
_ = a[mask]
if i % 100 == 0:
gc.collect()
Peringatan yang sama berlaku untuk ekspresi boolean majemuk seperti (a > lo) & (a < hi) -- setiap operator mengalokasikan array bool baru. Ketika mask digunakan kembali, bangun sekali dan simpan:
mask = a < threshold
foo[mask] = 0
bar[mask] = 1