Compilers can never generate even good code
zyvra.org
zyvra.org
Yes, because they are measurably slower than their equivalent primitive instruction sequences, and the space saved isn't really all that significant when you consider all the other loads and stores necessary to run them. I think they've been actually slower since either the 486 or Pentium days. I'm pretty sure, for instance, Watcom C++ would generate "rep movsd" sequences for memcpy back in the 386/486 days, back when it was actually an optimization to do so.
0x0000000100000cee <main+30>: cld
0x0000000100000cef <main+31>: mov $0x7,%ecx
0x0000000100000cf4 <main+36>: xor %eax,%eax
0x0000000100000cf6 <main+38>: mov %r15,%rdi
0x0000000100000cf9 <main+41>: rep stos %rax,%es:(%rdi)
0x0000000100000cfc <main+44>: ...
It even does pretty much the same thing with -m32 and with optimization turned down to -O1. So perhaps things have changed.And even special machine instructions seem quite achievable with a pattern-matching code generator.
Overall I think the HN title is very misleading - the article seems more "compilers don't do this well yet", definitely not "compilers can never do this".
You'd need dependent types and a compiler that understood exactly how much information in bits was required for a particular proposition, I'm guessing. I think this is the sort of thing (not using tricky assembly instructions specifically, but using unboxed types with expressive type systems) that makes languages like ATS[1] possibly faster than C.
The point is: good code generation is possible in theory, even if "sufficiently smart" compilers are too hard to write. (Or are equivalent to general A.I.)
It is not responsibility of a compiler to recognize specific problems in specific domains and perform optimizations, it is a job for a human programmer. Sure, you can make compiler aware of Riemann zeta function values in specific ranges, but what's the use of that outside of very specialized programs? Optimizations mentioned in the article could be programmed in C and compiler would happily translate these savings into assembly.
The reasons why we don't really program assembly anymore is that _on average and on a large scale_ compilers generate vastly better machine code than humans. Take for example register allocation problem which is proven to be NP-complete. Obviously compiler does an approximated algorithm but save for a case of few registers, no human can match it.
Some architectures, like VLIW, actually rely on the compiler to generate well-performing machine code.
Because ARM.
Today we even have a 64-bit version, which differs from its trail of 32-bit ancestors even more than they differed from one another...
There are fewer instructions and most importantly, every instruction can be used, there are no 'cruft, don't use! slow and old' instructions like in x86.
Even MIPS is more peculiar.
> I know of no compiler that generates the Intel string instructions.
The x86 instruction set has over 20 years of cruft. There are entire sections on modern processors dedicated to handling weird addressing modes and complex CISC instructions that are unnecessary now. I don't know the specific reason why 'scasd' isn't generated when he wants it, but I do know that a lot of these special purpose instructions are often superseded by more powerful instructions less than a decade after they are introduced.
In fact, due to the unique awesomeness of x86, sometimes instructions are superseded by inferior instructions. My favorite is 'loop.' Most x86 loops are executed with two instructions -- decrement a counter and jump if not zero (dec/jnz). x86 has a 'loop' instruction that does this operation in one step, yet on modern processors (Athlon and up) it is at least 8 times slower than dec/jnz (http://crackthecode.us/amd64_opt/OptExperiment.html).
How could that be? Does 'loop' handle some edge case or trigger some crufty microcode that slows things down? Nope. Windows 98 relied on the 'loop' instruction for hard-coded timing loops in drivers. The k6-2 executed these very efficiently, and as the processor hit 350 mhz the speed of the processor triggered race conditions making Windows 98 crash. Consumers obviously found fault with AMD for making a chip that was too good, so loop was artificially slowed in the Athlon (and all processors afterward) forever crippling a perfectly good instruction. A wily assembly programmer optimizing for a k6-2 would have dramatically slower execution on tight loops for no good reason.
More info: http://support.microsoft.com/kb/192841
There is the case that there is a solution is known to be algorithmically optimal, or there is only one leading contender for optimal and it's unlikely to be toppled and you have to have a PhD and a couple postdocs in theoretical CS to begin to approach a way to challenge it, in which case it would indeed be more worth your time to optimize the assembly code. I hear Adobe Photoshop uses an FFT written in optimized assembly because almost every CPU-intensive thing they do uses it. Has he taken a gander at that? Pretty sure the fastest known way to calculate prime numbers changes a couple times a year.
However, that is only the case with textbook CS problems, and I think most HN members would agree that in industry, software engineering is almost never actually about solving textbook problems, and tends to have quirks that lead to clever algorithmic optimizations that are continually being discovered.
But what about the features of a particular language? Mucking in assembly is great if you're trying to squeeze out every ounce of power, but it's much too verbose for building complex systems. If you're trying to build user-facing features, layers and layers of language abstraction is great. See http://www.paulgraham.com/hundred.html
If you're building a prime-number generator, go ahead and write in assembly. But if you're writing an application, like most of us on here (I assume?), use a high-level language. "[Taking] advantage of every idiosyncrasy of [the] instruction set" is a waste of time.
http://www.arnetminer.org/viewpub.do?pid=600808
http://citeseer.ist.psu.edu/viewdoc/download;jsessionid=8A1C... [PDF]
I've generally found that's the order I go in meeting performance requirements. When I've failed to meet those requirements, I've generally worked backwards up the list, with each step getting more expensive in an exponential fashion.
"Abstract - People often write less readable code because they think it will produce faster code. Unfortunately, in most cases, the code will not be faster. Warning: advanced topic, contains assembly language code."
On tiny and contrived micro-benchmarks, it's nearly impossible for a (non-contrived) expressive language to beat less expressive languages like machine code, X86 assembly, or C. For real, large software projects, the case for a less expressive language is not nearly as convincing, because the shortcomings of the language mean that programmers aren't able to write the same quality of code. For example, compare the outright mess that is concurrency in C or Java to the much easier concurrency available in more expressive languages. As parallelism becomes far more important for people who need high performance (yes, I know concurrency and parallelism aren't exactly the same thing, but they're similar) this advantage of the less expressive languages is likely to diminish, if not disappear outright.
[Edit: Added-- This is analogous to the static/dynamic typing debate. Dynamic typing test drives extremely well but multi-developer projects in dynamically typed languages usually turn into outright nightmares, due to problems around interfaces. Static typing's better for large projects. Same with expressive languages: assembly's going to win on microbenchmarks, but writing good code in less expressive languages becomes impossible as the size and scope of the project gets large.]
You are correct that the mess is language agnostic. For example, if he'd attempted to discard the low order bit of his prime numbers in C, it would result in the same awful maintenance.
Some optimizations waste too much programmer time to be worth tackling. Distorting the meaning of code for some tiny performance gains never constitutes "well-structured" code.
Number 3 (output of a sequence of integers as the sequence of differences between consecutive elements) is a very common optimization for inverted indexes formats, IIRC lucene, written in java, does it.