"Si un trabajador quiere hacer bien su trabajo, primero debe afilar sus herramientas." - Confucio, "Las Analectas de Confucio. Lu Linggong"
Página delantera > Programación > Un servidor web performitante y extensible con zig y python

Un servidor web performitante y extensible con zig y python

Publicado el 2025-03-22
Navegar:368

Prefacio

Me apasiona mi interés en el desarrollo de software, específicamente el rompecabezas de la creación ergonómica de creación de sistemas de software que resuelven el conjunto más amplio de problemas al tiempo que hace la menor cantidad de compromisos posible. También me gusta pensar en mí mismo como un desarrollador de sistemas, lo que, según la definición de Andrew Kelley, significa un desarrollador interesado en comprender completamente los sistemas con los que están trabajando. En este blog comparto con ustedes mis ideas sobre cómo resolver el siguiente problema: construyendo una aplicación empresarial completa confiable y de rendimiento real . Un gran desafío, ¿no? En el blog, me concentro en la parte del "servidor web performinante", ahí es donde siento que puedo ofrecer una nueva perspectiva, ya que el resto está bien pisado o no tengo nada que agregar.

una advertencia importante: habrá sin muestras de código , realmente no lo he probado. Sí, este es un defecto importante, pero en realidad implementar esto llevaría mucho tiempo, que no tengo, y entre publicar un blog defectuoso y no publicarlo en absoluto, me quedé con el primero. Te han advertido.

A performant and extensible Web Server with Zig and Python

y de qué piezas ensamblaríamos nuestra aplicación?

  • un frontend con el que se siente cómodo, pero si desea dependencias mínimas, hay zig en forma htmx.
  • un servidor web zig, estrechamente integrado con el kernel de Linux. Esta es la parte de rendimiento, en la que me centraré en este blog.
  • un backend de Python, integrado con zig. Esta es la parte compleja.
  • Integración con sistemas de ejecución duraderos como temporales y fluibles. Esta fiabilidad ayuda, y no se discutirá en el blog.

con nuestras herramientas decididas, ¡comenzemos!

¿Están las coroutinas sobrevaloradas de todos modos?

