Como explicaron otros, no se trata solo de copiar las referencias, sino que también aumenta el recuento de referencias dentro de los objetos y, por lo tanto, se accede a los objetos y la caché juega un papel.
Aquí solo quiero agregar más experimentos. No tanto sobre barajado vs no barajado (donde acceder a un elemento puede perder el caché pero obtener los siguientes elementos en el caché para que sean golpeados). Pero sobre la repetición de elementos, donde los accesos posteriores del mismo elemento pueden afectar la caché porque el elemento todavía está en la caché.
Probando un rango normal:
>>> from timeit import timeit
>>> a = range(10**7)
>>> [timeit(lambda: list(a), number=100) for _ in range(3)]
[5.1915339142808925, 5.1436351868889645, 5.18055115701749]
Una lista del mismo tamaño pero con un solo elemento repetido una y otra vez es más rápida porque llega a la caché todo el tiempo:
>>> a = [0] * 10**7
>>> [timeit(lambda: list(a), number=100) for _ in range(3)]
[4.125743135926939, 4.128927210087596, 4.0941229388550795]
Y no parece importar qué número sea:
>>> a = [1234567] * 10**7
>>> [timeit(lambda: list(a), number=100) for _ in range(3)]
[4.124106479141709, 4.156590225249886, 4.219242600790949]
Curiosamente, se vuelve aún más rápido cuando, en cambio, repito los mismos dos o cuatro elementos:
>>> 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]
Supongo que a algo no le gusta que el mismo contador se incremente todo el tiempo. Tal vez alguna tubería se detenga porque cada aumento tiene que esperar el resultado del aumento anterior, pero esta es una suposición descabellada.
De todos modos, intente esto para un número aún mayor de elementos repetidos:
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
El resultado (la primera columna es el número de elementos diferentes, para cada uno pruebo tres veces y luego tomo el promedio):
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
Entonces, de aproximadamente 2.8 segundos para un solo elemento (repetido), cae a aproximadamente 2.2 segundos para 2, 4, 8, 16, ... elementos diferentes y permanece en aproximadamente 2.2 segundos hasta los cien mil. Creo que esto usa mi caché L2 (4 × 256 KB, tengo un i7-6700 ).
Luego, en unos pocos pasos, los tiempos suben a 3,5 segundos. Creo que esto usa una mezcla de mi caché L2 y mi caché L3 (8 MB) hasta que eso también se "agota".
Al final, se mantiene en unos 3,5 segundos, supongo que porque mis cachés ya no ayudan con los elementos repetidos.