This is an individual project. All handins are electronic. Read code style guideline.
This lab will help you understand the impact that cache memories can have on the performance of your C programs.
The lab consists of two parts. In the first part you will write a small C program (about 200-300 lines) that simulates the behavior of a cache memory. In the second part, you will optimize a small matrix transpose function, with the goal of minimizing the number of cache misses.
Download cachelab.tar here.
Start by copying cachelab.tar to
a protected Linux directory in which you plan to do your work.
Or you can copy the file from:
linux> cp
~liqun/public_html/teaching/cs304_15f/labs/cachelab.tar.gz .
Then give the command
This will create a directory called cachelab that contains a number of files. You will be modifying two files: csim.c and trans.c. To compile these files, type:
WARNING: Do
not let the Windows WinZip program open up your .tar file (many Web browsers are
set to do this automatically). Instead, save the file to your
Linux directory and use the Linux tar
program to extract the files. In general, for this class
you should NEVER use any platform other than Linux to modify your
files. Doing so can cause loss of data (and important work!).
The lab has two parts. In Part A you will implement a cache simulator. In Part B you will write a matrix transpose function that is optimized for cache performance.
The traces subdirectory of the handout directory contains a collection of reference trace files that we will use to evaluate the correctness of the cache simulator you write in Part A. The trace files are generated by a Linux program called valgrind. For example, typing
on the command line runs the executable program “ls -l”, captures a trace of each of its memory accesses in the order they occur, and prints them on stdout.
Valgrind memory traces have the following form:
Each line denotes one or two memory accesses. The format of each line is
The operation field denotes the type of memory access: “I” denotes an instruction load, “L” a data load, “S” a data store, and “M” a data modify (i.e., a data load followed by a data store). There is never a space before each “I”. There is always a space before each “M”, “L”, and “S”. The address field specifies a 64-bit hexadecimal memory address. The size field specifies the number of bytes accessed by the operation.
In Part A you will write a cache simulator in csim.c that takes a valgrind memory trace as input, simulates the hit/miss behavior of a cache memory on this trace, and outputs the total number of hits, misses, and evictions.
We have provided you with the binary executable of a reference cache simulator, called csim-ref, that simulates the behavior of a cache with arbitrary size and associativity on a valgrind trace file. It uses the LRU (least-recently used) replacement policy when choosing which cache line to evict.
The reference simulator takes the following command-line arguments:
For example:
The same example in verbose mode:
Your job for Part A is to fill in the csim.c file so that it takes the
same command line arguments and produces the identical output as
the reference simulator. Notice that this file is almost
completely empty. You’ll need to write it from scratch.
Program outline and useful C functions:
---------------------------
Define your data structure.
Read the command line arguments.
Read the trace file.
Find (tag, set index, block offset).
Use set index to find the right set.
Within the set, compare tag to see if there is a cache line (a
cache line is a cache block) matching the tag. Check the valid
bit. Determine whether it is cache hit or miss.
IF Cache miss:
replace an entry using LRU algorithm
Update LRU information.
#include <stdio.h>
#include <getopt.h>#include <stdlib.h>
#include <unistd.h>
#include <string.h> #include <errno.h>
#include <getopt.h>If you use atoi() which convert a string to an integer, you should add the following line:
#include <stdlib.h>
You may also need to use string copy function:#include <string.h>
char *strcpy(char *dest, const char *src)
strcpy(dest, src) copies a string from src to dest.
See string functions:
http://icecube.wisc.edu/~dglo/c_class/strmemfunc.htmlint sscanf(const char *str, const char *format, ...)
sscanf() is similar to scanf(). The difference is that sscanf() will read from a string instead of from standard input.
In the format part, use %c to read a character, %x to read a hexadecimal int, %s to read a string, %d to read a decimal int,
"%x,%d" will read a hexadecimal int and a comma, and a decimal int
Make sure to use pointer instead of a variable in sscanf()
If the address is more than 32 bits, that is, 64 bits, you can declare your address as unsigned long long int, which is 64 bits on x86-64.
Use %llx to get the unsigned long long hexadecimal int.
unsigned long long int address;
scanf("%llx",
&address);
In Part B you will write a transpose function in trans.c that causes as few cache misses as possible.
Let A denote a matrix, and Aij denote the component on the ith row and jth column. The transpose of A, denoted AT , is a matrix such that Aij = AjiT .
To help you get started, we have given you an example transpose function in trans.c that computes the transpose of N × M matrix A and stores the results in M × N matrix B:
The example transpose function is correct, but it is inefficient because the access pattern results in relatively many cache misses.
Your job in Part B is to write a similar function, called transpose_submit, that minimizes the number of cache misses across different sized matrices:
Do not change
the description string (“Transpose submission”)
for your transpose_submit
function. The autograder searches for this string to determine
which transpose function to evaluate for credit.
For simple transpose, we are constantly accessing new values from memory and obtain very little reuse of cached data! We can improve the amount of data reuse in the caches by implementing a technique called cache blocking. More formally, cache blocking is a technique that attempts to reduce the cache miss rate by improving the temporal and/or spatial locality of memory accesses. In the case of matrix transposition we consider performing the transposition one block at a time.
In the above image, each block Aij of matrix A is
transposed into its final location in the output matrix. With this
scheme, we significantly reduce the magnitude of the working set
in cache at any one point in time. This (if implemented correctly)
will result in a substantial improvement in performance. For this
lab, you will implement a cache blocking scheme for matrix
transposition.
This section describes how your work will be evaluated. The full score for this lab is 60 points:
For Part A, we will run your cache simulator using different cache parameters and traces. There are eight test cases, each worth 3 points, except for the last case, which is worth 6 points:
You can use the reference simulator csim-ref to obtain the correct answer for each of these test cases. During debugging, use the -v option for a detailed record of each hit and miss.
For each test case, outputting the correct number of cache hits, misses and evictions will give you full credit for that test case. Each of your reported number of hits, misses and evictions is worth 1/3 of the credit for that test case. That is, if a particular test case is worth 3 points, and your simulator outputs the correct number of hits and misses, but reports the wrong number of evictions, then you will earn 2 points.
For Part B, we will evaluate the correctness and performance of your transpose_submit function on three different-sized output matrices:
For each matrix size, the performance of your transpose_submit function is evaluated by using valgrind to extract the address trace for your function, and then using the reference simulator to replay this trace on a cache with parameters (s = 5, E = 1, b = 5).
Your performance score for each matrix size scales linearly with the number of misses, m, up to some threshold:
Your code must be correct to receive any performance points for a particular size. Your code only needs to be correct for these three cases and you can optimize it specifically for these three cases. In particular, it is perfectly OK for your function to explicitly check for the input sizes and implement separate code optimized for each case.
There are 7 points for coding style. These will be assigned manually by the course staff. Style guidelines can be found on the page of code style guideline.
The course staff will inspect your code in Part B for illegal arrays and excessive local variables.
We have provided you with an autograding program, called test-csim, that tests the correctness of your cache simulator on the reference traces. Be sure to compile your simulator before running the test:
For each test, it shows the number of points you earned, the cache parameters, the input trace file, and a comparison of the results from your simulator and the reference simulator.
Here are some hints and suggestions for working on Part A:
See “man 3 getopt” for details.
We have provided you with an autograding program, called test-trans.c, that tests the correctness and performance of each of the transpose functions that you have registered with the autograder.
You can register up to 100 versions of the transpose function in your trans.c file. Each transpose version has the following form:
Register a particular transpose function with the autograder by making a call of the form:
in the registerFunctions routine in trans.c. At runtime, the autograder will evaluate each registered transpose function and print the results. Of course, one of the registered functions must be the transpose_submit function that you are submitting for credit:
See the default trans.c function for an example of how this works.
The autograder takes the matrix size as input. It uses valgrind to generate a trace of each registered transpose function. It then evaluates each trace by running the reference simulator on a cache with parameters (s = 5, E = 1, b = 5).
For example, to test your registered transpose functions on a 32 × 32 matrix, rebuild test-trans, and then run it with the appropriate values for M and N:
In this example, we have registered four different transpose functions in trans.c. The test-trans program tests each of the registered functions, displays the results for each, and extracts the results for the official submission.
Here are some hints and suggestions for working on Part B.
for more information.
We have provided you with a driver program, called ./driver.py, that performs a complete evaluation of your simulator and transpose code. This is the same program your instructor uses to evaluate your handins. The driver uses test-csim to evaluate your simulator, and it uses test-trans to evaluate your submitted transpose function on the three matrix sizes. Then it prints a summary of your results and the points you have earned.
To run the driver, type:
Each time you type make
in the cachelab
directory, the Makefile creates a tarball, called userid-handin.tar, that contains
your current csim.c and trans.c files.
Submit your work: To submit, read the
README file, or
To submit, run:
linux> make submit
or use:
linux> ~fluo/bin/submit cs304 lab4 handin.tar
Before submit your project, please compile your
code
linux> make
Thus your handin.tar will be the latest.
IMPORTANT: Do
not create the handin tarball on a Windows or Mac machine, and do
not handin files in any other archive format, such as .zip, .gzip, or .tgz files.
1The reason for this restriction is that
our testing code is not able to count references to the stack.
We want you to limit your references to the stack and focus on the access patterns
of the source and destination arrays.
2Because valgrind introduces many stack accesses that have nothing to do with your code, we have filtered out all stack accesses from the trace. This is why we have banned local arrays and placed limits on the number of local variables.