¡hola, soy el spoiler de este post! |
#include <stdio.h> #include <time.h> #include <stdlib.h> #include <vector> #define MYSIZE 1000000 // variable dummy con el fin de evitar el vaciado total de las funciones usando G++ -O2 int avoid_optimization; // prototipos locales void testVLA(int size); void testMallocVLA(int size); void testStackFLA(int dum); void testHeapFLA(int dum); void testAllocaVLA(int size); void testVectorVLA(int size); void testNewVLA(int size); void runTest(int iterations, void (*funcptr)(int), int size, const char *name); // función main() int main(int argc, char* argv[]) { // test argumentos if (argc != 2) { // error args printf("%s: wrong arguments counts\n", argv[0]); printf("usage: %s vla iterations [e.g.: %s 10000]\n", argv[0], argv[0]); return EXIT_FAILURE; } // extrae iteraciones int iterations = atoi(argv[1]); // ejecuta test runTest(iterations, &testVLA, MYSIZE, "testVLA"); runTest(iterations, &testMallocVLA, MYSIZE, "testMallocVLA"); runTest(iterations, &testStackFLA, 0, "testStackFLA"); runTest(iterations, &testHeapFLA, 0, "testHeapFLA"); runTest(iterations, &testAllocaVLA, MYSIZE, "testAllocaVLA"); runTest(iterations, &testVectorVLA, MYSIZE, "testVectorVLA"); runTest(iterations, &testNewVLA, MYSIZE, "testNewVLA"); // sale return EXIT_SUCCESS; } // función testAllocaVLA() void testAllocaVLA( int size) // size para alloca() { int *allocavla = (int*)alloca(size * sizeof(int)); // loop de test for (int i = 0; i < size; i++) allocavla[i] = i; // instrucción con el fin de evitar el vaciado total de las funciones usando G++ -O2 avoid_optimization = allocavla[size / 2]; } // función testNewVLA() void testNewVLA( int size) // size para new { int *newvla = new int[size]; // loop de test for (int i = 0; i < size; i++) newvla[i] = i; // instrucción con el fin de evitar el vaciado total de las funciones usando G++ -O2 avoid_optimization = newvla[size / 2]; delete[] newvla; }Como se puede ver, las dos funciones añadidas están perfectamente alineadas estilísticamente con las demás que ya había propuesto y son, como siempre, hiper-comentadas, así que ni siquiera tengo que detenerme en largas explicaciones. ¿Y los resultados de las pruebas? ¡Vamos a verlos!
aldo@ao-linux-nb:~/blogtest$ g++ vlacpp.cpp -o vlacpp aldo@ao-linux-nb:~/blogtest$ ./vlacpp 2000 testVLA - Tiempo transcurrido: 4.318492 segundos testMallocVLA - Tiempo transcurrido: 3.676805 segundos testStackFLA - Tiempo transcurrido: 4.339859 segundos testHeapFLA - Tiempo transcurrido: 4.340040 segundos testAllocaVLA - Tiempo transcurrido: 3.678644 segundos testVectorVLA - Tiempo transcurrido: 10.934088 segundos testNewVLA - Tiempo transcurrido: 3.679624 segundos aldo@ao-linux-nb:~/blogtest$ g++ -O2 vlacpp.cpp -o vlacpp aldo@ao-linux-nb:~/blogtest$ ./vlacpp 2000 testVLA - Tiempo transcurrido: 0.746956 segundos testMallocVLA - Tiempo transcurrido: 0.697261 segundos testStackFLA - Tiempo transcurrido: 0.696310 segundos testHeapFLA - Tiempo transcurrido: 0.700047 segundos testAllocaVLA - Tiempo transcurrido: 0.691677 segundos testVectorVLA - Tiempo transcurrido: 1.384563 segundos testNewVLA - Tiempo transcurrido: 0.695037 segundosEntonces, ¿qué se puede decir? Los resultados de los test de los post anteriores ya los hemos ampliamente comentados, por lo que ahora sólo podemos añadir que: alloca() es muy rápida, ya que es, en práctica, una malloc() en el stack (y utilizándola de manera más apropriada, podría/debería ser la más rápida del grupo). ¿Y la new? Bueno, se comporta (como se esperaba) muy bien, también porque, casi siempre, la new internamente utiliza la malloc().
De acuerdo, la alloca() es rápida, pero lo es (sólo un poco menos) también un VLA, y esto no le ha salvado de ser elegido como el malo de la película. Así que vamos a hacer de nuevo una lista de pros y contras, y a ver cuál es el lado más pesado. Veamos primero los pros:
- la alloca() es muy rápida, ya que usa el stack en lugar del heap.
- la alloca() es fácil de usar, es una malloc() sin free(). La variable alocada tiene un scope a nivel de función, entonces sigue siendo válida hasta que la función retorna al caller, exactamente como cualquier variable automática local (también un VLA funciona más o menos así, pero su scope es a nivel de bloque, no de función, y esto es, probablemente, un punto a favor de los VLAs).
- para la razón explicada en la sección 2, la alloca() no deja residuos de memoria en caso de errores graves en las actividades de una función (con malloc() + free() no es tan fácil lograr esto). Y si estás acostumbrado a utilizar cositas como longjmp() las ventajas en este sentido son grandes.
- debido a su aplicación interna (sin entrar en más detalles) la alloca() no provoca la fragmentación de memoria.
- la gestión de errores es problemática, porque no hay forma de saber si la alloca() ha alocado bien o ha causado un stack overflow (en este caso provoca efectos similares a los de un error para recursión infinita)... uh, esto es exactamente el mismo problema de los VLAs.
- la alloca() no es muy portable, ya que no es una función estándar y su funcionamiento/presencia depende en gran medida del compilador en uso.
- la alloca() es error prone (parte 1): hay que utilizarla con cuidado, ya que induce, por lo general, a errores cómo utilizar la variable asignada cuando ya no es válida (pasarla con un return o insertarla en una estructura de datos externa a la función, por ejemplo)... pero nosotros somos buenos programadores y este punto no nos da miedo, ¿no?
- la alloca() es error prone (parte 2): hay problemas aún más sutiles que considerar en el uso, por ejemplo puede ser MUY peligroso poner una alloca() dentro de un bucle o de una función recursiva (¡pobre stack!) o en una función inline (que utiliza el stack de manera que choca un poco con la forma de utilizar el stack de la alloca()) ... pero nosotros somos buenos programadores y este punto no nos da miedo, ¿no?
- la alloca() es error prone (parte 3): alloca() utiliza el stack, que normalmente está limitado con respecto al heap (especialmente en entornos embedded que son muy frecuentados por los programadores C...). Así agotar el stack y causar un stack overflow es fácil (y difícil de controlar, véase el punto 1)... pero nosotros somos buenos programadores y este punto no nos da miedo, ¿no?
¡Hasta el próximo post!