Live data from Hacker News

I was wrong, reflecting on the .NET design choices

ayende.com

71–80 of 139 posts

Re: I was wrong, reflecting on the .NET design choices

#71
post #56

Earlier quoted context omitted.

As a low-level game dev, I consider C# a nearly perfect, wonderful language, tragically self-defeated by garbage collection.

Could you explain why garbage collection in your use case is a bad thing? The GC vs non-GC always fascinates me, and I have no strong opinion either way. Also, doesn't C# have the ability to limit (or pretty much disable) GC?

He's a game dev, says it all. The problem with GC is that often it imposes a performance overhead and, more importantly, it results in non-deterministic performance characteristics. In game dev it's typical to want to be able to complete an entire cycle of computations for calculating game mechanics within a single frame, which could be just 17 or even 7 milliseconds in entirety (60/144 fps). If you get even a fraction of a millisecond delay in computing a chunk of work that can screw up your frame rate, produce noticeable visual effects, etc.

Re: I was wrong, reflecting on the .NET design choices

#72
post #70
post #56

Earlier quoted context omitted.

As a low-level game dev, I consider C# a nearly perfect, wonderful language, tragically self-defeated by garbage collection.

It's getting easier with every release to not allocate, with things like ref returns and locals[1] and new types like Span [2]. I think there are more things like that coming in the pipeline[3]. [1] https://github.com/dotnet/roslyn/issues/118 [2] https://github.com/dotnet/corefxlab/blob/master/docs/specs/s... [3] https://github.com/dotnet/roslyn/issues/10378

Here's a comment that mirrors my own thoughts, from the comments on that third link[1]:

"C# heap allocations aren't expensive and are faster than C/C++. They are even faster than stack allocations (if the stack alloc is too large to be a register) as the stack needs to zero out the memory first; whereas the heap has done this ahead of time. However, they do come at a cost, which is deallocation.

C/C++ have both an allocation and deallocation cost. The main advantage is you know when you are paying that deallocation cost; the main disadvantage is you often do it at the wrong time or don't do it at all which leads to memory leaks, using unallocated memory or worse trashing something else's memory (as it has been reallocated).

The GC isn't the bad guy; its the liver and blaming it is like blaming a hangover on the liver.

However; the free drinks the clr allocation bar gives you are very tempting, and does mean there is a tendency to over indulge so the hangovers can be quite bad... (GC pauses)."

[1] https://github.com/dotnet/roslyn/issues/10378#issuecomment-2...

Re: I was wrong, reflecting on the .NET design choices

#73
post #56

Earlier quoted context omitted.

As a low-level game dev, I consider C# a nearly perfect, wonderful language, tragically self-defeated by garbage collection.

Could you explain why garbage collection in your use case is a bad thing? The GC vs non-GC always fascinates me, and I have no strong opinion either way. Also, doesn't C# have the ability to limit (or pretty much disable) GC?

Garbage collection is troublesome for real-time processing(including game engines) because it doesn't allow you to plan around hitting a latency deadline. When the collector triggers, it consumes a lot of time, which can add up to missing a deadline even in very relaxed situations.

Historically, many game engines have a "never allocate" policy: Everything is done with static buffers and arenas of temporary data. The broad strokes of this policy are mostly achievable in a GC language that allows value type collections(fewer managed references = less to trace = cheaper garbage). The problem typically comes in little bits of algorithmic code that need their own allocation: Because the language is garbage collected, all your algorithms are using the GC by default. And if you want to reclaim it, you have to fight a very uphill battle.

IME, though, most of the problem is resolvable if you have enough introspection into GC configuration. If a game can tell the collector that it needs a cheap scratch buffer to process an update loop and then get thrown out, that covers a lot of the problem. The last bit of it is fine detail over memory size and alignment, which some GC languages do give you introspection into already.

Edit: Also, I should note that the relative value of GC changes a lot when your process is long-lived and unbounded in scale(servers) or involves a lot of "compiler-like" behaviors(transformations over a large, complex data graph). The advantage of doing without it in a game engine has a lot to do with the game being able to be tuned around simple processing of previously authored data, with limited bounds in all directions.

Re: I was wrong, reflecting on the .NET design choices

#74
post #11

One of the greatest lingering flaws in both C# and Java is the lack of metaclasses. Because classes aren't real objects and therefore not necessarily also instances of other classes (their metaclasses) as they would be in Smalltalk, there is no class-side equivalent of "self/this," nor of "super." In effect, you cannot write static (class) methods that call other static methods without explicitly referencing the clas…

C# has reified generics, which solve a lot of problems that would otherwise be addressed with metaclasses. For example, factories can be setup quickly by reflection over a generic type variable.

