5 ms·
No, sorry. There are a bunch of factors that make the point a little more complicated. If you're writing, let's say, scenegraph traversal, it's really tempting
by hythloday 14y ago
No, sorry. There are a bunch of factors that make the point a little more complicated.
If you're writing, let's say, scenegraph traversal, it's really tempting to get it working on the PPU and then port it to run on an SPU. A vtable dereference to main memory will crash your SPU job and it is not always obvious why. Obviously, you can't use static analysis tools to make sure the right instructions are transferred, so you have to do it by hand. A modern PS3 game has on the order of low hundreds f different SPU jobs...not much fun. Even discounting that, SPUs will work best when fed branchless parallelized jobs, I wouldn't be surprised to see an order of magnitude difference between a vtable call on a pointer array and a static call on an object array.
Keeping pointers to objects in a polymorphic array is a games performance anti-pattern because of the dereferencing cost, but it's necessary to call virtual functions...there's a hidden cost right there.
Which loop is faster, by eyeballing, and by how much?
void load(Assets* a) {
for (int j=0; j<m_numAssets; j++) {
loadAsset(a[j]);
m_numLoadedAssets++;
}
}
void load(Assets* a) {
int numLoadedAssets=0;
for (int j=0; j<m_numAssets; j++) {
loadAsset(a[j]);
numLoadedAssets++;
}
m_numLoadedAssets = numLoadedAssets;
}
I've seen the former style run literally 1000 times slower than the latter...that's obvious? I submit in a world of out-of-order processors it is not at all.
I don't think that all the author's points are spot on...Koenig lookup is Byzantine but IDEs do a good job of it, ditto source-level reasoning about dispatch. The underlying theme, that C++ is not a good fit for modern game development, shouldn't be so trivially dismissed.
- deleted 14y ago[deleted]
- rubashov 14y agoHow is your example a language thing? Would a JITed language improve the locality of the m_numLoadedAssets variable? I'm not quite sure I get it.
- loup-vaillant 14y agom_numLoadedAssets is a member of some unnamed class the snipet of code is extracted from. When you call the method, it is likely deeper in the stack than any local variable, or even in the heap. It depends where *this is allocated. In the worst case, it is allocated in main memory while you wanted to stay in the graphic memory, or something. A naive compiler would then access memory (or the cache) instead of using registers. A Sufficiently Advanced Compiler would guess that calling ++ many times is the same as incrementing in one go, and hoist that out of the loop, but apparently this one is a bit cruder. Now the same could be said about m_numAssets, but this one isn't written to, so the compiler only have to put a copy in a register, which I guess is a simpler optimization to do.
- rubashov 14y agoYou explain the memory locality issue well but the question is how is the situation better in any other language.
- loup-vaillant 14y agoOops. To answer your question, that particular situation would be better in any language that forces you to explicit the reference to "this" (or "self"). Imagine how we could modify C++: void member_function() { int local_variable++; local_variable++; // This is okay member_variable++; // That should not be allowed this->member_variable++; // This should be written instead } Applied to the example in the GGP above: void load(Assets* a) { for (int j=0; j<this->m_numAssets; j++) { loadAsset(a[j]); this->m_numLoadedAssets++; } } We see that every non-local access is prefixed by something ("this->" and "a[" here). The heavier syntax suggests a heavier cost, so the programmer will more easily think of hoisting those out of the loop, if possible (either manually or through compiler optimizations).
- hythloday 14y agoAny language that gave you more explicit insight into cache spilling, pipeline stalls, DMA wait etc. would be better. Maybe the solution there is better tools, but if you've ever written a C++ parser you might agree that C++ tooling is a language issue. :-)
- loup-vaillant 14y agoNitpick: didn't you mean something like this? void load(Assets* a) { for (int j=0; j<m_numAssets; j++) { loadAsset(a[j]); } m_numLoadedAssets += j; // Increment once, out of the loop }
- hythloday 14y agoNo, the performance problem I'm referring to is the Load Hit Store problem, where the pipeline is constantly stalled (in-order execution) waiting for the CPU to write to L1 cache.
- loup-vaillant 14y agoThis is more by chance than anything, but I think my correction addresses precisely that: the point is not to hoist the increment out of the loop. The point is to hoist the member access. That way, you end up with only one write to memory, instead of a write and a read. (There is another member access in the condition of the loop, but I guess the compiler can optimize it.) I explained that here: http://news.ycombinator.com/item?id=4540107 http://news.ycombinator.com/item?id=4540107 I did not get the Load Hit Store problem, but I did sense there was a problem with the repeated read access to memory. By the way, it looks like your problem is a bit more subtle than the textbook Load Hit Store: you don't read the location you just wrote to. I guess the write access dirties more than one word (at least a cache line, maybe more).