¿Es mejor asignar memoria en potencia de dos?
Cuando utilizamos malloc()
para asignar memoria, ¿deberíamos dar el tamaño que está en potencia de dos? ¿O simplemente le damos el tamaño exacto que necesitamos?
Como
//char *ptr= malloc( 200 );
char *ptr= malloc( 256 );//instead of 200 we use 256
Si es mejor dar un tamaño que sea potencia de dos, ¿a qué se debe? ¿Por qué es mejor?
Gracias
Editar
El motivo de mi confusión es la siguiente cita del blog de Joel Back to Basics.
Los programadores inteligentes minimizan la posible interrupción de malloc asignando siempre bloques de memoria que tienen potencias de 2 en tamaño. Ya sabes, 4 bytes, 8 bytes, 16 bytes, 18446744073709551616 bytes, etc. Por razones que deberían ser intuitivas para cualquiera que juegue con Lego, esto minimiza la cantidad de fragmentación extraña que ocurre en la cadena libre. Aunque pueda parecer que esto desperdicia espacio, también es fácil ver cómo nunca desperdicia más del 50% del espacio. Por lo tanto, su programa no utiliza más del doble de memoria de la que necesita, lo cual no es gran cosa.
Lo siento, debería haber publicado la cita anterior antes. ¡Mis disculpas!
La mayoría de las respuestas, hasta ahora, dicen que asignar memoria en potencia de dos es una mala idea, entonces, ¿en qué escenario es mejor seguir el punto de Joel malloc()
? ¿Por qué dijo eso? ¿La sugerencia citada anteriormente está obsoleta ahora?
Por favor explíquelo.
Gracias
Sólo da el tamaño exacto que necesitas. La única razón por la que un tamaño de potencia de dos podría ser "mejor" es para permitir una asignación más rápida y/o evitar la fragmentación de la memoria.
Sin embargo, cualquier implementación no trivial malloc
que se preocupe por ser eficiente redondeará internamente las asignaciones de esta manera cuando sea apropiado hacerlo. No necesita preocuparse por "ayudar" a malloc; malloc puede funcionar bien por sí solo.
Editar:
En respuesta a su cita del artículo de Joel sobre software, el punto de Joel en esa sección (que es difícil de discernir correctamente sin el contexto que sigue al párrafo que usted citó) es que si espera reasignar un búfer con frecuencia, es Es mejor hacerlo de forma multiplicativa, en lugar de aditiva. De hecho, esto es exactamente lo que hacen las clases std::string
y std::vector
en C++ (entre otras).
La razón por la que esto es una mejora no es porque esté ayudando malloc
proporcionando números convenientes, sino porque la asignación de memoria es una operación costosa y está tratando de minimizar la cantidad de veces que lo hace. Joel presenta un ejemplo concreto de la idea de una compensación tiempo-espacio. Argumenta que, en muchos casos en los que la cantidad de memoria necesaria cambia dinámicamente, es mejor desperdiciar algo de espacio (asignando hasta el doble de lo necesario en cada expansión) para ahorrar el tiempo que se necesitaría para abordar repetidamente en exactamente n
bytes de memoria, cada vez necesitas n
más bytes.
El multiplicador no tiene por qué ser dos: podrías asignar hasta tres veces más espacio del que necesitas y terminar con asignaciones en potencias de tres, o asignar hasta cincuenta y siete veces más espacio del que necesitas y terminar con asignaciones en potencias de cincuenta y siete. Cuanta más sobreasignación haga, con menos frecuencia necesitará reasignar, pero más memoria desperdiciará. La asignación en potencias de dos, que utiliza como máximo el doble de memoria de la necesaria, resulta ser un buen punto de partida hasta que tenga una mejor idea de cuáles son exactamente sus necesidades.
Menciona de pasada que esto ayuda a reducir la "fragmentación en la cadena libre", pero la razón de esto es más por el número y la uniformidad de las asignaciones que se realizan, que por su tamaño exacto. Por un lado, cuantas más veces asigne y desasigne memoria, más probabilidades tendrá de fragmentar el montón, sin importar el tamaño que esté asignando. En segundo lugar, si tiene varios buffers cuyo tamaño está cambiando dinámicamente utilizando el mismo algoritmo de cambio de tamaño multiplicativo, entonces es probable que si uno cambia de 32 a 64 y otro de 16 a 32, entonces la reasignación del segundo puede encajar justo donde la primera. solía ser. Este no sería el caso si uno cambiara el tamaño de 25 a 60 y el otro de 16 a 26.
Y nuevamente, nada de lo que él está diciendo se aplica si vas a realizar el paso de asignación solo una vez.
Sólo para hacer el papel del abogado del diablo, así es como lo hace Qt :
Supongamos que agregamos 15000 caracteres a la cadena QString. Luego, las siguientes 18 reasignaciones (de 15000 posibles) ocurren cuando QString se queda sin espacio: 4, 8, 12, 16, 20, 52, 116, 244, 500, 1012, 2036, 4084, 6132, 8180, 10228, 12276, 14324, 16372. Al final, QString tiene 16372 caracteres Unicode asignados, 15000 de los cuales están ocupados.
Los valores anteriores pueden parecer un poco extraños, pero estos son los principios rectores:
QString asigna 4 caracteres a la vez hasta alcanzar el tamaño 20. De 20 a 4084, avanza duplicando el tamaño cada vez. Más precisamente, avanza a la siguiente potencia de dos, menos 12. ( Algunos asignadores de memoria funcionan peor cuando se les solicita potencias exactas de dos , porque usan unos pocos bytes por bloque para llevar la contabilidad). A partir de 4084, avanza por bloques de 2048 caracteres (4096 bytes). Esto tiene sentido porque los sistemas operativos modernos no copian todos los datos al reasignar un búfer ; las páginas de la memoria física simplemente se reordenan y solo es necesario copiar los datos de la primera y la última página.
Me gusta la forma en que anticipan las características del sistema operativo en un código destinado a funcionar bien desde teléfonos inteligentes hasta granjas de servidores. Dado que son personas más inteligentes que yo, supongo que dicha función está disponible en todos los sistemas operativos modernos.