Re: I was wrong, reflecting on the .NET design choices

#75
post #56

Earlier quoted context omitted.

As a low-level game dev, I consider C# a nearly perfect, wonderful language, tragically self-defeated by garbage collection.

Could you explain why garbage collection in your use case is a bad thing? The GC vs non-GC always fascinates me, and I have no strong opinion either way. Also, doesn't C# have the ability to limit (or pretty much disable) GC?

Non-deterministic performance. It can be fast, even faster than managed code in some cases. But it's not predictable.

Re: I was wrong, reflecting on the .NET design choices

#76
post #7

> However, given that I’m working on a database engine now, not on business software, I can see a whole different world of constraints. This might be my own confirmation bias, but this is my takeaway: point of view and the constraints that you see or believe are there are the main determinant of choices, and not whether some particular pattern or feature of a language is intrinsically good. The older I get and the mo…

I have many years of programming in Java under my belt. Until I started using dynamic languages I thought static typing was really important. It's not.

Re: I was wrong, reflecting on the .NET design choices

#77
post #56

Earlier quoted context omitted.

As a low-level game dev, I consider C# a nearly perfect, wonderful language, tragically self-defeated by garbage collection.

Could you explain why garbage collection in your use case is a bad thing? The GC vs non-GC always fascinates me, and I have no strong opinion either way. Also, doesn't C# have the ability to limit (or pretty much disable) GC?

GC is a no-go for a lot of games in general, just because the periodic stop-the-world cleanup causes unpredictable frame-rate-destroying performance hits. That doesn't matter a lot for some games, but it tends to stutter things like FPSs unacceptably.

Re: I was wrong, reflecting on the .NET design choices

#78
post #51

Earlier quoted context omitted.

Not having type erasure is pretty nice (except that reflection code with generics is really hairy).

I like type erasure and depend on it a lot for Scala and Clojure interop. Any JVM language works with the same objects and methods, and can implement its own type system/syntax on top.

That's true in the clr too as long as everything supports generics.

Re: I was wrong, reflecting on the .NET design choices

#79
post #4

If I attempt to generalize it, I think most of C#'s differences in language design that have opposites in Java are superior. Examples include: + not virtual by default (so base classes can be changed more easily without breaking/recompiling downstream clients that the base class writer doesn't know about; specifying something as "virtual" should be a deliberate conscious decision by the class author) + value types (f…

> + unsigned types (very handy for P/Invoke API boundaries because legacy Win32 has unsigned params everywhere; yes, yes, Gosling said that unsigned types are confusing and dangerous but nevertheless, they are still very useful)

The lack of unsigned types in Java is a constant pain when doing cross platform development, or when trying to consume (or write out!) a binary wire format.

It can be worked around, but it is just needlessly stupid.

Re: I was wrong, reflecting on the .NET design choices

#80
post #56

Earlier quoted context omitted.

As a low-level game dev, I consider C# a nearly perfect, wonderful language, tragically self-defeated by garbage collection.

Could you explain why garbage collection in your use case is a bad thing? The GC vs non-GC always fascinates me, and I have no strong opinion either way. Also, doesn't C# have the ability to limit (or pretty much disable) GC?

Garbage collection is the bane of smooth framerates.

Players notice when the framerate drops. Presuming a pretty typical 60 Frames Per Second you have a tight 16.6ms time budget to do all of the work for the entire frame. All of the physics, all of the sound processing, all of the AI and everything else needs to be sliced up into little bits that can be distributed across the time the game is played.

There are many good ways to achieve this, and many ways that just appear good until someone else plays your game. If you allocate dynamically even occasionally you need a strategy to allocated about as much as you deallocate each frame or otherwise mitigate the costs.

C++ has this problem completely solved with strong deterministic semantics between destructors, smart pointers and allocators. This can be handled in C# a few ways as well, but sometimes a bunch of unallocated memory builds up and is cleaned between level loads or during downtime. When the Frame rate drops because the garbage collector consumes 1 of the 2 hardware threads in the middle of a firefight players get mad. If you only ever tested on your nice shiny i7 with 8 hardware threads you might never notice until a bug report lands in your inbox. That presumes it wasn't one of the stop the world collections and you couldn't use that last hardware thread better than the GC, both of which negate GC altogether.

Done right deterministic resource allocation costs almost nothing. You can get to zero runtime cost and nearly zero extra dev time. In practice a little runtime cost is fine, and a little time spent learning is OK, but a bug report in the final hour before shipping that the frame rate drops on some supported hardware setups but not others is really scary.

Post reply on HN