5 ms·
that sounds great but I guess that will make the compilation time bigger.
by merb 10y ago
that sounds great but I guess that will make the compilation time bigger.
- pcwalton 10y agoCompared to what?
- merb 10y agocompared to a non free rust future
- deleted 10y ago[deleted]
- Manishearth 10y agoYeah, using closures in zero-cost abstractions (like iterator or now future adaptors) does make compilation slow. LLVM isn't used to this kind of code, and takes time optimizing it. However, now that we have MIR we can optimize this on the Rust side first, before the type info has been lost. This was one of the reasons MIR was added :)
- deleted 10y ago[deleted]
- dbaupp 10y agoI'm... curious about why you think LLVM isn't used to the sort of code that results from these sort of zero-cost abstractions: C++ code is also very aggressive about templates that inline away to nothing, and modern C++ especially has many similar abstractions to Rust (cf. closures, and the range proposal). Rust isn't special in that particular regard. Maybe you mean LLVM is very low-level and thus it sees everything in the abstractions, which obviously takes time to sort through (and is especially wasteful to do multiple times for different monomorphisations). Thus, using MIR to optimise at a higher level can simplify code more efficiently before it hits LLVM.
- yazaddaruvala 10y agoI believe he meant, lots of closures. Which is not normal in C or C++, but is the normal in Rust because they are Zero-Cost. Without knowing anything about LLVM's internals, I would assume it doesn't anticipate so much closure chaining, and therefore doesn't leverage the fact that they are so easy to inline.
- int_19h 10y agoLambdas in modern idiomatic C++ are also essentially zero-cost, or at least they're supposed to be - they're not heap-allocated, and are strong candidates for inlining.
- dbaupp 10y agoLambdas in C++ are almost identical to Rust ones (each closure is a unique unnameable struct containing the captures, with an overloaded operator()), in fact, the current Rust scheme was explicitly inspired by C++11 closures. Historically (C++98), it's true that not much code used closures, because they didn't exist at a language level, but the modern language uses them much more aggressively, even pushing hard on making them more and more flexible (e.g. auto/generic closures in C++14). For instance, sort[1] can take a closure which is more efficient that way than a function pointer, and easier to use than a manually implemented custom type. Also, closures are just structs with functions that take them as arguments, so if the compiler can handle those, it can handle closures. [1]: http://en.cppreference.com/w/cpp/algorithm/sort http://en.cppreference.com/w/cpp/algorithm/sort
- gpderetta 10y agoExactly. And hand written function objects were already common in C++98. C++1* lambdas are just (very sweet) syntactic sugar.
- adrianN 10y agoDoes clang directly go for LLVM or is there an intermediate form that is optimized similar to MIR?
- alexcrichton 10y agoIn Rust it's frequently the case that slow compilations are dominated by generating and optimizing LLVM IR. This codegen step (generating LLVM IR) often takes awhile just because we're generating so much IR. Rust takes an approach with generic functions called monomorphization which means that we generate a new version of each function for each set of generics it's instantiated with. This means that a future of a String will generate entirely different code from a future of an integer. This allows generics to be a zero cost abstraction because code is optimized as if you had substituted all the generics by hand. Putting all that together, highly generic programs will generally trend towards higher compile times. With all the generics in play, there tends to be a lot of monomorphization which causes quite a lot of LLVM IR to get generated. As with many aspects of Rust, however, you have a choice! Rust supports what we call "trait objects" which is a way to take a future and put it behind an allocation with a vtable (virtual dispatch). This forces the compiler to generate code immediately when a trait object is created, rather than down the line when something is monomorphized. Put another way, you've got control over compile times if you're using futures. If you're taking a future generically and that takes too long to compile, you can instead take a trait object (or quickly convert it to a trait object). This will help cut down on the amount of code getting monomorphized. So in general futures shouldn't make compilation worse. You'll have a choice between performance (no boxes) and compile times (boxing) occasionally, but that's basically already the case of what happens in Rust today.
- eddyb 10y agoDo note that with MIR the focus is polymorphic optimizations - reducing the LLVM IR for all monomorphizations of a generic function, at once. Unlike C++ templates, Rust enforces a single definition with uniform semantics, for a generic type/function (specialization going through the existing trait static dispatch mechanism), so we can take advantage of that to reduce compile times.
- SideburnsOfDoom 10y agoHm. I've heard arguments that C# or Java is slow for multiple reasons, but never because of the minuscule overhead of a virtual method dispatch when using objects behind interfaces (kinds similar to trait objects). It's interesting that this is seen as significant here. Are we dealing with much shorter timescales, or just being eager to optimise everything?