Switch IS NOT FASTER than if, (in C++)

preview_player
Показать описание
In this video, I will show why in C++, switch is as fast as if. Or at least not way faster as people say.

Join my Discord:

Check out my Steam Game Midnight Arrow:

Join this channel if you want to support me 😻:

My C-- toy language 😱:

Code:

Youtube Talk:

GCC update log:

Music: Evan King - Invisible Walls; Pocket Universe; Spicy Boom

Minecraft soundtrack: C418 - Aria Math
Рекомендации по теме
Комментарии
Автор

Thank you so much for making this video. I worked on LLVM and Low Level Learning's video on this topic drove me INSANE since I worked with the exact bits of LLVM that optimize ifs and switches to jump tables and similar. Unfortunately, Low Level Learning has plenty other misleading and harmful videos, and not just him...

cFyugThCzvAqYaGmxRgfCKTuvHMEjQ
Автор

I 100% agree with the "code readability and maintainability instead of chasing dubious optimizations" mentality. However, I fail to see how multiple chained ifs are more readable than a switch.

sledgex
Автор

So, yandere dev already knew to trust the compiler, he was ahead of his time.

LuisCassih
Автор

here is my 2 reasons why i still use a switch case:
1. you can be 100% sure that compiler understands what you are trying to do.
2. it CAN look cleaner than a if statement

LinguisticMirage
Автор

i need to trust him, he sounds like a Russian hacker.

otrqffaimajg
Автор

In C#, the compiler does automatically convert if's to switches if appropriate. In fact, it sometimes does the opposite in scenarios where it is deemed superior, such as reducing the size of the function which helps the runtime optimize call sites more, or within string comparisons.

_emik
Автор

Does the universe explode when two YouTubers with 'Low Level' in their name appear in the same video? probably not but the frequency of such a naming scheme is curious

marinesciencedude
Автор

TLDW on an optimizing compiler, the distinction isn't too important, but for non-optimizing compilers and interpreters, it actually matters to use a switch statement/expression wherever appropriate.

SimGunther
Автор

In some languages using switch statements can help make sure you've covered all cases of an enum, which is nice. It'll also usually error if you have duplicate values.

ITR
Автор

The real reason to use switch is enums. Adding or removing a case breaks things. And that’s a good thing bc it forces you to fix code that might otherwise break at runtime

markuspfeifer
Автор

To 9:25 : At least in the case you've shown on screen, MSVC isn't "failing" to optimize the switch statement but has most likely decided that maintaining a jump table is not worth the cost (perhaps of potential cache misses? who knows for sure.). If you add some more cases, MSVC too will employ a jump table eventually. Or at least that's what I have gathered from playing around with it.

toyb-chan
Автор

Many years ago CPUs did execute code instruction by instruction and you got a nice fat printed book with a list how many cycles each instruction takes, so you could compare the speed by counting the assembler lines.
Right now, you need to benchmark the actual code on real data that is in real positions in memory to get a real comparison because of the things a CPU can and will do.

"Oh how bothersome!"

If creating a representative benchmark is not worth the time you don't need the optimization.

sealsharp
Автор

Hi. C++ game dev here.
I appreciate the idea of the video, but unfortunately there's many things wrong.

First, the explanation of jump table as an array of function pointers is misleading, at least when it comes to switch statements. You could argue it's for easier explanation since C++ doesn't allow for actual jump tables (unless you use non-standard extensions like gcc's Labels as Values), but you should at least explain that, since it makes it look like switch statement compiles to function pointer calls, which would be pretty counterproductive.

Second, the whole benchmark is kind of invalid. Calling rand will overshadow any low level switch/if optimizations. It doesn't affect the result, as you can see from the assembly, but it makes the charts and timings completely meaningless. Or since you're testing such low level stuff, another potential inefficiency is the modulo operator, which can be surprisingly slow. Although I'm pretty sure since it's a constant here, most compilers should optimize it even if it's not power of two. (Side note, the assembly doesn't have div at all. Since they're constants, even the inefficient divisions get optimized into crazy bit-trickery multiplication instructions instead. It's pretty cool, but off-topic for this.)

