Hacker News new | ask | show | jobs
by pjmlp 3344 days ago
Java only failed because Sun didn't care enough for pushing it into game developers after their initial presentations. Actually they didn't care for any other scenario that wasn't JEE, in terms of putting resources into it.

Switching from Assembly into C, Pascal, Modula-2, QuickBasic was mostly an amateur thing until SDKs started to be based on C.

Same with C++, we were the outliers until console vendors started pushing C++ into devs. See Mike Acton's talks about about how he enjoyed having been forced to move from C into C++.

The thing with GC is not the freedom to manage memory, you still need to take care about how memory is managed. What one gets is type safety and an uniform way to manage memory across libraries.

You know exactly who is responsible for releasing it, there are no double deallocations, releasing invalid addresses, or having to deal with library specific smart pointers (CPtr, QSmartPtr, unique_ptr, my_in-house_ptr, ...).

> due to cache issues, SIMD issues, off-line vs JIT quality issues etc

Having a GC doesn't preclude lack of support for cache handling or SIMD.

Many GC based languages have such support and actually C# 7 with Microsoft's toolchain (not Unity's one) does offer such features, even if they can still be improved.

As for JIT quality, it is no different from any other compiler, otherwise as I referred, why not discuss C performance using MS-DOS compiler's benchmarks.

2 comments

One problem I have with memory-managed languages for writing high-performance-code is that you need to give up the simplicity of not having to care about memory management. You need to know exactly how the garbage collector in this specific language (and current version of the language) works, and how to build your code around that. The resulting 'mental burden' is the same or even bigger as in C or C++, and the resulting code often looks worse then C++ code which does the same. An extreme example of this is asm.js, much higher performance than typical JS since it only uses numbers, not JS objects (and thus basically removes the need for garbage collection), but so much different from manually written JS that it is basically a different language.

As for smart pointers in C++: they are only useful in a code architecture that essentially emulates C# or Java (all objects as single entities on the heap) using C or C++ in such a "memory-managed style" really doesn't make much sense since it will be slower then a GC. The point about manual memory management is to reduce dynamic allocations as much as possible and have all your data either on the stack or in long-lived, big memory chunks. If you need to allocate and deallocate all the time, or track the ownership and lifetime for every little memory chunk, you're not using C/C++ to its advantage.

PS: C# for tools is great though (not so much because of the language, but because of the standard framework, which is much nicer than the C++ stdlib).

[edit: typos]

Just like you need to know how malloc()/new/STL allocators for your specific C or C++ compiler are implemented.

And most implementations actually suck at multi-threaded code, to the point there are companies selling better implementations of them.

> The point about manual memory management is to reduce dynamic allocations as much as possible and have all your data either on the stack or in long-lived, big memory chunks.

Also possible in GC enabled languages, grated not available in all of them.

As former C++ dev, I can guarantee you that unless you write 100% of the code yourself, there will be leaks, double frees, delete calls instead of delete[] ones, releasing unallocated memory and totally lack of control of third party leaks.

The point I'm trying to make is that you loose the advantage of a garbage collected language once performance becomes important, so there's no point in using such a language for high-performance code in the first place.

Integrating third party dependencies into C++ is its real achilles heel, I agree. I only accept dependencies that come with source and either don't allocate dynamic memory at all, or allow full control over allocation by providing custom hooks. I also learned over time that typical C code is usually much easier to integrate than typical C++ code (which very often is an over-engineered template mess).

The way to deal effectively with memory management bugs is to allocate as little as possible. There are good analyzer and runtime tools (for instance the new runtime memory debuggers in Xcode and Visual Studio, the static analyzers, the clang address sanitizer in Xcode etc), these provide a much better view on what's going on under the hood than most managed-language-tools. And those tools make it quite trivial to catch most memory-related bugs. If you're doing hundreds-of-thousands of allocations it becomes much harder though, because than the problems will be lost in all the noise.

It is a valid argument though to use higher-level languages in high-level gameplay code, but this should only do scripting-style stuff, glueing subsystems and game objects together. Up in this area I don't want to care about ownership and lifetimes. It's important to find the balance though where the high-level code should better be moved into a low-level, central system.

>Same with C++, we were the outliers until console vendors started pushing C++ into devs. See Mike Acton's talks about about how he enjoyed having been forced to move from C into C++.

