1. 33
  1. 2

    Not if you’re willing to make other compromises. If you restrict pointer arithmetic, pointer aliasing, etc. you can make a fast language with no undefined behavior. You just won’t be able to write operating systems or such with it.

    1. 7

      A talk I was at last week cited a paper that showed that optimisations that depend on UB give ‘only’ an average 1.7% (I think - definitely in the 1-2% range) speedup. The speaker seemed to think that this meant that they weren’t necessary, but these days a 1% speedup on average is a huge win. That usually translates to a 10% speedup on a single important workload. Even if it didn’t, if I committed something to LLVM that gave a 1% speedup on average than I’d be confident that I was carbon negative for my entire life. Reducing CPU usage by 1% across a cloud deployment is millions of dollars.

      UB exists because some things can’t easily be statically proven. It is UB to access an object after it has been deallocated. This means that the compiler doesn’t need to insert dynamic checks to ensure that there is deterministic behaviour if a deallocated object is referenced on any code path where it can’t see the complete lifetime of the object. Even in Rust, you can violate lifetime safety in unsafe blocks, but if you do then it’s UB.

      UB is useful at multiple levels. If your language is being used to build the abstract machine guarantees for higher-level languages (as C/C++ are for most safe languages) then it is used for the programmer to assert that a condition won’t occur without requiring the low-level implementation to check it. It’s also useful in mid-level IRs, for the front end to assert to the optimisers that certain things aren’t possible (either because you’re coming from a language like C where it’s nasal demon territory, or because your source-language type system prevents them).

      The thing that most objections to UB miss is that there isn’t a single bit in the compiler saying ‘aha, you’ve invoked UB, we will be evil now!’. There are a set of analyses that make claims about whether things may happen in code and a set of transforms that take advantage of these claims. UB increases the space of things that the analyses are allowed to claim (or makes the reasoning more local, which makes it tractable).

      1. 9

        Reducing CPU usage by 1% across a cloud deployment is millions of dollars.

        I liked the way Patrick Walton stated this:

        The problem with all the articles that talk about the effects of undefined behavior optimizations in C/C++ compilers is that they never acknowledge what is by far the most important issue: economics. C/C++ compiler devs are disproportionately employed by the largest companies where a 1% win in performance translates to massive amounts of money saved. Their job is to eke out those tiny wins. Any article that does not acknowledge this economic reality is irrelevant.

        So true.

        1. 3

          Do you have references for the talk or the paper? Part of the tricky nature of UB is that its performance effects are hard as heck to measure. Partially ’cause performance is hard to measure in the first place, partially because “increasing the space of things that analyses are allowed to claim” is a hard feature to turn on and off in a compiler.

          Edit: It just occurred to me that at a large-institution level, a bug or vulnerability can also cost millions of dollars, to say nothing about the costs of developer time, tooling, QA, etc. It’s a tough balance. :/

        2. 6

          I don’t know about that. Ada’s selling point is that it has very little undefined behavior and you can write operating systems with it.

          1. 1

            It does sound nice, I’ll be looking into it more someday. Rust similarly has less UB than C does. Can Ada be optimized as well as C and such? I actually don’t know; I’m sure it comes close, but as the sibling post points out, 1% difference is a big deal to some people.

            1. 4

              Can Ada be optimized as well as C and such?

              Yes, there’s this story about one of the first maintainers/contributor to GNAT (the Ada compiler based on GCC) maintaining a collection of C and Ada programs that compiled down to the exact same object file. There are some cases where Ada can even do better, since it allows specifying e.g. pointers that can’t be null, which is an assumption that C compilers sometimes aren’t allowed to make, hindering optimizations.

              Usually the reason Ada has bad results in online benchmarks (like, e.g. https://youtu.be/pv4Yq35Chx0 ) is that the Ada program is not using the same algorithm as the C/C++/Rust one and/or that the optimizations used aren’t the same.

              I’ll admit that Ada is at a disadvantage when compared to C++ though. C++ has benefited from a lot of investment from a lot of companies and the major implementations of its runtime are state of the art when it comes to performances. Ada implementations do not have nearly the same amount of resources behind them, but that’s a separate question from whether the semantics of the language allow writing fast programs.

              1. 2

                Where would you start learning ada? It seems the resources aren’t quite as well organized as for many languages (or I’m worse at googling them).

                1. 4

                  https://learn.adacore.com/ is probably the best way to get started. The “Programming in Ada 2012” book is also very frequently recommended (although I’m told the 2022 version will come out soon, it might make sense to wait for that), along with the Ada reference manual itself ( http://www.ada-auth.org/standards/12rm/html/RM-TTL.html , but can be fairly hard to understand unfortunately). You might also find these pages useful - they have been written by someone who started programming in Ada in 2021, so should be fairly up to date.

                  1. 1

                    Thank you!