See every bug and exploit with C arrays or pointers that exists because C devs think even minimal attempts at safety are too complicated or slow, or old-style PHP code that builds SQL queries out of printf strings directly from POST values, or probably countless other examples in other languages. C++ code that uses raw pointers instead of references or that uses std::vector but never actually bothers to bounds-check anything.
It's entirely possible for code to be too dumb for its own good.
> Not always, though. See every bug and exploit with C arrays ...
That can also be perceived as a a flaw of the language design in that it does not allow one to write dumb, safe and fast code. Which are such languages in existence today?
I would say Java, which I suspect is bound to be a bit of an unpopular opinion here (I expect more startup people than enterprise denizens in HN); but the more I think about it, the more sure I'm that it fits the bill:
- Dumb: you bet. The inclusion of lambdas has shaken things a little, but usually Java code is straightforward with little space for "cleverness". The counterpoint to this, of course, would be the memes about AbstractFactoryFactoryImplementationSubclassDispatcher class names and all that, which IMHO does not represent much actual Java code out there. There are good reasons why big corps prefer Java, and readability is one of them. As a programmer, I've found it easier to jump into a Java codebase I didn't know much about than in any other language. And this has happened even when I had little experience in Java.
- Safe: yes. You have to go out of your way to be unsafe in Java. Memory allocation is done for you and the try-with-resources idiom is almost as good as C++ RAII.
- Fast: also yes. Usually about 2x or 3x the run time of C/C++ code, some times even less.
> Fast: also yes. Usually about 2x or 3x the run time of C/C++ code, some times even less.
not commenting on the Java part as I believe it's usually faster than that (though not so sure when you see the years of hoops that Minecraft java had to go through to stop being so damn slow all the time...) , but it's kinda frustrating to be fighting for microseconds almost daily and then hear people saying that 2x slower is fast... 2x slower means going from 100fps to 50fps which ... well, gets you fired ?
I know this thread is about game development, but not everyone has hard deadlines to churn out frames. The comparison is useful because it separates Java from many other languages that are possibly 10x slower, which make them unsuitable for a huge number of domains where Java can still be useful.
> Why is 1.8 allocating so much memory? This is the best part - over 90% of the memory allocation is not needed at all. Most of the memory is probably allocated to make the life of the developers easier.
HFT is as high demanding as games and it makes use of Java, however I think that Java developers with such skills rather have a HFT salary than what game devs earn on average.
How would avoiding GC even work? As far as I know that Valhalla thing still isn't there - wonder if it ever comes. Last I used it, you could only have "structs" together with GC. Maybe there is just no practical way to do this? What prominent examples are there?
I remember a story of a HFT trading software written in Java. Supposedly it had big issues with GC. That's why they built a system where multiple threads would attempt the same operation, and the first thread wins. This approach reduces the likelyhood of a GC ruining the timings. Funny story.
My bachelor's thesis involved writing a software in Java that would manage dozens or hundreds of millions of small objects. These objects were all instances of the same class; the contained only three ints. It was very slow, and especially in an OOM situation the GC would work for more than a minute before finally giving up. I changed the software to use SOA instaed of AOS - moving from a huge array of these objects to three int[] arrays. Since ints aren't boxed, that left me with only 3 objects instead of many millions. The code was uglier for it, but the performance was another world. Unfortunately, such a change is not practical if you have many classes.
That was 5 years ago with Java 8. Disclaimer: I haven't followed Java since then. I know next to nothing about it.
But that is exactly what you do when going after performance in game development, even in C and C++, and it isn't less ugly by using those languages instead of Java.
There is an EA available for Valhalla and there is now the roadmap to incrementally bring such features into the platform. Java 14 has a new native memory support as experimental and it might reach stable already by 15.
The problem why it is taking so long is engineering effort to keep ABI compatibility, namely how to keep 20 year old jars running in a post Valhala world, while at the same time migrate value like classes into real value types.
Java's biggest mistake, from my point of view, was to ignore the GC enabled systems languages that had value types, non traced references, and AOT compilation from the get go, then again I guess no one on the team imagined that 25 years later the language would be one of the choices in enterprise computing.
Back to Minecraft, the game isn't Crysis or Fortnight in hardware requirements, so a language like Java is quite alright for such game, what isn't alright is what the new development team apparently lacking experience eventually ended up doing to the game engine.
If one is to believe a couple of posts like the one I referred to.
In C and C++ you don't need to make int-arrays. You can group data that is accessed together in structs and keep arrays of these structs.
With regards to performance, there must be some fine art in splitting structs into smaller structs, and keep them as parallel arrays, but there is also a limit to it. At some point you will need too many pointers to point at the same position in all these arrays.
I've never cared to split a lot, since it makes code harder to read. My guideline has always been to optimize for modularization: In OOP there tend to be large objects containing links to "all" related information. That violates the rule of separation of concerns. With parallel arrays you get perfect separation of concerns. One parallel array doesn't even need to know that there are others.
> Back to Minecraft, the game isn't Crysis or Fortnight in hardware requirements, so a language like Java is quite alright for such game, what isn't alright is what the new development team apparently lacking experience eventually ended up doing to the game engine.
I'm not in a position to judge, and I've never even played it, but it seems to me that Minecraft has a lot of voxels to maintain. Also massive multiplayer requirements?
Fair enough, however regarding massive multiplayer requirements most game shops are anyway using Java or .NET on their backends, as you can easily check going over their job adverts.
As on the client side, proper coding plus offloading stuff into shaders already goes quite far.
And even in the debatable point that Java isn't the best language for any kind of game development, well maybe Mojang would never have taken off if Markus had decided to prototype the same in something else.
Nowadays the Java version is only kept around due to the modding community, as the C++ version is found lacking in this area.
Just to add something that I forgot to mention on my previous comment that I think it is worthwhile mentioning.
Naturally at some level you will have a class full of native methods as FFI to OS APIs or libraries written in C or C++.
From that point of view, I consider Java still a better option as game engine scripting language as Python/Lua/JavaScript, because you get strong typing, there is still dynamic loading, a good set of AOT/JIT/GC infrastructure and more control over memory layout than those languages allow for.
Java won because of its humungous and stable standard library with the full backing of Sun (and Sun was huge presence at the time). It was quite a joy to have pretty much all the functions you could ever need (not really but it felt like it at least) at your fingertips without having to do manual dependency management.
As a technology it really didn't have much to give. Object Pascal was born in 1986, Ada in 1980 with language support for design by contract, JIT with Lisp in 1960 and Java came in 1995.
It wasn't just Sun, though they did the heavy lifting marketing-wise. The Apache Project also hopped on the Java train way early and produced a crapton of libraries which made developing internet applications way easier, especially for corporate schlubs who might have been previously exposed to Microsoft (or, ugh, IBM) systems but didn't have acceas to the internet foljlore Unix gurus had.
Cleverness is more a function of a programmer's habits and attitude than of the language: one tries to be clever in any language.
C++ offers "efficient" ways to be clever, with varied and difficult challenges that can be addressed in relatively little difficult code; some are good or harmless (e.g. aligning struct fields to cache lines or concise towers of useful templates) and some are bad (e.g. flaky homemade not-too-smart pointers and almost-STL-compatible containers).
Java, on the contrary, facilitates the creation of large, boring and easy to read generic object-oriented tumors, that become satisfactorily clever only when they go very far (e.g. one more layer of indirection than everyone else) or reach theoretical limits (e.g. nothing left to invert control of).
Java as a language is dumb, but as an overall platform is not.
In a way it is a two-layer platform: you have the outer layer, a boring language that is used by a lot of people to write most of the code, then you have a hidden layer, that most people ignore, made of bytecode manipulation, runtime code generation and language agents that let you do cool things like adding compile-time nullness checks, generate mapping classes to avoid writing a lot of boilerplate code or good old ORMs that automatically generate your sql queries for you.
Such functionalities are not exactly easy and straightforward to use, but in my opinion it is a good thing: they are there and can be used, but for most programmers will be hidden behind a few "magic" annotations.
This is in contrast to other languages where the advanced functionalities are "all over the place" and every programmer must be aware of them (I'm thinking of C++ and Common Lisp for example).
If you have a teams of great programmers you may achieve better results with the latter approach, but for the average company the Java approach is better because you can have average programmers write boring code while taking advantage of a few clever tricks here and there by using libraries/frameworks written by better programmers.
- Too many jumps in code logic instead of serial logic
- Premature optimization 1: messy code (using lots of unclear variables etc), this is common within calculations, and games have quite some of those.
- Premature optimization 2: failing to properly architect
- Single-character variables. Java IDEs default to fullvars
- Bad function/method names
- Not expressing what you mean (for i= vs foreach)
- Too many abstraction layers / IoC
- Too many sideeffects / complex code (interwoven code)
- Callback hell
Nr 1 is important.. You need to be able to "follow the code". If that requires you to create a DSL in order to code something async in a serial way, then by all means do so.
For example with: network (duh.), but also in games: character dialogs, animations etc. etc.
The inclusion of lambdas has shaken things a little
I'm curious, do you consider lambdas as more or less "dumb"? Because I consider them the dumbest, simplest and maybe best way to do polymorphism. In a way, OOP's whole shtick was about not using them and instead extend stuff with classes.
It's less dumb than usual Java code in the sense that it's less obvious. Java Lambdas are anonymous, inline implementations of single method interfaces (or abstract classes with a single abstract method). In classic Java you would instantiate an explicit object, from an explicitly named interface (anonymous classes were still allowed, but at least the code would have the name of the implemented interface and the overridden method). This made the code more explicit, therefore more cumbersome but also more obvious. I like lambdas because they make the code considerably less cumbersome but only a little less obvious. But they do make the code a little less obvious, and as such, I would say that they make Java less "dumb".
> The key point here is our programmers are Googlers, they’re not researchers. They’re typically, fairly young, fresh out of school, probably learned Java, maybe learned C or C++, probably learned Python. They’re not capable of understanding a brilliant language but we want to use them to build good software. So, the language that we give them has to be easy for them to understand and easy to adopt.
This sounds unnecessarily dismissive of C programmers. I see a lot of C programmers that shit on C++ but are intrigued by Rust, e.g. Linux kernel developers allowing modules to be written in Rust.
Your view sounds very jaded to me. Maybe Rust is liked more by C programmers because they prefer its approach, rather than C++'s? Calling it a marketing ploy seems without merit to me.
The experience of writing new code in one is very, very similar to the experience of writing new code in the other, right down to the compile times. The lifetime analysis in Rust is nice and pretty far ahead of what static analyzers can do in C++, but Rust Generics are a pretty weak approximation to Templates. Rust has better Browser integration, C++ has Qt. One imagines the languages will catch up to one another on these fronts. C++ has Inheritance, Rust settles for Interface Polymorphism (one can reasonably prefer either).
The one really big difference here is actually cultural - the Rust community all agrees on Cargo, and it's a bit happier to compile the world and distribute static binaries, which removes massive headaches for both the developer and the end user while setting the language up for an eventual, Electron-style tragedy of the commons where a user is running like 8 Rust apps with their own copies of the same 14 libraries resident in memory (but that's a good problem to have because it means you've displaced C/C++ as the linguae francae of native app development).
I guess the other really big difference is that there is no legacy Rust code.
I like C++, but I can understand hating it. But if you have written new code in C++17, and hated it ... I suspect you are going to hate writing Rust too. And if you love Rust and hate C++ ... I suspect what you hate is legacy C++ from 2005.
Finally, I was explicitly not concluding anything about C Programmers beyond that they hate C++.
As someone who built a career in C++ I like that Rust's generics are a poor approximation of templates(and that includes having worked with some of the modern C++ features). I have months of my life I've lost to the increased compile times from Boost on the applications I've worked on.
C++ also makes it way too easy to reach for shared_ptr instead of unique_ptr leading to all sorts of unfortunate things. Rust makes that much harder and RefCell/Rc/Arc push towards design that are "single owner" which I've found scale out much better once you move into programs that are of a significant complexity.
C++ still wins in portability on some platforms but I have a hard time picking it for anything greenfield at this point.
Right now Rust eco-system still isn't as mature as C++ in what concerns integration with Java and .NET, and GPGPU programming. The domains I care about.
However with the support of companies like Microsoft, Rust will eventually get there.
I would pick C++ for anything to do with high-performance linear algebra. There are a few other domains (desktop GUI, CAD) where I don't trust the Rust library ecosystem.
But, yeah, there are a ton of domains (notably embedded) where I would want Rust.
I agree with most of what you're saying here except for the point on generics versus templates. I wouldn't say generics are an approximation of templates at all, templates are something in between generic programming and macros and that leads to them being hazardous, slow, and generally speaking unergonomic.
Rust's generics allow for some seriously powerful abstractions to be built in a very clean and readable way, although there can be friction with stuff that would be simple with templates in C++ and quite verbose in Rust.
> I wouldn't say generics are an approximation of templates
Template (at their origins) are nothing more than generics, and a pretty clean, powerful and zero cost way of doing generics.
What you name "hazardous, unergonomic" macros style is not the template system itself. It is mainly due to all the 2005-styles SFINAE hacks that have been invented by abusing templates properties.
SFINAE in C++ is nothing natural, it's at best a dangerous trick to have compile time resolution/execution.
Fortunately all of that should die progressively with C++-17 constexpr for the good of humankind.
Non-type Template Parameters ("const Generics") are on the short-term road-map for Rust. Small step from there to recursion and compile-time factorial.
> C++ code that uses raw pointers instead of references or that uses std::vector but never actually bothers to bounds-check anything.
When is the last time you accidentally mutated a raw pointer? My opinion is that references are just another C++ feature that solves a non-existent problem and has severe disadvantages. And that is non-orthogonality / combinatoric type system explosion. I've seen more than one codebase that consisted to a large degree of adapters for calling the same functionality.
Not always, though.
See every bug and exploit with C arrays or pointers that exists because C devs think even minimal attempts at safety are too complicated or slow, or old-style PHP code that builds SQL queries out of printf strings directly from POST values, or probably countless other examples in other languages. C++ code that uses raw pointers instead of references or that uses std::vector but never actually bothers to bounds-check anything.
It's entirely possible for code to be too dumb for its own good.