7 ms·
More suitable sure but I'm not convinced you'd want to use anything but C or C++ for a server that needs to be as "real-time" as possible. I wish GO could use R
by BinaryIdiot 12y ago
More suitable sure but I'm not convinced you'd want to use anything but C or C++ for a server that needs to be as "real-time" as possible. I wish GO could use RAII or another memory scheme so they could drop GC altogether.
- Thaxll 12y agoIf you have pause of only 10ms it's pretty good even for a gameserver. 10ms is 100fps ( server side ) the only game server that fast I know of is the source engine with CS:Go.
- mrob 12y agoThat's assuming you can do all your other calculations in 0ms, which is unrealistic.
- stcredzero 12y agoBut it's usable for an upper bound.
- comex 12y agoHere's another way of seeing it: if your target tick rate is 60Hz (~16ms), when the GC decides to trigger a collection, you lose most of your time budget. You'd have 6ms left on average, sometimes less, so to avoid dropping any frames you'd probably want your tick routine to complete in 5ms. In other words, when it comes to work that must be done every tick (which is probably most of it), you could only safely use less than a third of available CPU time! I guess dropping a single frame on occasion may not be that bad, and the GC parameters could be retuned (and other GC languages have lower latency), and you could minimize the amount of garbage created, but still, that's not a good situation to be in.
- tormeh 12y agoWhat I've heard is that games aren't particularly heavy on the GC anyway, because they load in the objects in the level and then they don't create much else in terms of objects. There's just not all that much to be collected. I guess it's a bit more complicated for open-world games that need to load and delete game world objects on-the-fly, though.
- aethr 12y agoIt depends. Sure you can preload all your world props and level entities, but what about things like projectiles being fired, travelling, and then being de-allocated? A good strategy for handling this in a GC'd language is using the factory / pool patterns, so during level loading you instantiate 100 "empty" projectile entities that you can re-use. When a projectile is "fired" you request a Projectile from the ProjectilePool, add its initial position/speed/whatever, then add it to the world. It goes through its trajectory, maybe hits a wall, and then you release it back to the ProjectilePool where it is cleared and can be re-used later. But what if you need 101 projectiles...?
- AYBABTME 12y agoHow big would a projectile be? I'm not super familiar with how games encode their state, but I doubt things like projectiles would account for much memory. Which makes me think that it wouldn't cause problems with GC. Just guessing =P
- aethr 12y agoThat's true, but as slimsag pointed out below, long GC pauses (which are what we're trying to avoid) are caused by large numbers of heap-allocated objects, not by the size of the objects.
- chipsy 12y agoThe 101 projectiles case is not such a useful what-if in practice. It's a game, so your final tuning comes down to "what will hit my framerate target and memory budget," not what a user requested. If it struggles to keep up with 101 projectiles, you have to either design it down or accept dropped frames in the most demanding situations. So a pool's size limit acts as a guide for the other budgeting considerations anyway - and it plays well with the traditionally constrained memory budgets of console games. Of course, AAA game engines today will employ custom low-level allocation strategies in order to squeeze out the last few percentage points where they deal with dynamic data - but if your aims are more modest, and you harken back to 80's era gaming, most of those games had a nearly or entirely static memory map. Besides being a poor fit for those tiny microprocessors, dynamic allocation enables out-of-memory and fragmentation errors to exist, leading us back to the "hard limits on instancing" strategy again. GC still incurs overhead where it has to trace, even if you've pooled everything. Reference counting pulls ahead of tracing collectors in the situation of heavy object persistence, despite being more expensive as a general allocation method. As well, it has predictable latency, which is more important for meeting real-time requirements. (N.B. as systems parallelize, they become more bound by latency considerations. Seven workers may finish early, but delivery will still get bottlenecked by the eighth slow one.)
- mseepgood 12y agoYou both are "calculating" nonsense
- stcredzero 12y agoMore suitable sure but I'm not convinced you'd want to use anything but C or C++ for a server that needs to be as "real-time" as possible. For what value of "as possible?" It's kind of a miracle that twitch game mechanics FPS work at all with typical Internet latencies, which is one of the various reasons why I'm not doing that. "As possible" in my situation, doesn't include the resources to do that. I wish Go could use RAII or another memory scheme so they could drop GC altogether. There's always Rust. Also, Erlang can "cheat" at GC in some ways due to immutability. Server side Swift has potential as well.
- BinaryIdiot 12y ago> For what value of "as possible?" Really depends on the use case. Obviously "real-time" in a money context is very different than gaming. For gaming GO will probably be fine but that's also depending on how the game can scale. > There's always Rust. I've heard good things about rust though C++ is also pretty fantastic but I guess it's not as popular anymore.
- m0th87 12y agoTo expand on immutability and GC, this article explains why it's a big win for GHC: https://wiki.haskell.org/GHC/Memory_Management https://wiki.haskell.org/GHC/Memory_Management Additionally, Erlang can also make "soft real-time" guarantees because each actor has its own heap, and consequently each actor is GC'd separately.
- azth 12y agoYou might want to check out Rust. It was developed to be a C++ replacement, and it does have RAII.
- jaimeyap 12y agoYou can still stack allocate things in Go. And use closures when you need nested scopes. This lets you take garbage off the heap, and avoid GC all together if you want to. I believe Go does escape analysis so you can still shuffle around pointers to local structs within a stack frame without causing a heap allocation. edit: That being said you can't really implement something like refcounting for structs you pass around by pointer outside the local stack frame without the compiler emitting an allocation on the heap. To totally avoid using the GC you'll need to do a lot of copying. And there aren't destructors.
- PopsiclePete 12y agoNo thanks. Watching you "manual memory all the way" dudes struggle with simple concurrency as much as you do, I'd hate to throw away a GC just for that extra teeny bit of performance.
- comex 12y agoWell, the 10ms cited as desired GC latency is hardly an extra teeny bit of performance for a game server which would like to tick at at least 60fps (~16ms frame time), which is common for FPS; other types of games are different of course. I suppose it could be tuned to further decrease latency at the cost of throughput...
- crawshaw 12y agoThe 10ms number is an upper bound. You will only see pauses that long if you generating large amounts of garbage, which is relatively easy to avoid.
- oofabz 12y agoThe duration of a pause has more to do with the size of your heap than with how much garbage you generate. If you generate a lot of garbage, the pauses don't get longer, they get more frequent.
- slimsag 12y agocrawshaw isn't wrong -- and he's also the guy working on Android support. Go's GC scans pointers in the heap. - A 10GB heap with one single heap-allocated []byte will have short <1ms pauses. - A 512MB heap with 500k heap-allocated []byte will have long pauses.
- steveklabnik 12y agoWhile I agree in the general case, I'm really excited to show off what Rust can do here. We've made some changes lately that make concurrent stuff safe that I'm not sure other languages can do. My favorite latest example: extern crate threadpool; use threadpool::ScopedPool; fn main() { let mut numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; { let pool = ScopedPool::new(4); for x in &mut numbers[..] { pool.execute(move || { *x += 1; }); } } println!("{:?}", numbers); } This allocates a mutable array on the stack, and then creates a threadpool which adds one to each element of the array, four threads at a time. We need the inner {}s so that we know that the pool is done working before we try to print the result. Yes, mutable pointers into the parent stack frame. But, the compiler can verify that this is absolutely safe. Say, for example, that we left off the inner scope, so that the pool might not be destroyed and therefore join before we try to print out the array. That'd be racy in most languages. In Rust, it's a compile-time error: error: cannot borrow `numbers` as immutable because it is also borrowed as mutable println!("{:?}", numbers); ^~~~~~~ note: previous borrow of `numbers` occurs here; the mutable borrow prevents subsequent moves, borrows, or modification of `numbers` until the borrow ends for x in &mut numbers[..] { ^~~~~~~ note: previous borrow ends here fn main() { } ^ Rust knows that you're still holding a mutable reference to the array, and so taking a new immutable one could case a race. As you can see from the `extern crate`, this pool isn't a compiler built-in: it's a library. There's actually a secondary implementation of a threadpool that makes some different choices internally. But this kind of safety can be gained in whatever concurrent code you're writing, and you can write new concurrent abstractions and ensure that they don't have data races.
- pjmlp 12y agoGiven that game studios are using Erlang, JVM and .NET for their game servers, I doubt that is an issue.