[HN Gopher] Constrained languages are easier to optimize
       ___________________________________________________________________
        
       Constrained languages are easier to optimize
        
       Author : PaulHoule
       Score  : 60 points
       Date   : 2025-07-27 09:21 UTC (13 hours ago)
        
 (HTM) web link (jyn.dev)
 (TXT) w3m dump (jyn.dev)
        
       | greatgib wrote:
       | Thank you Captain Obvious!
       | 
       | But easier to optimize doesn't necessarily means that they could
       | be optimized to be more performant than less constrained
       | languages.
        
       | holowoodman wrote:
       | 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 wrote:
         | > 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 wrote:
           | 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.
        
             | Ygg2 wrote:
             | > LLVM just never bothered, despite the early claims that
             | all the intermediate language and "more modern" compiler
             | would enable more optimisation potential.
             | 
             | They did bother but no one seemed to be using it, so a bug
             | snuck in. It took Rust exercising that corner of LLVM to
             | find the bug.
             | 
             | > LLVM is still slower than GCC, even in plain C.
             | 
             | Citation needed. Last time I checked LLVM beat GCC on -O3.
             | Unless you mean compilation performance.
             | 
             | > Where is the problem to quickly null-terminate a
             | pointer+len string and use the quick null-terminated SIMD
             | search?
             | 
             | Why should two nearly idenfical operations have such wildly
             | different performance? And why isn't the safer/saner
             | interface more performant?
             | 
             | My point if compilers and hardware are optimizing for C,
             | it's no surprise no one can approach its speed.
             | 
             | It's like asking why when everyone is optimizing for Chrome
             | no other web browser can approach its speed.
        
               | holowoodman wrote:
               | >> LLVM is still slower than GCC, even in plain C.
               | 
               | > Citation needed. Last time I checked LLVM beat GCC on
               | -O3.
               | 
               | https://benchmarksgame-
               | team.pages.debian.net/benchmarksgame/...
               | 
               | https://www.phoronix.com/review/gcc-clang-eoy2023/8
               | 
               | There is tons more, just ask your favourite internet
               | search engine.
               | 
               | > Why should two nearly idenfical operations have such
               | wildly different performance? And why isn't the
               | safer/saner interface more performant?
               | 
               | Because in hardware, checking a zero-flag on a register
               | is very cheap. Big NAND over all bits of a sub-register,
               | big OR over all those flags for the whole SIMD register.
               | Very short path, very few transistors.
               | 
               | Checking a length is expensive: Increment a length
               | register through addition, including a carry, which is a
               | long path. Compare with another register to out if you
               | are at the last iteration. Compare usually is also
               | expensive since it is subtract with carry internally,
               | even though you could get away with xor + zero flag. But
               | you can't get around the length register addition.
               | 
               | There can be an optimisation because you do have to
               | increment the address you fetch from in any case. But in
               | most modern CPUs, that happens in special pointer
               | registers internally, if you do additional arithmetics on
               | those it'll be expensive again. x86 actually has more
               | complex addressing modes that handle those, but compilers
               | never really used them, so those were dropped for SIMD.
        
               | jcranmer wrote:
               | A loop that's basically:                 for (int i = 0;
               | i < len; i++)         /* do something */
               | 
               | is going to compile down to something like:
               | label: ; blah blah blah         INC r0         CMP r0, r1
               | JNZ label
               | 
               | (Okay, to be fair, compilers tend to change the loop
               | bounds so that the final comparison is a comparison
               | against 0. This will reduce the register usage of the
               | loop, but otherwise doesn't change the analysis). Taking
               | into account micro-op fusion, there is at best two actual
               | hardware ops in that assembly pattern, since the
               | comparison and branch will be fused, and the
               | increment(/decrement) is also a decent candidate for
               | fusion. The branch can also be predicted with a high
               | degree of fidelity--an advanced processor should be able
               | to predict the branch with exactly 100% accuracy.
               | 
               | A null-terminated loop instead compiles down to:
               | label: ; blah blah blah       LD r0, r1       INC r1
               | CMP r0, 0       JNZ label
               | 
               | You still have the some basic pattern at the end of the
               | loop, except that the INC (which might not have been
               | previously fused) is no longer in the critical path, but
               | now the critical path instead has a load in it. That load
               | is a _much_ slower instruction than a test-against-0 or a
               | comparison: no load completes in one cycle, whereas both
               | tests (bitwise and) and comparisons (subtraction)
               | complete in one cycle. Splitting hairs by saying that the
               | test-against-0 is going to be less expensive than a
               | subtract-and-check-carry, so therefore null-terminated is
               | faster ignores the fact that they execute _in the same
               | time_ and brushes the extra load, which _is definitely_
               | slower, that exists in the null-terminated string.
               | 
               | It goes worse, though. When you have a pointer+length
               | combo, the length of the array, and the legal
               | dereferenceability, is now known to the compiler in
               | symbolic form that is easy to compute. That makes it
               | possible to do things like unroll loops, or vectorize
               | loops, or rewrite loop bounds in better forms, etc. When
               | it's null-terminated, the length comes from reading
               | memory, and the amount of memory you can read is not
               | known ahead of time. Unrolling the loop or other steps
               | are no longer really possible since you don't know how
               | many iterations the loop will take, and the extra loop
               | exits you'd generate tend not to be tolerable in
               | optimization.
        
               | holowoodman wrote:
               | No. First that isn't SIMD code. Second, CMP r0,0 is
               | redundant, operating on a register that is zero will set
               | the zero flag automatically. You also skipped the string
               | search/comparison part, but that isn't relevant, except
               | that the operation will implicitly set the zero flag.
               | Generally, plain JZ is faster than CMP + JZ. Even more so
               | if it is SIMD.
               | 
               | And loads are amortized by tons of speculative
               | preloading. Which isn't (yet, unfortunately) optimized by
               | preload-length hints.
               | 
               | Oh, and since we are talking about comparing strings,
               | there is no extra load. This isn't strlen(), this is
               | strchr() or strstr() we are talking about. You always
               | have to load the string into some register.
        
               | aengelke wrote:
               | Storing the string length explicitly as an 8-byte integer
               | does have a measurable cost. Consider llvm::Twine as an
               | example, it supports storing a null-terminated string and
               | a ptr+len string (among other options). I once changed
               | the implementation to store string literals (length known
               | at compile-time) as ptr+len instead of a pointer to a C
               | string, with the intention of avoiding the strlen in the
               | callee on constant strings. However, this change made
               | things _slower_ , because of the cost of storing the
               | length everywhere. (That's why I never proposed such a
               | change upstream.)
               | 
               | The critical (data) path of the null-terminated loop,
               | however, does _not_ include the load -- the actually
               | loaded value is not a loop-carried dependency in your
               | example. The re-steering of the branch at the end of the
               | loop might happen much later, however.
               | 
               | Vectorization with null-terminated strings is possible
               | and done, but requires alignment checking, which adds
               | some cost.
        
               | miloignis wrote:
               | Your Phoronix link shows clang as faster than GCC (more
               | is better on the graph). The benchmark game results seem
               | to go back and forth.
               | 
               | I don't think that it's convincing evidence that clang is
               | slower than GCC.
        
               | holowoodman wrote:
               | Yes, you are right. I think they are head-to-head
               | nowadays.
               | 
               | I was thinking about, and meaning to post that one, but
               | got the wrong search result:
               | 
               | https://www.phoronix.com/review/clang20-gcc15-amd-
               | znver5/5
               | 
               | But they swapped places a few times over the last years,
               | so basically there doesn't seem to be a fundamental
               | difference in performance anymore.
        
             | chrismorgan wrote:
             | LLVM did do _some_ with noalias, but it wasn't very mature
             | because it wasn't broadly useful in C /C++, because it was
             | too hard to use correctly.
             | 
             | Then Rust made it trivial to use correctly, and found that
             | what LLVM _did_ have was quite buggy, because it hadn't
             | been exercised. These were bugs that could in theory have
             | been found in C /C++, but in practice never would have
             | been. And so for quite some years Rust would toggle the
             | noalias optimisations on and off, depending on whether
             | there were any known bad-codegen issues at the time. I
             | think the last toggle was a few years ago by now, this
             | stuff is finally actually stable and appreciated.
             | 
             | My recollection is that the figures for the Rust compiler
             | are in the vicinity of a 5% improvement from emitting
             | noalias in the LLVM IR.
             | 
             | And it's likely that quite a bit more is possible.
        
       | habibur wrote:
       | 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.
        
         | carlmr wrote:
         | 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.
        
           | Sesse__ wrote:
           | I was there in the 2000s and remember several people saying
           | that.
        
           | holowoodman wrote:
           | 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...
        
           | chubot wrote:
           | 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)
        
             | gf000 wrote:
             | If you bring parallel processing into the picture (shared
             | memory model), then the achievable performance caps per dev
             | work change significantly though.
             | 
             | I will very easily write a faster parallelizable program in
             | Java, before I get the C one even remotely correct and then
             | we haven't even taken into account every future maintenance
             | cost. Also, the way C devs often hide the cost is simply..
             | less efficient code like copying stuff right and left, etc,
             | which _can_ actually be more  "bravely" avoided in managed
             | languages.
        
           | mrkeen wrote:
           | Look at your sibling comments. Some people are still saying
           | it now.
        
         | pegasus wrote:
         | 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 wrote:
           | 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.
        
             | pegasus wrote:
             | Seriously? Since 2011, so since Java 7, stagnation in
             | performance? That's a startling claim. I'd like to see some
             | numbers as well. I'll start the first round with this
             | article:
             | 
             | https://kstefanj.github.io/2021/11/24/gc-progress-8-17.html
             | 
             | Note: this is comparing to Java 17 to Java 8 (released in
             | 2014), and we are now at Java 24!
        
               | holowoodman wrote:
               | That is garbage collection performance, something that
               | only GC languages even have to consider as additional
               | overhead. It also doesn't even try to compare against
               | anything non-Java.
               | 
               | There are no serious Java performance comparisons on
               | stuff like transaction throughput, which would be what
               | the "long running server application" usecase should aim
               | for. You yourself claimed that enterprise data processing
               | is what you are talking about. There are benchmarks that
               | even with long warmup times make Java come up far worse
               | than all the compiled languages (even Golang nowadays).
               | https://benchmarksgame-
               | team.pages.debian.net/benchmarksgame/...
               | 
               | Imho, people have stopped benchmarking Java ages ago,
               | since it is known to be hopeless.
        
               | igouy wrote:
               | I think you need to say what you mean by "far worse".
        
               | holowoodman wrote:
               | If you look at the extremes, it is up to a factor of 3:
               | https://benchmarksgame-
               | team.pages.debian.net/benchmarksgame/...
               | 
               | If you exclude stuff like SSE intrinics, it's usually 50%
               | slower.
        
               | igouy wrote:
               | To be clear, you're saying for the benchmark tasks with
               | the worst performing Java programs; the Java programs
               | perform worse than the Go programs for the benchmark
               | tasks with the worst performing Go programs?
               | 
               | What about the median performance benchmark tasks?
        
               | holowoodman wrote:
               | I'm saying that Golang is one of the worst-performing
               | compiled languages, and even Golang is faster than Java
               | for the respectively fastest program on the page I've
               | referenced. Other compiled languages are even better than
               | Golang.
        
               | ColonelPhantom wrote:
               | Calling GC time "additional overhead" is not entirely
               | correct, imo. Non-GC languages often spend plenty of time
               | dealing with malloc/free, and I consider both GC and
               | malloc/free to be "memory management". Both are non-
               | trivial!
               | 
               | GC usually also comes in "copying" variants, which can
               | have advantages like reducing memory fragmentation.
        
               | holowoodman wrote:
               | Granted, malloc/free isn't totally free. But malloc/free
               | languages always seem to be faster than Java in
               | benchmarks, at least the ones I've read and referenced
               | already in the comments around this article. GC also,
               | even if it were faster, comes at the expense of eating
               | RAM like mad, at least if judged by the RAM Java likes to
               | eat. Usually, Java software just consumes 50% of your RAM
               | until the first GC pause. And even before that GC pause,
               | it's dog slow.
               | 
               | Memory fragmentation can more easily be reduced by pooled
               | allocators. For long-running server processes, it can
               | also be possible to just kill the worker
               | processes/threads from time to time. And I've never seen
               | memory fragmentation in manually allocated languages lead
               | to more memory consumption or worse performance than in
               | Java.
        
               | gf000 wrote:
               | https://www.techempower.com/benchmarks/#section=data-r23&
               | tes...
               | 
               | Here is your transaction throughput. But I'm sure AWS's
               | whole platform team, Alibaba, third of Google, etc don't
               | know what they are doing.
        
             | hyperpape wrote:
             | > While there have been improvements, Java performance work
             | seems to have stopped after 2011 or so.
             | 
             | No idea how you got that impression. Here is an incomplete
             | set of important changes:
             | 
             | - major developments on Graal and native code compilation
             | for applications that prioritize startup time and memory
             | use (I think Graal was started before 2011, but has changed
             | a huge amount since then).
             | 
             | - the concurrent G1 garbage collector became the default,
             | making most applications have much lower pause times (I
             | believe it was released in Java 7 in 2011, but was not
             | mature, only became a default in Java 9, and has regular
             | improvements since then).
             | 
             | - the Shenendoah and ZGC concurrent garbage collectors were
             | not started until well after 2011
             | 
             | - project lilliput, which reduces the header size of Java
             | objects from 16 to 8 bytes is landing in Java 25, which
             | comes out this fall
             | 
             | - specializing strings to use 1 byte/character for ascii
             | strings, and 2 bytes for non-ascii strings
             | 
             | - virtual threads shipped in Java 22
             | 
             | - work on a vectorization API
             | 
             | - plenty of work on reducing the startup time of the JDK
             | and modularizing it
             | 
             | One where I'm not sure about the timeline:
             | 
             | - https://docs.oracle.com/javase/8/docs/technotes/guides/vm
             | /cl... class data sharing to lower overhead of running
             | multiple JVMs on a single node
             | 
             | P.S. I'm not writing this to comment on the the parent's
             | claim that Java is faster in some contexts.
        
               | holowoodman wrote:
               | The grandparent made specific claims about scenarios
               | where Java is supposedly faster:
               | 
               | > 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.
               | 
               | The only thing that might be relevant in your list is the
               | vectorization API.
               | 
               | The rest of your list is just a bunch of band-aids for
               | the worst problems that Java specifically has, that other
               | languages lack. That is at best playing catch-up with
               | everything else, since in C there is no GC overhead,
               | almost no startup time, no object overhead, no GC-induced
               | memory hogging, no GC pauses. Yes, I agree that things
               | did happen. But nothing in there makes Java any more
               | desirable for relevant workloads, it just makes it less
               | undesirable.
        
               | gf000 wrote:
               | And I'm gonna walk everywhere on foot from now on,
               | because I don't have to drink petrol.
               | 
               | Are you even aware of the word "tradeoff"?
        
             | nu11ptr wrote:
             | > To this day, Java applications are the slowest and most
             | memory hungry long-running server applications by far
             | 
             | I am not a fan of Java, but the slow part is just plain
             | wrong. On properly sized hardware Java is blisteringly
             | fast. Did it exceed the speed of C as hyped? No, in most
             | cases it falls solidly short, but I'm sure there are
             | isolated cases where it does. Regardless, 2-3x slower than
             | C using a JIT is a serious feat of engineering.
             | 
             | All GC languages require more memory, and Java is even
             | worse given lack of value types, but again, memory/hardware
             | is cheap compared to programmer time and if you have enough
             | of it, it generally isn't a problem.
             | 
             | All this does mean you will need a bit more hardware than a
             | C program, so perhaps that is the posters critique, and
             | that is true, but compared to other high level languages,
             | it does great in the speed dept, and is often only a bit
             | higher in the memory dept.
        
             | gf000 wrote:
             | What was the core of the hype at the time was parallel
             | processing. And to be honest, here Java absolutely beats C,
             | which most of the time will simply.. refuse to parallelize
             | because it can't be maintainably and reliably done in the
             | language without some very extreme cost. It's simply too
             | dangerous. Meanwhile in Java people could freely experiment
             | with concurrent data structures and algorithms that will
             | simply be in a completely different league than the serial
             | C program. Sure, maybe the latter's hot loop is faster, but
             | it doesn't mean much if I can just run 100s of threads with
             | Java.
             | 
             | Your second paragraph is just thoroughly uninformed,
             | though. And unused RAM is useless, so in a server
             | application not making use of it is just dumb. Maybe think
             | about why Java is used extensively by pretty much every
             | FAANG company. Also, Java has improved steadily after
             | Oracle's takeover, and is in fact the open-source reference
             | implementation. Again, uninformed bullshit.
        
         | yccs27 wrote:
         | 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.
        
       | withoutboats3 wrote:
       | 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.
        
       | JimDabell wrote:
       | 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 wrote:
         | 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.
        
       | donatj wrote:
       | They are also easier to statically analyze and write tooling
       | around.
        
         | holowoodman wrote:
         | Then were is my easy Haskell tooling to find the place where I
         | should put a strictness hint? Where is the equivalent of
         | kcachegrind?
         | 
         | That it's supposedly easier might be true. But that usually
         | doesn't lead to someone really doing it.
        
       | ImPostingOnHN wrote:
       | I have constructed a language which can only say "hello world"
       | and there is only 1 way to do it.
       | 
       | It is the most easily optimized language I know. If you write a
       | working program in it, you know you're doing it in the most
       | optimal way for that language already.
        
       | saghm wrote:
       | 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 wrote:
         | 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.
        
       | crq-yml wrote:
       | 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 wrote:
         | > 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 wrote:
           | 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 wrote:
       | 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.
        
       | septune wrote:
       | In the same way, I find that constrained languages like Go are a
       | great choice for achieving high-quality code generation with
       | LLMs.
        
       | sdfdsfdsffdsf wrote:
       | I think a good example here is Unity's Burst compiler - it uses a
       | subset of C#, which allows for more assumptions and better
       | optimization. Compared to regular Unity Mono environment, the
       | code can be faster by one or two orders of magnitude and I've
       | read in some cases it's even faster than C.
        
       | Animats wrote:
       | 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 wrote:
         | 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 wrote:
           | Because, in Python, you can monkey-patch anything from
           | anywhere. It's not done much, but the capability is there.
        
           | ufo wrote:
           | 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.
        
       ___________________________________________________________________
       (page generated 2025-07-27 23:01 UTC)