Desempenho Python: loops nativos NumPy, callbacks e custos de conversão
Um teste numérico controlado apenas por CPU segue valores idênticos através de loops CPython, aritmética NumPy, um invólucro de callback Python e um loop Numba compilado.
Um teste numérico controlado apenas por CPU segue valores idênticos através de loops CPython, aritmética NumPy, um invólucro de callback Python e um loop Numba compilado. O NumPy com entrada pronta tinha uma grande vantagem, enquanto a conversão de e para listas Python reduziu essa vantagem para um empate. O episódio explica o limite de execução e como medir o resultado que uma aplicação precisa.
Ler a edição escrita (Inglês) ↗
O que este vídeo aborda
- O CPython compila o código fonte em bytecode. O trabalho repetido do interpretador/objeto e um loop numérico nativo tipado são arranjos de execução diferentes.
- A aritmética numérica NumPy pode entregar lotes tipados a operações compiladas; o loop aritmético ainda existe, e as visualizações podem ter layouts não contíguos.
- Meça a conversão e a representação de saída necessária. Nesta execução, a conversão de listas Python em arrays e vice-versa apagou a grande vantagem do array pronto.
- Pequenas cargas de trabalho podem favorecer o Python comum porque os custos fixos de chamada de array importam. A compreensão de oito valores ganhou esta comparação medida.
- np.vectorize é um invólucro de conveniência que chama Python para elementos sucessivos; uma interface de lote por si só não estabelece a execução numérica nativa.
- Loops compilados de sintaxe Python também podem ser rápidos. Inclua os custos de compilação/configuração a frio quando eles importam para o chamador.
- Execução nativa, CPU SIMD, multithreading e backends de GPU são ideias separadas. As funcionalidades de CPU disponíveis não rastreiam as instruções usadas numa execução particular.
Transcrição traduzida
Traduzido da narração original em inglês. Áudio e legendas disponíveis são controlados pelo YouTube.
Por que o mesmo CPU pode terminar mais cedo?
0:00 Acha que o Python precisa de uma nova linguagem para ser rápido. O mesmo CPU executou o nosso lote mais rapidamente depois que o loop se moveu. Para onde foi o trabalho? Quando é que o aumento de velocidade desaparece? Qual atalho traz o Python de volta? Este é o The Daily Diff, nos bastidores. Fique atento a esse atalho. CPython é a implementação padrão do Python.
0:17 Ele compila o seu código-fonte em bytecode e depois executa essas instruções num interpretador. NumPy é uma biblioteca numérica cujos arrays armazenam valores com um tipo de dados declarado. A sua aritmética embutida pode entregar um lote inteiro a operações compiladas. Pegue um milhão de valores de ponto flutuante, eleve cada um ao quadrado, depois adicione o valor original. Cada versão obtém valores idênticos e produz respostas correspondentes. Os valores são pequenos o suficiente para manter as diferenças de arredondamento fora da corrida.
0:40 Nesta máquina, a compreensão levou cerca de trinta e três milissegundos. NumPy levou menos de um. Ambos executaram na CPU. Isso é uma evidência útil para esta carga de trabalho, e um obituário terrível para uma linguagem de programação. Comece com o loop comum.
Que trabalho um loop Python repete?
0:53 O Python obtém o próximo valor, executa a aritmética e anexa a resposta. Depois faz isso de novo. A multiplicação é barata. Organizar a próxima multiplicação e lidar com os objetos Python à sua volta, adiciona trabalho a cada vez. Esta é a desmontagem real da função testada. Iteração, multiplicação e adição aparecem como instruções de bytecode. As versões e interpretadores Python fazem escolhas diferentes.
1:14 Esses tempos pertencem à versão em tela, que é Python três ponto dez. Uma lista Python armazena referências a objetos, para que diferentes tipos de coisas possam partilhar uma lista. O nosso trabalho aritmético usa números em todo o lado. Paga por um arranjo geral enquanto solicita um serviço repetitivo. Imagine pedir o mesmo café um milhão de vezes e preencher um formulário novo para cada chávena. A máquina de café está bem.
1:36 A burocracia desenvolveu a sua própria carreira.
Para onde o NumPy move o loop?
1:38 NumPy altera o contrato. Este array contém valores de ponto flutuante de tamanho fixo com um tipo declarado. O nosso array é contíguo, então os seus valores ficam juntos na memória. As vistas podem ter outros layouts. A operação aritmética sabe que tipo de dados está a receber. A chamada entrega o lote a uma operação nativa. Um loop ainda visita os valores dentro do código compilado. Ele pode reutilizar o conhecimento do tipo em todo esse trabalho.
2:00 Python permanece a pessoa que entrega o trabalho. Itamar Turner Trauring explica a distinção em Python Speed. Interfaces em lote, implementações nativas e instruções vetoriais do processador são coisas separadas. O nome da função fornece muito pouca evidência.
Rápido significa GPU ou muitos núcleos de CPU?
2:13 Este resultado usou NumPy na CPU. Um backend de GPU como o CuPy escolhe um lar diferente para arrays e operações. Comprar uma GPU deixa este loop Python comum a correr onde já corria. As instruções vetoriais da CPU podem processar vários valores em conjunto. O NumPy possui mecanismos para escolher kernels suportados. O nosso tempo de execução reporta as funcionalidades disponíveis, mas não rastreia as instruções desta expressão. Atribuir todo o ganho medido às instruções vetoriais exageraria a evidência.
2:40 O carrinho de compras da GPU pode ficar fechado. Há algo profundamente satisfatório numa correção de desempenho que também cancela uma compra. Infelizmente, o benchmark continua.
Quando o impressionante aumento de velocidade desaparece?
2:49 Com oito valores, a compreensão ganhou. Chamar o mecanismo de array custou mais do que a pequena tarefa. Arrays maiores e prontos assumiram a liderança. Usamos testes repetidos e um processo aquecido. Novas construções Python podem mudar o ponto de cruzamento. Então mude o que a aplicação precisa. Comece com uma lista Python e termine com uma nova lista Python. O NumPy deve converter a entrada num array e transformar a resposta de volta em
3:11 objetos Python. Esses passos pertencem à conta se a sua aplicação precisar deles. Com um milhão de valores, esse caminho completo levou cerca de vinte e oito milissegundos. A compreensão também levou cerca de vinte e oito. A nossa vantagem dramática tornou-se um empate próximo. A parte rápida era real. A vantagem dependia de deixar os dados na sua forma numérica.
Quais limites pertencem ao benchmark?
3:28 Siga os dados. Se a próxima operação aceitar um array, mantenha o array. Se converter repetidamente entre listas e arrays, meça essas travessias. Parar antes de uma passagem cara dá números precisos para a pergunta errada. Multiplicar este array cria um resultado temporário. Adicionar os valores originais cria a saída. A execução nativa ainda precisa de memória e move dados.
3:47 Algumas operações aceitam um buffer de saída, reduzindo alocações quando o seu código pode reutilizá-lo com segurança. Uma linha bonita pode encomendar vários buffers grandes. Python Speed também aponta este problema. O alocador é notavelmente indiferente à sua estética.
Um loop Python compilado também pode ser rápido?
4:00 Também pode manter a sintaxe do loop e mudar a sua execução. Numba pode compilar funções numéricas Python suportadas em código nativo. Aqui está o loop do nosso teste com o seu decorador de compilador. Ainda diz 'for'. O caminho caro do interpretador mudou. Após a compilação, este loop levou menos de meio milissegundo com arrays prontos. A própria compilação levou cerca de um segundo nesta execução. Esse custo de configuração foi excluído dos tempos repetidos,
4:22 portanto, um pequeno trabalho único merece uma comparação diferente. O próprio guia da Numba está feliz em mostrar loops explícitos. Iniciar uma migração de linguagem antes de encontrar o caminho quente é uma maneira elaborada de evitar abrir um profiler. Primeiro, descubra o que consome o tempo. Esperas de rede, análise de objetos e aritmética em massa exigem correções diferentes. As operações de array nativas ajudam quando o cálculo e os dados se encaixam.
4:43 Meça o resultado que o seu chamador realmente precisa. Agora, para esse atalho. Tem uma função Python e quer que ela aceite um array. Um invólucro chamado vectorize parece o botão de desempenho que faltava. A sua documentação tem uma frase importante à espera.
O que np.vectorize realmente executa?
4:56 NumPy diz que o seu invólucro vectorize é fornecido principalmente por conveniência. A sua implementação é essencialmente um loop 'for'. O invólucro aceita lotes, mas continua a chamar a sua função Python para elementos sucessivos. A interface ficou mais curta. O limite de execução manteve-se ocupado. Observe os mesmos valores a regressar através da callback Python. Com arrays prontos, esta versão levou cerca de oitenta e sete milissegundos. Foi mais lento do que a nossa compreensão.
5:18 Um invólucro de difusão útil pode preservar o trabalho exato por elemento que estava a tentar remover. Verifique a implementação por trás de uma interface de lote, mantenha dados compatíveis na sua forma útil e inclua a conversão na comparação. Loops nativos, de callback e compilados podem parecer semelhantes da perspetiva do chamador. O nome vectorize fez uma promessa que o seu cronómetro nunca assinou. Mantenha-o quando a conveniência é o que precisa. Para desempenho, verifique o loop que executa.
O que deve mudar no seu código?
5:41 O meu veredito é SHIP IT para processar um caminho crítico numérico medido em lote. Mantenha os loops comuns quando já funcionam bem. Verifique onde o trabalho acontece e o que o próximo chamador precisa. Subscreva, clique no sino e diga-me nos comentários se o teria carimbado de forma diferente. E essa é a diferença de hoje. Sou o Niko da Axrisi. Faça a fusão responsavelmente.
Fontes
- Universal functions (ufunc) basics — NumPy 2.2NumPy
- The N-dimensional array — NumPy 2.2NumPy
- numpy.vectorize — convenience and performance notesNumPy
- CPU/SIMD optimizations — NumPy 2.2NumPy
- dis — Disassembler for Python bytecodePython Software Foundation
- CPython 3.10.12 list representationCPython contributors
- The limits of Python vectorization as a performance techniqueItamar Turner-Trauring / Python⇒Speed
- Performance TipsNumba
- Basics of CuPyCuPy



