Readit News logoReadit News
holowoodman · a month ago
But the potential of easy optimization does not mean that they will be optimized. Compilers for higher-level languages are full of broken promises around their potential. "Faster than C because we can optimize" is never really realized, except in maybe one benchmark that only works in that one exact scenario and nowhere in my code...
Ygg2 · a month ago
> Compilers for higher-level languages are full of broken promises

Sometimes because C is lingua franca of low level.

Some noalias optimizations Rust had didn't work in LLVM, because no one bothered using it before in C.

This goes even further to hardware. C-like null terminated string search SIMD is faster than a saner (pointer + len ) string view. So it's faster to append null to end of string view than to invoke the SIMD on string slice.

holowoodman · a month ago
And here we go again with the compiler excuses.

C standards with the 'restrict' keyword to allow aliasing optimisations have been existing longer than Rust. LLVM just never bothered, despite the early claims that all the intermediate language and "more modern" compiler would enable more optimisation potential. LLVM is still slower than GCC, even in plain C.

Where is the problem to quickly null-terminate a pointer+len string and use the quick null-terminated SIMD search? Should only be 1 move slower than the native C. "Space after that string" shouldn't be a problem, since in higher-level languages, allocations can make enough room, it is higher-level after all (and you need null-termination for syscalls anyways).

It is always the same story. Compiler and programming language people make claims about future optimisations but never ever follow through. It's always just theory, never implementation.

jlouis · a month ago
It's also that whenever an hour is poured into Haskell, Scheme, or OCaml, there's hundreds of hours poured into Javascript, Python, C, Java, ...

Eventually that lets you optimize things more at the compiler level because you have the extra time to maintain it as well.

My primary argument is that a more restrictive language, if designed correctly, has programs which are resistant against bit-rot over time. You can take a program from 15 years ago and compile it. It'll run faster due to optimizations, and hardware improvements. And the program will still be the "right thing to do."

In contrast, if you take C code that's older, you'd shudder and immediately begin identifying things left and right which needs to be redone because the modern machine is different from the aged machine. Part of the efficiency comes from the fact we are building the C code such that it has affordance on current hardware for efficient execution. But when the hardware updates, then the code needs amendment to keep up. Meanwhile, your SQL statement is the same because it's written declaratively.

thaumasiotes · a month ago
> Meanwhile, your SQL statement is the same because it's written declaratively.

I must have imagined everything I've read about query planners.

sn9 · a month ago
I don't know about "faster than C", but usually the promise is that you can get to within the same order of magnitude of performance with higher level abstractions, more guarantees of correctness (or at least higher likelihood), and for far less effort than the optimal handwritten C equivalent.
JimDabell · a month ago
This is just one aspect of the principle of least power:

https://www.w3.org/DesignIssues/Principles.html#PLP

By restricting the power of a language, you enable it to be used in more ways than just “execute it and see what the result is”.

nyrikki · a month ago
While i subscribe to the "principle of least power"; Rice's theorem extends to even total functions in finite time. So while you can make choices on what to over/under constrain, you are still stuck with “execute it and see what the result is” in the general case.
Nevermark · a month ago
The vast majority of code isn't the general case. Very few practical applications legitimately intend to execute halt-undecidable code for instance.

But if the language itself strongly obfuscates (or makes it difficult or impossible to express/guarantee) reliable relationships, you miss out on all the desired, and leveragable, certainty that you still intend.

Animats · a month ago
I used to argue with the Python crowd about that. In Python, any code can, if it works at it, find and mess with any data object anywhere, even in another thread. So the compiler can't examine a function, decide that it can't possibly do something, and optimize. That's held back PyPy.

Most useful Python programs could be hard-compiled to machine code, because they're not doing anything weird enough to require a JIT compiler. But some low-level library might be, which breaks hard compilation.

("But then my domain-specific language / interface to another language / code generator / tracing package / debugger won't work!")

somat · a month ago
If the compiler is examining the function to know what to optimize. why does it not know what that function does so it can't optimize? That is, I assume you are correct but you lost me in the logic. is it eval?
Animats · a month ago
Because, in Python, you can monkey-patch anything from anywhere. It's not done much, but the capability is there.
ufo · a month ago
The issue in dynamic languages like python is that almost every little thing is impossible to be sure at compile time:

* The type of arguments passed to the function. Everything might point to the function always being passed values of a particular type, but you still can't be sure.

* If you call another function, you can't be sure that you'll actually call the function you expect, because any function or global variable may be overwritten at runtime.

* There are many places in the language that might call an invisible function call (method overloads, garbage collection methods, etc) and any of those invisible calls could potentially monkey patch something

