¿La gestión de la memoria en la programación se está convirtiendo en una preocupación irrelevante?

38

Antecedentes
Volví a visitar un sitio antiguo (pero excelente) en el que no había estado durante años: el Alioth Language Shootout ( http://benchmarksgame.alioth.debian.org/ ).

Comencé a programar en C / C ++ hace varios años, pero desde entonces he estado trabajando casi exclusivamente en Java debido a restricciones de lenguaje en los proyectos en los que he estado involucrado. Sin recordar las cifras, quería ver, aproximadamente, qué tan bien Java fue contra C / C ++ en términos de uso de recursos.

Los tiempos de ejecución aún eran relativamente buenos, con Java en el peor de los casos 4 veces más lento que C / C ++, pero en promedio alrededor de (o menos) 2 veces . Debido a la naturaleza de la implementación de Java en sí, esto no fue una sorpresa, y su tiempo de rendimiento fue en realidad más bajo de lo que esperaba.

El verdadero ladrillo fue la asignación de memoria ; en el peor de los casos, Java asignó:

  • un enorme 52x más memoria que C
  • y 25 veces más que C ++.

52 veces el recuerdo ... Absolutamente desagradable, ¿verdad? ... ¿O es eso? La memoria es relativamente barata ahora.

Pregunta:
Si no hablamos en términos de plataformas de destino con límites estrictos en la memoria de trabajo (es decir, sistemas integrados y similares), ¿ debería ser un problema el uso de la memoria al elegir un lenguaje de uso general en la actualidad?

Lo pregunto en parte porque estoy considerando migrar a Scala como mi idioma principal. Me gustan mucho los aspectos funcionales, pero por lo que puedo ver es aún más costoso en términos de memoria que Java. Sin embargo, dado que la memoria parece ser cada vez más rápida, más barata y más abundante cada año (parece cada vez más difícil encontrar una computadora portátil de consumo sin al menos 4 GB de RAM DDR3), ¿no podría argumentarse que la gestión de recursos es cada vez más? ¿irrelevante en comparación con las características de lenguaje de alto nivel (posiblemente costosas para la implementación) que permiten una construcción más rápida de soluciones más legibles?

csvan
fuente
32
No olvide que el hecho de que Java asigne 52 veces más memoria que C para un punto de referencia pequeño, no significa que usará 52 veces más memoria para una aplicación grande. La mayor parte de esa memoria será una cantidad fija requerida por la JVM, y cuanto más grande sea su aplicación, menos significativa será esa porción.
Carson63000
44
Si el desarrollo móvil es irrelevante, entonces sí.
JeffO
3
La pregunta es qué tan malo es el punto de referencia de Java frente a C / C ++ y qué significa en términos de elegir entre los dos lenguajes. Veo esto como un tema, relevante para todos los programadores, claro, enfocado y capaz de ser respondido razonablemente en su forma actual. He votado para reabrir.
GlenPeterson
La mayoría de los problemas de rendimiento son causados ​​y arreglados a nivel de diseño, no a nivel de herramienta. Algunos problemas necesitan una granularidad de 1 ms y, por lo tanto, requieren C / C ++. Si tiene margen de maniobra, como 10 ms, entonces tal vez Scala o Java sea una buena opción. La mayoría de los controladores de entrada para juegos funcionan en el nivel de 50-100 ms. Muchas personas hoy escriben secciones críticas en un idioma y el resto de un programa en otro.
GlenPeterson
44
Cuando se mira el "25x más que C ++" en esta prueba, se debe tener en cuenta la adición constante del tiempo de ejecución (aproximadamente 13 Mb). A medida que el problema se agrava, el requisito de memoria de tiempo de ejecución se reduce como porcentaje del programa completo. Cuando el uso de la memoria C ++ es inferior a 1 MB, si resta el uso de la memoria C ++ del uso de la memoria Java, obtendrá un valor bastante constante.

Respuestas:

34

La gestión de la memoria es completamente relevante ya que gobierna qué tan rápido aparece algo, incluso si ese algo tiene una gran cantidad de memoria. El mejor y más canónico ejemplo son los juegos con títulos AAA como Call of Duty o Bioshock. Estas son efectivamente aplicaciones en tiempo real que requieren grandes cantidades de control en términos de optimización y uso. El problema no es el uso en sí, sino la administración.

Se reduce a dos palabras: recolección de basura. Los algoritmos de Recolección de basura pueden causar pequeños inconvenientes en el rendimiento o incluso hacer que la aplicación se bloquee por un segundo o dos. Mayormente inofensivo en una aplicación de contabilidad pero potencialmente ruinoso en términos de experiencia del usuario en un juego de Call of Duty. Por lo tanto, en aplicaciones donde el tiempo importa, los lenguajes recolectados de basura pueden ser muy problemáticos. Es uno de los objetivos de diseño de Squirrel, por ejemplo, que busca remediar el problema que Lua tiene con su GC mediante el uso del recuento de referencias.

¿Es más un dolor de cabeza? Claro, pero si necesita un control preciso, lo soporta.

Ingeniero mundial
fuente
14
-1 "... literalmente letal en un juego ..." - Mi trabajo diario es un sistema crítico de seguridad como en la seguridad de la vida. Lo peor que sucede en el software del juego es que el escritor se arruina porque es malo y nadie lo compra. Esta es una diferencia que no debe ser trivializada.
mattnz
44
@mattnz Mala elección de palabras de mi parte. Ha sido arreglado. No era mi intención trivializar nada.
Ingeniero mundial
19
@Mattnz: Si estás familiarizado con los juegos, obviamente quiere decir que podría ser letal para tu personaje , lo cual es una declaración completamente cierta.
Mason Wheeler
8
+1 porque el respondedor tiene un diamante, por lo que la respuesta debe ser correcta.
psr
8
Los recolectores de basura en tiempo real han existido por siglos.
Jörg W Mittag
30

El verdadero ladrillo fue la asignación de memoria: en el peor de los casos, Java asignó 52 veces más memoria que C y 25 veces más que C ++.

¿ Entiende los números en los que basa su pregunta?

  • ¿Cuánta memoria se asignó?
  • ¿Qué estaban haciendo los programas?

Cuando hay una gran disparidad entre esos programas Java y C, es principalmente la asignación de memoria JVM predeterminada frente a lo que necesite libc:


  • Programa Java n-body 13,996KB :: Programa C 320KB :: Pascal gratis 8KB

Observe las tareas que requieren que se asigne memoria (o use memorias intermedias adicionales para acumular resultados de programas multinúcleo):

  • mandelbrot
    Java program 67 , 880KB :: C program 30 , 444KB


  • Programa Java de k-nucleótidos 494 , 040KB :: Programa C 153 , 452KB


  • programa Java de complemento inverso 511 , 484KB :: programa C 248 , 632KB


  • programa Java 557 de regex-dna , 080 KB :: programa C 289 , 088 KB

  • binary-trees
    Java program 506 , 592KB :: C program 99 , 448KB

... ¿debería ser un problema el uso de la memoria al elegir un lenguaje de uso general hoy?

Depende de si el uso específico , para su enfoque específico para resolver los problemas específicos que necesita resolver, estará limitado por los límites específicos de la memoria disponible en la plataforma específica que se utilizará.

igouy
fuente
3
Su punto sobre la búsqueda en los números es válido, y ese sitio ciertamente tiene bastantes renuncias en torno a sus pruebas. Su respuesta se fortalecería al abordar directamente la pregunta central, que es "¿debería ser una preocupación el uso de la memoria?"
1
Excelente respuesta que rescató una pregunta relativamente pobre (el punto de referencia vagamente especificado es incluso peor que la optimización prematura :). Los datos que respaldan el análisis están bien presentados, son concretos y constituyen una excelente fuente de reflexión. Definitivamente vale la pena una recompensa de "respuesta ejemplar" .
mosquito
17

Como con todas las cosas, es una compensación.

Si está creando una aplicación que se ejecutará en un escritorio de un solo usuario y se puede esperar razonablemente que controle una gran fracción de la RAM en esa máquina, puede valer la pena sacrificar el uso de memoria por la velocidad de implementación. Si está apuntando a esa misma máquina pero está construyendo una pequeña utilidad que competirá con un montón de otras aplicaciones que requieren mucha memoria y que se ejecutan simultáneamente, es posible que desee ser más cauteloso con respecto a esa compensación. Un usuario puede estar bien con un juego que quiere toda su memoria cuando se está ejecutando (aunque, como señala World Engineer, ellos ' Le preocupará si el recolector de basura decide pausar la acción periódicamente para hacer un barrido); es probable que estén mucho menos entusiasmados si el reproductor de música que ejecutan en segundo plano mientras hace otras cosas decide engullir un montón de memoria y interfiere con su capacidad de trabajo. Si está creando una aplicación basada en la web, cualquier memoria que use en los servidores limita su capacidad de escalar, lo que le obliga a gastar más dinero en más servidores de aplicaciones para admitir el mismo conjunto de usuarios. Eso puede tener un gran impacto en la economía de la empresa, por lo que es posible que desee ser muy cauteloso al hacer esa compensación. cualquier memoria que use en los servidores limita su capacidad de escalar, lo que le obliga a gastar más dinero en más servidores de aplicaciones para admitir el mismo conjunto de usuarios. Eso puede tener un gran impacto en la economía de la empresa, por lo que es posible que desee ser muy cauteloso al hacer esa compensación. cualquier memoria que use en los servidores limita su capacidad de escalar, lo que le obliga a gastar más dinero en más servidores de aplicaciones para admitir el mismo conjunto de usuarios. Eso puede tener un gran impacto en la economía de la empresa, por lo que es posible que desee ser muy cauteloso al hacer esa compensación.

Justin Cave
fuente
8

Depende de una serie de factores, especialmente la escala en la que está trabajando.

Solo por el argumento, supongamos una diferencia de 30x en la memoria y 2x en el uso de la CPU.

Si se trata de un programa interactivo que tomaría 10 megabytes de memoria y 1 milisegundo de CPU si está escrito en C, es prácticamente intrascendente: 300 megabytes de memoria y 2 milisegundos para ejecutar normalmente son completamente irrelevantes en un escritorio típico, y es poco probable que signifique mucho incluso en un teléfono o tableta.

Sin embargo, la diferencia entre necesitar alrededor de la mitad de los recursos de 1 servidor y necesitar 15 servidores es un paso mucho mayor, especialmente dado que la ampliación a 15 servidores probablemente requerirá mucho trabajo adicional para desarrollarse en lugar de menos. En lo que respecta a la expansión futura, los mismos factores que menciona tienden a sugerir que, a menos que su base de clientes experimente un crecimiento masivo , que si se ejecuta ahora en un servidor, es muy probable que cuando supere ese servidor, se capaz de reemplazar eso con un servidor más nuevo sin ningún problema.

El otro factor que realmente debe considerar es exactamente cuánta diferencia en el costo de desarrollo verá para su tarea particular. En este momento, básicamente estás mirando a un lado de una ecuación. Para tener una buena idea de los costos frente a los beneficios, debe (obviamente) considerar los costos y los beneficios, no solo uno de forma aislada. La pregunta real es básicamente: "¿es x mayor que y?" - pero no puedes determinar eso mirando solo x. Claramente, también debes mirar y.

Jerry Coffin
fuente
2
+1 para observar la escala. Eche un vistazo a este artículo para apreciar realmente la gestión de recursos a gran escala.
Guy Coder
6

La gestión de la memoria es absolutamente relevante en el mundo de hoy. Sin embargo, no de la manera que cabría esperar. Incluso en los idiomas recolectados de basura, debe asegurarse de no tener una fuga de referencia

Estás haciendo algo mal si este es tu código:

static List<string> Cache;

...
Cache.Add(foo); //and then never remove anything from Cache

La recolección de basura no puede saber por arte de magia que nunca volverá a usar alguna referencia a menos que lo haga para que no pueda volver a usarla, es decir, al hacerlo Cache=null, efectivamente alerta al recolector de basura que "oye, no voy a poder accede a él más. Haz lo que quieras con él "

Es más complicado que eso, pero las fugas de referencia son igual de dañinas que las fugas de memoria tradicionales, si no más.

También hay algunos lugares donde no puedes colocar un recolector de basura. Por ejemplo, el ATTiny84 es un microcontrolador con 512 bytes de código ROM y 32 bytes de RAM. ¡Buena suerte! Eso es un extremo, y probablemente no se programaría en otra cosa que ensamblar, pero aún así. En otros casos, puede tener 1M de memoria. Claro, podría colocar un recolector de basura, pero si el procesador es muy lento (ya sea por limitaciones o para conservar la batería), entonces no querrá usar un recolector de basura porque es demasiado costoso rastrear lo que un programador podría saber .

También se vuelve mucho más difícil usar la recolección de basura cuando necesita tiempos de respuesta garantizados. Por ejemplo, si tiene un monitor cardíaco o algo y cuando recibe uno 1en algún puerto, debe garantizar que puede responder con una señal adecuada o algo dentro de los 10 ms. Si en el medio de su rutina de respuesta, el recolector de basura necesita hacer un pase y termina tomando 100 ms para responder, podría ser alguien muerto. La recolección de basura es muy difícil, si no imposible, de usar cuando se deben garantizar los requisitos de tiempo.

Y, por supuesto, incluso en el hardware moderno, hay algunos casos en los que necesita ese 2% adicional de rendimiento al no preocuparse por los gastos generales de un recolector de basura.

Earlz
fuente
3

Como dijo Donald Knuth, la optimización prematura es la raíz de todo mal. A menos que tenga una razón para creer que la memoria será el cuello de botella, no se preocupe. Y dado que la ley de Moore sigue brindando una mayor capacidad de memoria (a pesar de que no estamos obteniendo un código de subproceso único más rápido), hay muchas razones para creer que en el futuro estaremos aún menos limitados que nosotros. son hoy

Dicho esto, si la optimización no es prematura, hágalo por todos los medios. Personalmente, estoy trabajando en un proyecto en este momento en el que entiendo el uso de mi memoria con gran detalle, en realidad necesito un control preciso y un barrido de basura me mataría. Por lo tanto, estoy haciendo este proyecto en C ++. Pero esa elección parece ser un evento de una vez cada varios años para mí. (Espero que dentro de unas semanas no vuelva a tocar C ++ por unos años más).

btilly
fuente
44
Esta actitud es la forma en que terminamos con el software empresarial inflado en computadoras increíblemente lentas que siguen pagándose. Todos dicen 'Claro que mi aplicación requiere más memoria, pero a quién le importa, ¡es prácticamente gratis!' y luego terminas con una pila completa de aplicaciones que consumen mucha memoria que hacen que una máquina de 4 GB de RAM funcione más lentamente que una máquina de 512 MB de RAM hace 10 años.
MrFox
@MrFox En realidad, el problema con el software empresarial es que las personas que deciden usarlo no son las personas que sufren con él. Consulte lists.canonical.org/pipermail/kragen-tol/2005-April/000772.html para obtener una excelente descripción de por qué está roto. En cuanto al resto, ¿echas de menos que te haya señalado que a veces es necesario preocuparse por el uso de la memoria?
btilly
3

Para las personas que se ocupan de la gestión de memoria de "grandes datos" sigue siendo un gran problema. Los programas de astronomía, física, bioinformática, aprendizaje automático, etc., tienen que lidiar con conjuntos de datos de varios gigabytes, y los programas se ejecutan mucho más rápido si las partes relevantes pueden mantenerse en la memoria. Incluso ejecutar en una máquina con 128 GB de RAM no resuelve el problema.

También está la cuestión de aprovechar la GPU, aunque quizás lo clasifiques como un sistema integrado. La mayor parte del pensamiento duro al usar CUDA u OpenCL se reduce a problemas de administración de memoria al transferir datos desde la memoria principal a la memoria de la GPU.

Charles E. Grant
fuente
1

Para ser justos, una gran cantidad de Java ofrece algunos patrones verdaderamente explosivos e inútiles de clase que simplemente matan el rendimiento y la memoria de los cerdos, pero me pregunto cuánto de esa memoria es solo la JVM, que en teoría (heh) te permite ejecutar el misma aplicación en múltiples entornos sin tener que reescribir completamente nuevos. Por lo tanto, la pregunta de compensación del diseño se reduce a: "¿Cuánto de la memoria de sus usuarios vale para usted una ventaja de desarrollo?"

Esto es, la OMI es una compensación razonable y que vale la pena considerar. Sin embargo, lo que me molesta es la noción de que debido a que las PC modernas son tan poderosas y la memoria es tan barata, podemos ignorar por completo tales preocupaciones y características de hinchazón y código de hinchazón, y ser flojos con respecto a las opciones hasta el punto en que parece que muchas de las cosas Lo hago en una PC con Windows ahora, lleva tanto tiempo como lo hizo en Windows '95. ¿En serio, Word? ¿Cuánta basura nueva que realmente necesita el 80% de su base de usuarios podría haber agregado en 18 años? Estoy bastante seguro de que teníamos correcciones ortográficas pre-windows ¿verdad? Pero estábamos hablando de memoria, que no es necesariamente velocidad si tienes suficiente, así que estoy divagando.

Pero, por supuesto, si puede realizar la aplicación en 2 semanas a un costo de unos pocos megabytes adicionales en lugar de 2 años para obtener la versión que solo necesita unas pocas K, vale la pena considerar cómo se comparan algunas megas ( Supongo) 4-12 conciertos en la máquina de los usuarios promedio antes de burlarse de la idea de ser tan descuidado.

Pero, ¿qué tiene esto que ver con Scala más allá de la cuestión del compromiso? El hecho de que se trate de recolección de basura no significa que no siempre debas tratar de pensar en el flujo de datos en términos de lo que hay en los ámbitos y cierres y si se debe dejar sentado o usar de tal manera que sea desasignado por GC cuando ya no es necesario. Eso es algo que incluso nosotros, los desarrolladores web de la interfaz de usuario de JavaScript, hemos tenido que pensar y con suerte continuaremos haciéndolo a medida que nos extendemos a otros dominios problemáticos como el cáncer conocedor del rendimiento (que todos deberían haber matado con Flash o Applets o algo así cuando tuvieron la oportunidad) que somos

Erik Reppen
fuente
0

¿La gestión de la memoria en la programación se está convirtiendo en una preocupación irrelevante?

La administración de la memoria (o control) es en realidad la razón principal por la que estoy usando C y C ++.

La memoria es relativamente barata ahora.

No es memoria rápida. Todavía estamos viendo una pequeña cantidad de registros, algo así como caché de datos de 32 KB para L1 en i7, 256 KB para L2 y 2 MB para L3 / core. Eso dijo:

Si no hablamos en términos de plataformas de destino con límites estrictos en la memoria de trabajo (es decir, sistemas embebidos y similares), ¿debería ser una preocupación el uso de la memoria al elegir un lenguaje de uso general en la actualidad?

Uso de memoria en un nivel general, tal vez no. Soy un poco poco práctico en el sentido de que no me gusta la idea de un bloc de notas que tome, digamos, 50 megabytes de DRAM y cientos de megabytes de espacio en el disco duro, aunque tengo eso de sobra y abundante. He estado presente por mucho tiempo y me parece extraño y un poco desagradable ver que una aplicación tan simple requiere relativamente tanta memoria para lo que debería ser posible con kilobytes. Dicho esto, podría vivir conmigo mismo si me encontrara con algo así si aún fuera agradable y receptivo.

La razón por la que la administración de memoria es importante para mí en mi campo no es reducir el uso de memoria en general. Cientos de megabytes de uso de memoria no necesariamente retrasarán una aplicación de una manera no trivial si no se accede a ninguna de esa memoria con frecuencia (por ejemplo, solo al hacer clic en un botón o alguna otra forma de entrada del usuario, que es extremadamente infrecuente a menos que usted están hablando de jugadores coreanos de Starcraft que podrían hacer clic en un botón un millón de veces por segundo).

La razón por la que es importante en mi campo es mantener la memoria apretada y muy cerca, a la que se accede con mucha frecuencia (por ejemplo, estar en bucle en cada cuadro) en esas rutas críticas. No queremos perder un caché cada vez que accedemos a solo uno de un millón de elementos a los que se debe acceder en un bucle en cada cuadro. Cuando bajamos la memoria de la jerarquía de la memoria lenta a la memoria rápida en grandes fragmentos, digamos líneas de caché de 64 bytes, es realmente útil si esos 64 bytes contienen datos relevantes, si podemos encajar múltiples elementos en esos 64 bytes, y si nuestros patrones de acceso son tales que los usamos todos antes de que se desalojen los datos.

Esos datos a los que se accede con frecuencia para el millón de elementos solo pueden abarcar 20 megabytes aunque tengamos gigabytes. Todavía hace un mundo de diferencia en las velocidades de cuadros que se repiten sobre esos datos cada cuadro dibujado si la memoria está apretada y estrecha para minimizar las fallas de caché, y ahí es donde la administración / control de la memoria es tan útil. Ejemplo visual simple en una esfera con algunos millones de vértices:

ingrese la descripción de la imagen aquí

Lo anterior es en realidad más lento que mi versión mutable, ya que está probando una representación persistente de la estructura de datos de una malla, pero aparte de eso, solía tener dificultades para lograr tales velocidades de cuadros incluso en la mitad de esos datos (es cierto que el hardware se ha vuelto más rápido desde mis dificultades) ) porque no aprendí a minimizar los errores de caché y el uso de memoria para datos de malla. Las mallas son algunas de las estructuras de datos más complicadas con las que he tratado a este respecto porque almacenan tantos datos interdependientes que tienen que estar sincronizados, como polígonos, bordes, vértices, tantos mapas de textura como el usuario quiera adjuntar, pesos óseos, mapas de colores, conjuntos de selección, objetivos de transformación, pesos de borde, materiales poligonales, etc., etc., etc.

He diseñado e implementado una serie de sistemas de malla en las últimas dos décadas y su velocidad fue a menudo muy proporcional al uso de su memoria. Aunque estoy trabajando con mucha más memoria que cuando comencé, mis nuevos sistemas de malla son 10 veces más rápidos que mi primer diseño (hace casi 20 años) y en gran medida porque usan alrededor de 1/10 de la memoria. La versión más reciente incluso utiliza compresión indexada para acumular la mayor cantidad de datos posible, y a pesar de la sobrecarga de procesamiento de la descompresión, la compresión en realidad mejoró el rendimiento porque, nuevamente, tenemos muy poca memoria rápida preciosa. Ahora puedo ajustar un millón de mallas poligonales con coordenadas de textura, arrugas de bordes, asignaciones de materiales, etc. junto con un índice espacial para ello en aproximadamente 30 megabytes.

Aquí está el prototipo mutable con más de 8 millones de cuadrángulos y un esquema de subdivisión multires en un i3 con un GF 8400 (esto fue de hace algunos años). Es más rápido que mi versión inmutable, pero no se usa en producción ya que he encontrado que la versión inmutable es mucho más fácil de mantener y el impacto en el rendimiento no es tan malo. Tenga en cuenta que la estructura de alambre no indica facetas, sino parches (los cables son en realidad curvas, de lo contrario toda la malla sería negra), aunque el pincel modifica todos los puntos de una faceta.

ingrese la descripción de la imagen aquí

De todos modos, solo quería mostrar algo de esto arriba para mostrar algunos ejemplos concretos y áreas donde la administración de memoria es tan útil y, con suerte, para que la gente no piense que estoy hablando de mi trasero. Tiendo a irritarme un poco cuando la gente dice que la memoria es tan abundante y barata, porque se trata de memoria lenta como DRAM y discos duros. Todavía es tan pequeño y tan valioso cuando hablamos de memoria rápida, y el rendimiento para rutas realmente críticas (es decir, caso común, no para todo) se relaciona con jugar a esa pequeña cantidad de memoria rápida y utilizarla de la manera más efectiva posible. .

Para este tipo de cosas, es realmente útil trabajar con un lenguaje que le permita diseñar objetos de alto nivel como C ++, por ejemplo, y al mismo tiempo poder almacenar estos objetos en una o más matrices contiguas con la garantía de que la memoria de Todos estos objetos estarán representados de manera contigua y sin sobrecarga de memoria innecesaria por objeto (por ejemplo: no todos los objetos necesitan reflexión o despacho virtual). Cuando realmente se muda a esas áreas críticas para el rendimiento, en realidad se convierte en un aumento de la productividad tener ese control de memoria sobre, por ejemplo, jugar con grupos de objetos y usar tipos de datos primitivos para evitar sobrecarga de objetos, costos de GC y mantener la memoria a la que se accede con frecuencia juntos contiguos

Por lo tanto, la gestión / control de la memoria (o la falta de ella) es en realidad una razón dominante en mi caso para elegir qué idioma de manera más productiva me permite abordar los problemas. Definitivamente escribo mi parte de código que no es crítico para el rendimiento, y para eso tiendo a usar Lua, que es bastante fácil de incrustar desde C.


fuente