Tuning Code and Data
2026-09-22
c4d are postedWorkflow recommendation (keep your stuff separate!):
# Clone the reference repo, then re-clone for solution version
git clone git@github.coecis.cornell.edu:cs4414-f26/p2_edit.git
git clone p2_edit p2_edit_mine
# In the solution version, rename origin (allow future pulls)
cd p2_edit_mine
git remote rename origin upstream
# Add origin
git remote add origin git@github.coecis.cornell.edu:dsb253/p2_edit.git
git branch -M main
git push -u origin mainNB: Can’t accidentally push to upstream (not bare)
This module: fundamentals of single-thread performance
Concurrency and multi-threaded performance comes later.
Combine HW context, tricks, and principles:
A certain computation repeatedly access the first word of each element of a large array in which each element type is [usize; 1024]. Cache re-use in this setting is terrible! Is this because of
Respond at: https://PollEv.com/davidbindel252
or send davidbindel252 to 22333
P2: Mean Hamming distances in a dictionary
Tuning knocks a lot of time off the base code!
We use two primitives:
Ex: Compare two bit numbers (1, 3).
0b001, 0b0110b0100b010 + 0b011 = 0b101
0b101 & 0b100 = 0b1000b001u16:
Hamming distance between tuples of three four-bit numbers:
| Tuple | Fields |
|---|---|
x = (1,5,10) |
0b00001_00101_00101 |
y = (2,5,7) |
0b00010_00101_00111 |
d = x^y |
0b00011_00000_00010 |
s = d+0b01111 (x3) |
0b10010_01111_10110 |
c = s&0b10000 (x3) |
0b10000_00000_10000 |
r = c>>4 |
0b00001_00000_00001 |
z = r%31 |
2 |
| Platform | Dictionary | Naive | Blocked | Packed | SWAR | Fast |
|---|---|---|---|---|---|---|
| M1 | popular |
5.3s | 4.1s | 2.9s | 0.95ms | 0.54s |
| M1 | enable1 |
259.0s | 222.0s | 94.5s | 43.3s | 24.3s |
| c4d | popular |
4.3s | 2.9s | 2.7s | 1.3s | 0.64s |
| c4d | enable1 |
221.7s | 160.1s | 126.4s | 61.7s | 33.3s |
SWAR/Fast: 2ns (10 cycle at 5GHz boost) per comparison.
Performance scoring thresholds (C4D and enable)
| Score (of 2) | Blocked | Packed | SWAR |
|---|---|---|---|
| 3 | 175s | 140s | 70s |
| 2 | 185s | 150s | 80s |
| 1 | 195s | 160s | 90s |
No credit for fast-but-broken.
WRITEUP.md answers (eight Q)!For compulsory misses:
\[T_{\mathrm{data}} \mbox{ (s)} \geq \frac{\mbox{data required (bytes)}}{\mbox{peak BW (bytes/s)}}\]
Possible optimizations:
Reality is more complicated…
Access is not the only cost!
Two thoughts to consider:
Desiderata:
Can use zip to iterate over SoA like AoS.
Can copy between formats to accelerate, e.g.
Performance gains > copy costs?
Plays great with tiling!
Can get (some) programmer control over
But usually best left to compiler / HW.
Consider computing longest common prefixes over pairs of strings. For this computation, would it be more reasonable to store this as a structure of arrays or an array of structures?
Respond at: https://PollEv.com/davidbindel252
or send davidbindel252 to 22333
In decreasing order of effectiveness:
Mostly leave these to modern compilers
This one is worth knowing for later…
Let’s look at a few…
Sometimes these can be decoupled. Ex:
Apparent linear dependency chain:
(Yes, Rust 1.98 algebraic add might auto-optimize.)
Auto-vectorizer probably kicks in here…
Why can this not vectorize easily?
Q: What if result overlaps a or b?
restrict promises no aliasing&x changes?Compiler assumes arbitrary wackiness:
Rust borrow checker again prevents this!
Harder to manage successive writes to same array!
Several possible optimizations:
But these change semantics! Often needs a human.
Hardware context, some tricks, and some principles:
c4d nodesNo outro Q today, good luck on the exam!