The optimization is the linear memory layout of the nodes -- value speculation is decoration.
The example is poorly chosen in terms of practicality for this reason, but otherwise, no, this is a poor summary that misses something interesting. The memory layout isn't changing in the faster versions, and there are no additional cache misses. It's easy to convince yourself that the only difference between the naive linked list and assuming linear layout is the extra pointer load - but TFA shows this is false! The…
Beating the L1 cache with value speculation (2021)
41–50 of 87 posts
Re: Beating the L1 cache with value speculation (2021)
#42Earlier quoted context omitted.
Well in the articles case, it's the linked list `next` pointers: https://mazzo.li/posts/value-speculation.html#value-speculat... In a happy case, those will be laid out sequentially in memory so you can guess the value of the pointer easily. (That said your comment still stands, since using linked lists in the first place is much more rare). But I suppose there's probably a lot of other domains where you might have a…
Not only are linked lists rare, they are also mainly useful exactly in situations where you cannot guarantee a (even mostly) linear allocation order.
Re: Beating the L1 cache with value speculation (2021)
#43Earlier quoted context omitted.
Not only are linked lists rare, they are also mainly useful exactly in situations where you cannot guarantee a (even mostly) linear allocation order.
Memory pools are commonly allocated in a contiguous block and sliced into nodes placed onto a free list. They will be sequential until the pattern of allocations mixes them up.
Re: Beating the L1 cache with value speculation (2021)
#44I enjoyed the read and it taught me new things, I just wish that the reference example would have some minimal practical value. I don’t think there is any reasonable scenario where you would be using a linked list but the memory is contiguous most of the time.
It's not that unlikely. - Create the list in some weird order. You know you're going to traverse it a bunch, so you sort it. - The list is in creation order, and creation is in allocation order. Once in a while you go back and insert or delete a small handful of nodes, hence the linked list. - There is a natural sort order that you can make contiguous, but you support relatively rare alternative orderings and optimiz…
Someone mentioned sparse vectors for example, where you are guessing 0 rather than pointer++. Anytime where a value is somewhat predictable this could come in handy.
Re: Beating the L1 cache with value speculation (2021)
#45Re: Beating the L1 cache with value speculation (2021)
#46Hmm. What do we think about the alternative guess that the address of the next node’s value field is the next address after our current node’s value field memory address? This is, I guess, essentially a guess that we’re pointing at sequential elements of a big array, which sort of begs the question “why not just use an array?” But I’m wonder if a slightly permuted array is not so unusual, or at least might come up oc…
Re: Beating the L1 cache with value speculation (2021)
#47It’s interesting to me that the final assembly-trick-free version almost no longer looks like a hack. If you commented the inner loop with something like “// Linear scan for adjacent nodes”, the reader gets an OK, if incomplete, intuition for why it’s faster. Even if you don’t know the exact CPU details, if you’re aware that flat arrays usually loop faster than contiguous linked lists, the nested loop immediately rea…
I want to understand, in:
uint64_t sum5(Node *node) {
uint64_t value = 0;
Node *next = NULL;
for (; node; node = node->next) {
for (;;) {
value += node->value;
if (node + 1 != node->next) {
break;
}
node++;
}
}
return value;
}
How is this magic: if (node + 1 != node->next) {
break;
}
Better than the more intuitive: if (node->next == null) {
break;
}
Also.. why is `node + 1' even a valid comparison? (Please forgive my rusty C/C++, college was awhile ago)Re: Beating the L1 cache with value speculation (2021)
#48Earlier quoted context omitted.
It's possible to have a "sparse" matrixes where most of the values are 0 and only a few are not null. So you can guess 0 and cross your fingers. (There are libraries that implement sparse matrixes in a more memory efficient way. I needed them for a program in Python, but I'm not an expert in Python. I found a few ways, but they are only useful for big matrixes with very few coeficients and have other restrictions to…
How sparse is your matrix?
In al old case they like 1000x1000, like a block checkboard where one of the colors had only 0, so like 50% full, but the blocks had different sizes. It was an old project in Fortran, and we used a "for"(do) for the blocks and another inside each block.
Re: Beating the L1 cache with value speculation (2021)
#49So, are there any programming languages that have updated architectural models, something that takes into account branch prediction, CPU caches, etc?
Re: Beating the L1 cache with value speculation (2021)
#50It’s interesting to me that the final assembly-trick-free version almost no longer looks like a hack. If you commented the inner loop with something like “// Linear scan for adjacent nodes”, the reader gets an OK, if incomplete, intuition for why it’s faster. Even if you don’t know the exact CPU details, if you’re aware that flat arrays usually loop faster than contiguous linked lists, the nested loop immediately rea…
That is amazing, isn't it? I want to understand, in: uint64_t sum5(Node *node) { uint64_t value = 0; Node *next = NULL; for (; node; node = node->next) { for (;;) { value += node->value; if (node + 1 != node->next) { break; } node++; } } return value; } How is this magic: if (node + 1 != node->next) { break; } Better than the more intuitive: if (node->next == null) { break; } Also.. why is `node + 1' even a valid com…
In C and C++, when you add 1 to a pointer, you actually make it point to the next object of that size in memory, e.g. if I have a pointer to a 4-byte integer at address 0x8000, incrementing it will make it point to address 0x8004.
Because arrays are contiguous chunks of memory, you can use this to iterate through arrays.
int array[50];
int *p = &array[0]; // Take the address of the first array element
int sum = 0;
for (int i = 50; i != 0; i--) {
sum += *p;
p++; // Point to the next element
}
After this loop, p will be equal to &array[50], which is one past the last element of the array, because the loop will have run 50 times, and p is incremented once per loop.What OP did is allocate an array of linked list nodes, and test to see if the next linked list node was actually just the next array element.