The article has a great example of this in the int_to_str code. While it's far from optimal code (e.g. the "mov rbx, 10" should really be outside of the loop, the "add rdx, 0x0" is useless, etc.), it's using the stack in a way that a compiler would probably never do, and something that likely isn't possible to express at all in a HLL. The C version would involve at least allocating an array with some pointers into it to act as a stack.
It's in these approaches - fully exploiting the features of the machine - where human-written Asm really shines. In some ways, the compiler "has its hands tied" when it comes to generating code, since it is limited to generating code that represents ideas in the corresponding HLL; it can make use of extensive knowledge of microarchitecture to schedule instructions/uops, sophisticated algorithms for register allocation and instruction selection, and generate code that is faster on a particular model of CPU than if it did not, but this falls short of the human who realises that neither a separately allocated array, nor such instructions to manipulate it, are actually needed.
The human can apply even more extreme optimisation making use of microarchitectural details if she so chooses; but the fact is that even without this, the code is probably already better than what a compiler can do. In practice, for general-purpose-code (i.e. excluding additional features) most models of x86 are all quite similar since the P6, and Intel has been doing a great job at making existing code faster with each new generation, so there is little need to go to that extreme; the optimal sequence of instructions for one model is often the same or close to optimal for the next one. (The odd ones out are the P4, and possibly some of the early Atoms.)
Another item that appeared on HN a while ago illustrates this "think outside the HLL/compiler" idea too: http://davidad.github.io/blog/2014/02/25/overkilling-the-8-q... ( https://news.ycombinator.com/item?id=7301481 )
However, the advances in tech have outpaced the advantages of ASM. Yes your code is 10x more efficient than mine, but I can throw 1000x as much processing power at this via cloud & a scripting languages.
Yes you've seen more "compiler-generated code" than I have but realistically...who is going to win this p!ssing contest? You writing ASM code or me scaling a scripting language across clouds? I bet I can deploy cloud processing power faster than you can
MOV AX,CS
MOV DS,AX
Also - I'm not in the cloud game - the above is a simple example/play the devils advocate.
Note that it's exceedingly rare that one should be writing an entire program in assembly. But knowing assembly allows you, as you bump up against performance constraints, to inspect what code is being generated and understand what's slowing things down, which can help guide you in adjusting the source so it will be faster, applying a better set of optimization options, hand-tweaking the generated assembly, or ultimately rewriting sections while having a reference implementation.
I'll concede that you'll need ASM for core speed critical applications like stock exchanges...then again I conceded that point before we started debating...
>Note that it's exceedingly rare that one should be writing an entire program in assembly.
My mention of in-line ASM should have made that obvious that I'm not arguing for pure ASM.
>applying a better set of optimization options, hand-tweaking
You hand tweak your ASM, I deploy additional cloud clusters. We'll see which scales better (pro-tip: optimization is notoriously prone to diminishing returns).
My mention of in-line ASM should have made that obvious that I'm not arguing for pure ASM."
That wasn't me arguing with you, it was an attempt to clarify my position on the matter in light of what else I said.
"You hand tweak your ASM, I deploy additional cloud clusters. We'll see which scales better"
That depends tremendously on, again, the context - including where one already stands on the various curves and on how parallel the problem is.
"(pro-tip: optimization is notoriously prone to diminishing returns)."
As is parallelism: http://en.wikipedia.org/wiki/Amdahl%27s_law
This kind of stuff is why I stopped teaching and mentoring. It's like trying to convince creationists with facts.
Also, it's wide knowledge programmers spend the vast majority of time debugging. The other day I wasted an hour because a popular high level language doesn't warn users when comparing apples to oranges. I've spent on ocassion a lot of time chasing bugs in cloud services that make debugging close to impossible, too. There's no free lunch.
It is certainly not hard to beat a compiler on speed or size (often both), and I believe that the only ones who can't are the ones who learnt Asm the stupid way that compilers generate code and not how the machine really works. E.g. it's commonly taught that x86 has 6 general-purpose registers (reserving eBP/eSP), but in reality eBP-based stack frames and the use of the stack is nothing more than a compiler-generated artificial construct. Even eSP can be used for something else if you really need one more register[1]!
Compilers follow the rules of their source language and impose strict, often unnecessary conventions on their output. Asm follows the rules of the machine, which are far richer and more expressive than the abstracted simplicity of any HLL. That being said, they have improved significantly over the years - the days when compilers would push/pop every register on entry/exit to a function regardless of whether it was used (or its caller needed the value preserved), or when the start of every function could be identified by a distinctive 55 89 E5 (push bp; mov bp, sp) in the binary are fortunately mostly history.
[1] http://www.virtualdub.org/blog/pivot/entry.php?id=85