Stack vs Heap allocation. Is it worth it?

2 minute read

Published:

Is it worth to allocate things in the stack instead of in the heap? I kind of new the answer, but I didn’t know how much. Allocating things in the stack is super-cheap and, in most cases, uses a single instruction while allocating things in the heap is quite expensive (expensive system calls). Let’s do a simple example that proves this:

In the code below, I have created three implementations of the fibonacci series. In the first implementation, I allocated a long long in the heap (function fibonacci and helper function fib). The second implementation allocates a long_stack struct (contains two long long) in the stack (functions fib3 and fibonacci3). The third implemnentation allocates a long long in the stack (functions fib2 and fibonacci2). With these simple 3 functions we are going to measure how much time it takes to compute the 50th number in the fibonacci series.

The results couldn’t be clearer:

  1. Total time when allocating in the heap: 2.740 ms
  2. Total time when allocating in the stack (struct): 0.450 ms
  3. Total time when allocating in the stack: 0.340 ms

This represents a 6x speedup over the allocation in the heap.

#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>

static int NUMBER = 50;

typedef struct long_s {
  long long num;
} long_s;

typedef struct long_stack {
  long long p;
  long long n;
} long_stack;


long long fib(long_s *p, long_s *n, int remaining){
  if(remaining==0){
    return n->num;
  }else{
    long_s *nn = malloc(sizeof *nn);
    *nn = (long_s){.num = p->num + n->num};
    return fib(n, nn, remaining-1);
  }
}

long long fib2(long long p, long long n, int remaining){
  if(remaining==0){
    return n;
  }else{
    long long nn = p + n;
    return fib2(n, nn, remaining-1);
  }
}

long long fib3(long_stack s, int remaining){
  if(remaining==0){
    return s.n;
  }else{
    return fib3((long_stack){.p=s.n, .n=s.n+s.p}, remaining-1);
  }
}

long long fibonacci(int initial){
  long_s* prev = malloc(sizeof* prev);
  long_s* next = malloc(sizeof* next);
  *prev = (long_s) {.num = 1};
  *next = (long_s) {.num = 1};
  return fib(prev, next, initial-2);
}

long long fibonacci2(int initial){
  return fib2((long long)1, (long long)1, initial-2);
}

long long fibonacci3(int initial){
  return fib3((long_stack){.n = 1, .p = 1}, initial-2);
}

void chronos(struct timeval t1, struct timeval t2){
  double elapsedTime;
  // compute and print the elapsed time in millisec
  elapsedTime = (t2.tv_sec - t1.tv_sec) * 1000.0;      // sec to ms
  elapsedTime += (t2.tv_usec - t1.tv_usec) / 1000.0;   // us to ms

  printf("Elapsed time: %e\n", elapsedTime);
}

int main(){
  struct timeval t1, t2;

  // Example that allocates in the heap
  gettimeofday(&t1, NULL);
  for(unsigned int i=0; i<1000; i++){
    fibonacci(NUMBER);
  }
  gettimeofday(&t2, NULL);
  chronos(t1, t2);

  // Example that allocates in the stack a long
  gettimeofday(&t1, NULL);
  for(unsigned int i=0; i<1000; i++){
    fibonacci2(NUMBER);
  }
  gettimeofday(&t2, NULL);
  chronos(t1, t2);

  // Example that allocates a struck in the stack
  gettimeofday(&t1, NULL);
  for(unsigned int i=0; i<1000; i++){
    fibonacci3(NUMBER);
  }
  gettimeofday(&t2, NULL);
  chronos(t1, t2);
}