Eu esperava array.array
ser mais rápido que as listas, pois as matrizes parecem estar fora da caixa.
No entanto, recebo o seguinte resultado:
In [1]: import array
In [2]: L = list(range(100000000))
In [3]: A = array.array('l', range(100000000))
In [4]: %timeit sum(L)
1 loop, best of 3: 667 ms per loop
In [5]: %timeit sum(A)
1 loop, best of 3: 1.41 s per loop
In [6]: %timeit sum(L)
1 loop, best of 3: 627 ms per loop
In [7]: %timeit sum(A)
1 loop, best of 3: 1.39 s per loop
Qual poderia ser a causa dessa diferença?
python
arrays
performance
boxing
python-internals
Valentin Lorentz
fonte
fonte
array
pacote. Se você deseja fazer quantidades significativas de matemática, o Numpy opera na velocidade da luz (ou seja, C), e geralmente melhor do que implementações ingênuas de coisas comosum()
).array
é muito rápido na conversão de uma sequência de números inteiros (representando bytes ASCII) em umstr
objeto. O próprio Guido só conseguiu isso depois de muitas outras soluções e ficou bastante surpreso com o desempenho. Enfim, este é o único lugar em que me lembro de vê-lo sendo útil.numpy
é muito melhor para lidar com matrizes, mas é uma dependência de terceiros.Respostas:
O armazenamento está "fora da caixa", mas toda vez que você acessa um elemento, o Python precisa "encaixotá-lo" (incorporá-lo em um objeto Python comum) para fazer qualquer coisa com ele. Por exemplo, você
sum(A)
itera sobre a matriz e encaixa cada número inteiro, um de cada vez, em umint
objeto Python comum . Isso custa tempo. No seusum(L)
, todo o boxe foi feito no momento em que a lista foi criada.Portanto, no final, uma matriz geralmente é mais lenta, mas requer substancialmente menos memória.
Aqui está o código relevante de uma versão recente do Python 3, mas as mesmas idéias básicas se aplicam a todas as implementações do CPython desde que o Python foi lançado.
Aqui está o código para acessar um item da lista:
Há muito pouco:
somelist[i]
apenas retorna oi
'ésimo objeto na lista (e todos os objetos Python no CPython são ponteiros para uma estrutura cujo segmento inicial está em conformidade com o layout de astruct PyObject
).E aqui está a
__getitem__
implementação para umarray
código de tipol
:A memória bruta é tratada como um vetor de
C
long
números inteiros nativos da plataforma ; oi
thC long
é lido; e entãoPyLong_FromLong()
é chamado para quebrar ("box") o nativoC long
em umlong
objeto Python (que, no Python 3, que elimina a distinção do Python 2 entreint
elong
, é realmente mostrado como tipoint
).Esse boxe precisa alocar nova memória para um
int
objeto Python e pulverizar osC long
bits do nativo nele. No contexto do exemplo original, a vida útil desse objeto é muito curta (apenas o tempo suficiente parasum()
adicionar o conteúdo a um total em execução) e, em seguida, é necessário mais tempo para desalocar o novoint
objeto.É daí que a diferença de velocidade vem, sempre veio e sempre virá na implementação do CPython.
fonte
Para adicionar à excelente resposta de Tim Peters, as matrizes implementam o protocolo de buffer , enquanto as listas não. Isso significa que, se você estiver escrevendo uma extensão C (ou o equivalente moral, como escrever um módulo Cython ), poderá acessar e trabalhar com os elementos de uma matriz muito mais rapidamente do que qualquer coisa que o Python possa fazer. Isso fornecerá melhorias consideráveis na velocidade, possivelmente bem acima de uma ordem de magnitude. No entanto, ele tem várias desvantagens:
Indo direto para extensões C pode estar usando uma marreta para golpear uma mosca, dependendo do seu caso de uso. Você deve primeiro investigar o NumPy e verificar se ele é poderoso o suficiente para fazer qualquer matemática que você esteja tentando fazer. Também será muito mais rápido que o Python nativo, se usado corretamente.
fonte
Tim Peters respondeu por que isso é lento, mas vamos ver como melhorá- lo.
Seguindo o seu exemplo de
sum(range(...))
(fator 10 menor que o seu exemplo para caber na memória aqui):Dessa forma, o numpy também precisa encaixotar / descompactar, o que tem sobrecarga adicional. Para torná-lo mais rápido, é necessário permanecer dentro do código c numpy:
Portanto, da solução da lista para a versão numpy, esse é um fator 16 no tempo de execução.
Vamos também verificar quanto tempo a criação dessas estruturas de dados leva
Vencedor claro: Numpy
Observe também que a criação da estrutura de dados leva tanto tempo quanto a soma, se não mais. A alocação de memória está lenta.
Uso de memória daqueles:
Portanto, esses dados levam 8 bytes por número, com sobrecarga variável. Para o intervalo que usamos, as entradas de 32 bits são suficientes, para que possamos guardar alguma memória.
Mas acontece que a adição de ints de 64 bits é mais rápida que a de 32 bits na minha máquina, então isso só vale a pena se você estiver limitado pela memória / largura de banda.
fonte
observe que
100000000
é igual a10^8
não10^7
, e meus resultados são os seguintes:fonte