Go language use in performance-critical code

Started by
15 comments, last by ApochPiQ 11 years, 4 months ago
I've not seen the 2 (or 3) compared but .net and the JVM both manage to do ok on modern hardware and they must be far slower than Go. I think the only issue with Go is the lack of libraries, a problem which can be resolved.
Advertisement

[quote name='Telastyn' timestamp='1354994045' post='5008570']
[quote name='Nairou' timestamp='1354990643' post='5008549']
What I mean is, things like strings and maps, which are variable in size. They don't have a fixed memory footprint at compile time.


Unlike C++, which uses no strings or maps or variable size structures in non-trivial programs...
[/quote]

Well in C++ those are just part of STL, not build into the language. You can choose to use a std::string, or you can just use a char array. You aren't forced to use a string, because it isn't actually part of the language. In Go, you just have a string, and it's a part of the language, so they expect you to use it. Which is fine, I just want to understand what's going on behind the scenes.
[/quote]

To expand on Telastyn's response: [font=courier new,courier,monospace]std::string[/font], [font=courier new,courier,monospace]std::map[/font], [font=courier new,courier,monospace]std::vector[/font], etc. absolutely are part of the language (see section 21 for the Strings library, section 23 for the Containers library, etc.). They just aren't primitive data types.

As for "STL"... The "Standard Template Library" was written before C++ was ever actually standardized, and is different from the C++ Standard Library. When C++ was finally standardized in 1998, it incorporated parts (but not the whole thing!) of the popular STL into the standard itself (and the C++ standard defines what we call the "standard library"). The STL is not, however, fully incorporated into the standard library (for example, the STL's rope never made it into the standard library), and there are some differences between the STL and the standard library (aside from the fact that the standard library adds more than was originally in the STL). Instead of saying "the STL is the standard library," we should say "the STL influenced the creation of the standard library." Unless you're specifically referring to the Standard Template Library that was written by Alexander Stepanov and Meng Lee before C++ was ever standardized, you probably mean (and should say) the C++ Standard Library smile.png
[size=2][ I was ninja'd 71 times before I stopped counting a long time ago ] [ f.k.a. MikeTacular ] [ My Blog ] [ SWFer: Gaplessly looped MP3s in your Flash games ]

There is also this page I found but I don't really know whats GOing on here: http://golang.org/test/bench/shootout/

har har

To expand on Telastyn's response: [font=courier new,courier,monospace]std::string[/font], [font=courier new,courier,monospace]std::map[/font], [font=courier new,courier,monospace]std::vector[/font], etc. absolutely are part of the language (see section 21 for the Strings library, section 23 for the Containers library, etc.). They just aren't primitive data types.

As for "STL"... The "Standard Template Library" was written before C++ was ever actually standardized, and is different from the C++ Standard Library. When C++ was finally standardized in 1998, it incorporated parts (but not the whole thing!) of the popular STL into the standard itself (and the C++ standard defines what we call the "standard library"). The STL is not, however, fully incorporated into the standard library (for example, the STL's rope never made it into the standard library), and there are some differences between the STL and the standard library (aside from the fact that the standard library adds more than was originally in the STL). Instead of saying "the STL is the standard library," we should say "the STL influenced the creation of the standard library." Unless you're specifically referring to the Standard Template Library that was written by Alexander Stepanov and Meng Lee before C++ was ever standardized, you probably mean (and should say) the C++ Standard Library smile.png

Very interesting! :)

The point I was trying to make is that, being part of the standard library and not a core part of the language itself, they are optional. You can use a std::string if you want, with it's pro's and con's, or you can use a char*. The latter is a direct part of the language, and is very explicit and simple, while the former is part of a library and can have side effects (i.e. dynamic memory allocation behind the scenes). Once you understand how std::string works, it isn't a big deal. It's memory allocation is just a part of it's pros/cons list. But it's something to be learned.

Likewise, as someone who doesn't know Go very well, I was curious about which parts of the Go language have these sorts of memory allocation side effects. While std::string is an optional library component in C++, it appears to be a core part of the language in Go (you don't need to import anything to use a string). This makes it harder, on the surface, to know where these side effects are.

(And to those who think I'm being obsessive, read this.)
That's the biggest load of garbage I've read in a long time.

It is FAR more likely that you're going to overlook some bound or overflow some buffer than you kneecap your game because you ran out of memory via the boogeyman of inefficient standard library structures.
I am using Go for a MMORPG game. The server side is coded in Go, but the client side is coded in C++. The reason for using C++ on the client side was the lack of support libraries when the project started 2 years ago. I may have done it differently if I started with the client today.

Some details of the server design:

  • Every connected player has a goroutine of its own.
  • Quadtree algorithms (one for players and one for creatures) are used to avoid quadratic costs of information distribution.
  • As far as possible, tasks are organized as separate goroutines to enable scaling with more CPU cores.
  • Communication between goroutines is done, where feasible, in the common way of using channels instead of semaphores.
  • There are data (player information, creature data, world geography, etc.) that need to be accessed by many readers. Those cases are implemented using rwmutex.
  • To minimize the need of using more than one semaphore at the same time (for resource A and B), some cases have been possible to serialize, for example, by having a first "read-only" pass that identifies objects in A, creating a closure that does the job, and as a next step iterating that function over B (sometimes sending the closure on a channel to the responsible goroutine).
  • Player persistent data is managed in a SQL database, now being replaced by MongoDB.
  • World geography data is aggregated in chunks of 32x32x32 blocks, saved in a compressed format on the file system.
  • Chunks that have not been modified can be uniquely reconstructed by the terrain generator, independent on neighbor chunks. A chunk is cached at 4 different levels (including client+server), to minimize workload and communication, with special mechanisms to ensure consistency.

Given that the system has no logical internal boundaries, and that the world itself is very dynamic, it is difficult to support sharding. It could be done using "instancing", but has not been further elaborated for now. Source code for the server is available at https://github.com/larspensjo/ephenation-server and the client at https://github.com/larspensjo/ephenation-client.

Regarding questions of performance, the server is aggressively targeted to support 10,000 simultaneous players. This has not been verified, so I can't prove that it works. I have done some synthetic tests with 1000 players, giving a load on a Core I7 at about 10%. There are some algorithms that need to be improved to scale from 1,000 to 10,000 players, though. As mentioned elsewhere, one way to avoid high cost GC is to allocate less dynamic memory. Go is a little tricky about that, as it is not always obvious what data will be on the heap and what data will be on the stack. Especially as the compiler has some abilities to optimize (detect if data need to remain when functions is going out of scope). But there are simple things that can be done, like re-use of buffers instead of throwing them away for the GC.

The game server is not a "hard real time system", but players will sure take notice if there are delays above a certain threshold. I have various timers running to measure delays, and it "holds up" so far. A RPG game can usually allow for higher latencies than shooters.
[size=2]Current project: Ephenation.
[size=2]Sharing OpenGL experiences: http://ephenationopengl.blogspot.com/
How heavy is your world simulation though? The difference between 1k and 10k players on a server isn't necessarily linear, especially if you have any nontrivial simulation being done per-tick. Keep in mind that a lot of other factors can grow nonlinearly as well, such as dispatching socket traffic to various handlers. Especially as thread contention for shared resources increases, things can degrade at very unpredictable points.

In short, I'd caution heavily against assuming that 10% CPU at 1k players implies 10k potential players. 10k is a very, very ambitious target unless your game simulation is borderline trivial. I'd strongly recommend doing genuine load tests: the fun part of network systems is that they tend to fall over in ways you did not anticipate. Usually that means sooner than you hoped ;-)

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

This topic is closed to new replies.

Advertisement