¿Estoy optimizando prematuramente?

9

Actualmente estoy en la etapa de diseño de una arquitectura basada en componentes en C ++.

Mi diseño actual incluye el uso de funciones como:

  • std::vectors de std::shared_ptrs para sostener los componentes
  • std::dynamic_pointer_cast
  • std::unordered_map<std::string,[yada]>

Los componentes representarán los datos y la lógica de varios elementos que se necesitan en un software similar a un juego, como gráficos, física, inteligencia artificial, audio, etc.

He leído por todas partes que las fallas de caché son difíciles para el rendimiento, por lo que realicé algunas pruebas, lo que me llevó a creer que, de hecho, puede ralentizar una aplicación.

No he podido probar las características del lenguaje antes mencionadas, pero en muchos lugares se dice que tienden a costar mucho y, si es posible, deben evitarse.

Dado que estoy en la etapa de diseño de la arquitectura, y estos se incluirán en el núcleo del diseño, ¿debería tratar de encontrar formas de evitarlos ahora, ya que será muy difícil cambiarlo más adelante si hay rendimiento? ¿cuestiones?

¿O simplemente estoy atrapado haciendo una optimización prematura?

Vaillancourt
fuente
3
Sería muy reacio a establecer un diseño que hiciera muy difícil cambiarlo más adelante, independientemente de los problemas de rendimiento. Evita eso si puedes. Hay muchos diseños que son flexibles y rápidos.
candied_orange
1
Sin siquiera saber los detalles, la respuesta a esta pregunta es casi siempre un rotundo "¡SÍ!".
Mawg dice que reinstalar a Monica el
2
@Mawg "... Sin embargo, no debemos dejar pasar nuestras oportunidades en ese crítico 3%". Dado que este es el núcleo del diseño, ¿cómo podría saber si estoy trabajando en este 3%?
Vaillancourt
1
Excelentes puntos, Alexandre (+1), y, sí, sé que la mitad de la cita, que casi nunca se menciona :-) Pero, para volver a mi comentario anterior (que se refleja en la respuesta acertada) , the answer to this question is almost always a resounding "YES !!". Todavía siento que es mejor hacer que funcione primero y optimizar después, pero YMMV, todos tienen su opinión, todos los cuales son válidos, y solo el OP realmente puede responder a su propia pregunta subjetiva.
Mawg dice reinstalar a Mónica el
1
@AlexandreVaillancourt Siga leyendo el documento de Knuth (PDF, la cita viene del lado derecho de la página etiquetada 268, página 8 en un lector de PDF). "... será prudente mirar detenidamente el código crítico; pero solo después de que se haya identificado ese código. A menudo es un error hacer juicios a priori sobre qué partes de un programa son realmente críticas, ya que la experiencia universal de los programadores que han estado utilizando herramientas de medición han fallado sus suposiciones intuitivas ". (el énfasis es suyo)
8bittree

Respuestas:

26

Sin leer nada más que el título: Sí.

Después de leer el texto: sí. Aunque es cierto que los mapas y los punteros compartidos, etc., no funcionan bien en caché, seguramente descubrirá que para lo que desea usarlos, por lo que yo entiendo, no es el cuello de botella y no se mantendrá use la memoria caché de manera eficiente independientemente de la estructura de datos.

¡Escriba el software evitando los errores más estúpidos, luego pruebe, luego encuentre los cuellos de botella y luego optimice!

Fwiw: https://xkcd.com/1691/

steffen
fuente
3
Convenido. Haga que funcione bien primero, porque no importará qué tan rápido no funcione. Y recuerde siempre que las optimizaciones más efectivas no implican ajustar el código, sino encontrar un algoritmo diferente y más eficiente.
Todd Knarr
10
Me gustaría señalar que la primera línea no es verdadera porque la optimización siempre es prematura, sino porque la optimización no es prematura si sabes que la necesitas, en cuyo caso no estarías preguntando al respecto. Por lo tanto, la primera línea solo es cierta porque el hecho mismo de que esté haciendo una pregunta sobre si la optimización es prematura o no significa que no está seguro de que necesita optimización, lo que por definición lo hace prematuro. Uf.
Jörg W Mittag
@ JörgWMittag: de acuerdo.
steffen
3

No estoy familiarizado con C ++, pero en general depende.

No necesita optimizar prematuramente los algoritmos aislados donde puede optimizar fácilmente cuando se trata de eso.

Sin embargo, necesita obtener un diseño general de la aplicación para lograr los indicadores clave de rendimiento deseados.

Por ejemplo, si necesita diseñar una aplicación para atender millones de solicitudes por segundo, debe pensar en la escalabilidad de la aplicación al diseñarla, en lugar de hacer que la aplicación funcione.

Pelican de bajo vuelo
fuente
3

