Rendemento de Python: bucles nativos de NumPy, callbacks e custos de conversión
Unha proba numérica controlada só con CPU segue valores idénticos a través de bucles CPython, aritmética de NumPy, un envoltorio de callback de Python e un bucle Numba compilado.
Unha proba numérica controlada só con CPU segue valores idénticos a través de bucles CPython, aritmética de NumPy, un envoltorio de callback de Python e un bucle Numba compilado. NumPy con entrada lista tiña unha gran vantaxe, mentres que a conversión de e cara a listas de Python reduciu esa vantaxe a un case empate. O episodio explica o límite de execución e como medir o resultado que unha aplicación necesita.
Ler a edición escrita (inglés) ↗
Que abrangue este vídeo
- CPython compila o código fonte a bytecode. O traballo repetido do intérprete/obxectos e un bucle numérico nativo tipado son arranxos de execución diferentes.
- A aritmética numérica de NumPy pode entregar lotes tipados a operacións compiladas; o bucle aritmético aínda existe, e as vistas poden ter disposicións non contiguas.
- Mide a conversión e a representación de saída requirida. Nesta execución, a conversión de listas de Python en arrays e de volta eliminou a gran vantaxe do array listo.
- Pequenas cargas de traballo poden favorecer o Python ordinario porque os custos fixos de chamadas a arrays importan. A comprensión de oito valores gañou esta comparación medida.
- np.vectorize é un envoltorio de conveniencia que chama a Python para elementos sucesivos; unha interface de lote por si soa non establece unha execución numérica nativa.
- Os bucles compilados con sintaxe de Python tamén poden ser rápidos. Inclúe os custos de compilación/configuración en frío cando sexan importantes para o chamador.
- A execución nativa, o SIMD da CPU, o multithreading e os backends da GPU son ideas separadas. As características da CPU dispoñibles non rastrexan as instrucións utilizadas nunha execución particular.
Transcrición traducida
Traducido da narración orixinal en inglés. O audio e os subtítulos dispoñibles son controlados por YouTube.
Por que a mesma CPU pode rematar antes?
0:00 Pensas que Python necesita unha nova linguaxe para ser rápido. A mesma CPU executou o noso lote máis rápido despois de que o bucle se moveu. Onde foi o traballo? Cando se esvai a aceleración? Que atallo trae de volta a Python? Isto é The Daily Diff, entre bastidores. Non perdas de vista ese atallo. CPython é a implementación estándar de Python.
0:17 Compila o teu código fonte en bytecode e despois executa esas instrucións nun intérprete. NumPy é unha biblioteca numérica cuxos arrays conteñen valores cun tipo de datos declarado. A súa aritmética integrada pode entregar un lote completo a operacións compiladas. Toma un millón de valores de punto flotante, eleva cada un ao cadrado, despois engade o valor orixinal. Cada versión obtén valores idénticos e produce respostas coincidentes. Os valores son o suficientemente pequenos como para manter as diferenzas de redondeo fóra da carreira.
0:40 Nesta máquina, a comprensión levou uns trinta e tres milisegundos. NumPy levou menos dun. Ambos executáronse na CPU. Iso é unha proba útil para esta carga de traballo, e un obituario terrible para unha linguaxe de programación. Comecemos co bucle ordinario.
Que traballo repite un bucle de Python?
0:53 Python obtén o seguinte valor, realiza a aritmética e engade a resposta. Despois faino de novo. A multiplicación é barata. Organizar a seguinte multiplicación e xestionar os obxectos de Python arredor dela, engade traballo cada vez. Esta é a desassemblaxe real da función probada. A iteración, a multiplicación e a adición aparecen como instrucións de bytecode. As versións e intérpretes de Python toman decisións diferentes.
1:14 Estes tempos pertencen á versión en pantalla, que é Python 3.10. Unha lista de Python almacena referencias a obxectos, polo que diferentes tipos de cousas poden compartir unha lista. O noso traballo aritmético usa números en todas partes. Paga por unha disposición xeral mentres solicita un servizo repetitivo. Imaxina pedir o mesmo café un millón de veces e encher un formulario novo para cada cunca. A máquina de café está ben.
1:36 O papeleo desenvolveu a súa propia carreira.
Onde move NumPy o bucle?
1:38 NumPy cambia o contrato. Este array contén valores de punto flotante de tamaño fixo cun tipo declarado. O noso array é contiguo, polo que os seus valores están xuntos na memoria. As vistas poden ter outras disposicións. A operación aritmética sabe que tipo de datos está a recibir. A chamada entrega o lote a unha operación nativa. Un bucle aínda visita os valores dentro do código compilado. Pode reutilizar o coñecemento do tipo en todo ese traballo.
2:00 Python segue sendo a persoa que entrega o traballo. Itamar Turner Trauring explica a distinción en Python Speed. As interfaces de lotes, as implementacións nativas e as instrucións vectoriais do procesador son cousas separadas. O nome da función achega moi pouca evidencia.
Significa rápido GPU ou moitos núcleos de CPU?
2:13 Este resultado utilizou NumPy na CPU. Un backend de GPU como CuPy elixe un fogar diferente para os arrays e as operacións. Comprar unha GPU deixa este bucle ordinario de Python executándose onde xa se executaba. As instrucións vectoriais da CPU poden procesar varios valores xuntos. NumPy ten a maquinaria para escoller os kernels compatibles. O noso tempo de execución informa das características dispoñibles, pero non rastrexa as instrucións desta expresión. Atribuír todo o ganancia medida ás instrucións vectoriais esaxeraría a evidencia.
2:40 O carro da compra da GPU pode permanecer pechado. Hai algo profundamente satisfactorio nunha corrección de rendemento que tamén cancela unha compra. Desafortunadamente, o benchmark continúa.
Cando desaparece a impresionante aceleración?
2:49 Con oito valores, a comprensión gañou. Chamar á maquinaria do array custou máis que o pequeno traballo. Arrays listos máis grandes adiantáronse. Utilizamos probas repetidas e un proceso quentado. As versións máis recentes de Python poden cambiar o punto de cruzamento. Despois cambia o que a aplicación necesita. Comeza cunha lista de Python e remata cunha nova lista de Python. NumPy debe converter a entrada nun array e devolver a resposta a obxectos
3:11 de Python. Eses pasos pertencen á factura se a túa aplicación os necesita. Cun millón de valores, ese camiño completo levou uns vinte e oito milisegundos. A comprensión tamén levou uns vinte e oito. A nosa vantaxe dramática converteuse nun case empate. A parte rápida foi real. A vantaxe dependía de deixar que os datos permanecesen na súa forma numérica.
Que límites pertencen ao benchmark?
3:28 Sigue os datos. Se a seguinte operación acepta un array, mantén o array. Se conviertes repetidamente entre listas e arrays, mide esas cruces. Parar antes dunha entrega cara dá números precisos para a pregunta incorrecta. Multiplicar este array crea un resultado temporal. Engadir os valores orixinais crea a saída. A execución nativa aínda necesita memoria e move datos.
3:47 Algunhas operacións aceptan un buffer de saída, reducindo as asignacións cando o teu código pode reutilizalo de forma segura. Unha liña bonita pode solicitar varios buffers grandes. Python Speed tamén sinala este problema. O asignador é notablemente indiferente á túa estética.
Pode un bucle de Python compilado ser rápido tamén?
4:00 Tamén podes manter a sintaxe do bucle e cambiar a súa execución. Numba pode compilar funcións numéricas de Python compatibles en código nativo. Aquí está o bucle da nosa proba co seu decorador de compilador. Aínda di for. O camiño caro do intérprete cambiou. Despois da compilación, este bucle levou menos de medio milisegundo con arrays listos. A compilación en si levou aproximadamente un segundo nesta execución. Ese custo de configuración foi excluído dos tempos repetidos,
4:22 polo que un pequeno traballo único merece unha comparación diferente. A propia guía de Numba está feliz de mostrar bucles explícitos. Comezar unha migración de linguaxe antes de atopar o camiño crítico é unha forma elaborada de evitar abrir un perfilador. Primeiro atopa o que consome o tempo. As esperas da rede, a análise de obxectos e a aritmética en masa requiren solucións diferentes. As operacións de array nativas axudan cando o cálculo e os datos encaixan.
4:43 Mide o resultado que o teu chamador realmente necesita. Agora, por ese atallo. Tes unha función de Python e queres que acepte un array. Un envoltorio chamado vectorize soa como o botón de rendemento que falta. A súa documentación ten unha frase importante á espera.
Que executa realmente np.vectorize?
4:56 NumPy di que o seu envoltorio vectorize se proporciona principalmente por conveniencia. A súa implementación é esencialmente un bucle for. O envoltorio acepta lotes, pero segue chamando á túa función de Python para elementos sucesivos. A interface acurtouse. O límite de execución mantívose ocupado. Observa os mesmos valores regresar a través do callback de Python. Con arrays listos, esta versión levou uns oitenta e sete milisegundos. Foi máis lenta que a nosa comprensión.
5:18 Un envoltorio de "broadcasting" útil pode preservar o traballo exacto por elemento que estaba tentando eliminar. Verifica a implementación detrás dunha interface de lote, mantén os datos compatibles na súa forma útil e inclúe a conversión na comparación. Os bucles nativos, de callback e compilados poden parecer similares dende a perspectiva do chamador. O nome vectorize fixo unha promesa que o teu cronómetro nunca asinou. Mánteno cando a conveniencia é o que necesitas. Para o rendemento, verifica o bucle que se executa.
Que debería cambiar no seu código?
5:41 O meu veredicto é SHIP IT para procesar por lotes un camiño crítico numérico medido. Mantén os bucles ordinarios cando xa funcionan ben. Verifica onde ocorre o traballo e que necesita o seguinte chamador. Subscríbete, preme a campá e dime nos comentarios se o terías marcado de forma diferente. E esa é a diferenza por hoxe. Son Niko de Axrisi. Fusiónase con responsabilidade.
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