All of these complexities, and more, are why JIT compilers like PyPy exist. Instead of optimizing at compile time, they observe things at runtime, optimize optimistically assuming that the program won't try anything funny, but stays ready to roll back the optimizations in case the program does try something funny.

yongjik · a month ago
For example, you can create an instance x of class Foo, change its one method x.bar to do something completely different at runtime, and any existing code that calls Foo.bar will happily execute your version. Which means Python cannot optimize any function that calls Foo.bar even if it "knows" what Foo.bar does.
Jensson · a month ago
> why does it not know what that function does so it can't optimize?

Because that seem to require the compiler to be an AGI. Its very hard to explain why its hard to make a truly intelligent program, but you just have to trust that tons of people have tried for entire careers to solve this and so far none have solved it.

withoutboats3 · a month ago
Initial comments as I write this are all negative, and also responding to something the blog post didn't claim. The only time it says faster than C is when talking about a language targeting the GPU; it is not controversial that GPUs perform better than CPUs for many workloads.

On the other hand, the results of this true fact are already seen in practice in real systems. Rust's iterator adapters, for example, allow high level "functional style" with closures to be compiled to code that is exactly the same as hand rolled C loops without unamortized bounds checks - despite guaranteeing the absence of out of bound accesses even in the face of programmer error - because the constraints of these interfaces and the design of the language enable tons of optimizations by LLVM. This has been true since Rust 1.0 more than a decade ago, it's not a promise of the future.

saghm · a month ago
To me, the best argument for constraints imposed by a language is around correctness, not efficiency. When we write a program, we tend to have an idea of how we want it to behave, but it's a pretty universal fact that this is hard to get exactly right on the first try (and often it can be hard to even tell without extensive testing, which is why bugs aren't all fixed by the time software actually gets released). In a certain sense, the act of writing and debugging a program can be thought of as searching through the space of all the possible programs that you could be writing, repeatedly narrowing down the set of potential choices by ruling out ones you know are incorrect, and then eventually picking one to use as the candidate you think is the one you want. From this perspective, language constraints can help with this process pretty much every step of the way; some programs are ruled out because you can't even express them in the first place, others are able to be rejected as you narrow down the set you're looking at based each new line of code you write and how the constraints interact with that, and potentially even with debugging after the fact when trying to figure out what went wrong with an incorrect selection (i.e. one that has bugs).

When we're using Turing complete languages for pretty much everything, constraints are pretty much the only thing that semantically differentiates the code we write in them at all. To me, this is basically the concept people are trying to convey when they argue for picking "the right tool for the right job". At the end of the day, what makes a language useful is just as much defined by what you _can't_ say as exact you can.

holowoodman · a month ago
Problem is that those constraints are often very hard to express, even in more expressive languages.

One example would be integers. There are bigint-by-default languages like Haskell, where the type you usually use is an arbitrary-sized bigint. But usually you don't need that, and you usually know that something like a 32bit integer is sufficient. Often you get a int32 type that does that stuff for you. But then the question becomes about overflow behaviour, signedness, existence of -0, behaviour of -INT_MAX and stuff like that. Even in C, you are in undefined-behaviour-launch-the-rockets territory very quickly. And there are usually no "screw it, I don't care, give me whatever the CPU does" types. You don't get to choose your overflow/underflow behaviour. Often no bounded types either (unsigned integer day from 1 to 365). And if those types are available in libraries, the compiler won't be able to optimize.

There are tons more of those examples. It's always a compromise between the expressiveness that we want and that would be great, and the complexity that will make an implementation non-viable. So you get as much expressivenes as the language designer thinks he could maybe cram into his compiler. But it's always to much to be really optimized all the way through. And it's always too little for what one would actually need to be really exact.

habibur · a month ago
Brings back nostalgic memories from Java days in the 2000s. "Java will be faster than C" for exactly the same reasons laid out in the article.

And here we are, 20 years later.

pegasus · a month ago
Yes, 20 years later, the JVM is considered a marvel of engineering, and, surprisingly, some of those marketing claims have in fact come true. Or at least, more than would be expected after doing the hype-correction always called for in such situations. Yes, Java can be faster in certain contexts: the kind of contexts Java is usually employed in addressing these days. Stuff like enterprise backend systems and big data processing, where startup time or precise memory control are not important. Then there's GraalVM which allows for AOT compilation, etc.
holowoodman · a month ago
I'd like to see those numbers please. While there have been improvements, Java performance work seems to have stopped after 2011 or so. Mobile CPUs became fast enough for Android, so no movement there. And commercial software got stuck on numerous proprietary Java VMs that didn't improve after the Oracle takeover of SUN. Alterative VMs improved, but didn't get adopted, fragemented and in the end didn't really raise the bar a lot.

