¿Se debe evitar el STL en aplicaciones grandes?

24

Esto puede sonar como una pregunta extraña, pero en mi departamento estamos teniendo problemas con la siguiente situación:

Estamos trabajando aquí en una aplicación de servidor, que está creciendo cada vez más, incluso en el punto en que estamos considerando dividirla en diferentes partes (archivos DLL), cargando dinámicamente cuando sea necesario y descargando luego, para poder manejar Los problemas de rendimiento.

Pero: las funciones que estamos utilizando pasan parámetros de entrada y salida como objetos STL, y como se menciona en una respuesta de desbordamiento de pila , esta es una muy mala idea. (La publicación contiene algunas soluciones ± y hacks, pero no todo parece muy sólido).

Obviamente, podríamos reemplazar los parámetros de entrada / salida por tipos C ++ estándar y crear objetos STL a partir de aquellos que alguna vez estuvieron dentro de las funciones, pero esto podría estar causando caídas en el rendimiento.

¿Está bien concluir que, en caso de que esté considerando construir una aplicación, que podría crecer tanto que una sola PC ya no pueda manejarla, no debe usar STL como tecnología?

Más información sobre esta pregunta:
parece haber algunos malentendidos sobre la pregunta: el problema es el siguiente:
mi aplicación está utilizando una gran cantidad de rendimiento (CPU, memoria) para completar su trabajo, y me gustaría dividir este trabajo en diferentes partes (ya que el programa ya está dividido en múltiples funciones), no es tan difícil crear algunas DLL fuera de mi aplicación y poner algunas de las funciones en la tabla de exportación de esas DLL. Esto daría como resultado la siguiente situación:

+-----------+-----------+----
| Machine1  | Machine2  | ...
| App_Inst1 | App_Inst2 | ...
|           |           |    
| DLL1.1    | DLL2.1    | ...
| DLL1.2    | DLL2.2    | ...
| DLL1.x    | DLL2.x    | ...
+-----------+-----------+----

App_Inst1 es la instancia de la aplicación, instalada en Machine1, mientras que App_Inst2 es la instancia de la misma aplicación, instalada en Machine2.
DLL1.x es una DLL, instalada en Machine1, mientras que DLL2.x es una DLL, instalada en Machine2.
DLLx.1 cubre la función exportada1.
DLLx.2 cubre la función exportada2.

Ahora en Machine1 me gustaría ejecutar function1 y function2. Sé que esto sobrecargará Machine1, por lo que me gustaría enviar un mensaje a App_Inst2, pidiéndole a esa instancia de la aplicación que realice la función2.

Los parámetros de entrada / salida de function1 y function2 son objetos STL (Biblioteca de tipos estándar de C ++), y regularmente podría esperar que el cliente realice actualizaciones de App_Inst1, App_Inst2, DLLx.y (pero no todas, el cliente podría actualizar Machine1 pero no Machine2, o solo actualice las aplicaciones pero no las DLL o viceversa, ...). Obviamente, si la interfaz (parámetros de entrada / salida) cambia, entonces el cliente se ve obligado a realizar actualizaciones completas.

Sin embargo, como se menciona en la URL de StackOverflow referida, una simple compilación de App_Inst1 o una de las DLL puede hacer que todo el sistema se desmorone, de ahí mi título original de esta publicación, desaconsejando el uso de STL (Plantilla estándar de C ++ Biblioteca) para grandes aplicaciones.

Espero haber aclarado algunas preguntas / dudas.

