Aws-lambda-dotnet: Rendimiento de C# Lambda frente a nodo frente a Python

Creado en 12 dic. 2016  ·  35Comentarios  ·  Fuente: aws/aws-lambda-dotnet

Primero. ¡Gracias por llevar C# a Lambda! ¡Me encanta!

Hice una prueba de rendimiento rápida y sucia para comparar qué tan rápido se pueden invocar varias funciones lambda. Todas las pruebas se realizaron enviando la cadena "foo" como entrada a cada función respectiva mediante la consola de AWS. La prueba fue extremadamente simple: seguí haciendo clic repetidamente en el botón Test en la consola de AWS Lambda y seleccioné una declaración de registro representativa.

Pitón

REPORT RequestId: 6c2026c2-c028-11e6-aecf-116ec5921e69    Duration: 0.20 ms    Billed Duration: 100 ms     Memory Size: 128 MB    Max Memory Used: 15 MB

Javascript/NodeJS

REPORT RequestId: 10a2ac96-c028-11e6-b5eb-978ea2c1c2bd    Duration: 0.27 ms    Billed Duration: 100 ms     Memory Size: 128 MB    Max Memory Used: 16 MB

C#

REPORT RequestId: d9afca33-c028-11e6-99df-0f93927a56a6    Duration: 0.85 ms    Billed Duration: 100 ms     Memory Size: 256 MB    Max Memory Used: 42 MB

Las funciones se desplegaron en us-east-1 con sus respectivas configuraciones por defecto. La función de C# se implementó con dotnet lambda deploy-function . Las funciones Node y Python se implementaron usando el código de muestra HelloWorld para cada idioma respectivo y cambiando la implementación para que se aceptara una cadena simple y se convirtiera a mayúsculas.

Puede encontrar el código aquí: https://github.com/LambdaSharp/LambdaPerformance

¿Hay algo que pueda hacer para optimizar los tiempos de invocación de mi parte? ¿Es algo en lo que todavía estás trabajando también? Solo curiosidad por el estado. ¡Gracias!

guidance

Comentario más útil

@yunspace herejía! :)

C# Lambda All Things!

Todos 35 comentarios

El rendimiento es algo en lo que siempre continuaremos trabajando con Lambda y todos los tiempos de ejecución compatibles. Cada idioma tendrá sus fortalezas y debilidades, por eso tenemos guerras de idiomas tan divertidas :)

Esta prueba solo mide el tiempo de inicio del tiempo de ejecución del lenguaje, que es algo en lo que los lenguajes dinámicos como Node.js y Python siempre han sido rápidos en comparación con los lenguajes estáticos como C# y Java debido a la falta de verificación de tipos y la carga más lenta de las dependencias.

Lo siento, no quise cerrarlo. Siéntete libre de continuar la conversación.

Gracias por mantener esto abierto. No pretende ser una crítica, sino la base para una discusión abierta.

En su respuesta, mencionó que la prueba mide el tiempo de inicio del idioma. Me gustaría obtener más claridad sobre esta declaración. Pensé en el primer golpe, la aplicación se aprovisiona (si es necesario) y luego se inicia, pero en los golpes posteriores, se calienta. Si se inicia cada vez, entonces no hay ningún beneficio en mover el código de ejecución única (por ejemplo, leer los valores de configuración) al constructor de la clase del controlador, ya que se construye en cada invocación de todos modos. Sin embargo, no es así como lo entendí de tu presentación de re:Invent. ¿Podría aclarar? ¡Gracias!

Solo para confirmar mi comprensión, volví a leer cómo funciona el contenedor de Lambda aquí: http://docs.aws.amazon.com/lambda/latest/dg/lambda-introduction.html

