Skip to content

Instantly share code, notes, and snippets.

@jimblandy
Created November 1, 2012 18:58
Show Gist options
  • Save jimblandy/3995727 to your computer and use it in GitHub Desktop.
Save jimblandy/3995727 to your computer and use it in GitHub Desktop.
Microbenchmark for bump allocators with guard pages or limit pointer checks
#define _GNU_SOURCE // for gregset_t indices
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include <setjmp.h>
#if defined(LIMIT_PTR) == defined(GUARD_PAGE)
#error Define one of LIMIT_PTR or GUARD_PAGE.
#endif
void
syscall_failed(char *when)
{
fprintf(stderr, "%s: error: %s\n", when, strerror(errno));
exit(40);
}
const char *progname;
void usage()
{
fprintf(stderr, "usage: %s NUM_BLOCKS PAGES_PER_BLOCK BYTES_PER_OBJECT\n", progname);
exit(1);
}
// One allocated block of memory.
struct region {
char *base; // start of the memory we've allocated so far
char *end; // end of allocated memory
char *next; // next available byte
};
// Try to force the compiler to fetch the limit address from a structure,
// to make the cost of the check a bit more realistic.
static volatile struct region r;
#ifdef GUARD_PAGE
sigjmp_buf guard_resume;
// Handler for SIGSEGVs caused by touching the guard page.
void
handler(int signum)
{
siglongjmp(guard_resume, 1);
}
#endif
int main(int argc, char **argv)
{
progname = argv[0];
if (argc != 4)
usage();
volatile int blocks_left = atoi(argv[1]);
if (blocks_left <= 0)
usage();
int pages_per_block = atoi(argv[2]);
if (pages_per_block <= 0)
usage();
int object_size = atoi(argv[3]);
if (object_size <= 0)
usage();
size_t bytes_per_page = sysconf(_SC_PAGESIZE);
size_t bytes_per_block = pages_per_block * bytes_per_page;
size_t bytes_per_guard_page;
#ifdef GUARD_PAGE
bytes_per_guard_page = bytes_per_page;
#else
bytes_per_guard_page = 0;
#endif
/* Allocate the block. */
r.base = mmap(NULL, bytes_per_block + bytes_per_guard_page,
PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
if (r.base == MAP_FAILED) syscall_failed("mmap a block");
r.end = r.base + bytes_per_block;
#ifdef GUARD_PAGE
// Re-protect the last page to be a guard page.
if (mprotect(r.end, bytes_per_guard_page, PROT_NONE) < 0)
syscall_failed("mprotecting guard page");
// Install the SIGSEGV handler.
struct sigaction sa;
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGSEGV, &sa, NULL) < 0)
syscall_failed("setting SIGSEGV handler");
#endif
// Each iteration of this loop "uses" the entire block once.
for (;;) {
#ifdef GUARD_PAGE
sigsetjmp(guard_resume, 1);
#endif
r.next = r.base;
if (blocks_left-- <= 0)
exit(0);
// Each iteration of this loop "allocates" one "object" in the block.
while (
#ifdef LIMIT_PTR
r.next < r.end
#endif
#ifdef GUARD_PAGE
1
#endif
) {
*r.next = 'x';
r.next += object_size;
}
}
}
@jimblandy
Copy link
Author

define _GNU_SOURCE // for gregset_t indices

include <errno.h>

include <signal.h>

include <stdio.h>

include <stdlib.h>

include <string.h>

include <sys/mman.h>

include <unistd.h>

include <setjmp.h>

if defined(LIMIT_PTR) == defined(GUARD_PAGE)

error Define one of LIMIT_PTR or GUARD_PAGE.

endif

void
syscall_failed(char *when)
{
fprintf(stderr, "%s: error: %s\n", when, strerror(errno));
exit(40);
}

const char *progname;

void usage()
{
fprintf(stderr, "usage: %s NUM_BLOCKS PAGES_PER_BLOCK BYTES_PER_OBJECT\n", progname);
exit(1);
}

// One allocated block of memory.
struct region {
char *base; // start of the memory we've allocated so far
char *end; // end of allocated memory
char *next; // next available byte
};

// Try to force the compiler to fetch the limit address from a structure,
// to make the cost of the check a bit more realistic.
static volatile struct region r;

ifdef GUARD_PAGE

sigjmp_buf guard_resume;

// Handler for SIGSEGVs caused by touching the guard page.
void
handler(int signum)
{
siglongjmp(guard_resume, 1);
}

endif

int main(int argc, char **argv)
{
progname = argv[0];

if (argc != 4)
usage();

volatile int blocks_left = atoi(argv[1]);
if (blocks_left <= 0)
usage();

int pages_per_block = atoi(argv[2]);
if (pages_per_block <= 0)
usage();

int object_size = atoi(argv[3]);
if (object_size <= 0)
usage();

size_t bytes_per_page = sysconf(_SC_PAGESIZE);
size_t bytes_per_block = pages_per_block * bytes_per_page;
size_t bytes_per_guard_page;

ifdef GUARD_PAGE

bytes_per_guard_page = bytes_per_page;

else

bytes_per_guard_page = 0;

endif

/* Allocate the block. */
r.base = mmap(NULL, bytes_per_block + bytes_per_guard_page,
PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
if (r.base == MAP_FAILED) syscall_failed("mmap a block");
r.end = r.base + bytes_per_block;

ifdef GUARD_PAGE

// Re-protect the last page to be a guard page.
if (mprotect(r.end, bytes_per_guard_page, PROT_NONE) < 0)
syscall_failed("mprotecting guard page");

// Install the SIGSEGV handler.
struct sigaction sa;
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGSEGV, &sa, NULL) < 0)
syscall_failed("setting SIGSEGV handler");

endif

// Each iteration of this loop "uses" the entire block once.
for (;;) {

ifdef GUARD_PAGE

sigsetjmp(guard_resume, 1);

endif

r.next = r.base;

if (blocks_left-- <= 0)
  exit(0);

// Each iteration of this loop "allocates" one "object" in the block.
while (

ifdef LIMIT_PTR

       r.next < r.end

endif

ifdef GUARD_PAGE

       1

endif

       ) {

  *r.next = 'x';
  r.next += object_size;
}

}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment