Hacker Newsnew | past | comments | ask | show | jobs | submit | xorblurb's commentslogin

That's, given the question, is a really moronic answer.

There are means to apply the modern approach of optimizations beyond O0 on C without importing all kind of UB from the language level. You just have to actually proove the properties you want to rely on, instead of relying on wishful "UB => authorized by the standard => programmer fault if anything goes bad" thinking.

And promoting local variable to registers CERTAINLY NOT depends on language level UB. It would be permitted by the as-if general rule even if anything prevented it to happen in the first place, which is not the case. You don't have to have an address if nobody wants it and random pointers haver never been required to allow access to all objects, especially those who might never have an address at all. Plus nobody ever expected that anyway. People expect 2s complement. Or at least something that can not result in nasal daemons, and given C history, something that matches what the processor does. So 2s complements is at least not utterly stupid. So conflating the two is dishonest to the highest point -- except maybe if the only intended audience of the C language is now experts who e.g. write compilers. What a bright future this would be.

Hell, we dropped the hypothetical flat memory model even without strict aliasing for maybe 20 years (and probably 30, to be honest), and this had NEVER caused the kind of issues we are talking about. So don't pretend it did, just to dismiss the real issues. So ok even then it was actually probably informal as hell and in some ways worse for experts, but the amount of exploited UB was also WAY smaller. Quantity matters in this area. And context too. Do you want secure OR fast embedded systems? I would prefer reasonably secure and reasonably fast. Certainly NOT fast to execute and exploit, or more probably fast to crash pathetically.

You know very well that compiling in O0 is not going to happen in prod on tons on projects.

Don't dismiss real concerns with false "solutions", especially when mixed with proofs of your misunderstanding of the situation.


> You don't have to have an address if nobody wants it

The spec says otherwise:

> An object exists, has a constant address, and retains its last-stored value throughout its lifetime.

[C11 6.2.4.2]

But that's more of a pedantic detail. More pragmatically, you do have a point. It is indeed possible to build a C compiler that has no "undefined behavior", but only unspecified behavior, as long as the program doesn't actually violate memory safety by, say, writing to some random address it can't prove it has permission to write. For example, guessing the stack slot used for a variable and overwriting it pretty much has to be undefined behavior – it's hard to optimize anything if variables can randomly change their values without being referenced. But that's okay, because overwriting random memory is inherently unsafe without obtaining a guarantee of what that memory will be used for. On the other hand, reading from random stack memory could be unspecified. A particular stack slot might be used for a variable, a temporary expression, or nothing at all, so it's unspecified what you might find there. But the compiler will always generate a single, real load instruction, without making any assumptions about aliasing that it can't prove; thus, you'll never get logical impossibilities like "x + 1 > x".

And such a compiler could definitely produce code that's better optimized than -O0 – because -O0 is a very, very low bar (it doesn't even do register allocation, in the compilers I've seen). But I expect it would do substantially worse than a modern compiler's -O2 even on average code, with a lot of little missed optimizations that add up. (Though if you're using -fno-strict-aliasing, you're probably already eating a decent percentage of that penalty.) And in the worst cases, like tight loops that can be autovectorized only by taking advantage of undefined behavior, it might be only a fraction of the speed of the better-optimized version.

Still, it might be an interesting project, especially if you could formalize the "no undefined behavior" guarantee.


Those kind of discussions have various effects, some of which I believe to be far from moot.

First, they permit that some people even take notice about this situation. Few developers read the standard and even less write it or follow the discussions to change it (are they even open?) or write a compiler for it. The rationales are not even tracked [1]. It actually would be insanely hard to get a good understanding of those subjects by e.g. just reading the standard, without having those kind of discussions on forums typically used by more devs than just a few dozens of compiler writers...

[1]: but while I'm thinking about it, an impressive independent book as been written by Derek Jones: The New C Standard: An Economic and Cultural Commentary http://www.knosof.co.uk/cbook/cbook.html


Could have been (should, in some -most?- cases) implementation defined.


Microbenchmark can be very misleading compared to real impact in real programs. Still, the gains allowed by UB of signed overflow (when you are lucky enough that this transformation is actually correct in the context of what the original programmer had in mind...) are positive and probably measurable even in real programs, or if hardly measurable, maybe they at least permit a few percent of whole system perf improvement when using SMT processors. But they are more suited to other programming language than C, and actually yes, in C++ (and probably in most languages at this point) it is better both of code readability (most important!) and performance (nice to have, but very secondary compared to code readability) to use for each constructs compared to maintaining an index yourself.

Technically there is no overflow flag to reset, it is just that some CPU instruction sets do not support indexing with a 32 bit register when using 64 bits addressing, so you have to insert an extra sign extend instruction if you want to support 2s-complement signed overflow on 32 bits indexes. So you typically already don't have any cost if your indexes are already size_t/ptrdiff_t, but ptrdiff_t signed overflow is still UB according to the C standard, which is also a shame, because it allows for far less interesting "optimizations" at this point (maybe a + w >= a --> true if w is positive, but that's actually typically dangerous, because that was historically what was used to check for overflow at source level, and now the compiler is suppressing all the checks!)

