How many loads and stores can the M6 keep in flight?
This follows the out-of-order window post and uses the same test with different fillers. Every load and store a core has started but not retired needs an entry in a load or store queue, and those queues are much smaller than the window. The short version: a Super core can hold about 137 stores and about 487 loads in flight, a Performance core about 102 stores and 220 loads, and an Efficiency core about 58 stores and 64 loads. The same test also explains the slow creep before each step that I couldn't explain last time.
The test
As before: two independent pointer chases that miss to DRAM, with N fillers after each miss. While the second miss fits behind the first one's fillers, the two overlap and an iteration costs one DRAM latency (about 90 ns). Once it doesn't fit, they serialize and the cost doubles. New fillers:
str x9, [x4, #off]: a store to one of eight cache lines that stay in L1. A store writes no register, so the obvious thing for it to run out of is store-queue space. The window is far bigger.ldr d17, [x4, #off]: a load from the same eight lines into an FP register. FP registers run out at about 880 on a Super core, so a smaller limit has to be something that counts loads.- The same two with a nop between each pair. Nops didn't take window space in the last post, so if the limit counts loads or stores, the step should move to twice the N. If it counts instructions, it shouldn't move.
Loads into integer registers turned out to be the wrong filler: on a Super core they step at about 400, the same place as the integer adds did, because the core runs out of integer registers before it runs out of load-queue entries.
Super cores
- Stores: 136 fit, 138 don't. With a nop between each store, 274 fit and 276 don't, which is 137 stores either way. About 137 stores.
- Loads: 484 fillers fit, 488 don't. Counting the two pointer-chase loads, that's about 487 loads. With nops in between, the step moves out past 950, where the creep described below has already reached the serialized time, so there's no sharp edge left to see. That's consistent with a load count, but it's the weaker of the two checks.
The creep before the step
Each limit shows up twice: a gentle ramp that starts at about half the size, then the step at the full size. Stores ramp from N of 72 and step at 137. Loads ramp from about 250 and step at about 487. The integer adds in the last post had a small rise at about 210 before their step at 400, and the FP adds crept up from about 450 before stepping at 880. I called both unexplained. They're the same thing.
The step is where load B no longer fits behind load A's N fillers. But the next iteration's load A also has to fit, behind B's fillers, while A's own fillers are still waiting to retire. Once both groups of fillers together, 2N, are more than the structure holds, the next A can only get in after the oldest group starts retiring, which happens when the first miss comes back. So it starts a little late, by an amount that grows with the overflow, and the iteration time rises in a straight line until the step. With a limit of Q, the ramp starts near Q/2 and the step comes at Q, and every filler type here fits that.
Performance and Efficiency cores
Same method, with the same core fingerprint as the earlier posts to sort runs by tier. Steering onto Performance cores is unreliable, so those rows are fewer and noisier. The Efficiency runs ran at about 1 GHz, which stretches the timings but not the counts, since N is in instructions.
- Performance: stores step between 100 and 104 (about 102), and between 204 and 220 with nops in between, which is the same count. Loads step between 216 and 224 (about 220), and between 432 and 448 with nops.
- Efficiency: stores step between 56 and 60, and between 116 and 120 with nops (about 58). Loads step between 60 and 64, and between 124 and 128 with nops (about 64).
| Super | Performance | Efficiency | |
|---|---|---|---|
| stores in flight | ~137 | ~102 | ~58 |
| loads in flight | ~487 | ~220 | ~64 |
| window (last post) | ~1,050 | >445 | ~185 |
| integer results (last post) | ~400 | ~210 | >185 |
What I'm unsure about
- The names. "Load queue" and "store queue" are the natural reading of a limit that counts loads or stores and nothing else. I haven't shown that each is one structure rather than, say, a store queue plus something that tracks stores after they retire.
- The Super load count. About 487 is large next to the 137 stores. It's clean in the plain test (484 fits, 488 doesn't), but the nop version only confirms it loosely, as described above.
- Hits only. Every filler hits L1. Loads that miss might hold other resources (miss buffers) that run out much sooner, which this test doesn't touch.
The takeaway: on a Super core, store-heavy code runs out of room after about 137 stores, long before the 1,050-instruction window fills. Loads get about 487 entries, but a load into an integer register hits the integer limit at about 400 first. The smaller cores scale down hard: an Efficiency core holds only about 58 stores and 64 loads.