@bjorg @normj la documentación es bastante vaga a este respecto :(
He proporcionado comentarios a continuación y espero que se aclare

  • '¿Cómo podemos hacerlo mejor?
    sea ​​exacto sobre dónde se puede buscar la hora real de 'algún tiempo' para la declaración: 'AWS Lambda mantiene el contenedor durante algún tiempo en previsión de otra invocación de función de Lambda'.
  • ¿Que estás tratando de hacer?
    comprender cuánto tiempo es probable que la reutilización de un contenedor realmente suceda para predecir el rendimiento

@bjorg lo que publicaste aquí no es el _tiempo de inicio en frío_ de Lambda. Pero el _tiempo de respuesta cálido_.

Cuando una función de C# se inicia por primera vez desde el frío, puede tardar unos cientos de milisegundos (en mi caso, son 500 ms). Una vez que se haya calentado, permanecerá vivo y atenderá las solicitudes a un ritmo más rápido similar al que ha publicado (en mi caso, fue alrededor de 0,9 a 1 ms). Eventualmente morirá al final de su vida útil, o se producirá un ajuste de escala automático y se iniciarán más lambdas frías. Para obtener la hora de inicio, espere unos 15 minutos y luego haga clic en Probar.

Entonces sí, todavía pones cosas en constructores. Porque no obtiene un Lambda nuevo cada vez que hace clic en Probar. Pero obtendrá un Lambda nuevo si hace clic solo una vez por hora.

En términos de optimización, para el _tiempo de inicio en frío_ podría:

  1. asegúrese de recibir solicitudes las 24 horas del día, los 7 días de la semana para que sus lambdas estén siempre calientes
  2. configure un ping periódico en su lambda (ya sea horarios de vigilancia en la nube o newrelic o algo así), para que sus lambdas estén siempre calientes.

Para el _tiempo de respuesta cálido_ realmente no tiene mucho sentido optimizar:

  1. el tiempo de respuesta humano generalmente aceptable es de 200 ms
  2. menos de 1 ms es bastante bueno. Rara vez obtendría esta tasa fuera de helloworld o toUpper .
  3. Si coloca una puerta de enlace API frente a su lambda y expone las llamadas HTTP al mundo. La llamada promedio almacenada en caché desde la web sería de alrededor de 50 ms.
  4. AWS le factura a intervalos de 100 ms de todos modos
  5. 0,85 ms probablemente esté cerca del rendimiento bruto de un código C# de pieza normal. Intente ejecutar su función en su máquina dentro de un método principal y vea.

Además, no confunda el rendimiento con el tiempo de respuesta. El hecho de que Node responda en 20 ms cuando hace clic secuencialmente de forma manual, no significa que se comportará de la misma manera cuando haya 100 000 solicitudes automatizadas inundando por segundo y aumentando cada segundo. Haz algunas pruebas reales de concurrencia y carga. Es posible que los lenguajes de subprocesos múltiples, como Java o C#, manejen más solicitudes por segundo bajo carga. De hecho, vi las estadísticas lambda en algún lugar: Python = inicio en frío más rápido, Java = solicitudes en caliente más rápidas por segundo, pero no puedo encontrarlo ahora.

De todos modos, esto debería responder a su pregunta con suerte.

Encontré la discusión que contiene el punto de referencia https://www.quora.com/Which-language-better-suits-for-AWS-Lambda

@yunspace , gracias por el artículo detallado. Tengo curiosidad sobre cómo la implementación actual de .NET Core clasifica los datos de la invocación interna sin procesar al controlador de C#. Dado que el controlador puede variar en la firma (tanto por tipo como por número de argumentos), supongo que se invoca a través de la reflexión. Así que mi pregunta era básicamente si había una forma de ser invocado sin tocar la capa de cálculo de referencias. Por ejemplo, una firma Foo(Stream, ILambdaContext) podría ser un patrón de controlador predefinido que omita cualquier lógica para convertir la carga útil en una instancia POCO.

Lamentablemente, el código de invocación no está disponible para su inspección, por lo que no sé si podría optimizarse aún más. Esperaría que una invocación cálida tenga un rendimiento muy similar al de esos otros tiempos de ejecución.

Para desarmar JSON de entrada sin procesar en POCO, de forma predeterminada, lambdas usa Manejo de tipos de datos estándar

Veo a que te refieres. La mayoría de los serializadores (incluido Json.Net) utilizan la reflexión, que es lenta. Para probar esta teoría, supongo que podría ver si Foo(Stream, ILambdaContext) le da un mejor tiempo de respuesta para las invocaciones cálidas. Y si es así, probablemente valga la pena implementar su propio serializador de clientes para cadenas y POCO.

En realidad, no estaba hablando del deserializador de datos, sino del método que invoca al controlador lambda. Dado que el controlador lambda puede tener diferentes firmas (tanto en tipo como en número de argumentos), el método que lo invoca debe basarse en la reflexión. AFAIK, no hay forma de escribir el controlador lambda de tal manera que se omita esta maquinaria de conveniencia.

Recientemente hice algunas pruebas sobre esto y descubrí que el serializador realmente no está teniendo un gran impacto. De hecho, al eliminar casi todas las partes móviles y devolver datos estáticos o simplemente flujos vacíos, parece que lo mejor que puede obtener es aproximadamente 0,85 ms en una función cálida. Sospecho que es probable que el código que invoca la función de C# sea el culpable y parece que solo el equipo de lambda ( @normj ) puede

Para todos los demás tiempos de ejecución, incluido Java, puede obtener entre 0,22 y 0,30 ms en una función cálida. Básicamente, significa que la sobrecarga de invocación de lambda es 3x-4x peor para C#.

Si bien estoy de acuerdo en que esto no cuenta toda la historia, ya que es probable que C# sea más rápido para hacer un trabajo real, esta sobrecarga del marco merece ser analizada. Supongo que algo de esto podría atribuirse a que es nuevo y el rendimiento no ha sido la mayor prioridad. También huele a que el uso excesivo de la reflexión sin algún tipo de almacenamiento en caché podría ser el culpable.

También sospecho que esto se debe a un código anterior. Ojalá pudiéramos ver cómo se ve para poder ayudar a optimizarlo. Alternativamente, también ayudaría tener un gancho de nivel inferior para experimentar.

Yo también desearía que pudiéramos ayudar. Estoy justo al límite, esperando poner el núcleo de C# a trabajar de muchas maneras. Este es un factor definitorio crítico entre Azure y Amazon. El enfoque de servidor sin C# es mucho mejor que tener que mantener varias máquinas EC2 con actualizaciones de Windows, comprobaciones de rendimiento, actualizaciones de SQL, etc. Es casi un trabajo de tiempo completo mantener actualizados estos entornos para los clientes.

El enfoque sin servidor de C# es más rentable, requiere mucha menos mano de obra, es escalable y reutilizable automáticamente.

El único otro problema pendiente es un enfoque RDC escalable y rentable ahora :-)

