Ancient CS 61 Content Warning!!!!!1!!!
This is not the current version of the class.
This site was automatically translated from a wiki. The translation may have introduced mistakes (and the content might have been wrong to begin with).

Section 2: Arena allocator

Skills

Background

Linked Lists

Linked lists are different than arrays because they are dynamically allocated. When creating an array, you have to set aside a chunk of memory of a static size, but for a linked list, you just add elements in as you go.

In a singly linked list, each element of the list points to the next element of the list like so:

struct list_element {
    int id;
    char letter;
    struct list_element* next;
};

If you need to be able to find out what the previous element in the list is, you should use a doubly linked list. In a doubly linked list, each element of the list has two pointers: one to the previous element and one to the next element, like so:

struct list_element {
    int id;
    char letter;
    struct list_element* prev;
    struct list_element* next;
};

To keep track of a linked list, you really only need one pointer:

struct list_element* head;

head points to the first element of the list.

Unions

Unions and structs are defined in similar ways:

union info_union {
    short a;
    int b;
    char c[5];
};

The difference between union and struct is that the contents of a struct are stored one after another while a union can realistically only store one value at a time. Whatever is stored in this memory can be interpreted in different ways depending on the type, but the actual contents of the memory will be the same. What this means is that the contents of a union all share the same memory space. For example, for the above union info, this program:

union info_union info;
info.a = 72;
printf("%hi\n", info.a);
info.b = 73;
printf("%d\n", info.b);
strcpy(info.c, "abcd");
printf("%s\n", info.c);

outputs the following:

72
73
abcd

While this program:

union info_union info;
strcpy(info.c, "abcd");
info.b = 73;
info.a = 72;
printf("%hi\n", info.a);
printf("%d\n", info.b);
printf("%s\n", info.c);

outputs the following:

72
72
H

This is because the last value assigned in the union was to a, so the values of all elements of the union are dependent on the contents of the memory last defined. 72 is H in ASCII.

Assignment

Pull the latest version of the sections repository. If you have not cloned it yet, open terminal and enter

git clone <https://github.com/cs61/cs61-sections.git>

The cs61-sections/s02 directory contains a program called membench that benchmarks dynamic memory allocators. Your task is to complete the simple arena allocator in arena.c, making it beat the performance of malloc without leaking memory.

An arena (also called a zone, area, pool, memory context, etc.) represents a group of allocated objects. Often an arena holds related objects, such as a set of objects that all have the same type, or a set of objects that will all be freed at the same time. Special functions are used to allocate and free objects from the arena. These functions can be faster, more convenient, and sometimes safer than general-purpose malloc and free, because they can make assumptions about the kind of objects being allocated.

arena.c` contains the start of an arena allocator for `chunk

objects—but it leaks memory (in some senses), and is slower than it needs to be. You should fix this by introducing a free list. Your allocator should:

(More on arenas)

Test your arena allocator by running ./membench-arena with different arguments. The arguments are:

And use command-line tools to test your allocator’s performance and memory usage. A particularly useful command is time, which comes in two varieties. Simply running time COMMAND reports the amount of time it took to run a command. (The real number is what you want; we’ll hear more about the others later). Running /usr/bin/time COMMAND reports more information, including memory usage. On my laptop:

$ /usr/bin/time ./membench-malloc -k1
1.72user 0.00system 0:01.72elapsed 99%CPU (0avgtext+0avgdata 5932maxresident)k
0inputs+0outputs (0major+90minor)pagefaults 0swaps

The maxresident number is the maximum amount of memory used in kilobytes; this program used about 6MB. And use make SANITIZE=1 or valgrind --tool=memcheck to check for memory leaks. (NB: make SANITIZE=1 only finds memory leaks on Linux.)

Comprehension questions

Advanced features for optional after-class work

(1) Change the interface of membench_arena_new to take a size_t sz argument. An arena of size sz should allocate objects of size

sz`, meaning every object returned by `membench_alloc(arena)

should have size sz.

(2) Implement an arena allocator that can efficiently allocate objects smaller than a pointer (for instance, an allocator for single ints).