1. 14
  1. 15

    I saw a video (posted here, I think) of someone using modern C++ and translating the output to a 6502, with a similar example: a large lookup table that generated a massive output and reduced to a handful of instructions with the addition of const.

    The fact that static gave the same result as const here is quite interesting. The variable never has its address taken and so the compiler is able to discover that nothing ever stores to it and treat it as constant. This isn’t possible with common / external linkage because something else might write to it from some code that the compiler can’t analyse.

    The conclusion at the end isn’t quite right. The compiler can’t know that you’re producing an executable, only the linker knows that. If you’re doing LTO, then the linker may be able to know that nothing else ever resolves the address of this variable and therefore never accesses it, but that’s true only in static linking. Even if none of the libraries that you link to now refers to the variables then either of the following could happen:

    • A new version of a .so that you link to references the symbol.
    • You dlopen (or equivalent) a library and it references the symbol and modifies it.

    Using const and static are quite different here. If you use const then you are asserting that the variable does not change and that the compiler is free to break any code that tries to break it (for example, by putting the variable in a .ro_data section that is mapped read-only or put in ROM on an embedded system, causing a trap if you try to write to it). If you say static then you are telling the compiler that noting outside of the current compilation unit may access it and so the compiler may assume that it is immutable if nothing in the current compilation unit (for this current compiler invocation, including the set of predefined macros that are passed on the command line).

    In general, there is one simple rule to follow for generating good code: tell your compiler as much as you can. If a value is immutable, make it const / constexpr. If a variable is not referenced outside of a compilation unit, mark it static. If it’s not used outside of a shared library, mark it as hidden visibility. Use __builtin_assume, __builtin_expect and friends to tell the compiler about your invariants and your fast-path expectations. Use always_inline / __forceinline attributes / declspecs for common paths and noinline for cold paths.

    1. 6

      These kinds of outcomes makes me wish C++ required “mutable” and “nonstatic” keywords and everything was assumed static const unless requiring otherwise.

      For better or worse people will default to the minimal way of defining variables. Then their program will not compile when they try to modify something immutable (then learn about mutable), or use something between two compilation units (and learn about “nonstatic”, or whatever it should be called). Otherwise they stay on the happy path.

      A similar argument for nonconstexpr might be made as well, although this one might be tougher. My gut says this wouldn’t work too well.

      1. 2

        Great explanation though I don’t quite agree with the “tell the compiler as much as you can” part. To me this suggests that you don’t really need to understand why, just tell everything you can and hope for the best. I much prefer “understand and pay attention”. As in, understand that external linkage has costs and pay attention to making variables that don’t need to be visible from other translation units static.

        1. 10

          To me this suggests that you don’t really need to understand why

          I would hope that you don’t. If a C++ programmer needs to understand everything that the compiler does then there’s a serious failure of abstraction.

          1. 5

            Unless they are very deeply involved with the development of the compiler, I’d be very surprised if most developers had anywhere near a comprehensive, detailed knowledge of all the optimisations their compiler applies. And what would be the point, exactly?

          2. 2

            I think Rich Code for Tiny Computers: A Simple Commodore 64 Game in C++17 is the talk you’re referring to.

            1. 2

              Yes, that’s the one, thanks!

          3. 6

            Clickbait for C programmers:

            1. I read the title as static storage duration is 10 times faster.
            2. Oh, it was the other kind of static.
            3. No, const actually.
            1. 2

              So I don’t know the standard inside out, but there’s no reason the compiler can’t optimise this, right? There’s no volatile or synchronisation in the loop, so multiple reads are valid to be extracted?

              1. 2

                For what it’s worth, it’s more than integer division/modulus can be really, really slow, but if you know the divisor ahead of time then it can be optimised. Eg: libdivide does does exactly this, for one.

              2. 1

                Actually, the mod of a power of two is always a simple and with 1^^n - 1… If you compute the mod of a value with 11, all your values will be between 0 and 10… If you compute the mod of v with 8, all your values will be between 0 and 7, which is v & 0b111