He pasado este hilo al equipo de servicio para que eche un vistazo. Principalmente mantengo las herramientas del cliente como estas bibliotecas y la integración de Visual Studio, por lo que no puedo hablar mucho sobre lo que sucede en el lado del servicio. Puedo decirle en mi visualización limitada del código de servicio que el uso de la reflexión está optimizado, pero siempre hay otros factores que analizar.

@genifycom tengo entendido que este hilo está rastreando la diferencia de rendimiento de 0,5 ms para arranques en caliente en comparación con otros tiempos de ejecución. Dado que Lambda factura en incrementos de 100 ms, ¿esta diferencia de rendimiento bloquea su uso? No estoy tratando de disminuir la importancia de llegar al fondo de esta discrepancia, pero el área principal en la que el equipo de Lambda está trabajando para mejorar el rendimiento en este momento es el tiempo de arranque en frío.

Para aclarar, no detiene nuestra adopción de C# Lambda (o LambdaSharp, como lo hemos bautizado). Es más una fuente de orgullo. :)

¡Entiendo totalmente!

No estoy seguro de si este es el lugar correcto, pero actualmente estamos creando un backend web en C# y tenemos tiempos de inicio en frío de varios segundos, a veces hasta diez o incluso más. Sin embargo, después de ese primer golpe, todo está bien. ¿Sigue siendo esto normal para un proyecto de C# no trivial, o hay algo que podamos hacer para reducirlo? Nos encanta poder trabajar en nuestro ecosistema .NET familiar, pero esto nos está dando algunos dolores de cabeza.

