I would really appreciate if somebody could tell me why compilation of this program:
double data[123456789];
int main() {}
takes 10 times longer then compilation of this one:
int main() {
double* data=new double[123456789];
}
when both are compiled with:
$ g++ -O0
and the executables are almost the same size.
I am using gcc 4.4.3 on Ubuntu 10.04.
Thank you.
Static Memory Allocation is done before program execution. Dynamic Memory Allocation is done during program execution. In static memory allocation, once the memory is allocated, the memory size can not change. In dynamic memory allocation, when memory is allocated the memory size can be changed.
Static allocation is a procedure which is used for allocation of all the data objects at compile time. Static allocation is possible only when the compiler knows the size of data object at compile time. In this type of allocation, formation of data objects is not possible under any circumstances at run time.
For better memory management, memory requirements must be known before memory allocation. For example, the array declaration is an example of a static memory allocation and the size of the array must be known beforehand. You cannot change the size of the array once the memory is allocated.
Stack is allocated at runtime; layout of each stack frame, however, is decided at compile time, except for variable-size arrays. In addition to the layout, it is the stack base address that is decided before the program runs.
Dynamic Allocation
Your second program allocates memory at run-time; from the compiler's perspective, there is no real difference between compiling any of the following:
double *data = new double[123456789];
double *data = malloc(123456789);
double data = sqrt(123456789);
They all do different things, but all the compiler needs to do is generate a call to an external function with a fixed argument. You can see this if you use g++ -S
to generate assembly:
.text
main:
subq $8, %rsp /* Allocate stack space. */
movl $987654312, %edi /* Load value "123456789 * 8" as argument. */
call _Znam /* Call the allocation function. */
xorl %eax, %eax /* Return 0. */
addq $8, %rsp /* Deallocate stack space. */
ret
This is simple for any compiler to generate, and any linker to link.
Static Allocation
Your first program is a little tricker, though, as you have noticed. If we have a look at the assembly for it, we see something different going on:
.text
main:
xorl %eax, %eax /* Return 0. */
ret
.bss
data:
.zero 987654312 /* Reserve "123456789 * 8" bytes of space. */
The generated assembly asks for 123456789 * sizeof(double)
bytes of space to be reserved when the program is first started. When this is assembled and later linked (which happens behind the scenes is you just run g++ foo.c
), the linker ld
will actually allocate all of this reserved space in memory. This is where the time is going. If you run top
while g++
is running, you will see ld
suck up a large amount of your system's memory.
Reduced Executable Sizes
A reasonable question might be "Why doesn't my executable end up really large, if the memory is being reserved at link-time?". The answer is hidden in the .bss
marker in the assembly. This tells the linker that the data defined below it should not be stored in the final executable, but instead allocated to zero at run-time.
This leaves us with the follow series of steps:
The assembler tells the linker that it needs to create a section of memory that is 1GB long.
The linker goes ahead and allocates this memory, in preparation for placing it in the final executable.
The linker realizes that this memory is in the .bss
section and is marked NOBITS
, meaning that the data is just 0, and doesn't need to be physically placed into the final executable. It avoids writing out the 1GB of data, instead just throwing the allocated memory away.
The linker writes out to the final ELF file just the compiled code, producing a small executable.
A smarter linker might be able to avoid steps 2 and 3 above, making your compile time much faster. In reality, scenarios such as yours don't come up often enough in practice to make such an optimization worthwhile.
Dynamic versus Static Allocation
If you are trying to work out which of the above (dynamic versus static allocation) to actually use in your program, here are a few thoughts:
The linker will need to use as much memory as your final program (plus a bit). If you want to statically allocate 4GB of RAM, you will need 4GB of RAM for your linker. This isn't implicit in how linkers work, but rather just seems to be just the way they are implemented.
When allocating large amounts of memory, doing it dynamically allows you to do better error handling (displaying a user-friendly message on screen explaining you don't have enough memory, instead of just failing to load the executable with a message from the OS going to the user);
Dynamically allocating memory allows you to chose how much memory to allocate based on the your actual needs. (If data
is a cache, the user can select the cache size, if it is storing intermediate results, you can size it based on the problem, etc.)
Dynamically allocating memory allows you to free it later on, if your program needs to go on and do more work after it is finished with the memory.
In the end, if the above points don't matter and you can deal with longer compile times, it probably doesn't matter too much. Statically allocating memory can be much simpler, and is often the right approach for smaller amounts of memory or throw-away applications.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With