A friend and I were surprised that C still has near-best performance among languages. I thought about why this is, and I wrote up a few paragraphs. I wonder if the friendly folks on CS stack could tell me where I'm wrong, too vague, or misleading. This will help me sharpen my understanding of the situation.
One reason it’s hard to improve on C in the current context is that current OS’s force everything to “pass through” something like C anyway.
When you write code in another language and execute it, the C-like thing it passes through need not be the C language itself (though pretty often it literally is, if some part of the language is implemented in C). But rather it’s the set of low-level primitives exposed by the OS (“system calls”). There aren’t terribly many of these (on the order of hundreds), and C is the modern language that is closest to them. In fact, it’s the only language(?) that actually contains the raw system calls inside the language.
Using the notation “more abstract layer —(API)—> less abstract layer”, you could write a simple diagram “your code —(system calls) —> the OS —(instruction set) —> the hardware”. Think of C as living pretty far to the right, toward the system calls. In fact it actually contains some (all?) of the system calls in the language itself, which I believe is unique among languages. So some parts of C are more like an API than they are like a language, and “API’s ossify”.
You could ask, why not more system calls, each more specialized and optimized? Several hundred isn’t that many, in the face of the multitude of things computers can do. That question is probably deeper than I can answer, but my take is that all things being equal, a smaller API is a better API, because small API’s reduce combinatorial explosion.
As your code is executed, you can understand it in stages, like a ladder, where up/down corresponds to more/less abstract. (It’s really a DAG, not a ladder, and maybe it need not even be strictly acyclic always, but whatever. Let’s pretend no branching for now.) Each rung in the ladder communicates with the rung below it through an API, that is, a little language which the lower rung created so you can tell it what you want it to do. You could even say that the rung is just some useful combinations of the API beneath it, packaged into an API for some higher rungs to use.
Breaking things up into layers/stages/rungs is beneficial for understanding and reuse. First and fairly obviously, if you can factor out the function of one rung, you can reuse that rung, and it becomes worth it to devote significant effort to optimizing that rung. But second and maybe less obvious, if your program is in layers which communicate through limited-size APIs, it helps reduce combinatorial explosion in the space of how programs can operate. Another way to say this is with layers, there are fewer ways that things can go wrong at any layer. This is more true when the API’s on each layer are kept smaller. And third and maybe most important, the smaller an API exposed by a rung, the less you constrain the operation of the rung, so that the rung can change how it works and adapt to new situations, but still be used exactly as before.
But breaking things up like this has costs. You’re always sacrificing efficiency in each individual case when you constrain your architecture like that. Also, the stability of APIs is a double-edged sword: nothing ossifies like an API. Because their benefit is that they’re reliable and stable, so many things come to rely on them, so no one wants to change them even if they’re long outmoded, because at any given moment no one wants to break everything. Hence the arcane commands at the terminal (bash language), and the relative stability of the set of system calls.