So all of that really only are trade-offs, and in the modern age (with e.g. a security picture that is kind of worrying, etc.) some people are arguing that this was a terrible idea to use this approach so carelessly, in their opinion. Most experts now think that no non-trivial codebase exist with no potential UB in it, so it is not just rants all around, some even are working on the mathematical model of the llvm optimizer to make it actually sound (for now even internally, it seems that it is not -- so unfortunately with this approach of optimisation for now there is no mathematical justification as for why the optimizations performed are actually correct even with the hypothesis of strict conformance to the C standard, so I let you imagine what happens in practice when almost no program is actually conforming...)


If there are microbenchmarks, I didn’t write them. And I’ll acknowledge that my instruction-counting approach has limits, especially since I don’t really know the details of the platform. And my approach also doesn’t account for pipelining.

But I would expect someone complaining about this optimization to do more than simply hand wave with a “supposedly.” They could instead say that the optimization can be applied when the compiler can prove x < x + 1, which it can show when both the beginning and end of the loop are known at compile time. In fact, I think it’s better to say “omit the pessimization that applies when the compiler has to allow for overflow.”

But going no farther than labeling it a “supposed optimization” turns the complaint into a standard rant.


(Technically it is not really an overflow check the problem in that case, but typically more the need of extending an index from 32 to 64 bits because the instruction set in 64 bits do not support indexing with 32 bits index)

If you want perf in a critical tight loop that has been identified by some profiling, you can easily optimize it yourself (and yes, typically bumping the counter type to size_t / ptrdiff_t is enough to optimize, but the advantage you have is that you can actually check that this transformation is sound according of the intent of the programmer of the original code, whether in the context of the C programming language, the compiler doesn't even try to check that itself, it merely blindly makes the hypothesis that there is absolutely no UB ever, and to hell if there actually was)

But anyway we have since invented sane languages in which we BOTH have safety, and the capability to apply that kind of transformations that have a small but positive perf impact. In some cases it is actually way easier to optimize using those approaches from safe languages than from mostly unchecked ones culturally full of type punning and other kind of insanity (like C is), and this is not even a recent discovery: there is a reason for why number crunching stuck to Fortran. So C should be kind of considered as a legacy programming language, at this point. A very important one for historical reasons, but one should think twice before writing new critical infrastructure with it...


Large pieces of sw have been able to switch some plateform specific code to other compilers (chrome for windows comes to mind).

This is probably way smaller than the whole Windows, but I would not be surprised if some MS dev are already internally compiling some of their components with clang for their own dev/testing (even if just for extra warnings, etc.)

And a major part of the work of the MSVC team today seems to be about standard compliance.

But yes, I do not really expect that they switch, and actually they probably don't even have the beginning of a serious reason to do so. This is not even a case of NIH. Their compiler derives from an ancient codebase and has been continuously maintained for several decades. They "invented" it. The only modern serious competition (that cares enough about Windows compat and some of their specific techs) has been started way after... They probably also have all kind of patents and whatnot about some security mitigations that are implemented by collaboration between the generated code and (the most modern versions of) low level parts of the Windows platform.


Sane compilers should do that. The standard should eventually specify that. But before it does, you can not write portable code that expect that (but hopefully once enough compilers are sane but before the standard is updated, you can write code that targets only the compilers, and don't give a fuck about the other broken garbage that try to trap the world)


Modern DMA is cache coherent (maybe except if you opt-out of it? I'm not even sure you can). It is still costly though.


It's also integrated to new instructions to flush things and wait for them to be made persistent.


Intel actually backed off on those plans, and now any regular cache flush will suffice. There are still some new cache flushing instructions, but they merely offer performance enhancements, not stronger memory safety guarantees.

https://software.intel.com/en-us/blogs/2016/09/12/deprecate-...


I think you can still do non burst transactions and even set masks on byte granularity for writes. Classic processors probably don't do that, though.


I don't really know much about memory controllers, but being able to mask at the byte level seems like an important optimization. Without that, many writes will have to do a read first to them merge the read bytes with the dirty bytes.


The capacitors inside of DRAM cells are so small, that the very act of reading the DRAM cell obliterates the data. I'm not kidding.

The "Full procedure" of reading a DRAM cell is:

1. Row-Address -- Load a "row" (usually 1k to 8k. DDR4 is 8k IIRC) to the sense amplifiers. Sense-amplifiers can indefinitely hold data, but there's relatively few of them.

2. Column Address -- Once loaded, you talk to the sense-amplifiers.

3. Precharge -- You begin to move the data from the sense-amplifiers back to the DRAM cells. Again, step #1 obliterated the data, you have to write it back regardless.

4. Row-Address -- After the old data is loaded, you send it back.

So regardless, you have to Read-then-write EVERY time. In fact, DDR4 has faster write-speeds because you don't have to do the read step if you are only writing.


While this is true, the point was that it's an important optimization to avoid doing RMW at the memory controller level. If you did it there, it would cost tens of nanoseconds. Doing an on-die refresh in parallel with the write is almost free.


I think with both DDR3 and DDR4, the number of bits in the row address depends on the DRAM density.


Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: