Skip to content

Commit 3953614

Browse files
committed
Lecture 5 GC exercise
1 parent 982edee commit 3953614

File tree

8 files changed

+375
-1
lines changed

8 files changed

+375
-1
lines changed

common/rules.mk

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# are we using clang?
22
ISCLANG := $(shell if $(CC) --version | grep LLVM >/dev/null; then echo 1; else echo 0; fi)
33

4-
CFLAGS ?= -std=gnu11 -W -Wall -Wshadow -g $(DEFS)
4+
CFLAGS := -std=gnu11 -W -Wall -Wshadow -g $(DEFS) $(CFLAGS)
55
O ?= -O3
66
ifeq ($(filter 0 1 2 3 s,$(O)),$(strip $(O)))
77
override O := -O$(O)

fundamentals2/.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
addrspace
2+
ptr
3+
struct
4+
union
5+
vars[1-4]

fundamentals5x/.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
nogcbench
2+
gcbench
3+
gcbench-sol

fundamentals5x/GNUmakefile

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
PROGRAMS = nogcbench gcbench
2+
all: $(PROGRAMS)
3+
4+
include ../common/rules.mk
5+
6+
%.o: %.c $(BUILDSTAMP)
7+
$(CC) $(CPPFLAGS) $(CFLAGS) $(DEPCFLAGS) $(O) -o $@ -c $<
8+
9+
nogcbench: nogcbench.o m61gc.o
10+
$(CC) $(CFLAGS) $(O) -o $@ $^
11+
12+
gcbench: gcbench.o m61gc.o
13+
$(CC) $(CFLAGS) $(O) -o $@ $^
14+
15+
clean:
16+
rm -f *.o $(PROGRAMS) gcbench-sol
17+
rm -rf $(DEPSDIR) *.dSYM
18+
19+
.PHONY: all clean

fundamentals5x/gcbench.c

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#include "m61gc.h"
2+
#include <stdlib.h>
3+
#include <stdio.h>
4+
#include <string.h>
5+
#include <assert.h>
6+
#include <sys/time.h>
7+
#include <sys/resource.h>
8+
#include <getopt.h>
9+
10+
static unsigned n = 1000000;
11+
static unsigned k = 2048;
12+
13+
static void benchmark(void) {
14+
char** chunks = (char**) m61_malloc(sizeof(char*) * k);
15+
for (unsigned s = 0; s != k; ++s)
16+
chunks[s] = NULL;
17+
18+
// `n` chunk allocations of 1-4K each
19+
for (unsigned i = 0; i != n; ++i) {
20+
if (i % 100000 == 0 && i != 0)
21+
printf("Chunk allocation %u/%u\n", i, n);
22+
unsigned s = random() % k;
23+
// modify chunk, so valgrind will complain if the memory is free
24+
if (chunks[s]) {
25+
assert(chunks[s][0] == 62);
26+
chunks[s][0] = 61;
27+
}
28+
size_t sz = 1024 + random() % 3072;
29+
chunks[s] = m61_malloc(sz);
30+
chunks[s][0] = 62;
31+
}
32+
33+
// modify chunks, so valgrind will complain if the memory is free
34+
for (unsigned s = 0; s != k; ++s)
35+
if (chunks[s]) {
36+
assert(chunks[s][0] == 62);
37+
chunks[s][0] = 61;
38+
}
39+
}
40+
41+
int main(int argc, char* argv[]) {
42+
// Find an approximation for the bottom of the stack: an address
43+
// greater than or equal to the address of any local. We use the
44+
// address of the `argv` array, which the OS initializes on the
45+
// stack before `main` is called (i.e., below `main`'s stack
46+
// frame).
47+
m61_stack_bottom = (char*) &argv[0];
48+
49+
int opt, limit = 1;
50+
while ((opt = getopt(argc, argv, "n:k:l")) != -1)
51+
switch (opt) {
52+
case 'n':
53+
n = strtoul(optarg, NULL, 0);
54+
break;
55+
case 'k':
56+
k = strtoul(optarg, NULL, 0);
57+
break;
58+
case 'l':
59+
limit = 0;
60+
break;
61+
default:
62+
fprintf(stderr, "Usage: ./nogcbench [-n NOPS] [-k NALLOCS] [-l]\n");
63+
exit(EXIT_FAILURE);
64+
}
65+
66+
if (limit) {
67+
// Limit memory to at most 256MB (does not work on OS X).
68+
struct rlimit memlimit = { 256L << 20, 256L << 20 };
69+
int r = setrlimit(RLIMIT_AS, &memlimit);
70+
assert(r >= 0);
71+
}
72+
73+
benchmark();
74+
75+
m61_gc();
76+
// this should print "0 allocations" or some other small number
77+
m61_print_allocations();
78+
}

fundamentals5x/m61gc.c

+160
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
#include "m61gc.h"
2+
#include <stdlib.h>
3+
#include <stdio.h>
4+
#include <string.h>
5+
#include <assert.h>
6+
7+
8+
// In this exercise, we track allocations in an array sorted by allocated
9+
// pointer (not a linked list). The sorted array lets us quickly find the
10+
// allocation containing a pointer, using binary search.
11+
12+
// structure representing a single allocation
13+
typedef struct allocation {
14+
char* ptr; // pointer to first allocated byte
15+
size_t sz; // size of allocation
16+
int marked; // used in GC
17+
} allocation;
18+
19+
static allocation* allocs = NULL; // active allocations (sorted by ptr)
20+
static size_t nallocs = 0; // number of active allocations
21+
static size_t allocs_capacity = 0; // capacity of `allocs` array
22+
23+
char* m61_stack_bottom;
24+
25+
/// find_allocation_index(ptr)
26+
/// Return the index in the `allocs` array where `ptr` belongs.
27+
/// Uses binary search for speed.
28+
static size_t find_allocation_index(char* ptr);
29+
30+
/// find_allocation(ptr)
31+
/// Return a pointer to the active allocation containing `ptr`, or
32+
/// NULL if no active allocation contains `ptr`.
33+
static allocation* find_allocation(char* ptr);
34+
35+
/// allocation_atexit()
36+
/// Used to clean up the `allocs` array.
37+
static void allocation_atexit(void) {
38+
free(allocs);
39+
}
40+
41+
42+
void* m61_malloc(size_t sz) {
43+
static size_t allocation_count = 0;
44+
++allocation_count;
45+
46+
void* ptr = malloc(sz);
47+
// Garbage collect every 2**16 allocations, or sooner if malloc fails
48+
if (!ptr || allocation_count % (1U << 16) == 0) {
49+
m61_gc();
50+
ptr = malloc(sz);
51+
}
52+
if (!ptr)
53+
return NULL;
54+
55+
// find index to insert this allocation
56+
size_t i = find_allocation_index(ptr);
57+
// this ptr must not overlap with a current allocation
58+
assert(i == nallocs || (char*) ptr + sz <= allocs[i].ptr);
59+
// make space for the new allocation
60+
if (nallocs == allocs_capacity) {
61+
if (!allocs_capacity) {
62+
atexit(allocation_atexit); // free allocations array on exit
63+
allocs_capacity = 1024;
64+
} else
65+
allocs_capacity *= 2;
66+
allocs = realloc(allocs, sizeof(allocation) * allocs_capacity);
67+
assert(allocs);
68+
}
69+
memmove(&allocs[i + 1], &allocs[i], sizeof(allocation) * (nallocs - i));
70+
// store the new allocation
71+
allocs[i].ptr = ptr;
72+
allocs[i].sz = sz;
73+
++nallocs;
74+
// clear and return it
75+
memset(ptr, 0, sz);
76+
return ptr;
77+
}
78+
79+
void m61_free(void* ptr) {
80+
if (!ptr)
81+
return;
82+
// find index of this allocation
83+
size_t i = find_allocation_index(ptr);
84+
// that allocation must match `ptr` exactly
85+
assert(i < nallocs && allocs[i].ptr == ptr);
86+
// remove the allocation from the list and free `ptr`
87+
memmove(&allocs[i], &allocs[i + 1], sizeof(allocation) * (nallocs - i - 1));
88+
--nallocs;
89+
free(ptr);
90+
}
91+
92+
void m61_print_allocations(void) {
93+
printf("%zu allocations\n", nallocs);
94+
for (size_t i = 0; i != nallocs; ++i)
95+
printf(" #%zu: %p: %zu bytes\n", i, allocs[i].ptr, allocs[i].sz);
96+
}
97+
98+
static void mark_allocations(const char* base, size_t sz) {
99+
(void) base;
100+
if (sz < sizeof(void*))
101+
return;
102+
for (size_t i = 0; i <= sz - sizeof(void*); ++i) {
103+
// check if the data at `base + i` contains a pointer
104+
// YOUR CODE HERE
105+
}
106+
}
107+
108+
void m61_gc(void) {
109+
#if __x86_64__
110+
// ensure all of our callers' variables are located on the stack,
111+
// rather than in registers
112+
__asm__ __volatile__("" : : : "rbx", "r12", "r13", "r14", "r15", "memory");
113+
#endif
114+
115+
char* stack_top = (char*) __builtin_frame_address(0);
116+
117+
// unmark all active allocations
118+
// YOUR CODE HERE
119+
120+
// mark allocations in the stack
121+
mark_allocations(stack_top, m61_stack_bottom - stack_top);
122+
123+
#if __linux__
124+
// mark allocations in globals
125+
extern char data_start[];
126+
extern char _end[];
127+
mark_allocations(data_start, _end - data_start);
128+
#endif
129+
130+
// free unmarked allocations
131+
// YOUR CODE HERE
132+
}
133+
134+
135+
////////////////////////////////////////////////////////////////////////////////
136+
/// Helper method definitions
137+
138+
static size_t find_allocation_index(char* ptr) {
139+
size_t l = 0, r = nallocs;
140+
while (l < r) {
141+
size_t m = l + (r - l) / 2;
142+
if (ptr < allocs[m].ptr)
143+
r = m;
144+
else if (ptr >= allocs[m].ptr + allocs[m].sz)
145+
l = m + 1;
146+
else
147+
return m;
148+
}
149+
return l;
150+
}
151+
152+
static allocation* find_allocation(char* ptr) __attribute__((used));
153+
static allocation* find_allocation(char* ptr) {
154+
size_t i = find_allocation_index(ptr);
155+
assert(i == nallocs || ptr < allocs[i].ptr + allocs[i].sz);
156+
if (i < nallocs && ptr >= allocs[i].ptr)
157+
return &allocs[i];
158+
else
159+
return NULL;
160+
}

fundamentals5x/m61gc.h

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#ifndef CS61_M61GC_H
2+
#define CS61_M61GC_H
3+
#include <stddef.h>
4+
5+
/// m61_malloc(sz)
6+
/// Allocate `sz` bytes and return a pointer to the allocated memory.
7+
void* m61_malloc(size_t sz);
8+
9+
/// m61_free(ptr)
10+
/// Free the memory pointed to by `ptr`, which must have been returned
11+
/// by a previous call to `m61_malloc()`.
12+
void m61_free(void* ptr);
13+
14+
/// m61_print_allocations()
15+
/// Print all active allocations to stderr.
16+
void m61_print_allocations(void);
17+
18+
/// m61_stack_bottom
19+
/// `main` should set this to an address at the bottom of the stack.
20+
extern char* m61_stack_bottom;
21+
22+
/// m61_gc()
23+
/// Invoke the garbage collector.
24+
void m61_gc(void);
25+
26+
#endif

fundamentals5x/nogcbench.c

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
#include "m61gc.h"
2+
#include <stdlib.h>
3+
#include <stdio.h>
4+
#include <string.h>
5+
#include <assert.h>
6+
#include <sys/time.h>
7+
#include <sys/resource.h>
8+
#include <getopt.h>
9+
10+
static unsigned n = 1000000;
11+
static unsigned k = 2048;
12+
13+
static void benchmark(void) {
14+
char** chunks = (char**) m61_malloc(sizeof(char*) * k);
15+
for (unsigned s = 0; s != k; ++s)
16+
chunks[s] = NULL;
17+
18+
// `n` chunk allocations of 1-4K each
19+
for (unsigned i = 0; i != n; ++i) {
20+
if (i % 100000 == 0 && i != 0)
21+
printf("Chunk allocation %u/%u\n", i, n);
22+
unsigned s = random() % k;
23+
// modify chunk, so valgrind will complain if the memory is free
24+
if (chunks[s]) {
25+
assert(chunks[s][0] == 62);
26+
chunks[s][0] = 61;
27+
}
28+
m61_free(chunks[s]);
29+
size_t sz = 1024 + random() % 3072;
30+
chunks[s] = m61_malloc(sz);
31+
chunks[s][0] = 62;
32+
}
33+
34+
// modify chunks, so valgrind will complain if the memory is free
35+
for (unsigned s = 0; s != k; ++s)
36+
if (chunks[s]) {
37+
assert(chunks[s][0] == 62);
38+
chunks[s][0] = 61;
39+
}
40+
41+
// clean up
42+
for (unsigned s = 0; s != k; ++s)
43+
m61_free(chunks[s]);
44+
m61_free(chunks);
45+
}
46+
47+
int main(int argc, char* argv[]) {
48+
// Find an approximation for the bottom of the stack: an address
49+
// greater than or equal to the address of any local. We use the
50+
// address of the `argv` array, which the OS initializes on the
51+
// stack before `main` is called (i.e., below `main`'s stack
52+
// frame).
53+
m61_stack_bottom = (char*) &argv[0];
54+
55+
int opt, limit = 1;
56+
while ((opt = getopt(argc, argv, "n:k:l")) != -1)
57+
switch (opt) {
58+
case 'n':
59+
n = strtoul(optarg, NULL, 0);
60+
break;
61+
case 'k':
62+
k = strtoul(optarg, NULL, 0);
63+
break;
64+
case 'l':
65+
limit = 0;
66+
break;
67+
default:
68+
fprintf(stderr, "Usage: ./nogcbench [-n NOPS] [-k NALLOCS] [-l]\n");
69+
exit(EXIT_FAILURE);
70+
}
71+
72+
if (limit) {
73+
// Limit memory to at most 256MB (does not work on OS X).
74+
struct rlimit memlimit = { 256L << 20, 256L << 20 };
75+
int r = setrlimit(RLIMIT_AS, &memlimit);
76+
assert(r >= 0);
77+
}
78+
79+
benchmark();
80+
81+
// this should print "0 allocations"
82+
m61_print_allocations();
83+
}

0 commit comments

Comments
 (0)