Supongo que tiene que ver con los paquetes externos que incluimos en el proyecto y cómo Amazon los maneja durante un arranque en frío, porque un proyecto vacío parece ir mucho mejor. Esperaba que alguien pudiera arrojar algo de luz sobre esto.

@normj ¿Hay alguna actualización sobre los problemas de rendimiento que estamos viendo?

@SpoonOfDoom ¿ https://www.iis.net/downloads /microsoft/application-initialization - que se recomiendan. Lambda cambia el modelo de costos, pero hasta cierto punto se enfrentan los mismos desafíos, independientemente de cómo lo diseñen.

@bitshop Eso es lo que estamos haciendo actualmente. Llamamos a todo una vez cada 10 minutos, y la mayor parte del tiempo ayuda. Pero todavía tenemos algunos picos cada pocas horas, donde volvemos a iniciar en frío: parece que las instancias de AWS que ejecutan .Net Lambdas tienen una vida útil máxima y luego se usa una nueva independientemente del estado actual caliente/frío. No estoy seguro.
Parece una decisión extraña de Amazon no dar a los desarrolladores la oportunidad de protegerse completamente contra esto (al menos por un precio). Claro, ahora solo alcanzamos el pico cada pocas horas, pero si lo hace un cliente en lugar de nuestro script, el cliente seguirá molesto y percibirá nuestra aplicación como poco confiable y/o lenta.

@yunspace Parece que la función de plantilla lambda aún necesitará un tiempo de inicio en frío de más de 2000 ms.
REPORT RequestId: d1e5f56c-0ea9-11e7-bb5d-bb039f76a793 Duration: 2120.69 ms Billed Duration: 2200 ms

@normj ¿Hice algo mal?

Actualización: descubrí que si la función toma una entrada de string , el tiempo de inicio será de ~ 800 ms, pero si realmente elijo otro tipo, será más de 2000 ms.

    // 800ms
    public string FunctionHandler(string input, ILambdaContext context)

    // 2000ms, Request being the other object type
    public string FunctionHandler(Request input, ILambdaContext context)

Tengo tiempos de inicio en frío de ~21 segundos en 128 MB para dos funciones lamba diferentes en C#, ambas reciben SNS. Obtengo casi el mismo tiempo con S3 put trigger vs SNS. Cálido, son ~2 segundos.

Si subo la memoria a 256M, veo que los tiempos de bacalao caen a aproximadamente ~ 10 segundos, etc.

Los registros de Lambda muestran que uso en la memoria total de 40 MB.

De una de las funciones, tiempo de ejecución total:

128 MB frío: INFORME Duración: 21775,92 ms Duración facturada: 21800 ms Tamaño de memoria: 128 MB Memoria máxima utilizada: 35 MB

128 MB tibio: INFORME Duración: 1326,76 ms Duración facturada: 1400 ms Tamaño de memoria: 128 MB Memoria máxima utilizada: 37 MB

256 MB frío: INFORME Duración: 11159,49 ms Duración facturada: 11200 ms Tamaño de memoria: 256 MB Memoria máxima utilizada: 39 MB

256 MB tibio: INFORME Duración: 792,37 ms Duración facturada: 800 ms Tamaño de memoria: 256 MB Memoria máxima utilizada: 39 MB

384 MB frío: INFORME Duración: 7566,07 ms Duración facturada: 7600 ms Tamaño de memoria: 384 MB Memoria máxima utilizada: 43 MB

384 MB tibio: INFORME Duración: 850,59 ms Duración facturada: 900 ms Tamaño de memoria: 384 MB Memoria máxima utilizada: 47 MB

solo por risas:

1024 MB en frío: INFORME Duración: 3309,12 ms Duración facturada: 3400 ms Tamaño de memoria: 1024 MB Memoria máxima utilizada: 38 MB

1024 MB tibio: INFORME Duración: 677,57 ms Duración facturada: 700 ms Tamaño de memoria: 1024 MB Memoria máxima utilizada: 41 MB

Eso es una gran cantidad de gastos generales para el arranque en frío.

En comparación, aquí hay una función nodejs que hace aproximadamente la mitad del trabajo (versión anterior antes del puerto a C #), pero el mismo tipo de trabajo (tomar un SNS, escribir algo en una base de datos, almacenar algo en S3) pero esto parece cierto en todo el tablero con otras funciones tenemos:

128 MB Frío: REPORTE Duración: 262,58 ms Duración facturada: 300 ms Tamaño de memoria: 128 MB Memoria máxima utilizada: 19 MB

128 MB Tibio: INFORME Duración: 134,79 ms Duración facturada: 200 ms Tamaño de memoria: 128 MB Memoria máxima utilizada: 19 MB

El porcentaje de gastos generales en frío parece mucho más razonable.

Estoy usando las herramientas de AWS de Visual Studio para cargar el paquete: el código está precompilado para la plataforma de destino antes de cargarse, ¿verdad? ¿Hay algo que me estoy perdiendo o esto es normal? Los otros números informados aquí son más pequeños, pero no conozco la asignación de memoria.

El tiempo de arranque en frío es un problema cuando se construyen Slack-bots, ya que Slack se agota después de 3000 ms. Realmente desearía que hubiera una manera de garantizar que una instancia esté siempre disponible.

He abierto un ticket de soporte técnico sobre este problema. Si aprendo algo útil, lo compartiré aquí.

Si bien existen muchas soluciones para mantener caliente la lambda a través de pings de vigilancia en la nube, etc., en última instancia, la hora de inicio en frío debería ser algo con lo que se sienta cómodo. Puede optimizar los arranques en frío, pero no puede evitarlos. Estoy bastante seguro de que cuando ocurra el escalado automático, las nuevas lambdas también se escalarán desde el frío.

@bjorg para garantizar que una instancia esté siempre disponible, probablemente sea mejor considerar EC2 o ECS

@InsidiousForce Me sorprende que tengas 21 arranques en frío. Sospecho firmemente que es su código en lugar de lambda en sí. ¿Tiene un bloque de inicialización pesado? Si ejecuta su código o pruebas unitarias localmente en su VS, ¿cuánto tiempo lleva? De todos modos, solo estoy especulando, ya que no conozco su código. Probablemente sea mejor si genera un ticket de soporte técnico como

@yunspace herejía! :)

C# Lambda All Things!

De acuerdo, después de largas idas y venidas con el amable personal de soporte técnico de Amazon, la conclusión básica de la conversación es la siguiente:
Puede intentar optimizar su código: reduzca el tamaño del archivo, elimine las bibliotecas que no son absolutamente necesarias, intente tener la menor cantidad posible de estática y otras cosas que se inicialicen en el inicio y, por supuesto, aumente la memoria asignada para aumentar la potencia de la CPU, como se discutió. en este hilo. Mantener vivo el Lambda llamándolo regularmente puede alargar el problema, pero no eliminarlo. Un intervalo de 4 minutos parece ser el mejor valor para la mayoría de los casos, pero aparentemente el comportamiento subyacente no es completamente determinista, por lo que no es una regla confiable. E incluso entonces, no elimina completamente el problema.

Desafortunadamente, la conclusión es que solo puede llegar hasta cierto punto haciendo todo esto y, en algún momento, la asignación de más memoria deja de ser factible. Siempre tendrá estos tiempos de inicio en frío y, si bien es posible que pueda reducirlos, probablemente no podrá reducirlos hasta un punto en el que sea razonable para una API pública o algo así. Parece que si no puede darse el lujo de esperar de vez en cuando, entonces C# Lambda no es para usted, y está mejor con una instancia EC2 o tal vez (como lo estamos haciendo ahora) Elastic Beanstalk, si desea una implementación fácil y escalado automático .
Ahora estamos convirtiendo nuestras Lambdas en un proyecto de API web de ASP.NET, lo que nos permite realizar una implementación cómoda desde Visual Studio y mantener algunas opciones de escalado automático.

TL; DR: No parece que haya una forma confiable de solucionar este problema. Utilice EBS o EC2 en su lugar.

Cierre por falta de actividad. Además, este repositorio es principalmente para admitir las bibliotecas y herramientas del cliente. Un mejor lugar para esta discusión son los foros de Lambda donde el equipo de servicio de Lambda supervisa.

