4 ms·
What is not obvious is why anyone would think using ternaries would be a "natural" way of writing branchless code.
by yxhuvud 4y ago
What is not obvious is why anyone would think using ternaries would be a "natural" way of writing branchless code.
- xscott 4y agoYou've got "natural" in quotes there, but I honestly don't understand your intent. Do you think ternaries are unnatural? Do think they can't be used generate branchless code? Something else?
- tialaramex 4y agoThey can't be "used generate branchless code" because it's completely the wrong layer. It's like imagining that using negative integers instead of positive will make your CPU run cooler, or that using a for loop in your code instead of a while loop would get rid of the "hourglass" busy cursor in your program. The C or C++ programmer is writing programs for an entirely imaginary machine (the "abstract machine") and even on that imaginary machine the compiler is allowed to perform "as if" transformations which change what happens so long as you can't observe the difference in certain ways. You would need a language in which branchless was a specific category so that the language cares that you didn't write a branch and won't transform what you wrote into something with a branch in it. Neither C nor C++ are that language.
- zasdffaa 4y ago> even on that imaginary machine the compiler is allowed to perform "as if" transformations which change what happens Yeah, but beginner programmers won't be told that. I can imagine even quite advanced programmers may be amazed because many do well in industry without digging deeply into their tools (for better or worse, there's often no need). A few years ago I discovered that CPUs equally don't do what the code tells them - they do out-of-order to a shocking degree, have store forwarding and shadow registers (code writes a value x to register r1 doesn't mean x gets written to r1) etc. etc. and my jaw was hanging open. Nobody knows anything until they find out.
- tialaramex 4y agoIt's much worse than that, the CPU is stuck in the real world which is obliged to obey Mother Nature's laws despite the fact - especially at that scale - we find them unintuitive. The abstract machine can have intuitive behaviour because we choose how it works, a real machine not so much. For example, humans behave as though "universal time" is a thing, even if they know intellectually that this isn't so. The CPU has no choice though, there is and can only be local time. Events definitely happen in order locally but with no larger coherency. So if you have two CPU cores, those aren't the same location and they necessarily don't experience the same time. When writing a program with concurrency (e.g. threads), your C compiler emits machine code which, based on the author's understanding of the CPU documentation, will perform the necessary dance if you expressed that's what you wanted so that it seems there's a larger coherent sense of time. This is called Sequential Consistency. If you forgot to express that properly or you get it wrong, the C standard says your program has "Undefined Behaviour" because the machine code emitted does something which makes sense locally to each CPU core but may not make any sense to you at all. In general humans cannot reason successfully about what their program does in this situation anyway, it's just a complete head fuck, so the C decision to mark this "Undefined behaviour", anything could happen, is actually a reasonable one.
- puffoflogic 4y agoThis is not a correct explanation of why there is not a global order of memory operations on modern CPUs. It has nothing at all to do with the passage of time on different cores. Nothing.
- zasdffaa 4y agoTrue, and yet isn't it a perfect example of plausible but wrong mental models, just as "but it's ?: so it gets translated to cmov" is plausible but wrong.
- tialaramex 4y agoI'm intrigued by how far off it is from literally impossible. How many orders of magnitude are we from the point where rather than cache semantics forbidding it for practical reasons, you just couldn't physically deliver global order. If it was practical people would do it because it makes it much easier to write programs. So, it's not practical today and yet your parent insists it's possible anyway and I wonder by how much.
- pjmlp 4y agoIt is ironic how many assume C and C++ compilers are the ultimate example of doing compiler optimizations, while some of us had the experience that on the early days their 8 and 16 bit versions were worse than what a junior Assembly coder was able to write without even bothering with profiling tools.
- tialaramex 4y agoThis is probably even true today on very small architectures like a retro-computing Commodore 64. But it's about scale in two distinct ways. Obviously these programs are smaller, meaning any clever trick has disproportionate impact on real performance, but also there's less need for abstraction because that's less global complexity to handle. A C64 has 65536 bytes of RAM, you can literally write down what each byte is used for, and your program is likewise smaller so you can write down what every instruction does exactly. This sort of global view is something a compiler takes advantage of, but is normally not practical for humans. At the scale of a Commodore 64 it is practical. People scale too. In Linux they can get into trouble if a function X() also happens to do Y 'cos that's not what the function said and nobody keeps all of this in their head. But if your entire software is the work of one guy, he remembers that the X routine also does Y so it's fine.
- xscott 4y ago> They can't be "used generate branchless code" because it's completely the wrong layer. I guess precision in language is valuable, and I did make a typo by neglecting to type the word "to". I should've said, "Do you think they can't be used as part of an expression which may possibly be optimized into branchless code?". Thank you for the metaphors about keeping the CPU cool, but I really just wanted to know what the other guy meant by what he said.
- yxhuvud 4y agoYes, as I quoted the term from the commenter and the OP. Ternaries are not unnatural, but thinking that they would be any different to normal if-statements is a very unnatural thought to me.
- zasdffaa 4y agoBecause if (..) c = 1 else c = 2; will "obviously" map on to a branches whereas c = .. ? 1 : 2; "obviously" corresponds to a cmov. It's a natural mapping, if not necessarily correct mapping with more knowledge.
- IshKebab 4y agoWhy though? Ternary is always introduced as syntactic sugar for if/else.
- zasdffaa 4y agoMy K&R C book agrees with you, but there when you know about cmov, it seems to map cleanly to ?:. It's just my opinion and what I feel. I can't back it up any better.
- monocasa 4y agoBecause it's an single assignment expression dependent on a result that'll probably trivially be in a status flag.
- kzrdude 4y agoWhy not? It's expression oriented and as such seems to be a good way to write it.
- yxhuvud 4y agoUh, because it is just syntax sugar for an if-statement. And those are typically implemented using branches. There may be possible optimizations to make them branchfree in certain cases, but it is certainly not something I'd assume would be the default.
- samatman 4y agoBecause it's syntax which looks like an expression but isn't fully evaluated. That's unusual, C syntax is complex and new users are often very focused on figuring out pointers, you can miss that `= a() && b() || c();` always calls b and c but that `= a() ? b() : c();` only calls one of them depending on whether a is zero.
- chaosite 4y agoAm I missing something? && are || are short-circuiting operators in C, so in that example b and c won't always be called either.
- samatman 4y agoNo you're correct of course. I could go back and edit it to be & and |, but I think leaving the easy-for-a-noob to make mistake in actually illustrates the point better. It wasn't on purpose, my brain took a shortcut from the eager logical operators I work with more frequently.