News for hackers. Stuff that matters.
Re:“Modern” C++ Lamentations by steveklabnik on Friday December 28, 2018 @02:26PM (#18778834)
With Rust, a lot of the issue is monomorphization. Changing one line may make hundreds of lines be needed to recompile. Additionally, we produce more LLVM IR than we must, and rely on LLVM ago boil it away.

It’s a complex set of issues that we’re constantly working on.

(So yes, Go and C do not have generics, and so do not have this problem.)

Re:“Modern” C++ Lamentations by Gibbon1 on Friday December 28, 2018 @03:22PM (#18779159)
Stupid question from a small brained primate.

Would it make sense to have two types of generic implementation with a boxing unboxing mechanism. AKA a fast one that requires a rebuild when you monkey with something. And a slow one that doesn't but compiles fast.

I think it'd be acceptable if the 'dev' version used GC as long as the final build didn't. Basically anyway to get the development cycle to go fast.

Re:“Modern” C++ Lamentations by steveklabnik on Friday December 28, 2018 @03:37PM (#18779231)
Not stupid! You wouldn’t need a GC to do this either. It’s been talked about but nobody has tried to implement it.
Re:“Modern” C++ Lamentations by thechao on Friday December 28, 2018 @04:35PM (#18779632)
Almost like combining Siek’s gradual types with parametric polymorphism: you can search for “gradual parametricity” and find a number of papers on the topic.
Re:“Modern” C++ Lamentations by mpolun on Friday December 28, 2018 @04:25PM (#18779548)
Rust actually has that they just can't be substituted automatically because they're different types:

    fn<T: MyTrait> compile_time(thing: &T) {}
    fn run_time(thing: &dyn MyTrait) {}
The reason that you can't just substitute them is that you can do more with compile-time generics than runtime ones -- runtime generics have unknown size (different concrete types have different size, so what size is the runtime generic version?) so they always have to be used through a pointer (and usually heap allocated). There are a variety of other restrictions on trait objects (runtime generics) for type-system reasons.

It would be possible to find cases where compile time polymorphism is replaced with runtime polymorphism, but I'm not sure it would really gain much given the restrictions.

Now if you start changing the language semantics a lot more becomes possible, but I don't even know what changes you would have to make to the language to let that happen.

Re:“Modern” C++ Lamentations by renox on Sunday December 30, 2018 @03:58AM (#18788187)
That's when Jai (if it ever becomes real) hold promise: fast compilation is a first rate goal, not a second rate goal where you decide language features first then try to make the compiler "not too slow".
Re:“Modern” C++ Lamentations by jules on Friday December 28, 2018 @04:43PM (#18779693)
Do you know what's causing that?

1. Maybe monomorphisation overduplicates code, so that different invocations produce the same machine code in the end?

2. If that's not happening, maybe idiomatic Rust code causes a lot of code to be generated where a C programmer would have written something differently, e.g. using a void* instead of passing by value?

3. If that's not happening either, maybe monomorphisation is more or less producing what a C programmer would have monomorphised manually, but it takes the compiler a long time to optimise away abstractions?

It seems that in this C++ example the problem is #3: it takes a long time to optimise away the range & lambda abstraction.

Re:“Modern” C++ Lamentations by steveklabnik on Friday December 28, 2018 @05:09PM (#18779860)
It’s all three.
Re:“Modern” C++ Lamentations by vmarsy on Friday December 28, 2018 @05:31PM (#18780013)
is monomorphization a more concise term for what is called "implicit template instantiation" in C++?

If so, is Rust worse than C++ when it comes to compile times of those? Heavily templated C++ code is known to be slow to compile due to this. Is it much worse in Rust? Are you aware whether C++ compilers do some special optimizations that Rust compiler doesn't have yet?

I don't see what can be done to make this better, the whole point of templates is to "pay" at compile time for some gains at runtime. (or in this case for gains in the amount of code a developer has to write)

Re:“Modern” C++ Lamentations by steveklabnik on Friday December 28, 2018 @05:38PM (#18780053)
“Monomorphization” is what happens when a template is instantiated. It’s essentially the same thing in both languages, and has roughly the same compiler performance implications.

There’s at least one thing that could be done, in Rust at least. But it’s only in some limited circumstances. See here: http://www.suspectsemantics.com/blog/2016/12/03/monomorphiza...

Re:“Modern” C++ Lamentations by jules on Saturday December 29, 2018 @05:40AM (#18783019)
The compiler could do that automatically. Suppose you're operating on the control flow graph and you are to specialise a basic block in some type environment. You can cache the result so that the next time the same basic block is specialised in the same type environment you look up the result in the cache and create a call to that existing basic block.

  pub fn big_function<T: Into<i32>>(x: T) {
      let y: i32 = x.into();
      ... code that uses y but not x ....
  }
In the first line of the function you have the environment {T: Into<i32>, x: T}. After the let you have the environment {T: Into<i32>, x: T, y: i32}. If you keyed the cache on the full type environment you wouldn't solve the issue because the code that only uses y would still get specialised to the {T: Into<i32>, x: T} too. However, you could detect that that code doesn't actually use T and x, so that you can specialise it to the type environment {y: i32} only.

That detection can happen as a side effect of specialising that code to some particular {T: Into<i32>, x: T, y: i32} for the first time. As you specialise that code you record which parts of the type environment actually got used, and you use only those parts as a key in the cache. The type environment object itself could take care of recording what the compiler looked up in it. Another advantage of doing it this way, rather than analysing the code ahead of time, is that it can handle cases where a particular type variable does or doesn't get used depending on what type some other type variable is instantiated to. You could also use the same system to avoid duplicating code that only relies on particular aspects of a type. For instance, a function that permutes the values in a &mut[T] might only care about the size of T and not about the precise type T, so that all its specialisations to T of 4 byte size can call into the same code.

Another thing you'd probably want to do is integrate a basic form of constant propagation & dead code elimination during monomorphisation, so that you don't spend a lot of time monomorphising code that ends up dead for particular type instantiations.

Re:“Modern” C++ Lamentations by steveklabnik on Saturday December 29, 2018 @08:39AM (#18783498)
> The compiler could do that automatically

Yes, that's what I'm saying.

> Another thing you'd probably want to do

This is a very interesting idea!

Re:“Modern” C++ Lamentations by strmpnk on Saturday December 29, 2018 @12:52AM (#18782140)
The translation units in C++ tend to pay this cost a number of times over because of the way header files are processed. With modules, this theoretically could start getting closer to Rust, but for large projects, C++ templates can still be orders of magnitude worse in terms of instantiation cost.

This is partly why there are hacks like unity builds (not related to the engine, where all source is bundled into a single translation unit). These have plenty of drawbacks too so it's not a clear win.

Adding to all of this, there are fancier mechanisms for template meta-programming like SFINAE rules + computed template values. Sure, it's "turing complete" but this is why we see such clever libraries with huge explosions in code generation size. I'm far from an expert in modern C++ features but it's clear that there is an entire interpreted programming language of templates bolted on the to the rest of C++. It reminds me of this post on a similar take on Haskell type level programming: https://aphyr.com/posts/342-typing-the-technical-interview (or similar feats by Oleg Kiselyov).

Re:“Modern” C++ Lamentations by vmarsy on Friday December 28, 2018 @05:55PM (#18780154)
Thanks, that's an interesting read. indeed a developer can use that trick "in the case of conversion traits".

Monomorphization seems to be the source of 2 major problems: compile time, and binary size.

An optimization that could reduce the compile time by caching the generated code (at least when compiling over-and-over the same code base. i.e. "Incremental compilation") - and it seems that Rust already is doing something with it [1]. I wonder if something specific is done for template instantiations there.

Optimizations aimed at reducing binary size seems much more tricky, if not impossible, except as you pointed out in the limited cases described above. In the regular cases, templates are kind of working as intended: the developer has to think if he/she would have written the same amount of code N times if templates were non existent.

[1] https://blog.rust-lang.org/2016/09/08/incremental.html

Re:“Modern” C++ Lamentations by pjmlp on Friday December 28, 2018 @04:15PM (#18779472)
C does have a kind of lightweight generics since C11.
Re:“Modern” C++ Lamentations by fanf2 on Friday December 28, 2018 @05:52PM (#18780140)
Sadly C’s `_Generic` typecase mechanism does not do SFINAE: all the branches have to typecheck OK, regardless of which one is selected. This is OK for <tgmath.h> where the cases are different sizes of float, where there are few opportunities to get different failures in the different cases, but it makes _Generic hard to use for more template-like code.
Re:“Modern” C++ Lamentations by pjmlp on Friday December 28, 2018 @06:34PM (#18780371)
Given how hard SFINAE is to understand for typical developers, I consider it a good thing, actually.
Re:“Modern” C++ Lamentations by steveklabnik on Friday December 28, 2018 @05:09PM (#18779866)
Sure, but I’m not aware of them being used in any significant capacity, so I don’t think it’s particilary pertinent to this discussion.
Re:“Modern” C++ Lamentations by pjmlp on Friday December 28, 2018 @05:23PM (#18779960)
Ah, in that regard you are right, I guess.