Como explicado por outros, não se trata apenas de copiar as referências, mas também aumenta as contagens de referência dentro dos objetos e, portanto, os objetos são acessados e o cache desempenha um papel.
Aqui, eu só quero adicionar mais experimentos. Não tanto sobre embaralhado versus não embaralhado (onde acessar um elemento pode perder o cache, mas obter os seguintes elementos no cache para que sejam atingidos). Mas sobre elementos de repetição, onde acessos posteriores do mesmo elemento podem atingir o cache porque o elemento ainda está no cache.
Testando uma faixa 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]
Uma lista do mesmo tamanho, mas com apenas um elemento repetido várias vezes, é mais rápida porque atinge o cache o tempo todo:
>>> a = [0] * 10**7
>>> [timeit(lambda: list(a), number=100) for _ in range(3)]
[4.125743135926939, 4.128927210087596, 4.0941229388550795]
E não parece importar qual é o número:
>>> a = [1234567] * 10**7
>>> [timeit(lambda: list(a), number=100) for _ in range(3)]
[4.124106479141709, 4.156590225249886, 4.219242600790949]
Curiosamente, fica ainda mais rápido quando, em vez disso, repito os mesmos dois ou quatro 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]
Acho que algo não gosta que o mesmo contador único esteja sempre aumentando. Talvez um pouco pipeline pare porque cada aumento precisa esperar pelo resultado do aumento anterior, mas esse é um palpite.
De qualquer forma, tentando fazer isso para um número ainda maior 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
A saída (a primeira coluna é o número de elementos diferentes, para cada um testo três vezes e depois tiro a média):
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
Portanto, de cerca de 2,8 segundos para um único elemento (repetido), ele cai para cerca de 2,2 segundos para 2, 4, 8, 16, ... elementos diferentes e permanece em cerca de 2,2 segundos até os cem mil. Eu acho que isso usa meu cache L2 (4 × 256 KB, eu tenho um i7-6700 ).
Depois de alguns passos, o tempo sobe para 3,5 segundos. Eu acho que isso usa uma mistura de meu cache L2 e meu cache L3 (8 MB) até que esteja "esgotado" também.
No final, ele fica em torno de 3,5 segundos, acho que porque meus caches não ajudam mais com os elementos repetidos.