Si tiene que preguntar, entonces sí. Optimización prematura significa optimización antes de estar seguro de que hay un problema de rendimiento significativo.

JacquesB
fuente
1

ECS? De hecho, sugeriré que puede no ser prematuro si es así, pensar mucho en el lado orientado a los datos del diseño y comparar diferentes representantes porque podría afectar los diseños de su interfaz , y esto último es muy costoso cambiar tarde el juego. Además, ECS solo exige mucho trabajo y pensamiento por adelantado y creo que vale la pena utilizar algo de ese tiempo para asegurarse de que no le dará problemas de rendimiento a nivel de diseño más adelante dado que va a ser el corazón de su todo el maldito motor. Esta parte me deslumbra:

unordered_map<string,[yada]>

Incluso con pequeñas optimizaciones de cadena, tiene un contenedor de tamaño variable (cadenas) dentro de otro contenedor de tamaño variable (unordered_maps). De hecho, las pequeñas optimizaciones de cuerda en realidad podría ser tan perjudicial como útil en este caso, si la tabla es muy escasa, ya que la pequeña optimización cadena implicaría que cada índice no utilizada de la tabla hash todavía utilizará más memoria para la optimización de las SS ( sizeof(string)sería ser mucho más grande) hasta el punto en que la sobrecarga de memoria total de su tabla hash podría costar más de lo que esté almacenando en ella, especialmente si es un componente simple como un componente de posición, además de incurrir en más errores de caché con el gran paso para ir de una entrada en la tabla hash a la siguiente.

Supongo que la cadena es algún tipo de clave, como una ID de componente. Si es así, esto ya hace las cosas dramáticamente más baratas:

unordered_map<int,[yada]>

... si desea los beneficios de poder tener nombres fáciles de usar que los scripters puedan usar, por ejemplo, las cadenas internas pueden brindarle lo mejor de ambos mundos aquí.

Dicho esto, si puede asignar la cadena a un rango razonablemente bajo de índices densamente utilizados, entonces podría ser capaz de hacer esto:

vector<[yada]> // the index and key become one and the same

La razón por la que no considero esto prematuro es porque, nuevamente, podría afectar los diseños de su interfaz. El objetivo del DOD no debería ser tratar de encontrar las representaciones de datos más eficientes imaginables de una vez IMO (que generalmente se debe lograr de forma iterativa según sea necesario), sino pensar en ellas lo suficiente como para diseñar interfaces en la parte superior para trabajar con eso datos que le dejan suficiente espacio para respirar para perfilar y optimizar sin cambios de diseño en cascada.

Como un ejemplo ingenuo, un software de procesamiento de video que combina todo su código contra esto:

// Abstract pixel that could be concretely represented by
// RGB, BGR, RGBA, BGRA, 1-bit channels, 8-bit channels, 
// 16-bit channels, 32-bit channels, grayscale, monochrome, 
// etc. pixels.
class IPixel
{
public:
    virtual ~IPixel() {}
    ...
};

No va a llegar lejos sin una reescritura potencialmente épica, ya que la idea de abstraer en el nivel de un solo píxel ya es extremadamente ineficiente (la vptrmisma a menudo costaría más memoria que el píxel completo) en comparación con el resumen en el nivel de la imagen (que a menudo representan millones de píxeles). Por lo tanto, piense lo suficiente en sus representaciones de datos de antemano para que no tenga que enfrentar un escenario de pesadilla, e idealmente no más, pero aquí creo que vale la pena pensar en estas cosas por adelantado, ya que no desea construir un intrincado motor alrededor de su ECS y descubra que el ECS en sí mismo es el cuello de botella en formas que requieren que cambie las cosas a nivel de diseño.

En cuanto a las fallas de caché ECS, en mi opinión, los desarrolladores a menudo se esfuerzan demasiado para que su caché ECS sea amigable. Comienza a producir muy poco dinero para que el dólar intente acceder a todos sus componentes de una manera perfectamente contigua, y a menudo implicará copiar y mezclar datos por todo el lugar. Por lo general, es lo suficientemente bueno, por ejemplo, simplemente mezclar índices de componentes de clasificación antes de acceder a ellos para que pueda acceder a ellos de una manera en la que al menos no esté cargando una región de memoria en una línea de caché, solo para desalojarla y luego cargarla todo de nuevo en el mismo bucle solo para acceder a una parte diferente de la misma línea de caché. Y un ECS no tiene que proporcionar una eficiencia sorprendente en todos los ámbitos. No es que un sistema de entrada se beneficie de eso tanto como un sistema de física o renderizado, por lo que recomiendo apuntar a "bueno" eficiencia en todos los ámbitos y "excelente" solo en los lugares donde realmente lo necesita. Dicho esto, el uso deunordered_mapy stringaquí son lo suficientemente fáciles de evitar.


fuente