To this day, Java applications are the slowest and most memory hungry long-running server applications by far. Only some scripting languages are worse, but only by performance, almost never by memory.

yccs27 · a month ago
TFA only talks about having strong or weak static guarantees. There's more that typically comes with a "high-level" language like Java: Nonzero-cost abstractions like boxed objects, automatic memory management and more.
carlmr · a month ago
I dunno, I was there in the 2000s and can't remember anyone saying that. I remember a lot of people saying Java's slightly slower execution will not matter that much when processing hardware gets better.
chubot · a month ago
I was also there in the 2000's and I remember people saying Java can be just as fast as C

And in particular that garbage collection can be faster than malloc/free.

This is technically true in the case you have a naive C program. There are tons of C programs out there, and some are slow. (But there are more slow Java programs out there)

But it's 100% clear now that GC has a significant cost, and while you can optimize a bad C program, it can be very hard in many cases to optimize the cost of GC away.

C offers more freedom and thus more footguns, but the freedom also means you don't really have a performance cap.

And I prefer GC -- most programs I write will have GC; I just recognize the performance cap, and design accordingly. (In fact I wrote a GC for a subset of C++ for https://oils.pub -- I actually think this is a language with nicer performance properties than Java, mainy due to being AOT)

holowoodman · a month ago
There were tons of people making those claims. https://stackoverflow.com/questions/64582/c-java-performance...

Especially profile-guided-optimization was hailed as the next big thing, only JIT-ed languages were the ones to be fast, because after some warmup time they would adapt to your data and hardware. Java is still dog-slow...

Sesse__ · a month ago
I was there in the 2000s and remember several people saying that.
mrkeen · a month ago
Look at your sibling comments. Some people are still saying it now.
CalChris · a month ago
I was there in the 90s and people at Sun were saying that Java would be faster than C++. This kind of talk even begat Jikes, a Java-in-Java JVM from IBM.
crq-yml · a month ago
I don't disagree, but I think the optimization potential tends to be limited to trivial automations in practice. The idioms we use to code at a higher level are mostly wrappers on something we know how to do at a lower level with a macro. The compiler still helps because it guard-rails it and puts you on the path to success as you build more complex algorithms, but you have to get into very specific niches to reach both of "faster than C" and "easier to code the idiom".

As ever, hardware is an underappreciated factor. We have make-C-go-fast boxes today. That's what the engineering effort has been thrown towards. They could provide other capabilities that prefer different models of computing, but they don't because the path dependency effect is very strong. The major exceptions come from including alternate modalities like GPU, hardware DSP and FPGA in this picture, where the basic aim of the programming model is different.

holowoodman · a month ago
> As ever, hardware is an underappreciated factor. We have make-C-go-fast boxes today.

That is a very common misconception. There have been numerous attempts at architectures that cater to higher-level languages. Java-bytecode-interpreting CPUs have been tried and were slower than contemporary "normal" CPUs at executing bytecode. Phones and smartphones were a supposed hot market for those, didn't fly, native bytecode execution is dead nowadays. Object-orientation in CPUs has been tried, like in Intels iAPX432. Type-tagged pointers and typed memory has been tried. HLLCAs were all the rage for some time ( https://en.wikipedia.org/wiki/High-level_language_computer_a... ). Early CISC CPUs had linked lists as a fundamental data type. Lisp machines did a ton of stuff in hardware, GC, types, tagging, polymorphism. All of it didn't work out, more primitive hardware with more complex interpreters and compilers always won. Not because C was all the rage at the time, but because high-level features in hardware are slow and inflexible.

What came of it was the realization that a CPU must be fast and flexible, not featureful. That's why we got RISC. That's why CISC processors like x86 internally translate down to RISC-like microcode. The only thing that added a little more complexity were SIMD and streaming architectures, but only in the sense that you could do more of the same in parallel. Not that HLL constructs were directly implemented into a CPU.

gf000 · a month ago
Memory tagging is making a comeback, and the primitives/API being "fast and flexible" doesn't account for the ridiculous complexity that goes into a CPU, that does indirectly help with linked lists/object oriented/etc.

Also, C is in no way special, not even particularly low-level.

cat-whisperer · a month ago
Reminds me of the talk on Rust's LLVM IR optimization issues. Constrained languages like Rust can be optimized way better than C/C++. Seen similar points made before.