Dominique
fuente
44
¿Está seguro de que tiene problemas de rendimiento debido a su tamaño ejecutable ? ¿Puede agregar algunos detalles sobre si es realista suponer que todo su software está compilado con el mismo compilador (por ejemplo, de una vez en el servidor de compilación) o si realmente desea dividirse en equipos independientes?
nvoigt
55
Básicamente, necesita una persona cuyo trabajo dedicado sea "administrador de compilación" y "administrador de versión", para asegurarse de que todos los proyectos de C ++ se compilan en la misma versión del compilador y con configuraciones idénticas del compilador de C ++, compiladas a partir de una instantánea consistente (versión) de la fuente código, etc. Normalmente, esto se soluciona bajo el lema de "integración continua". Si busca en línea, encontrará muchos artículos y herramientas. Las prácticas desactualizadas pueden reforzarse a sí mismas: una práctica desactualizada puede hacer que todas las prácticas se desactualicen.
rwong
8
La respuesta aceptada en la pregunta vinculada establece que el problema es con las llamadas de C ++ en general. Entonces, "C ++ pero no STL" no ayuda, debe ir con C desnudo para estar seguro (pero también vea las respuestas, la serialización es probablemente una mejor solución).
Frax
52
cargando dinámicamente cuando sea necesario y descargando luego, para poder manejar los problemas de rendimiento ¿Qué "problemas de rendimiento"? No conozco ningún problema que no sea usar demasiada memoria que se puede solucionar descargando cosas como archivos DLL de la memoria, y si ese es el problema, la solución más fácil es comprar más RAM. ¿Ha perfilado su aplicación para identificar los cuellos de botella del rendimiento real? Debido a que esto suena como un problema XY, tiene "problemas de rendimiento" no especificados y alguien ya ha decidido la solución.
Andrew Henle
44
@MaxBarraclough "The STL" se acepta perfectamente como un nombre alternativo para los contenedores y funciones con plantilla que se han incluido en la Biblioteca estándar de C ++. De hecho, las Pautas Básicas de C ++, escritas por Bjarne Stroustrup y Herb Sutter, hacen referencia en repetidas ocasiones a "la STL" cuando se habla de ellas. No puede obtener una fuente mucho más autorizada que esa.
Sean Burton

Respuestas:

110

Este es un problema clásico XY muy frío.

Su verdadero problema son los problemas de rendimiento. Sin embargo, su pregunta deja en claro que no ha realizado perfiles u otras evaluaciones de dónde provienen los problemas de rendimiento. En cambio, espera que dividir su código en archivos DLL resuelva mágicamente el problema (que no lo hará, para el registro), y ahora está preocupado por un aspecto de esa falta de solución.

En cambio, debes resolver el problema real. Si tiene múltiples ejecutables, verifique cuál está causando la desaceleración. Mientras lo hace, asegúrese de que realmente sea su programa el que tome todo el tiempo de procesamiento, y no un controlador Ethernet mal configurado o algo así. Y después de eso, comience a perfilar las diversas tareas en su código. El temporizador de alta precisión es tu amigo aquí. La solución clásica es monitorear los tiempos de procesamiento promedio y en el peor de los casos para una porción de código.

Cuando tenga datos, puede averiguar cómo lidiar con el problema, y ​​luego puede averiguar dónde optimizar.

Graham
fuente
54
"En cambio, espera que dividir su código en archivos DLL resuelva mágicamente el problema (que no lo hará, para el registro)" - +1 para esto. Es casi seguro que su sistema operativo implementa paginación de demanda que logra exactamente el mismo resultado que la funcionalidad de carga y descarga en archivos DLL, solo de forma automática en lugar de requerir intervención manual. Incluso si es mejor para predecir cuánto tiempo debe permanecer una pieza de código una vez que se utiliza el sistema de memoria virtual del sistema operativo (lo que es realmente poco probable), el sistema operativo almacenará en caché el archivo DLL y negará sus esfuerzos de todos modos .
Julio
@Jules Ver actualización: han aclarado que las DLL existen solo en máquinas separadas, por lo que tal vez pueda ver esta solución funcionando. Sin embargo, ahora hay una sobrecarga de comunicación, tan difícil de estar seguro.
Izkata
2
@Izkata: todavía no está del todo claro, pero creo que lo que se describe es que quieren seleccionar dinámicamente (según la configuración de tiempo de ejecución) una versión de cada función que sea local o remota. Pero cualquier parte del archivo EXE que nunca se use en una máquina determinada simplemente nunca se cargará en la memoria, por lo que no es necesario el uso de archivos DLL para este propósito. Simplemente incluya ambas versiones de todas las funciones en la compilación estándar y cree una tabla de punteros de función (u objetos invocables en C ++, o cualquier método que prefiera) para invocar la versión adecuada de cada función.
Julio
38

