Menggunakan insert
fungsi daftar jauh lebih lambat daripada mencapai efek yang sama menggunakan tugas slice:
> python -m timeit -n 100000 -s "a=[]" "a.insert(0,0)"
100000 loops, best of 5: 19.2 usec per loop
> python -m timeit -n 100000 -s "a=[]" "a[0:0]=[0]"
100000 loops, best of 5: 6.78 usec per loop
(Perhatikan bahwa a=[]
ini hanya pengaturan, jadi a
mulai kosong tetapi kemudian tumbuh hingga 100.000 elemen.)
Pada awalnya saya pikir mungkin itu atribut lookup atau function call overhead atau lebih, tetapi menyisipkan di dekat akhir menunjukkan bahwa itu dapat diabaikan:
> python -m timeit -n 100000 -s "a=[]" "a.insert(-1,0)"
100000 loops, best of 5: 79.1 nsec per loop
Mengapa fungsi "sisipkan satu elemen" khusus yang mungkin lebih sederhana sangat lambat?
Saya juga dapat mereproduksi di repl.it :
from timeit import repeat
for _ in range(3):
for stmt in 'a.insert(0,0)', 'a[0:0]=[0]', 'a.insert(-1,0)':
t = min(repeat(stmt, 'a=[]', number=10**5))
print('%.6f' % t, stmt)
print()
# Example output:
#
# 4.803514 a.insert(0,0)
# 1.807832 a[0:0]=[0]
# 0.012533 a.insert(-1,0)
#
# 4.967313 a.insert(0,0)
# 1.821665 a[0:0]=[0]
# 0.012738 a.insert(-1,0)
#
# 5.694100 a.insert(0,0)
# 1.899940 a[0:0]=[0]
# 0.012664 a.insert(-1,0)
Saya menggunakan Python 3.8.1 32-bit pada Windows 10 64-bit.
repl.it menggunakan Python 3.8.1 64-bit di Linux 64-bit.
python
performance
Heap Overflow
sumber
sumber
a=[]; a[0:0]=[0]
melakukan hal yang samaa=[]; a[100:200]=[0]
a=[1,2,3];a[100:200]=[4]
menambahkan4
ke akhir daftar yanga
menarik.a=[]; a[0:0]=[0]
atau yanga[0:0]=[0]
melakukan hal yang samaa[100:200]=[0]
...Jawaban:
Saya pikir itu mungkin hanya bahwa mereka lupa untuk menggunakan
memmove
dilist.insert
. Jika Anda melihat kode yanglist.insert
digunakan untuk menggeser elemen, Anda bisa melihatnya hanya loop manual:saat
list.__setitem__
di jalur penugasan irisan menggunakanmemmove
:memmove
biasanya memiliki banyak optimasi yang dimasukkan ke dalamnya, seperti memanfaatkan instruksi SSE / AVX.sumber
-O3
auto-vektorisasi diaktifkan, loop manual yang mungkin dikompilasi secara efisien. Tetapi kecuali jika kompiler mengenali loop sebagai memmove dan mengkompilasinya menjadi panggilan aktualmemmove
, ia hanya dapat memanfaatkan ekstensi set instruksi yang diaktifkan pada waktu kompilasi. (Baik jika Anda sedang membangun dengan Anda sendiri-march=native
, tidak terlalu banyak untuk biner distro yang dibangun dengan baseline). Dan GCC tidak akan membuka gulungan secara default kecuali jika Anda menggunakan PGO (-fprofile-generate
/ run /...-use
)memmove
panggilan aktual , yang kemudian dapat mengambil keuntungan dari semua ekstensi yang ada pada waktu eksekusi?cpuid
). Sama untuk beberapa fungsi mem / str lainnya. Jadi distro dapat dikompilasi dengan hanya-O2
untuk membuat binari run-dimanapun, tetapi setidaknya memcpy / memmove menggunakan AVX loop memuat / menyimpan 32 byte per instruksi. (Atau bahkan AVX512 pada beberapa CPU di mana itu ide yang bagus; Saya pikir hanya Xeon Phi.)memmove
versi duduk di sana di libc.so, pustaka bersama. Untuk setiap fungsi, pengiriman terjadi satu kali, selama resolusi simbol (penjilidan awal atau pada panggilan pertama dengan lazy binding tradisional). Seperti yang saya katakan, itu hanya overloads / kait bagaimana hubungan dinamis terjadi, bukan dengan membungkus fungsi itu sendiri. (khusus melalui mekanisme ifunc GCC: code.woboq.org/userspace/glibc/sysdeps/x86_64/multiarch/… ). Terkait: untuk memset pilihan yang biasa pada CPU modern adalah__memset_avx2_unaligned_erms
lihat T&J ini