Por que [ ] é mais rápido que list() em Python?
Qual das opções você prefere para criar listas em Python? Você ficará surpreso em saber que as duas opções não funcionam exatamente da mesma maneira.
Em alguns programas, costumamos inicializar as variáveis em um determinado trecho do código, de modo a melhorar a legibilidade no momento que a variável será usada. Algumas pessoas costumam usar o construtor list( ) para criar listas vazias, outros preferem criar listas vazias utilizando somente os colchetes []. Vamos comparar qual das duas opções tem uma performance melhor.
Para essa tarefa, utilizaremos o comando mágico %timeit, ele executa o comando uma vez e depois realiza a mesma operação um determinado número de vezes e mede o tempo para essa execução em segundos. O número de execuções padrão é de 1 milhão.
Segundo os nossos testes, []
executou mais de três vezes mais rápido que o list()
, foram 31.4 ns por loop utilizando os colchetes e 96.5 utilizando o list( ). Agora vamos testar se algo muda ao utilizarmos uma lista com conteúdo.
Praticamente o mesmo resultado, cerca de 80.2 ns para criar a lista com colchetes e 215 ns para criar utilizando o list().
Essa diferença de performance ocorre pois o []
é a sintaxe literal para o python. O Python pode criar um bytecode diretamente para criar a lista. Para entendermos melhor como isso funciona, vamos usar o módulo dis
.
Esse módulo nos dar suporte para analisarmos o bytecode através do disassembling do código em Python de alto nível.
Como podemos ver na figura acima, o nome list( ) funciona como uma variável separada e precisa ser resolvida pelo compilador. A pilha de execução é então chamada e precisa executar etapa por estava até a chamada da função. Tudo isso leva mais tempo que o utilização direta dos colchetes.
A execução da chamadaLOAD_NAME
seguida daCALL_FUNCTION
acaba consumindo muito mais tempo de processamento, apesar de produzirem o mesmo resultado ao final.
Vejam o disassembling para uma lista com valores numéricos:
O bytecode dos dois métodos são completamente diferentes, o que justifica a diferença de performance.
Quando utilizamos o []
, o bytecode mostra que somente duas etapas principais:
BUILD_LIST
— Constróia a lista em pythonRETURN_VALUE
— Retorna o valor da lista
O interpretador trata a expressão []
de modo direto, sem enrolação.
Já no caso do construtor list():
LOAD_NAME
— Tenta encontrar o objeto “list” em todos os escopos de variáveis possíveis.CALL_FUNCTION
— Chama a função "lista" para construir a lista em PythonRETURN_VALUE
— Retorna o valor da lista construída.
Todas as vezes que executamos algum comando com um nome, o interpretador do Python irá buscar por esse nome nos escopos de variáveis existentes. Essa busca respeita uma ordem como Escopo Local-> Global-> Built-in.
Essa busca certamente consumirá um tempo. Vejam o exemplo do bytecode de um print simples usando uma variável cujo o conteúdo é abcde e a string diretamente. Em uma operação o interpretador precisa carregar a constante 'abcde' e na outra operação precisa buscar o nome "variavel".
Essa mesma situação ocorre quando você utilizar o construtor dict contra o { } para um dictionary vazio. Há uma discussão sobre qual dos modos é mais pythonic para criar listas e dictionary vazios, mas o mais importante é ficar atento e não acabar criando uma variável do tipo set :
aqui_temos_set = {5}
temos_dict = {}
O primeiro criar uma variável do tipo set (conjunto) com um elemento. A segunda linha cria um dictionary vazio.
Agora que você já sabe, escolha criar listas com [ ] ao invés de list( ) e sempre que tiver curiosisdade, utilize o módulo dis para verificar como determinada linha se transforma em bytecode.
Para mais conteúdo, acesse @aprendadatascience ou o site https://aprendadatascience.com