¡Hola a todos!
Hice un artículo donde comparo Python vs PHP vs Java vs Ruby vs C#.
¿Puedes comprobarlo y dar tu opinión al respecto? (Este no es un material puramente técnico, sino más generalizador para principiantes)
https://www.cleveroad.com/blog/python-vs-other-programming-languages

Este artículo no fue útil para mí.

En primer lugar, no era una comparación. El título de la página dice "VENTAJAS DE USAR PYTHON SOBRE OTROS IDIOMAS", por lo que claramente NO es una comparación.

En segundo lugar, ¡NO HAY UN IDIOMA QUE SE AJUSTE A TODOS LOS REQUISITOS!

Por ejemplo, construir marcos complejos con un lenguaje de secuencias de comandos es increíblemente difícil. Su punto sobre Python como en "Podemos decir que Python es un lenguaje minimalista. Es muy fácil de escribir y leer. Y cuando llega el momento de pensar en un problema, un desarrollador puede enfocarse en el problema, no en el lenguaje y su sintaxis". ni siquiera comienza a ayudar con modelos enriquecidos e interacciones entre los componentes del modelo.

Aunque parece que hemos descendido a pensar que los microservicios responderán a todo, he estado en este juego el tiempo suficiente para saber que ese es un enfoque muy ingenuo.

Los problemas vienen en MUCHAS formas.

Estoy de acuerdo con genifycom. Además de eso, hay algunos puntos que parecen dudosos, si no completamente erróneos. Por ejemplo, decir que no puede hacer aplicaciones basadas en la red (supongo que eso significa computación distribuida) en Python parece desinformado , y afirmar que C# no tiene muchas bibliotecas disponibles es una declaración extraña considerando que hay aproximadamente 100k paquetes disponibles solo en Nuget .
Además, Python no tiene "una forma de resolver un problema": siempre hay varias formas de resolver problemas, y eso generalmente no tiene nada que ver con el lenguaje.
Luego hay un momento en el que pareces contradecirte, donde dices en tu gráfico que Python no puede hacer aplicaciones multiplataforma (¿eh? Eso parece incorrecto) y luego en el texto "Python es compatible con casi todos los sistemas operativos modernos". .
También hay otros problemas menores: por ejemplo, teóricamente puede codificar en C # con el Bloc de notas y compilar a través de la línea de comandos, no se necesita IDE, mientras que un IDE adecuado como IntelliJ también facilita mucho el desarrollo de Python.

Además de todo eso, no estoy seguro de que un problema de GitHub con casi un año de inactividad sea la forma adecuada de publicitar tu blog.

genifycom y SpoonOfDoom Gracias por su opinión, lo tendré en cuenta.

¿Algún consejo sobre cómo hacer que el código C# sea más eficiente desde la perspectiva de .NET para los controladores lambda?
Algunas de las preguntas de ejemplo que estoy tratando de abordar:
1) ¿Hacer que las funciones lambda sean estáticas aumentará la reutilización del contexto de Lambda y mejorará el rendimiento?
2) Si hago la clase Functions (que tiene todos los controladores lambda) clase singleton, ¿mejorará el rendimiento?
3) Si hago variables constantes/de solo lectura y las comparto entre funciones lambda, ¿mejorará el rendimiento?

Si alguien tiene alguna información, por favor sugiera

@niraj-bpsoftware ¿por qué lo intenta y publica sus hallazgos aquí? Estaría muy interesado en ver si hay una diferencia notable. Para ser honesto, me sorprendería bastante si alguno de estos tuviera un impacto.

1) El beneficio de prescindir de una creación de instancias, que es un costo fijo único durante la vida útil de la función, parece absolutamente mínimo y mucho más allá de cualquier umbral medible.

2) No puedo hablar de esto porque no lo hago. Sin embargo, si sus controladores son funciones lambda diferentes, entonces no comparten nada para empezar, ya que todos se ejecutan en procesos/contenedores separados, según tengo entendido.

3) Ídem.

¿Fue útil esta página
0 / 5 - 0 calificaciones