Si tiene que dividir un software entre varias máquinas físicas, debe tener alguna forma de serialización al pasar datos entre máquinas, ya que solo en algunos casos puede enviar el mismo binario exacto entre máquinas. La mayoría de los métodos de serialización no tienen problemas para manejar los tipos de STL, por lo que ese caso no es algo que me preocupe.

Si tiene que dividir una aplicación en Bibliotecas Compartidas (DLL) (antes de hacerlo por razones de rendimiento, realmente debe asegurarse de que realmente resolverá sus problemas de rendimiento) pasar objetos STL puede ser un problema, pero no es necesario. Como el enlace que proporcionó ya describe, pasar objetos STL funciona si usa el mismo compilador y la misma configuración del compilador. Si los usuarios proporcionan las DLL, es posible que no pueda contar fácilmente con esto. Sin embargo, si proporciona todas las DLL y compila todo junto, es posible que pueda contar con él y que sea muy posible utilizar objetos STL a través de los límites de DLL. Todavía debe tener cuidado con la configuración de su compilador para que no obtenga múltiples montones diferentes si pasa la propiedad del objeto, aunque eso no es un problema específico de STL.

Pierre Andersson
fuente
1
Sí, y especialmente la parte sobre pasar objetos asignados a través de límites de DLL / so. En términos generales, la única forma de evitar absolutamente el problema del asignador múltiple es asegurarse de que la DLL / so (¡o la biblioteca!) Que asignó la estructura también la libere. Es por eso que ve muchas y muchas API de estilo C escritas de esta manera: una API libre explícita para cada API que devuelve una matriz / estructura asignada. El problema adicional con STL es que la persona que llama puede esperar poder modificar la estructura de datos compleja pasada (agregar / eliminar elementos) y eso tampoco puede permitirse. Pero es difícil de hacer cumplir.
davidbak
1
Si tuviera que dividir una aplicación como esta, probablemente usaría COM, pero esto generalmente aumenta el tamaño del código ya que cada componente trae sus propias bibliotecas C y C ++ (que pueden compartirse cuando son iguales, pero pueden divergir cuando sea necesario, por ejemplo, durante las transiciones. Sin embargo, no estoy convencido de que este sea el curso de acción apropiado para el problema del OP.
Simon Richter
2
Como ejemplo específico, el programa es altamente probable que en algún lugar de querer enviar un texto a otra máquina. En algún momento, habrá un puntero a algunos caracteres involucrados en la representación de ese texto. No se puede simplemente transmitir los bits de esos punteros y esperar un comportamiento definido en el lado receptor
Caleth
20

Estamos trabajando aquí en una aplicación de servidor, que crece cada vez más, incluso en el punto que estamos considerando dividirla en diferentes partes (DLL), cargando dinámicamente cuando sea necesario y descargando luego, para poder manejar el problemas de desempeño

La RAM es barata y, por lo tanto, el código inactivo es barato. La carga y descarga de código (especialmente la descarga) es un proceso frágil y es poco probable que tenga un efecto significativo en el rendimiento de sus programas en hardware moderno de escritorio / servidor.

La memoria caché es más costosa, pero eso solo afecta el código que está activo recientemente, no el código que está en la memoria sin usar.

En general, los programas superan sus computadoras debido al tamaño de los datos o el tiempo de CPU, no el tamaño del código. Si el tamaño de su código se está volviendo tan grande que está causando problemas importantes, es probable que desee ver por qué eso está sucediendo en primer lugar.

Pero: las funciones que estamos utilizando pasan parámetros de entrada y salida como objetos STL, y como se menciona en esta URL de StackOverflow, esta es una muy mala idea.

Debería estar bien siempre que los archivos dlls y el ejecutable se creen con el mismo compilador y se vinculen dinámicamente con la misma biblioteca de tiempo de ejecución de C ++. De ello se deduce que si la aplicación y sus dlls asociados se crean y se implementan como una sola unidad, entonces no debería ser un problema.

Donde puede convertirse en un problema es cuando las bibliotecas están construidas por diferentes personas o se pueden actualizar por separado.

¿Está bien concluir que, en caso de que esté considerando construir una aplicación, que podría crecer tanto que una sola PC ya no pueda manejarla, no debe usar STL como tecnología?

Realmente no.

Una vez que comience a distribuir una aplicación en varias máquinas, tendrá muchas consideraciones sobre cómo pasar los datos entre esas máquinas. Es probable que los detalles de si se utilizan tipos STL o tipos más básicos se pierdan en el ruido.

Peter Green
fuente
2
El código inactivo probablemente nunca se carga en la RAM en primer lugar. La mayoría de los sistemas operativos solo cargan páginas de archivos ejecutables si realmente son necesarios.
Julio
1
@Jules: si el código muerto se mezcla con el código en vivo (con tamaño de página = granularidad de 4k), se asignará + cargará. La memoria caché funciona con una granularidad mucho más fina (64B), por lo que aún es cierto que las funciones no utilizadas no duelen mucho. Sin embargo, cada página necesita una entrada TLB y (a diferencia de RAM) que es un recurso de tiempo de ejecución escaso. (Las asignaciones respaldadas por archivos generalmente no usan páginas enormes, al menos no en Linux; una página enorme es de 2MiB en x86-64, por lo que puede cubrir mucho más código o datos sin que se pierda ningún TLB con páginas grandes).
Peter Cordes
1
Qué nota @PeterCordes: ¡Así que asegúrese de usar "PGO" como parte de su proceso de compilación para el lanzamiento!
JDługosz
13

No, no creo que esa conclusión siga. Incluso si su programa se distribuye en varias máquinas, no hay razón para que el uso de STL lo obligue internamente a usarlo en la comunicación entre módulos / procesos.

De hecho, diría que debe separar el diseño de interfaces externas de la implementación interna desde el principio, ya que la primera será más sólida / difícil de cambiar en comparación con lo que se usa internamente

Bwmat
fuente
7

Te estás perdiendo el punto de esa pregunta.

Básicamente hay dos tipos de DLL. La tuya y la de otra persona. El "problema STL" es que usted y ellos pueden no estar utilizando el mismo compilador. Obviamente, eso no es un problema para su propia DLL.

MSalters
fuente
5

Si compila las DLL desde el mismo árbol de origen al mismo tiempo con el mismo compilador y las mismas opciones de compilación, entonces funcionará bien.

Sin embargo, la forma "con sabor a Windows" de dividir una aplicación en varias partes, algunas de las cuales son reutilizables, son los componentes COM . Estos pueden ser pequeños (controles individuales o códecs) o grandes (IE está disponible como control COM, en mshtml.dll).

cargando dinámicamente cuando sea necesario y descargando luego

Para una aplicación de servidor, esto probablemente tendrá una eficiencia terrible ; solo es realmente viable cuando tiene una aplicación que se mueve a través de múltiples fases durante un largo período de tiempo para que sepa cuándo algo no será necesario nuevamente. Me recuerda a los juegos de DOS que utilizan el mecanismo de superposición.

Además, si su sistema de memoria virtual funciona correctamente, se encargará de esto por paginación de páginas de códigos no utilizadas.

podría crecer tanto que una sola PC ya no pueda soportarlo

Compre una PC más grande.

No olvide que con la optimización correcta, una computadora portátil puede superar a un clúster hadoop.

Si realmente necesita múltiples sistemas, debe pensar con mucho cuidado sobre el límite entre ellos, ya que ahí es donde está el costo de serialización. Aquí es donde debe comenzar a mirar marcos como MPI.

pjc50
fuente
1
"Solo es realmente viable cuando tienes una aplicación que se mueve a través de múltiples fases durante un largo período de tiempo para que sepas cuándo algo no será necesario nuevamente", incluso entonces es poco probable que ayude mucho, porque el sistema operativo lo hará guarde en caché los archivos DLL, lo que probablemente terminará tomando más memoria que solo incluir las funciones directamente en su ejecutable base. Las superposiciones solo son útiles en sistemas sin memoria virtual, o cuando el espacio de direcciones virtuales es el factor limitante (supongo que esta aplicación es de 64 bits, no de 32 ...).
Julio
3
"Compre una PC más grande" +1. Ahora puede adquirir sistemas con múltiples terabytes de RAM. Puede contratar uno de Amazon por menos de la tarifa por hora de un solo desarrollador. ¿Cuánto tiempo pasará el desarrollador optimizando su código para reducir el uso de memoria?
Julio
2
El mayor problema al que me he enfrentado con "comprar una PC más grande" estaba relacionado con la pregunta "¿hasta qué punto escalará su aplicación?". Mi respuesta fue "¿cuánto está dispuesto a gastar en una prueba? Porque espero que se amplíe tanto que alquilar una máquina adecuada y configurar una prueba grande adecuada costará miles de dólares. Ninguno de nuestros clientes está cerca a lo que puede hacer una PC con una sola CPU ". Muchos programadores mayores no tienen una idea realista de cuánto han crecido las PC; La tarjeta de video sola en las PC modernas es una supercomputadora según los estándares del siglo XX.
MSalters
Componentes COM? En la década de 1990 tal vez, pero ahora?
Peter Mortensen
@MSalters: correcto ... cualquier persona que tenga alguna pregunta acerca de hasta qué punto una aplicación puede escalar en una sola PC debe consultar las especificaciones del tipo de instancia Amazon EC2 x1e.32xlarge: 72 núcleos de procesadores físicos en total en la máquina que proporcionan 128 núcleos virtuales en 2.3GHz (que puede explotar a 3.1GHz), potencialmente hasta 340GB / s de ancho de banda de memoria (dependiendo del tipo de memoria instalada, que no se describe en la especificación), y 3.9TiB de RAM. Tiene suficiente caché para ejecutar la mayoría de las aplicaciones sin tocar la RAM principal. Incluso sin una GPU es tan potente como un clúster de supercomputadora de 500 nodos de 2000.
Julio
0

Estamos trabajando aquí en una aplicación de servidor, que está creciendo cada vez más, incluso en el punto en que estamos considerando dividirla en diferentes partes (archivos DLL), cargando dinámicamente cuando sea necesario y descargando luego, para poder manejar Los problemas de rendimiento.

La primera parte tiene sentido (dividir la aplicación en diferentes máquinas, por razones de rendimiento).

La segunda parte (carga y descarga de bibliotecas) no tiene sentido, ya que es un esfuerzo adicional y no mejorará (realmente) las cosas.

El problema que está describiendo se resuelve mejor con máquinas de computación dedicadas, pero estas no deberían funcionar con la misma aplicación (principal).

La solución clásica se ve así:

[user] [front-end] [machine1] [common resources]
                   [machine2]
                   [machine3]

Entre las máquinas de front-end y de cómputo, puede tener cosas adicionales, como equilibradores de carga y monitoreo de rendimiento, y mantener un procesamiento especializado en máquinas dedicadas es bueno para el almacenamiento en caché y las optimizaciones de rendimiento.

Esto de ninguna manera implica una carga / descarga adicional de archivos DLL, ni nada que ver con el STL.

Es decir, use STL internamente según sea necesario, y serialice sus datos entre los elementos (consulte las memorias intermedias de protocolo y grpc y el tipo de problemas que resuelven).

Dicho esto, con la información limitada que proporcionó, esto se parece al clásico problema xy (como dijo @Graham).

utnapistim
fuente