The first (major) console C++ SDK library is GNM/GNMX for PS4. Are you saying C++ games has been outliers till 201x? I guess this depends on how you define outliers then. Other than the guy casting his game writing on youtube (Code Hero?) I don't know of any significant titles in C past 2000. I have not seen all the games but I shipped a few myself and have friends working on many more and nobody that I know had been writing pure C. Think of all vtbl-> you'd have to write if you are targeting Windows/Xbox just for the pleasure to name your source file .c instead of .cpp!

>What one gets is type safety

Thanks, we've got this in C++ already.

>You know exactly who is responsible for releasing it, there are no double deallocations, releasing invalid addresses, or having to deal with library specific smart pointers (CPtr, QSmartPtr, unique_ptr, my_in-house_ptr, ...).

Never had been a problem for me. The only game I touched that had smart pointers had them because it used NetImmerse and it got cancelled anyways.

>Having a GC doesn't preclude lack of support for cache handling or SIMD.

I don't know what is cache handling, to be honest, but what I meant is that, since GC moves stuff around memory, it's likely to create cache hazards. And since it probably uses a single pool, you cannot change cache policies on per-entity basis.

>As for JIT quality, it is no different from any other compiler, otherwise as I referred, why not discuss C performance using MS-DOS compiler's benchmarks.

Really? You believe JIT will be on-par with a profile assisted, bruteforcing compiler, which can spend a week optimizing a single function?

> The first (major) console C++ SDK library is GNM/GNMX for PS4.

The major C++ SDK was the DirectX SDK for the Sega Saturn.

> Are you saying C++ games has been outliers till 201x?

Yes, the code was basically C compiled with C++ compiler.

> >What one gets is type safety

> Thanks, we've got this in C++ already.

Not when what most write is actually C compiled with C++ compiler.

> I don't know what is cache handling, to be honest, but what I meant is that, since GC moves stuff around memory

Is the ability to write cache friendly code.

If you don't want the GC to move memory around then don't allocate it via the GC.

Many GC enabled languages also allow for global statics and stack allocation. Even C# has some support here, even if it isn't comparable to what Modula-3 or D allow for.

Also there is also the possibility to just allocate it off GC heap.

> Really? You believe JIT will be on-par with a profile assisted, bruteforcing compiler, which can spend a week optimizing a single function?

No, but JITs can also make use of PGO just like AOT compilers. IBM J9 JIT and .NET RyuJIT have such support.

Also many GC enabled languages, including C# do have AOT compilers to native code code as well, it is not as JIT is the only viable approach.

>The major C++ SDK was the DirectX SDK for the Sega Saturn.

a) there was no DirectX SDK for Sega Saturn. b) DirectX SDK (including one for Sega Dreamcast) is not C++. It has C++ bindings but is usable from C.

>Yes, the code was basically C compiled with C++ compiler.

You mean if I have taken that code and compiled with C compiler it worked? You realize even the DirectX C++ wrapper is not already C, right? You know classes, overloads, namespaces are not C?

>Not when what most write is actually C compiled with C++ compiler.

Could you explain exactly how this works? The C++ compiler uses ML to recognize that the code is not exactly following Alexandrescu's book and turns off typechecks? I seriously just don't understand what you mean here. I usually take "C compiled with C++ compiler", "C with classes" etc as "not enough GoF patterns for my taste" but you are making some other claim here it seems.

>If you don't want the GC to move memory around then don't allocate it via the GC.

So, why do you want GC in the first place? For types, which somehow disappeared from C++?

>No, but JITs can also make use of PGO just like AOT compilers.

How exactly does it work? The code stops executing for a week, the profiler gets run to under user credentials and then JIT finally decides?

> there was no DirectX SDK for Sega Saturn.

Broken memories, I didn't bother to search for it (Saturn vs Dreamcast).

> You mean if I have taken that code and compiled with C compiler it worked?

Of course it would, that was one of the design goals of C++.

C90 is mostly a C++98 subset, except for stronger type conversion rules (no implicit void* conversions), precedence order for operator ?: and typedef/struct namespaces.

> You realize even the DirectX C++ wrapper is not already C, right? You know classes, overloads, namespaces are not C?

Yes, but COM is also callable from C by design. Also I have seen many codebases that have restricted C++ code to calling DX APIs, with everything else being compilable by a C compiler as well.

> Could you explain exactly how this works? ...

1 - Rename .c translation units to .cpp, .cxx, .C

2 - Invoke C++ compiler on them

