L'utilisation de la insert
fonction d' une liste est beaucoup plus lente que l'obtention du même effet en utilisant l'attribution de tranche:
> 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
(Notez que ce a=[]
n'est que la configuration, a
commence donc vide, puis augmente à 100 000 éléments.)
Au début, je pensais que c'était peut-être la recherche d'attribut ou la surcharge d'appel de fonction, mais l'insertion près de la fin montre que c'est négligeable:
> python -m timeit -n 100000 -s "a=[]" "a.insert(-1,0)"
100000 loops, best of 5: 79.1 nsec per loop
Pourquoi la fonction dédiée "insérer un seul élément" est-elle tellement plus lente?
Je peux également le reproduire sur 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)
J'utilise Python 3.8.1 32 bits sur Windows 10 64 bits.
repl.it utilise Python 3.8.1 64 bits sur Linux 64 bits.
python
performance
Débordement de tas
la source
la source
a=[]; a[0:0]=[0]
fait la même chose quea=[]; a[100:200]=[0]
a=[1,2,3];a[100:200]=[4]
est apposent4
à la fin de la listea
intéressante.a=[]; a[0:0]=[0]
ou quia[0:0]=[0]
fait la même chose quea[100:200]=[0]
...Réponses:
Je pense qu'il est probablement juste qu'ils ont oublié d'utiliser
memmove
danslist.insert
. Si vous regardez le codelist.insert
utilisé pour déplacer les éléments, vous pouvez voir que ce n'est qu'une boucle manuelle:tandis que
list.__setitem__
sur le chemin d'affectation de tranche utilisememmove
:memmove
a généralement beaucoup d'optimisation, comme tirer parti des instructions SSE / AVX.la source
-O3
la vectorisation automatique activée, cette boucle manuelle pourrait se compiler efficacement. Mais à moins que le compilateur reconnaisse la boucle comme étant un memmove et le compile en un appel réel àmemmove
, il ne peut que profiter des extensions de jeu d'instructions activées au moment de la compilation. (Très bien si vous construisez le vôtre avec-march=native
, pas tant pour les binaires de distribution construits avec la ligne de base). Et GCC ne déroulera pas les boucles par défaut sauf si vous utilisez PGO (-fprofile-generate
/ run /...-use
)memmove
appel réel , qui peut alors profiter de toutes les extensions présentes au moment de l'exécution?cpuid
). Idem pour plusieurs autres fonctions mem / str. Ainsi, les distributions peuvent être compilées avec juste-O2
pour créer des binaires exécutables n'importe où, mais au moins, memcpy / memmove utilise une boucle AVX non chargée chargeant / stockant 32 octets par instruction. (Ou même AVX512 sur les quelques processeurs où c'est une bonne idée; je pense que Xeon Phi.)memmove
versions se trouvent là dans libc.so, la bibliothèque partagée. Pour chaque fonction, l'envoi s'effectue une seule fois, lors de la résolution du symbole (liaison anticipée ou lors du premier appel avec liaison paresseuse traditionnelle). Comme je l'ai dit, il ne fait que surcharger / accrocher la façon dont la liaison dynamique se produit, pas en encapsulant la fonction elle-même. (spécifiquement via le mécanisme ifunc de GCC: code.woboq.org/userspace/glibc/sysdeps/x86_64/multiarch/… ). EN RELATION: pour memset, le choix habituel sur les processeurs modernes est de__memset_avx2_unaligned_erms
voir ce Q&R