Trabajando a través del principio de responsabilidad única (SRP) en Python cuando las llamadas son caras

12

Algunos puntos base:

  • Las llamadas al método Python son "caras" debido a su naturaleza interpretada . En teoría, si su código es lo suficientemente simple, desglosar el código de Python tiene un impacto negativo además de la legibilidad y la reutilización ( que es una gran ganancia para los desarrolladores, no tanto para los usuarios ).
  • El principio de responsabilidad única (SRP) mantiene el código legible, es más fácil de probar y mantener.
  • El proyecto tiene un tipo especial de antecedentes donde queremos código legible, pruebas y rendimiento de tiempo.

Por ejemplo, un código como este que invoca varios métodos (x4) es más lento que el siguiente, que es solo uno.

from operator import add

class Vector:
    def __init__(self,list_of_3):
        self.coordinates = list_of_3

    def move(self,movement):
        self.coordinates = list( map(add, self.coordinates, movement))
        return self.coordinates

    def revert(self):
        self.coordinates = self.coordinates[::-1]
        return self.coordinates

    def get_coordinates(self):
        return self.coordinates

## Operation with one vector
vec3 = Vector([1,2,3])
vec3.move([1,1,1])
vec3.revert()
vec3.get_coordinates()

En comparación con esto:

from operator import add

def move_and_revert_and_return(vector,movement):
    return list( map(add, vector, movement) )[::-1]

move_and_revert_and_return([1,2,3],[1,1,1])

Si tengo que paralelizar algo como eso, es bastante objetivo que pierda rendimiento. Eso sí que es solo un ejemplo; mi proyecto tiene varias mini rutinas con matemáticas como esa: aunque es mucho más fácil trabajar con ellas, a nuestros perfiladores no les gusta.


¿Cómo y dónde adoptamos el SRP sin comprometer el rendimiento en Python, ya que su implementación inherente lo afecta directamente?

¿Existen soluciones alternativas, como algún tipo de preprocesador que ponga las cosas en línea para su lanzamiento?

¿O es Python simplemente pobre en el manejo del desglose del código por completo?

lucasgcb
fuente
19
Por lo que vale, sus dos ejemplos de código no difieren en el número de responsabilidades. El SRP no es un ejercicio de conteo de métodos.
Robert Harvey
2
@RobertHarvey Tienes razón, perdón por el mal ejemplo y editaré uno mejor cuando tenga tiempo. En cualquier caso, la legibilidad y la facilidad de mantenimiento sufren y, finalmente, el SRP se descompone dentro de la base de código a medida que reducimos las clases y sus métodos.
lucasgcb
44
tenga en cuenta que las llamadas a funciones son caras en cualquier idioma , aunque los compiladores de AOT tienen el lujo de incluir en línea
Eevee
66
Use una implementación JITted de python como PyPy. En su mayoría debería solucionar este problema.
Bakuriu

Respuestas:

17

¿Python es simplemente pobre en el manejo del desglose del código?

Desafortunadamente sí, Python es lento y hay muchas anécdotas sobre personas que aumentan drásticamente el rendimiento al incorporar funciones y hacer que su código sea feo.

Hay una solución alternativa, Cython, que es una versión compilada de Python y mucho más rápida.

--Edite Solo quería abordar algunos de los comentarios y otras respuestas. Aunque el empuje de ellos no es quizás específico de Python. pero una optimización más general.

  1. No optimice hasta que tenga un problema y luego busque cuellos de botella

    En general un buen consejo. Pero la suposición es que el código 'normal' suele ser eficaz. Este no es siempre el caso. Los lenguajes y marcos individuales tienen sus propias idiosincrasias. En este caso, la función llama.

  2. Son solo unos pocos milisegundos, otras cosas serán más lentas

    Si está ejecutando su código en una computadora de escritorio potente, probablemente no le importe mientras su código de usuario único se ejecute en unos segundos.

    Pero el código comercial tiende a ejecutarse para múltiples usuarios y requiere más de una máquina para soportar la carga. Si su código se ejecuta el doble de rápido, significa que puede tener el doble de usuarios o la mitad de máquinas.

    Si posee sus máquinas y su centro de datos, generalmente tiene una gran cantidad de sobrecarga en la potencia de la CPU. Si su código funciona un poco lento, puede absorberlo, al menos hasta que necesite comprar una segunda máquina.

    En estos días de computación en la nube, donde solo usa exactamente la potencia de cómputo que necesita y no más, hay un costo directo para el código que no funciona.

    Mejorar el rendimiento puede reducir drásticamente el gasto principal para un negocio basado en la nube y el rendimiento realmente debería ser el centro y la delantera.