3 - Fix compiler errors related to semantic differences in C subset of C++

4 - Forbid use of any C++ specific feature beyond those required to use the OS SDK.

> So, why do you want GC in the first place? For types, which somehow disappeared from C++?

Productivity.

> How exactly does it work? The code stops executing for a week, the profiler gets run to under user credentials and then JIT finally decides?

PGO data generated by the JIT compiler gets updated after each application execution and is used as input for optimization selection just like in an AOT compiler by a multi-stage compiler.

Feel free to read Android 7 ART source code to learn how about a possible implementation.

>Of course it would, that was one of the design goals of C++. >C90 is mostly a C++98 subset, except for stronger type conversion rules (no implicit void* conversions), precedence order for operator ?: and typedef/struct namespaces.

I think either I don't understand something you are trying to say or you are confused. C being a subset of C++ (one of design goals) does not imply C++ is a subset of C. C++ code in general cannot be compiled with C compiler without rewriting.

>Yes, but COM is also callable from C by design. Also I have seen many codebases that have restricted C++ code to calling DX APIs, with everything else being compilable by a C compiler as well.

You have blah->Foo(bar); in C++. In C it won't compile by any design. That code has to be rewritten as blah->vtbl->Foo(blah,bar). It is not C code as you claimed. It won't compile with C compiler. It's C++.

>> Could you explain exactly how this works? ... >1 - Rename .c translation units to .cpp, .cxx, .C

I asked how type checking disappears in this process, not how you compile C with C++....

>PGO data generated by the JIT compiler gets updated after each application execution and is used as input for optimization selection just like in an AOT compiler by a multi-stage compiler.

How can a JIT compiler obtain the PGO data in the first place? Is it running under profiler all the time? You realize that you've just refuted your claim about performance not being affected, right?

You are the one not understanding what it means to pick C code and compile it with a C++ compiler, minus the semantic differences.

Should I enumerate all of them to make you happy?

> You have blah->Foo(bar); in C++. In C it won't compile by any design. That code has to be rewritten as blah->vtbl->Foo(blah,bar). It is not C code as you claimed. It won't compile with C compiler. It's C++.

Nothing prevents you to write COM calls in C++ code just like in C, blah->vtbl->Foo(blah,bar). The code won't stop compiling.

> I asked how type checking disappears in this process, not how you compile C with C++....

The whole point was about writing C like code with a C++ compiler.

> How can a JIT compiler obtain the PGO data in the first place? Is it running under profiler all the time? You realize that you've just refuted your claim about performance not being affected, right?

By using a multi-stage JIT compiler with different levels of optimization and making use of multi-cores.

99% of the applications are never able to saturate all cores to the point it matters to the overall performance.

I have always been on the Pascal and C++ side against C since the early 90's on BBS and USENET.

So this type of disbelief against better tooling is not strange to me.

Cry Engine, Unreal (C++ with GC), Unity, MonoGame, with their separation between lower level C++ and higher level languages, and the way those engines are being adopted by Sony, Nintendo, Microsoft, Amazon, Google speak for themselves.

Just like C and Pascal overtook Assembly, C++ overtook C, something else will overtake C++.

> Not when what most write is actually C compiled with C++ compiler.

Is this moving the goal posts a little bit? C++ now doesn't necessarily look anything like C++ written 20 years ago. Way back, people were writing C+, or C-with-classes, but that's what C++ was, in that era.

Not at all, just because C++11, C++14 and C++17 offer lots of improvements over C++98, doesn't mean people use them.

I can assure you that at enterprise level, every time I have to integrate C++ code with our Java or .NET stacks, it looks exactly like C++ written 20 years ago, even if it was actually written last week.

Just because C++ has been vastly improved, doesn't mean everyone using it are adopting the new features, some people rather stay in Python 2 forever.

> Really? You believe JIT will be on-par with a profile assisted, bruteforcing compiler, which can spend a week optimizing a single function?

I was in game development looong ago (C++ / assembler, lots of 3D software rendering). What kind of compiler is that ? Could you give some pointers ?

All modern compilers do this (MSVC, clang, I believe gcc too). Search for "PGO" (Profile Guided Optimization). Also, not just for C++ - PS3 shader compiler did brute force instruction scheduling optimization (using shaderperf instead of profiler).
damn time to refresh my knowledge :-) That's what happens when you do CRUD too long :-)

Last time I optimized code the hard way was using VTune to channel the right operation in the right pipeline.