Third, the problem with all these "the compilers are smart" statements, is that a lot of the time they fail in unexpected ways, ESPECIALLY if we're talking about a somewhat recent thing that is clearly non-trivial. There's been decades of "you don't need to do X anymore!", that then end up not getting optimized in some cases. I'm not saying it's for everything, like ++i vs i++ is probably optimized... or *2 into a bit shift. (Although, what if i is actually some weird iterator class with operator overloads? It's C++ after all...)

Sure, you proved that this specific function is the same on this specific version of this specific compiler on this specific platform. But what if you add some fall-throughs and if statement in a default case? Will it still optimize it? What about MSVC? Can you confidently say all the optimizations work the same on ARM as well, on all the compilers? Not so sure anymore. Mobile is a lot better these days, but they used to lag YEARS behind the trunk of GCC/clang. And since you're making game code (I assume), what if it gets ported to Playstation or Switch? They use GCC/clang as well, but generally they have their own modified versions that are based on older stuff that might even disable or lack some optimizations.

Another thing is that sometimes you need to help the optimizers. I've seen a lot of things fail to optimize because, for example, there was too many layers of function calls, even if all the function calls are very simple and should be all inlined. In very simple toy examples it might not matter, but in real code, things can get a bit more messy and the optimizers can get confused.

Check quick-bench bW4DybP8d8RbBFkV6ReJcfFOlYA for example. (I'd link it, but I think my comment would be eaten.) On clang, even that slight modification makes the optimizer confused. Interestingly, on GCC it still gets optimized, though. Pretty sure MSVC would fail, but I can't be bothered to check. I'm not trying to diss the LLVM/clang guys here, though, optimizations are hard.

But the main point: it's dangerous to make statements of "you don't need to do X anymore!", because there are so variables and edge cases, and suddenly your core loop is x2 slower.

You also can't just hand-wave away MSVC. I know a lot of people would rather pretend it doesn't exist, but if you're making general statements about C++, especially about game dev, you either have to take into account MSVC or title your video "switch statement is not faster than if (in C++, on GCC or clang)". That would still be wrong though. Maybe "(in C++ on newer versions of GCC or clang on x86, in simple cases)".

Of course saying "switch statement is faster than if" is incorrect. But so is "if statement is just as fast as switch". The real answer depends on your specific case.

I think these days most people use switch statements for the readability anyway, but even if we talk about performance only, one main benefit is that even if the compiler WAS super smart, the switch statement will force you to refactor your code in an efficient way. A lot of the time the statements are a bit more complicated than just 11 numbers from 0 to 10, so switch statement can be good because you simply can't make an unoptimizable if-else chain by accident. A lot of the time I get into a situation where I'm thinking "I mean... the compiler SHOULD optimize this, but will it?". So I have two options: 1) Spend time with extracting and compiling the code to profile or check godbolt or whatever. 2) Just use the just as easy to read and write version that I KNOW will get optimized.

Edit: oh yea also, as a tip, you can use volatile to force the compiler to not optimize some variables for testing. Just be careful, for example if you make nr volatile, it would make the ifs a lot a slower, because it would be forced to check the variable on every statement per standard. Which could in theory also be a counter argument for switch vs if, but volatile is not super common. However, it could be some other thing, like atomic. Which would be another layer of cognitive load for deciding if you want to go with switch or if.

Kuukunen
Автор

You misunderstand why we say switch is faster than if.
Sure if statements can in some cases be optimized to be equally fast as switch statements, but that is not guaranteed.

The difference is switch statements put a constraint on the programmer writing it. They force them to evaluate based only on integer like types such as char, int, or enum. You cannot switch on more complicated-to-evaluate datatypes such as strings. This is why switch statements are guaranteed to always produce jump table lookups or something better.

An if statement, on the contrary, can produce something much slower in the worst case scenario because it doesn't have this constraint. With if statements a programmer may be inclined to try to evaluate based on string comparison for example which is much much slower.

That is why we say switch statements are faster than if statements. They force the programmer to keep evaluation simple, which *is* faster.
You are correct in that you can always derive an equivalently fast if chain from a switch statement (this seems to be the focus of your video), however you may not realize you cannot always derive an equivalent switch statement from any if chain.

Essentially it's the same reason you'd say constexpr if is faster than regular if. Even though the compiler can usually optimize a regular if, that evaluates only compile time data, to be the same as a constexpr if. One is guaranteed compile time evaluation, the other is not.

DFsdfd
Автор

Switch statement allows safe usage of enumerations with compiler errors and lints from not properly handling all cases, in addition, you're not allowed to have more than 128 if/else chains in MSVC, which is the minimum guaranteed for **C**, . Also not using switch statements because you don't like the way it looks means you shouldn't be using C++ at all if that's what you value, most things you should be doing are harder to do than the things you shouldn't be doing in C++, like C style casts vs static_cast<...>, and you **really** don't want to be using C style casts unless you love heisenbugs and undefined behavior being a permanent fixture of your code.

snbvreal
Автор

I pretty much only use switch statements when I'm feeling all fancy.

Jarikraider
Автор

The reasons I tell people to use a switch instead of if are:
1. Embedded/Legacy code, it IS running on old compiler as well, switch has a better chance of good optimization, but you would just explicitely code a LUT in those cases.
2. Multiple cases with the same code allowing for the elimination of duplicate code in the source file - less code = less bugs.

dimanarinull
Автор

There is a point to be made about whether that performance gain from switch even matters (in C++ ofc it doesnt as much since it's mostly the same, but i'm talking about other languages). When people looked at yanderedev's code they often criticized the use of if's. And sure when we are talking about readability then that's a good case since it was just a bunch of nested dog shit. But performance wise it didn't matter and performance was usually the one thing that people talked about the most. Even in his terrible code, afaik the rendering was the main issue. Having said that. Great video :D

voodoo
Автор

Readability aside, I think it's better to be skeptical by default. As a wise programmer once said "I don't trust constants anymore".

Compilers can be extremely smart but they aren't AGI applied to code or anything similar. I think it's better to do whatever is more readable and then optimize as most people do. Good to know tho.

Vdevelasko
Автор

I also think, which is very important, is that you can set warning or error flags if you miss individual switch cases. Lets say you ALWAYS want to loop over an enum (class), a bunch of if-statements won't trigger that warning, but a switch condition will.

cryonisc