Comme expliqué par d' autres, ce n'est pas simplement copier les références , mais augmente également les comptes de référence à l' intérieur des objets et ainsi les objets sont accessibles et le cache joue un rôle.
Ici, je veux juste ajouter plus d'expériences. Pas tellement à propos de shuffled vs unshuffled (où accéder à un élément peut manquer le cache mais obtenir les éléments suivants dans le cache afin qu'ils soient touchés). Mais à propos des éléments répétitifs, où les accès ultérieurs du même élément peuvent atteindre le cache car l'élément est toujours dans le cache.
Test d'une plage normale:
>>> from timeit import timeit
>>> a = range(10**7)
>>> [timeit(lambda: list(a), number=100) for _ in range(3)]
[5.1915339142808925, 5.1436351868889645, 5.18055115701749]
Une liste de la même taille mais avec un seul élément répété encore et encore est plus rapide car elle atteint le cache tout le temps:
>>> a = [0] * 10**7
>>> [timeit(lambda: list(a), number=100) for _ in range(3)]
[4.125743135926939, 4.128927210087596, 4.0941229388550795]
Et peu importe de quel numéro il s'agit:
>>> a = [1234567] * 10**7
>>> [timeit(lambda: list(a), number=100) for _ in range(3)]
[4.124106479141709, 4.156590225249886, 4.219242600790949]
Fait intéressant, cela devient encore plus rapide lorsque je répète à la place les mêmes deux ou quatre éléments:
>>> a = [0, 1] * (10**7 / 2)
>>> [timeit(lambda: list(a), number=100) for _ in range(3)]
[3.130586101607932, 3.1001001764957294, 3.1318465707127814]
>>> a = [0, 1, 2, 3] * (10**7 / 4)
>>> [timeit(lambda: list(a), number=100) for _ in range(3)]
[3.096105435911994, 3.127148431279352, 3.132872673690855]
Je suppose que quelque chose n'aime pas que le même compteur unique augmente tout le temps. Peut-être que certains pipelines se bloquent parce que chaque augmentation doit attendre le résultat de l'augmentation précédente, mais c'est une supposition sauvage.
Quoi qu'il en soit, essayez ceci pour un nombre encore plus grand d'éléments répétés:
from timeit import timeit
for e in range(26):
n = 2**e
a = range(n) * (2**25 / n)
times = [timeit(lambda: list(a), number=20) for _ in range(3)]
print '%8d ' % n, ' '.join('%.3f' % t for t in times), ' => ', sum(times) / 3
La sortie (la première colonne est le nombre d'éléments différents, pour chaque je teste trois fois puis je prends la moyenne):
1 2.871 2.828 2.835 => 2.84446732686
2 2.144 2.097 2.157 => 2.13275338734
4 2.129 2.297 2.247 => 2.22436720645
8 2.151 2.174 2.170 => 2.16477771575
16 2.164 2.159 2.167 => 2.16328197911
32 2.102 2.117 2.154 => 2.12437970598
64 2.145 2.133 2.126 => 2.13462250728
128 2.135 2.122 2.137 => 2.13145065221
256 2.136 2.124 2.140 => 2.13336283943
512 2.140 2.188 2.179 => 2.1688431668
1024 2.162 2.158 2.167 => 2.16208440826
2048 2.207 2.176 2.213 => 2.19829998424
4096 2.180 2.196 2.202 => 2.19291917834
8192 2.173 2.215 2.188 => 2.19207065277
16384 2.258 2.232 2.249 => 2.24609975704
32768 2.262 2.251 2.274 => 2.26239771771
65536 2.298 2.264 2.246 => 2.26917420394
131072 2.285 2.266 2.313 => 2.28767871168
262144 2.351 2.333 2.366 => 2.35030805124
524288 2.932 2.816 2.834 => 2.86047313113
1048576 3.312 3.343 3.326 => 3.32721167007
2097152 3.461 3.451 3.547 => 3.48622758473
4194304 3.479 3.503 3.547 => 3.50964316455
8388608 3.733 3.496 3.532 => 3.58716466865
16777216 3.583 3.522 3.569 => 3.55790996695
33554432 3.550 3.556 3.512 => 3.53952594744
Donc, à partir d'environ 2,8 secondes pour un seul élément (répété), il tombe à environ 2,2 secondes pour 2, 4, 8, 16, ... éléments différents et reste à environ 2,2 secondes jusqu'à la centaine de milliers. Je pense que cela utilise mon cache L2 (4 × 256 Ko, j'ai un i7-6700 ).
Puis en quelques étapes, les temps passent à 3,5 secondes. Je pense que cela utilise un mélange de mon cache L2 et de mon cache L3 (8 Mo) jusqu'à ce qu'il soit également "épuisé".
À la fin, il reste à environ 3,5 secondes, je suppose que mes caches n'aident plus avec les éléments répétés.