Ewan
fuente
1
Si bien la Respuesta de Robert ayuda a cubrir algunas bases para posibles malentendidos detrás de este tipo de optimización (que se ajusta a esta pregunta ), creo que esto responde la situación un poco más directamente y en línea con el contexto de Python.
lucasgcb
2
lo siento es algo corto. No tengo tiempo para escribir más. Pero sí creo que Robert está equivocado en este caso. El mejor consejo con python parece ser el perfil a medida que codifica. No asuma que será eficiente y solo se optimizará si encuentra un problema
Ewan
2
@Ewan: No tienes que escribir todo el programa primero para seguir mi consejo. Un método o dos es más que suficiente para obtener un perfil adecuado.
Robert Harvey
1
también puedes probar pypy, que es una pitón JITted
Eevee
2
@Ewan Si realmente le preocupa la sobrecarga de rendimiento de las llamadas a funciones, lo que sea que esté haciendo probablemente no sea adecuado para Python. Pero entonces realmente no puedo pensar en muchos ejemplos allí. La gran mayoría del código de negocios está limitado a IO y las cosas pesadas de la CPU generalmente se manejan llamando a las bibliotecas nativas (numpy, tensorflow, etc.).
Voo
50

Muchas posibles preocupaciones de rendimiento no son realmente un problema en la práctica. El problema que plantea puede ser uno de ellos. En la lengua vernácula, llamamos preocuparse por esos problemas sin probar que son problemas reales de optimización prematura.

Si está escribiendo un front-end para un servicio web, su rendimiento no se verá afectado significativamente por las llamadas a funciones, ya que el costo de enviar datos a través de una red supera con creces el tiempo que lleva realizar una llamada al método.

Si está escribiendo un ciclo cerrado que actualiza una pantalla de video sesenta veces por segundo, entonces podría ser importante. Pero en ese momento, afirmo que tiene problemas más grandes si está tratando de usar Python para hacer eso, un trabajo para el cual Python probablemente no sea adecuado.

Como siempre, la forma de averiguarlo es medir. Ejecute un perfilador de rendimiento o algunos temporizadores sobre su código. Vea si es un problema real en la práctica.


El principio de responsabilidad única no es una ley o mandato; Es una directriz o principio. El diseño de software siempre se trata de compensaciones; No hay absolutos. No es raro intercambiar legibilidad y / o facilidad de mantenimiento por la velocidad, por lo que es posible que tenga que sacrificar SRP en el altar del rendimiento. Pero no haga esa compensación a menos que sepa que tiene un problema de rendimiento.

Robert Harvey
fuente
3
Creo que esto era cierto, hasta que inventamos la computación en la nube. Ahora una de las dos funciones cuesta efectivamente 4 veces más que la otra
Ewan
2
@Ewan 4 veces puede no importar hasta que lo haya medido como lo suficientemente significativo como para preocuparse. Si Foo tarda 1 ms y Bar tarda 4 ms, eso no es bueno. Hasta que se dé cuenta de que transmitir los datos a través de la red lleva 200 ms. En ese punto, Bar ser más lento no importa tanto. (Sólo un ejemplo posible de donde ser X veces más lenta no hace una diferencia notable o impactante, no quiere decir necesariamente que ser muy realista.)
Becuzz
8
@Ewan Si la reducción en la factura le ahorra $ 15 / mes pero le tomará a un contratista de $ 125 / hora 4 horas arreglarlo y probarlo, podría justificar fácilmente que no vale la pena el tiempo de una empresa (o al menos no hacerlo bien) ahora si el tiempo de comercialización es crucial, etc.). Siempre hay compensaciones. Y lo que tiene sentido en una circunstancia podría no serlo en otra.
Becuzz
3
sus facturas de AWS son realmente bajas
Ewan
66
@Ewan AWS redondea al techo por lotes de todos modos (el estándar es de 100 ms). Lo que significa que este tipo de optimización solo te ahorra algo si constantemente evita empujarte al siguiente fragmento.
Delioth
2

Primero, algunas aclaraciones: Python es un lenguaje. Hay varios intérpretes diferentes que pueden ejecutar código escrito en el lenguaje Python. La implementación de referencia (CPython) suele ser a lo que se hace referencia cuando alguien habla de "Python" como si fuera una implementación, pero es importante ser preciso al hablar de las características de rendimiento, ya que pueden diferir enormemente entre implementaciones.

¿Cómo y dónde adoptamos el SRP sin comprometer el rendimiento en Python, ya que su implementación inherente lo afecta directamente?