zig no tiene soporte de nivel de idioma para las coroutinas :( y los coroutines es con qué está construido cada servidor web de rendimiento. Entonces, ¿no tiene sentido intentarlo?

Hold, On, pusamos primero nuestro sombrero de programador de sistemas. Las corutinas no son una bala de plata, nada lo es. ¿Cuáles son los beneficios y los inconvenientes reales involucrados?

Es de conocimiento común que las coroutinas (hilos del espacio de usuario) sean más ligeros y más rápidos. ¿Pero de qué manera exactamente? (Las respuestas aquí son en gran medida especulaciones, toman con un grano de sal y pruebanlo usted mismo)

  • comienzan con menos espacio de pila por defecto (2kb en lugar de 4MB). Pero esto se puede ajustar manualmente.
  • ¡mejor cooperan con el planificador del espacio de usuario. Debido a que el planificador del núcleo es preventivo, las tareas realizadas por los subprocesos se asignan rebanadas de tiempo. Si las tareas reales no encajan en las rodajas, se desperdicia algún tiempo de CPU. A diferencia de, por ejemplo, Goroutinas, que se ajustan a tantos micro tareas realizadas por diferentes goroutinas en la misma porción de tiempo posible.

A performant and extensible Web Server with Zig and Python

el tiempo de ejecución de Go, por ejemplo, multiplexas goroutinas en los hilos del sistema operativo. Los hilos comparten la tabla de página, así como otros recursos propiedad de un proceso. Si introducimos el aislamiento y la afinidad de la CPU a la mezcla: los hilos se ejecutarán continuamente en sus respectivos núcleos de CPU, todas las estructuras de datos del sistema operativo permanecerán en la memoria sin necesidad de intercambiar, el programador del espacio de usuario asignará el tiempo de CPU a Goroutines con precisión, porque usa el modelo de multitarea cooperativa. ¿Es posible la competencia?

Las ganancias de rendimiento se logran al marcar la abstracción de nivel del sistema operativo de un hilo y reemplazándolo por la de una goroutina. ¿Pero no se pierde nada en la traducción?

¿Podemos cooperar con el núcleo?

Argumentaré que la abstracción de nivel del sistema operativo "verdadero" para una unidad de ejecución independiente ni siquiera es un hilo, en realidad es el proceso del sistema operativo. En realidad, la distinción aquí no es tan obvia: todo lo que distingue hilos y procesos son los diferentes valores PID y TID. En cuanto a los descriptores de archivos, la memoria virtual, los manejadores de señal, los recursos rastreados: si estos son separados para el niño se especifican en los argumentos a la syscall "clon". Por lo tanto, utilizaré el término "proceso" para significar un hilo de ejecución que posee los recursos de su propio sistema, principalmente tiempo de CPU, memoria, abierto de los descriptores de archivos.

A performant and extensible Web Server with Zig and Python

ahora ¿por qué es esto importante? Cada unidad de ejecución tiene sus propias demandas de recursos del sistema. Cada tarea compleja se puede dividir en unidades, donde cada una puede hacer su propia solicitud de recursos y recursos predecibles: memoria y tiempo de CPU. Y cuanto más arriba sea el árbol de las subtareas, hacia una tarea más general: el gráfico de recursos del sistema forma una curva de campana con colas largas. Y es su responsabilidad asegurarse de que las colas no invadan el límite de recursos del sistema. Pero, ¿cómo se hace eso y qué sucede si ese límite es de hecho?

Si usamos el modelo de un solo proceso y muchas corutinas para tareas independientes, cuando una corutina sobresalta el límite de memoria, porque el uso de la memoria se rastrea a nivel de proceso, todo el proceso se mata. Eso es en el mejor de los casos: si utiliza CGROUPS (que es automáticamente el caso de las cápsulas en Kubernetes, que tienen un Cloup por POD): se mata a todo el Cloup C. Hacer un sistema confiable necesita que esto se tenga en cuenta. ¿Y qué pasa con la hora de la CPU? Si nuestro servicio recibe muchas solicitudes intensivas en cómputo al mismo tiempo, no responderá. Luego fechas de plazos, cancelaciones, requisitos, reiniciados siguen.

La única forma realista de lidiar con estos escenarios para la mayoría de las pilas de software convencionales es dejar "goros" en el sistema, algunos recursos no utilizados para la cola de la curva de campana, y limitar el número de solicitudes concurrentes, que, nuevamente, conducen a recursos no utilizados. E incluso con eso, nos mataremos o no responderemos de vez en cuando, incluso para las solicitudes "inocentes" que están en el mismo proceso que el atípico. Este compromiso es aceptable para muchos y sirve sistemas de software en la práctica lo suficientemente bien. ¿Pero podemos hacerlo mejor?

Un modelo de concurrencia

Dado que el uso de recursos se rastrea por proceso, idealmente generaríamos un nuevo proceso para cada pequeña unidad de ejecución predecible. Luego establecemos el Ulimit para el tiempo y la memoria de la CPU, ¡y estamos listos! Ulimit tiene límites suaves y duros, lo que permitirá que el proceso termine con gracia al alcanzar el límite blando, y si eso no ocurre, posiblemente debido a un error, se termine con fuerza al alcanzar el límite duro. Desafortunadamente, el desove de nuevos procesos en Linux es lento, lo que desove un nuevo proceso por solicitud no es compatible con muchos marcos web, así como otros sistemas como temporal. Además, el cambio de proceso es más costoso, lo que se mitiga por la fijación de vacas y CPU, pero aún no es ideal. Los procesos de larga duración son una realidad inevitable, desafortunadamente.

A performant and extensible Web Server with Zig and Python

cuanto más avanzamos de la abstracción limpia de los procesos de corta duración, más trabajo del nivel del sistema operativo tendríamos que cuidarnos a nosotros mismos. Pero también hay beneficios a obtener, como hacer uso de IO_uring para un grupo de IO entre muchos hilos de ejecución. De hecho, si una tarea grande está compuesta de subasinas, ¿realmente nos importa su utilización de recursos individuales? Solo para perfilar. Pero si por la gran tarea pudiéramos administrar (cortar) las colas de la curva de campana de recursos, eso sería lo suficientemente bueno. Por lo tanto, podríamos generar tantos procesos como las solicitudes que deseamos manejar simultáneamente, hacer que sean de larga duración y simplemente reajustar el Ulimit para cada nueva solicitud. Entonces, cuando una solicitud invade sus restricciones de recursos, recibe una señal del sistema operativo y puede terminar con gracia, sin referir a otras solicitudes. O, si el alto uso de recursos es intencional, podríamos decirle al cliente que pague por una cuota de recursos más alta. Suena bastante bien para mí.

Pero el rendimiento aún sufrirá, en comparación con un enfoque de coroutine-per-solicit. Primero, copiar alrededor de la tabla de memoria del proceso es costosa. Debido a que la tabla contiene referencias a las páginas de memoria, podríamos hacer uso de grandes páginas, lo que limita el tamaño de los datos a copiar. Esto es solo directamente posible con idiomas de bajo nivel, como zig. Además, la multitarea de nivel de SO es preventiva y no cooperativa, lo que siempre será menos eficiente. ¿O es?

Multitarea cooperativa con Linux

Existe el syscall sched_yield, que permite que el hilo renuncie a la CPU cuando haya completado su parte de trabajo. Parece bastante cooperativo. ¿Podría haber una manera de solicitar una porción de tiempo de un tamaño dado también? En realidad, existe, con la política de programación Sched_Deadline. Esta es una política en tiempo real, lo que significa que para la porción de tiempo de la CPU solicitada, el hilo se ejecuta ininterrumpido. Pero si la porción se invade, la preferencia se activa y su hilo se cambia y se deprime. Y si la porción está subestimada, el hilo puede llamar a Sched_yield para indicar un acabado temprano, lo que permite que se ejecuten otros hilos. Eso parece lo mejor de ambos mundos: un modelo cooperativo y preemtivo.

A performant and extensible Web Server with Zig and Python

Una limitación es el hecho de que un hilo SHED_Deadline no puede bifurcarse. Esto nos deja con dos modelos para la concurrencia, ya sea un proceso por solicitud, que establece el plazo para sí mismo, y ejecuta un bucle de eventos para IO eficiente, o un proceso que desde el inicio genera un hilo para cada micro -tarea, cada una de las cuales establece su propia fecha límite, y utiliza colas para comunicarse entre sí. El primero es más sencillo, pero requiere un bucle de eventos en el espacio de usuarios, el segundo hace más uso del núcleo.

ambas estrategias logran el mismo fin que el modelo Coroutine - al cooperar con el kernel, es posible que las tareas de aplicación se ejecuten con interrupciones mínimas .

Python como un lenguaje de secuencias de comandos integrado

Todo esto es para el lado de las cosas de alto rendimiento, de bajo nivel y bajo nivel, donde brilla en zig. Pero cuando se trata del negocio real de la aplicación, la flexibilidad es mucho más valiosa que la latencia. Si un proceso involucra a las personas reales que firman documentos, la latencia de una computadora es insignificante. Además, a pesar de sufrir el rendimiento, los idiomas orientados a objetos le dan al desarrollador mejores primitivas para modelar el dominio del negocio. Y en el extremo más alejado de esto, los sistemas como Flowable y Camunda permiten al personal de gestión y operaciones programar la lógica comercial con más flexibilidad y una barrera de entrada más baja. Lenguajes como Zig no ayudarán con esto, y solo se interponen en su camino.

A performant and extensible Web Server with Zig and Python

Python, por otro lado, es uno de los idiomas más dinámicos que existen. Clases, objetos: todos son diccionarios debajo del capó y se pueden manipular en tiempo de ejecución como desee. Esto tiene una penalización de rendimiento, pero hace que modelar el negocio con clases y objetos y muchos trucos inteligentes prácticos. Zig es lo contrario de eso: hay pocos trucos inteligentes en zig, lo que le da el máximo control. ¿Podemos combinar sus poderes haciendo que se interoperen?

de hecho podemos, debido a que ambos soportan el C ABI. Podemos hacer que el intérprete de Python salga desde el proceso en zig, y no como un proceso separado, reduciendo la sobrecarga en el costo de tiempo de ejecución y el código de pegamento. Esto nos permite utilizar los asignadores personalizados de Zig dentro de Python, estableciendo un arena para procesar la solicitud individual, reduciendo así si no eliminar la sobrecarga de un recolector de basura y establecer una tapa de memoria. Una limitación importante serían los hilos de desove de tiempo de ejecución de CPython para la recolección de basura y IO, pero no encontré evidencia de que lo haga. Podríamos enganchar a Python en un bucle de eventos personalizado en zig, con el seguimiento de la memoria per-coroutina, haciendo uso del campo "Contexto" en AbstractMemoryloop. Las posibilidades son ilimitadas.

Conclusión

Discutimos los méritos de concurrencia, paralelismo y varias formas de integración con el kernel del sistema operativo. La exploración carece de puntos de referencia y código, que espero que lo compensan en la calidad de las ideas ofrecidas. ¿Has probado algo similar? ¿Cuáles son tus pensamientos? Comentarios Bienvenido :)

Lectura adicional

  • https://linux.die.net/man/2/clone
  • https://man7.org/linux/man-pages/man7/sched.7.html
  • https://man7.org/linux/man-pages/man2/sched_yield.2.html
  • https://rigtorp.se/low-latency-guide/
  • https://eli.thegreenplace.net/2018/measuring-context-switching-and-memory-overheads-for-linux-threads/
  • https://hadar.gr/2017/lightweight-goroutines
Declaración de liberación Este artículo se reproduce en: https://dev.to/brogrammerjohn/a-performant-and-extensible-web-server-with-zig-and-python-4adl?1 Si hay alguna infracción, comuníquese con [email protected] para eliminarlo.
Último tutorial Más>

Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.

Copyright© 2022 湘ICP备2022001581号-3