CTTI is Exponential, RTTI is Linear
11 points by clerno
11 points by clerno
Not a compiler expert, but: you can fix some of the code bloat with type erasure; that's what C++'s std::format does.
I am not that sympathetic to complaints about compile time. I hate long builds too, but everything is tradeoffs, and if something's gotta give, compile time is the least worst thing. The end user does not give a fsck how long your program took to build. They do care about size and performance.
I think it’s also missing the benefit of CTTI: it can produce specialised RTTI for a subset of types.
Objective-C is one of the more extreme cases for RTTI (C# and Java are similar but are not fully AoT compiled in most cases so aren’t quite a fair comparison). For every Objective-C class, you have, available at runt time:
This is big but it also imposes some secondary size issues. In particular, you can’t do compile-time dead-code elimination because a method that isn’t called directly may still be called via introspection APIs (even before you add the complexity that full reflection brings).
And occasionally, this is really useful. OpenStep / Cocoa has a mechanism called key-value coding, where you have a generic way of referring to properties (including property paths) on objects, which lets you write generic controllers. These may do direct ivar access, call methods, or, if neither exists, fall back to a generic method that allows the object to handle arbitrary properties. With reflection, this is also a building block for key-value observing, which pivots methods at run time to allow other code to be notified when it changes. Apple’s Cocoa Bindings used this to create completely generic controllers that just needed the names of property paths from model root objects to be able to display (and automatically update) views.
But I use well under 10% of the classes in a typical Cocoa program with KVC / KVO. With compile-time reflection, I could just slap a KVC and KVO attribute on those classes and not pay the costs for anything else. In most cases, I know the specific properties I want to expose via KVC / KVO (and, if you’re doing direct ivar assignment, you need to write some additional code for KVO anyway), so I don’t need to pay the cost of all of them.
The same is true for the most common use case for run-time introspection: serialisation. I can usually enumerate the classes I want to be able to serialise at design time. I don’t need everything to support it.
So, yes, in theory it might be linear versus polynomial, but it’s not linear versus polynomial with the same value of n.
And, if you have compile-time reflection, you can have a standard-library feature that provides any level of RTTI that you want and things that opt into it can get RTTI. And, if you care about compile-time performance, you can implement the entire standard-library feature with a compiler intrinsic that is exactly as efficient as RTTI generation in the compiler, but opt in.
I am not that sympathetic to complaints about compile time. I hate long builds too, but everything is tradeoffs, and if something's gotta give, compile time is the least worst thing.
I have a tinge of a feeling that binary size is roughly proportional to build time but no evidence to back it up at the moment.
Either way, I personally wouldn't really want to work on a codebase that makes me miserable with its build times. I feel like there's some psychological effect to it that would make me produce worse software than with a codebase that feels nice and snappy to edit.
I understand that this argument might fall apart in a team setting. For personal projects though, build time is key to enjoying myself.
I have a tinge of a feeling that binary size is roughly proportional to build time but no evidence to back it up at the moment.
It really depends. A lot of what we use C++ for in embedded development is compile-time checks so that we can elide run-time checks. Moderately complex templates take a lot of the total compile time, but they often compile down to 1-2 instructions.
I feel like there's some psychological effect to it that would make me produce worse software than with a codebase that feels nice and snappy to edit.
This is certainly true. Having a REPL or similar is useful for rapidly exploring parts of a design space. The CERN folks found that a REPL + JIT for C++ improved their iteration.
The compile-time tradeoff you're describing would be fine if it were real. In practice, virtually none of the long-compile-time projects I've worked on ever produced a program that was fast, or whose size anyone cared about. You pay the cost of compile time and don't get the performance. People state this hypothesis as if it is fact, even when the evidence shows it is wrong.
The other cost nobody accounts for is developer iteration speed. If your builds take 1 second, you program in a fundamentally different style than if they take 6 minutes. And I've yet to see a style that justifies 6-minute builds, even on medium-to-large projects. Our projects at work are Odin, 500–700 KLOC, and they compile in 1–2 seconds; Not MINUTES.
On std::format specifically: it doesn't work off compile-time type information, it recovers the formatting through a kind of ad-hoc polymorphism coupled with parametric polymorphism (i.e. templates). That gets you slow compiles, slow run times, code bloat, and not much flexibility in exchange. C++, sadly, cannot really do better given the nature of the language, and for Odin, I never had any interest in going down that road. Odin's RTTI is very simple and small because Odin is a much simpler language in comparison.
While C3 has quite a lot of CTTI available, I constantly encourage people to not use it too much. Even having a way to force inline functions can easily create hard-to-debug binary bloat. It's easy to write a lot of code that you don't see, and that's the problem.
One note: good error reporting is definitely a problem, but not completely intractable: C3 solves this by pushing checks to contracts while Jai allows compiler errors inside of functions to be reported as if they happened in a particular context outside of the function.