Caso 1.) Si tiene código Python puro (<= Python Language versión 3.5, 3.6 tiene "soporte de nivel beta") que solo se basa en módulos Python puros, puede adoptar SRP en todas partes y usar PyPy para ejecutarlo. PyPy ( https://morepypy.blogspot.com/2019/03/pypy-v71-released-now-uses-utf-8.html ) es un intérprete de Python que tiene un compilador Just in Time (JIT) y puede eliminar la función llamar a gastos generales siempre que tenga tiempo suficiente para "calentarse" rastreando el código ejecutado (unos segundos IIRC). ** **

Si está restringido a usar el intérprete CPython, puede extraer las funciones lentas en extensiones escritas en C, que se precompilarán y no sufrirán ninguna sobrecarga del intérprete. Todavía puede usar SRP en todas partes, pero su código se dividirá entre Python y C. Si esto es mejor o peor para la mantenibilidad que abandonar selectivamente SRP pero apegarse solo al código Python depende de su equipo, pero si tiene secciones críticas de rendimiento de su código, sin duda será más rápido que incluso el código Python puro más optimizado interpretado por CPython. Muchas de las bibliotecas matemáticas más rápidas de Python utilizan este método (IIRC numpy y scipy). Lo cual es un buen ejemplo del caso 2 ...

Caso 2.) Si tiene código Python que usa extensiones C (o se basa en bibliotecas que usan extensiones C), PyPy puede o no ser útil dependiendo de cómo estén escritas. Consulte http://doc.pypy.org/en/latest/extending.html para obtener detalles, pero el resumen es que CFFI tiene una sobrecarga mínima mientras que CTypes es más lento (usarlo con PyPy puede ser incluso más lento que CPython)

Cython ( https://cython.org/ ) es otra opción con la que no tengo tanta experiencia. Lo menciono por completo para que mi respuesta pueda "sostenerse por sí misma", pero no reclame ninguna experiencia. Debido a mi uso limitado, sentí que tenía que trabajar más para obtener las mismas mejoras de velocidad que podía obtener "gratis" con PyPy, y si necesitaba algo mejor que PyPy, era igual de fácil escribir mi propia extensión C ( lo que tiene la ventaja si reutilizo el código en otro lugar o extraigo parte de él en una biblioteca, todo mi código aún puede ejecutarse con cualquier intérprete de Python y no es necesario que Cython lo ejecute).

Tengo miedo de estar "encerrado" en Cython, mientras que cualquier código escrito para PyPy también puede ejecutarse en CPython.

** Algunas notas adicionales sobre PyPy en producción

Tenga mucho cuidado al hacer cualquier elección que tenga el efecto práctico de "encerrarlo" en PyPy en una gran base de código. Debido a que algunas bibliotecas de terceros (muy populares y útiles) no funcionan bien por las razones mencionadas anteriormente, puede causar decisiones muy difíciles más adelante si se da cuenta de que necesita una de esas bibliotecas. Mi experiencia es principalmente en el uso de PyPy para acelerar algunos (pero no todos) microservicios que son sensibles al rendimiento en un entorno de empresa donde agrega una complejidad insignificante a nuestro entorno de producción (ya tenemos varios idiomas implementados, algunos con diferentes versiones principales como 2.7 vs 3.5 corriendo de todos modos).

He descubierto que usar PyPy y CPython regularmente me obligó a escribir código que solo se basa en garantías hechas por la especificación del lenguaje en sí, y no en detalles de implementación que están sujetos a cambios en cualquier momento. Puede que pensar en estos detalles sea una carga adicional, pero lo encontré valioso en mi desarrollo profesional, y creo que es "saludable" para el ecosistema de Python en su conjunto.

Steven Jackson
fuente
¡Si! He estado considerando centrarme en las extensiones C para este caso en lugar de abandonar el principio y escribir código comodín, las otras respuestas me dieron la impresión de que sería lento, a menos que cambiara del intérprete de referencia: para aclararlo, OOP aún ser un enfoque sensato en su opinión?
lucasgcb
1
con el caso 1 (segundo párrafo), ¿no obtiene lo mismo de arriba llamando a las funciones, incluso si se cumplen las funciones mismas?
Ewan
CPython es el único intérprete que generalmente se toma en serio. PyPy es interesante , pero ciertamente no está viendo ningún tipo de adopción generalizada. Además, su comportamiento difiere de CPython, y no funciona con algunos paquetes importantes, por ejemplo, scipy. Pocos desarrolladores sensatos recomendarían PyPy para la producción. Como tal, la distinción entre el lenguaje y la implementación es intrascendente en la práctica.
jpmc26
Aunque creo que has dado en el clavo. No hay razón para que no puedas tener un mejor intérprete o un compilador. No es intrínseco a Python como lenguaje. Estás atrapado con realidades prácticas
Ewan
@ jpmc26 He usado PyPy en producción, y recomiendo considerar hacerlo a otros desarrolladores experimentados. Es ideal para microservicios que utilizan falconframework.org para API de descanso ligero (como un ejemplo). El comportamiento difiere porque los desarrolladores confían en los detalles de implementación que NO son una garantía del lenguaje, no es una razón para no usar PyPy. Es una razón para reescribir su código. El mismo código puede romperse de todos modos si CPython realiza cambios en su implementación (que es libre de hacer siempre y cuando cumpla con las especificaciones del lenguaje).
Steven Jackson