Changelog:
- 12 Sep 2025: move context switch
forcing
instructions out of main exercise part - 16 Sep 2025: update module load command to something that works with current portal modules
- 17 Sep 2025: mention using
module load gcc clang
to get access to clang on portal before runningmake - 17 Sep 2025: add note about trying to remove
venvdirectories in case they’re half setup
Work, perhaps with a partner:
Download and extract the skeleton code [last updated 1 Sep 2025] (either on portal or NX or on another Linux system. The Windows Subsystem for Linux will probably not work).
Look at the function
measure_times_plaininempty-time.cand/or read its description below.Use
maketo build theempty-timeandlong-loopprograms. (On portal, you may need to usemodule load gcc clangfirst or edit the Makefile to use gcc instead of clang or similar.)Obtain measurements from
empty-timefor at least 1 million iterations. You can do this with a command like./empty-time plain 1000000 output:In this command:
plainselects themeasure_time_plainfunction inempty-time.c; the other option isthreshold
10000000options selects to obtain 1000000 measurementsoutputselects the base name of the output files
This will produce three output files:
output-times.csv— records the amount of time between iterations of an otherwiseempty
loopoutput-before-int,output-after-int— records the contents of /proc/interrupts, which contains counters of asynchronous exceptions, separated by cause and which core handled them. The contents are sampled before and after (respectively) executing the timing loop
Examine the output files:
Plot the times you recorded (and save the plots for later). I strongly recommend plotting using a log-scale for the duration axis.
You can use the
plot.pyscript to do this, as described below.Look at what asynchronous exceptions occurred based on the
before-intandafter-intfiles. You can use theinterrupts-difference.pyprogram to aid in comparing these (since doing so by hand would be tedious.)
You’ll notice that, in contrast to what a very simple model of a processor would suggest, that the time varies significantly.
There are probably a few main causes:
- exceptions interrupting the execution of the program;
- variations in what data is cached (already loaded into fast memory) versus not;
- variations in speculative execution’s accuracy (the processor guessing what instructions to run before it is sure); and
- variations in processor clock speed (usually done by the hardware to control power usage and/or heat dissipation)
Try to estimate a threshold time which will separate time measurements which are high from an exception from the other measurements.
We can then use this threshold to avoid measuring these shorter measurements and get a sample of more longer durations.
empty-time.chas ameasure_time_thresholdfunction (described below) we’ll use for this purpose.Then verify and, if needed, refine your threshold:
Choose a core number to run on (one of the indices listed by
lscpu --extended=CPU).Obtain measurements only greater than the threshold and on that core using a command like
THRESHOLD=AAA taskset -c BBB ./empty-time threshold 10000 new-output
where
AAAis your threshold,BBBis your selected core number.In this command:
THRESHOLD=AAAsets anenvironment variable
calledTHRESHOLD, whichempty-loop.creads with thegetenvfunctiontaskset -c BBB ...runs...only on the cores specified by BBB.- 10000 is the number of measurements to obtain (you can adjust this number if you wish)
new-outputselects the name of the output files
Plot the resulting times (and save the plots for later). Try to determine if it’s likely that your program was context-switched away from for an extended period of time.
Use
interrupts-difference.pyto see how closely the actual number of asynchronous exceptions corresponds to the actual count on that core.Note that if your program was context switched away from, then we would not expect to see asynchronous exceptions from the time when it was not running.
Write a brief explanation of how you choose your threshold and whether your attempts to verify it give you confidence in it.
If you have time, estimate how long your measurement program is context switched away from. If your measurement program is not context switched away from, see the advice on
encouraging context switches
below.Show a TA:
Your plots.
Your explanation for item 8.
OR place an explanation for item 8 in a file
README.txtand submit your plots and explanation to the submission site.
1
measure_times_plain
static void measure_times_plain(int count, long *starts, long *durations) {
struct timespec first, current, previous;
clock_gettime(WHICH_CLOCK, &first);
clock_gettime(WHICH_CLOCK, &previous);
for (int i = 0; i < count; i += 1) {
clock_gettime(WHICH_CLOCK, ¤t);
starts[i] = nsec_difference(&first, &previous);
durations[i] = nsec_difference(&previous, ¤t);
previous = current;
}
}
This function reads a clock in a loop count times, and
records the results to the starts and
durations array. The only thing that happens between
obtaining samples is storing the time and related bookkeeping.
If this loop were the only thing running, we would expect to see the
durations are roughly constant. You should
2
measure_times_threhsold
This function adds the following to
measure_times_plain
Code to read from the environment variable
THRESHOLDinto the local variablethreshold:const char *threshold_string = getenv("THRESHOLD"); if (!threshold_string) { fprintf(stderr, "THRESHOLD environment variable not set (example: THRESHOLD=100 ./empty-time ...)\n"); exit(EXIT_FAILURE); } long threshold = atoi(threshold_string);Code to check the the threshold before recording a value (and corresponding adjustments to the loop):
long difference = nsec_difference(&previous, ¤t); if (difference > threshold) { starts[i] = nsec_difference(&first, &previous); durations[i] = nsec_difference(&previous, ¤t); i += 1; }
3 Plotting script
We supply a plotting script in plot.py, which requires
the Python libraries numpy and matplotlib.
To make these work on portal:
First run (you’ll need to repeat this for each shell or set this to run automatically):
module load gcc pythonThen, if you haven’t already run
bash venv.shwhich will create a venv directory containing a
virtual environment
and install numpy and matplotlib in that virtual environment.If you get an error about
ensurepipnot existing, one possibility is that you have a partially setupvenvdirectory; try removing thevenvdirectory and running the command again.Then (you’ll need to repeat this for each shell), run:
source venv/bin/activate(This should cause your shell prompt to be prefixed with
(venv).)
After you’ve done the above setup you can use
python plot.py time output-times.csv output-times.png
to plot data from output-times.csv and write the result
to the image file output-times.png.
4
interrupts-difference.py
The interrupts-difference.py script will compute the
difference between two /proc/interrupts snapshot.
python interrupts-difference.py output-before-int output-after-int
will show the number of interrupts, split up by the type idenified.
Interrupts will be attributes to specific cores, labeled
CPU0, CPU1 and so on. If an interrupt type or
CPU is not in the output, then the difference was zero.
If you only want the count of interrupts for a specific core, you can use a command like
python3 interrupts-difference.py --core=3 output-before-int output-after-int
to show only the differences for core number 3. (Core numbers start at 0.)
5 encouraging context switches
If you don’t think you have measurements including a context switch, you can run the long-loop program on a core using a command like
taskset -c AAA ./long-loop &and then do a measurement on the same core (before the long-loop program terminates in about a minute). Assuming enough samples, this should cause you to observe a context switch between the
empty-timeandlong-loop, even if no other programs are active.