Also consider the leverage factor. Improvements to the compiler benefit all users of the programming language, so it's worthwhile to invest in high quality compilers.
Yes, we can use caching compilers (https://wiki.archlinux.org/index.php/ccache) to speed up builds with few changes. We can lower optimization levels (although that barely gives you an increase in compile speed compared to the runtime speed you lose and the fact that it makes your program do slightly different things).
There's no slider from "pessimum" to "optimum". You need to do wildly different things to optimize past this point for compile speed. Erlang hot-reload and at-runtime-code-gen from other langs come to mind. But that will almost definitely slow down your program because of the new infrastructure your code has to deal with.
I have observed that there can be a nice balance with Java and the auto-reloading tools that are available for it. But I am unaware of their limitations and how a web browser might trigger those limitations.
D has very fast compilation times compared with C++.
Rust is another option.
* Everything up to and including typechecking (and borrowchecking) takes a third to a half of the time, with lowering from there to a binary taking the rest of the time; that means (a) you can get a 2-3x speedup if you only need to check the code is compilable, and (b) overall speed isn't likely to improve a lot unless LLVM gets a lot faster.
* Rust doesn't currently do good incremental compilation, so there are potential big wins for day-to-day use there.
* There is a mad plan to do debug builds (unoptimised, fast, for minute-to-minute development) using a different compiler backend, Cretonne [2]. If that ever happens, it could be much, much faster.
[1] https://www.reddit.com/r/rust/comments/6m97hl/how_do_typical...
[2] https://internals.rust-lang.org/t/possible-alternative-compi...
Have you actively tried to find one?
> You can have both.
Pretty bold claim, without proof.
A million lines of code represents an AST with a few million nodes in it, which compiles to a binary of a few megabytes. To do this we have computers with a dozen cores running at 4ghz each, 100GB of memory and blazingly fast SSD drives.
It's easy to forget, but computers themselves aren't slow. The software we write is just inefficient.
Most C++ devs have to work with tools that currently exist and so we are stuck with what the compiler devs give us. Believe it or not C++ compiler devs are pretty smart people and have largely optimized it as much as possible without a language redesign.
That language redesign is in the works with modules, but the dust hasn't settled yet, so that is also a discussion for the future. In the mean time no other language delivers the performance C++ does right now. So if I want to ship product right now the very real dilemma is fast product with slow builds (and a bunch of tools for dealing with that) with C++ or use some other language for a faster compiler and a slower product.
Then there is Rust, but that is another whole can of worms and not in use in most shops yet (Just switching to something has a huge cost).
C compiles a _lot_ faster than C++, so that's always an option. And as other people have pointed out, you can get C++ code to compile much more quickly by being very disciplined about what features you use and how your code is laid out.
So I agree that if you want to ship something right now all your options have significant downsides. I think software engineers as an industry don't take tooling nearly as seriously as they should. Tools are performance amplifiers and we currently waste a staggering number of manhours working with poorly designed, unreliable, poorly documented and agonizingly slow tools.
Where did I write "who cares about performance"? And why do you think any of what I said is going to cost 2x-3x performance? Performance has been either a major part of or simply my entire job for most of my career, and I usually make projects I run into at least an order of magnitude faster. For example by switching a project from pure C to Objective-C. Or ditching SQLite despite the fact that it's super-optimized. Or by turning a 12+ machine distributed system into a single JAR running on a single box.
The Web browser and WWW were invented on a NeXT with Objective-C. It wasn't just a browser, but also an editor. In ~5KLOC written in a couple of months by a single person. NCSA Mosaic took a team of 5 a year and was 100KLOC in C++. No editing. So pure code-size is also a problem. And of course these days code size has a significant performance impact all by itself, but also 20x the code in C++ is going to take a significantly longer time to compile.
In terms of performance, the myth that you need to use something like C++ for the entire project is just that: a myth. First, the entire codebase doesn't need to have the same performance levels, a lot of code is pretty cold and won't have measurable impact on performance, especially if you have good/hi-perf components to interact with. See 97:3 and "The End of Optimizing Compilers". Or my "BookLightning" imposition program, which has its core imposition routine written in Objective-Smalltalk, probably one of the slowest languages currently in existence. Yet it beats Apple's CoreGraphics (written in C and heavily optimized) at the similar task of n-up printing by orders of magnitude.
Second, time lost waiting for the compiler is not "convenience", it is productivity. If you get done more quickly, you have more time to spend on optimizing the parts of the program that really matter, and thoughtful optimization tends to have a much larger impact on performance than thoughtless performance. The idea that this is purely a language thing is naive. See, for example, https://www.youtube.com/watch?v=kHG_zw75SjE
Third, you don't need to have C++ style compilers and features to have a language that has fast code, see for example Turbo Pascal mentioned in other comments. When TP came out, we had a Pascal compiler running on our PDP-11 that used something like 4-5 passes and took ages to compile code. TP was essentially instantaneous, so fast that our CS teacher just kept hitting that compile key just for the joy of watching it do its thing. It also produced really fast code.
Use high-level interpreted languages to make your life easier, but also use them to make the page cache's life easier.
Although the lower levels are written in a mix of C and C++, the OS Frameworks are explicitly designed for Java, C# and VB.NET.
Trying to use C or C++ for anything more than moving pixels or audio around is a world of pain.
The Android team even dropped the idea of using C++ APIs on Brillo and instead brought the Android stack, with ability to write user space drivers in Java (!).
No, I will switch to firefox or something. I'm a user, I don't care how hard it is for developers I can about my workflow which is using a browser on various machines, some of which are very slow.
A faster compiler isn't just some frivolity. It's a power tool. A force multiplier.
Now we can see it as a big mistake, but on those days probably it was one of the reasons why C++'s adoption took off.
Also while C lacked modules, most Algol and PL/I derived languages supported them since the late 60's.
Swift's case has the issue of mixing type inference with subtyping, so lots of time is spent there.
All in all, I really miss TP compile times and at least on Java/.NET, even with AOT compilers are close enough.
EDIT: some typos
I know "Design and Evolution of C++" quite well, and have been a C++ user since Turbo C++ 1.0 for MS-DOS.
Stroustrup made the decision on purpose and consciously, but it turned out to have disastrous effects.
1. OCaml generates additional information that it stores in .cmi/.cmx files.
2. OCaml does not allow for mutual dependencies between modules, even in the linking stage. Object files must be provided in topologically sorted order to the linker.
3. OCaml supports shared generics, which cuts down on the amount of code replication (at the expense of requiring additional boxing and tagged integers in order to have a uniform data representation).
> 1. OCaml generates additional information that it stores in .cmi/.cmx files.
On this point I'd say that it could probably embed the cmx file as "NOTE" sections in the ELF object files, but likely they didn't do it that way because it's easier to make it work cross-platform. Every "pre-compiled header" system I've seen generates some kind of extra file of compiled data which you have to manage, so I don't think this is a roadblock.
> 2. OCaml does not allow for mutual dependencies between modules, even in the linking stage. Object files must be provided in topologically sorted order to the linker.
I believe this is to do with the language rather than to do with modules? For safety reasons, OCaml doesn't allow uninitialized data to exist.
Although (and I say this as someone who likes OCaml) it does sometimes produce contortions where you have to split a natural module in order to satisfy the dependency requirement. I've long said that OCaml needs a better system for hierarchical modules and hiding submodules (better than functors, which are obscure for most programmers).
> 3. [...] at the expense of requiring additional boxing and tagged integers [...]
I think this is fixed by OCaml GADTs: https://blogs.janestreet.com/why-gadts-matter-for-performanc... However this is a new feature and maybe not everyone is using it so #3 is still a fair point.
Both, sort of. The problem is that mutually recursive modules are tricky. So, it's a limitation of the language, but one that is there for a reason.
> I think this is fixed by OCaml GADTs
No, GADTs solve a different problem. Essentially, normal ADTs lose type information (due to runtime polymorphism). GADTs give you compile time polymorphism, so the compiler can track which variant a given expression uses. Consider this:
# type t = Int of int | String of string;;
type t = Int of int | String of string
# [ Int 1; String "x" ];;
- : t list = [Int 1; String "x"]
# type _ t = Int: int -> int t | String: string -> string t;;
type _ t = Int : int -> int t | String : string -> string t
# [ Int 1; String "x" ];;
Error: This expression has type string t
but an expression was expected of type int t
Type string is not compatible with type int
The problem with functors (and also type parameters) is the following. Assume that you have a functor such as: module F(S: sig type t val f: t -> t end) = struct ... end
To avoid code duplication, F has to pass arguments to S.f using the same stack layout, regardless of whether it's (say) a float, an int, or a list. This means that floats need to get boxed (so that they use the same memory layout) and integers have to be tagged (because the GC can't tell from the stack frame what the type of the value is).I am curious how it is done in a portable way across all OSes, specially crude system linkers and OSes without POSIX semantics.
For example, I imagine this can be made via ELF sections, but not all OSes use ELF.
The cmx data could be converted to ELF note sections, but the whole thing has to work on Windows as well, so I guess they didn't want to depend on ELF.
In most projects, you can add this to your Makefile and forget about it:
.mli.cmi:
ocamlfind ocamlc $(OCAMLFLAGS) $(OCAMLPACKAGES) \
-c $< -o $@
.ml.cmo:
ocamlfind ocamlc $(OCAMLFLAGS) $(OCAMLPACKAGES) \
-c $< -o $@
.ml.cmx:
ocamlfind ocamlopt $(OCAMLFLAGS) $(OCAMLPACKAGES) \
-c $< -o $@
clean:
rm -f *.cmi *.cmo *.cmx *.cma *.cmxaMore generally speaking: The trick must be to not generate identical instantiations multiple times. So you must have a way to check, if you already generated it. Of course, the devil is in the details (e.g. is equivalence on the syntactic level enough?).
In module based languages, the symbol table is expected to be stored on the binary, either to be directly used by tools or to generate human readable formats (.mli).
So if one uses the system C linker, it means being constrained to the file format used by such linker.
> Now we can see it as a big mistake, but on those days probably it was one of the reasons why C++'s adoption took off.
No mistake, just no choice - the original (1986 or so) C++ cfront compiled C++ to C which it fed to the C compiler and linker chain.
It is a mistake with 2017 eyes, because build times are now insupportable.
Of course it was the right decision in 1986 when trying to get adoption inside AT&T.
Also I have read "Design and Evolution of C++" back when it was published, and know C++ since Turbo C++ 1.0 for MS-DOS, so I grew with the language.
Which is also a reason why I still select it as member for my Java, .NET and C++ toolbox trio.
However the first ANSI C++ was approved in 1998, and many of us were expecting to get some kind of module support in C++0x.
C++ has been by next loved language after Turbo Pascal, since then I learned and used countless languages, but C++ was always on the "if you can only pick 5" kind of list.
Since 2006 I am mostly a Java/.NET languages guy, but still keep C++ on that list.
Mostly because I won't use C unless obliged to do so, and all languages intended to be "a better C++" still haven't proved themselves on the type of work we do, thus decreasing our productivity.
Because in spite of Swift, Java, .NET and JavaScript, C++ is the best supported option from OS vendors SDKs.
I dream of the day I could have an OpenJDK with AOT compilation to native code with support for value types, or a .NET Native that can target any OS instead of UWP apps.
Until then C++ it is, but only for those little things requiring low level systems code.
Check out http://www.scala-native.org/en/latest/
Maybe it will have value types after java gets them
The presentation at Scala days was interesting.
Fintech, HPC, aeronautics, robotics, infotainment,....
The only industry where devs are badly paid is games industry, but that is common to all languages.
They're slowly improving it to return to pre-1.5 performance, but last I checked, it wasn't there yet. The impact is insignificant on small projects, of course, but easily felt on larger (100Kloc+) ones.
While the recent optimizer improvements are great, my wish is for Go to switch to an architecture that uses LLVM as the backend, in order to leverage that project's considerable optimizer and code generator work. I don't know if this would be possible while at the same time retaining the current compilation speed, however.
Sometimes people don't realize this because they always use `go build` which, as the result of a design flaw, discards the incremental objects. When you use `go install` (or `go build -i`) each subsequent build is super fast.
The annoying thing is that "go install" also installs binaries if you run it against a "main" package. I believe the only way to build incrementally for all cases without installing binaries is to use "-o /dev/null" in the main case.
Go being bootstrapped is a good argument against people that don't belive it is suitable for systems programming.
Depending on C or C++ as implementation always gives arguments it could not have been done differently.
Also we should not turn our FOSS compilers into a LLVM monoculture.
Brilliant
How does that law improve the language without falling into the trap mentioned elsewhere in this thread? (Optimizing for a pleasant "compile experience" at the cost of everything else)
The justification, then, seems to be that if you legitimately need new features, then the language has failed and you should start over anyway. I think Python 3 is sort of an offshoot of this idea, except that many of the new features keep getting backported to 2.7 anyway.
There is also Active Oberon and Component Pascal, but he wasn't directly involved.
This specific issue prob impact other batch workloads with lots of small tasks (processes). There's no reason this should be happening on a 24 core machine.
With mainstream languages, code generation is done by the build system which can avoid repetition. Caching generated code feels like a good idea to me. Doing it with compile time execution is (unnecessarily?) hard.
What slows some Common Lisp native code compilers down is more advanced optimization: type inference, type propagation etc, lots of optimization rules, style checking, code inlining, etc.
Yes, and I mean "we" as in "this industry".
I just recently talked to someone whose Swift framework(s) were compiling at roughly 16 lines per second. Spurred on by Jonathan Blow's musings on compile times for Jai[1], I started tinkering with tcc a little. It compiled a generated 200KLOC file (lots of small functions) in around 200 ms.
Then there are Smalltalk and Lisp systems that are always up and pretty much only ever compile the current method.
We also used to have true separate compilation, but that appears to be falling out of favor.
Of course none of these are C++ and they also don't optimize as well etc. Yet, how much of the code really needs to be optimized that well? And how much of that code needs to be recompiled that much?
So we know how to solve this in principle, we just aren't putting the pieces together.
[1] https://www.youtube.com/watch?v=14zlJ98gJKA He mentioned that a decent size game should compile either instantly or in a couple of seconds