O que é mais eficiente no Python em termos de uso de memória e consumo de CPU - Dicionário ou Objeto?
Antecedentes: tenho que carregar uma quantidade enorme de dados no Python. Eu criei um objeto que é apenas um contêiner de campo. Criar instâncias da 4M e colocá-las em um dicionário levou cerca de 10 minutos e ~ 6 GB de memória. Depois que o dicionário estiver pronto, acessá-lo é um piscar de olhos.
Exemplo: Para verificar o desempenho, escrevi dois programas simples que fazem o mesmo - um está usando objetos, outro dicionário:
Objeto (tempo de execução ~ 18s):
class Obj(object):
def __init__(self, i):
self.i = i
self.l = []
all = {}
for i in range(1000000):
all[i] = Obj(i)
Dicionário (tempo de execução ~ 12seg):
all = {}
for i in range(1000000):
o = {}
o['i'] = i
o['l'] = []
all[i] = o
Pergunta: Estou fazendo algo errado ou o dicionário é apenas mais rápido que o objeto? Se de fato o dicionário tem um desempenho melhor, alguém pode explicar o porquê?
fonte
Respostas:
Você já tentou usar
__slots__
?A partir da documentação :
Isso economiza tempo e memória?
Comparando as três abordagens no meu computador:
test_slots.py:
test_obj.py:
test_dict.py:
test_namedtuple.py (suportado em 2.6):
Execute o benchmark (usando o CPython 2.5):
Usando o CPython 2.6.2, incluindo o teste de tupla nomeado:
Então, sim (não é realmente uma surpresa), usar
__slots__
é uma otimização de desempenho. O uso de uma tupla nomeada tem desempenho semelhante ao__slots__
.fonte
O acesso ao atributo em um objeto usa o acesso ao dicionário nos bastidores - portanto, usando o acesso ao atributo, você está adicionando uma sobrecarga extra. Além disso, no caso do objeto, você está enfrentando uma sobrecarga adicional devido a, por exemplo, alocações adicionais de memória e execução de código (por exemplo, do
__init__
método).No seu código, se
o
for umaObj
instância,o.attr
é equivalente ao.__dict__['attr']
uma pequena quantidade de sobrecarga extra.fonte
o.__dict__["attr"]
é aquele com sobrecarga extra, tendo um op extra de bytecode; obj.attr é mais rápido. (Claro acesso atributo não vai ser mais lento do que o acesso de assinatura - é, um caminho de código altamente otimizado crítica.)Você já pensou em usar um nomeado duplo ? ( link para python 2.4 / 2.5 )
É a nova maneira padrão de representar dados estruturados que fornece o desempenho de uma tupla e a conveniência de uma classe.
A única desvantagem em comparação com os dicionários é que (como tuplas), não permite alterar atributos após a criação.
fonte
Aqui está uma cópia da resposta @hughdbrown para python 3.6.1. Aumentei a contagem 5x e adicionei algum código para testar a pegada de memória do processo python no final de cada execução.
Antes que os downvotores o façam, lembre-se de que esse método de contagem do tamanho dos objetos não é preciso.
E esses são meus resultados
Minha conclusão é:
fonte
Resultados:
fonte
Não há dúvida.
Você tem dados, sem outros atributos (sem métodos, nada). Portanto, você tem um contêiner de dados (neste caso, um dicionário).
Normalmente, prefiro pensar em termos de modelagem de dados . Se houver algum problema de desempenho enorme, posso desistir de algo na abstração, mas apenas por boas razões.
Programar é gerenciar a complexidade e manter a abstração correta é muitas vezes uma das maneiras mais úteis de obter esse resultado.
Sobre as razões pelas quais um objeto é mais lento, acho que sua medida não está correta.
Você está executando muito poucas atribuições dentro do loop for e, portanto, o que vê é o tempo diferente necessário para instanciar um ditado (objeto intrínseco) e um objeto "personalizado". Embora da perspectiva da linguagem sejam iguais, eles têm uma implementação bem diferente.
Depois disso, o tempo de atribuição deve ser quase o mesmo para ambos, pois no final os membros são mantidos dentro de um dicionário.
fonte
Existe ainda outra maneira de reduzir o uso de memória se a estrutura de dados não deve conter ciclos de referência.
Vamos comparar duas classes:
e
Tornou-se possível, pois as
structclass
classes baseadas em não suportam a coleta de lixo cíclica, o que não é necessário nesses casos.Há também uma vantagem sobre a
__slots__
classe baseada em: você pode adicionar atributos extras:fonte
Aqui estão minhas execuções de teste do script muito bom de @ Jarrod-Chesney. Para comparação, também o executo no python2 com "range" substituído por "xrange".
Por curiosidade, também adicionei testes semelhantes com OrderedDict (ordenict) para comparação.
Python 3.6.9:
Python 2.7.15+:
Portanto, nas duas versões principais, as conclusões de @ Jarrod-Chesney ainda estão boas.
fonte