4 ms·
> No garbage collection, so no GC performance hiccups. If your models are naturally highly cyclic, dropping a GC can vary from painful to near impossible. I th
by johncolanduoni 10y ago
> No garbage collection, so no GC performance hiccups.
If your models are naturally highly cyclic, dropping a GC can vary from painful to near impossible. I think a better solution for most server-side code (from both an ease of use and performance standpoint) is to use a hybrid GC/arena approach, centered around tasks.
If most of your work and memory churn centers around discrete and relatively short-lived requests, you can allocate all objects whose lifetimes are bounded by the duration of the transaction in a thread-local arena. You can't beat unsynchronized bump-pointer allocation, and you can simply throw the memory away at the end.
I've been really disappointed with the lack of innovation in the memory management space. Rust is the only recent language I've seen that did something more significant than choose between GC and reference-counting.
- abalone 10y agoPainful in terms of what?
- johncolanduoni 10y agoJumping through hoops and hoping you put enough weak pointers. Such structures, if they are getting mutated, also tend to take significant performance hits even if you get it right, because of all the synchronized increment/decrementing. Another place where this can really hurt is "reactive" approaches, where cycles are very hard to avoid. Weak delegates (like what WinRT uses for events) can help here, but they raise a number of complications. The biggest is that they compose poorly: if you have an intermediary in your event chain, you need to make sure the subscription is the only place where a reference is being held.
- abalone 10y agoFWIW I will try to summarize the Swift architect's comments on this:[1] a) Declaring weak pointers does take some more reasoning about your object graph. A silver lining is it makes the intention of your code more explicit which helps with maintenance. b) Swift emphasizes value types which reduces the need for pointers vis-a-vis Java. c) Most garbage collectors also add overhead when updating object references. d) Swift is massively more memory efficient. Fast GC requires 3-4X the memory. [1] https://www.quora.com/Why-doesnt-Apple-Swift-adopt-the-memory-management-method-of-garbage-collection-like-in-Java https://www.quora.com/Why-doesnt-Apple-Swift-adopt-the-memor...
- johncolanduoni 10y agoa. This one smacks of sour grapes. It doesn't help with maintenance when you're dealing with a dense graph, and every time you add a relation you have to check if you need to make something weak. Reasoning about reference graphs is often not intuitive, due to it being hard to see indirect references. Also note that some cyclical structures cannot be broken by statically using weak references, and require special action like specialized manual reference counting (I recently had to implement such a system ). b. Very true, but these are the exact opposite of the cases that I'm talking about. c. Two things: First, memory has gotten incredibly cheap and dense for servers. Server GCs are generally more cache friendly than reference counting (due to both allocation strategies/compaction and the lack of need to touch reference counts), which is important because CPU performance gains are slowing down. Second, the graph he shows (I've seen it before) is looking at older GCs, and more importantly GCs where memory usage outside of the working set was not optimized for. In a paged system, it is of little benefit to ensure memory outside your working set is released. Obviously on mobile the situation is different, but there are also GCs that have far lower memory usage because they have been optimized for constrained scenarios. Android's new ART collector is sort of an example of this, but their efforts were massively undermined by the fact that they had to adapt to all of the existing Android library/app code which uses tons of object pooling. Manual object pooling with GCed objects creates all kinds of problems for GCs, particularly generational ones. I agree with the decision to go with ARC for Swift from a lot of perspectives: Swift's target market is not usually dealing with complex interconnected models, using a GC would make ObjC interop difficult, etc. But it is far from obvious based on this information that it is beneficial to have a language stick with only a GC or only reference counting.
- abalone 10y agoWithout knowing specifics it seems like you're focusing on complicated "dense graph" scenarios. Perhaps Swift is a bit harder for those.[1] However your initial claim was that dropping GC was bad for "most server-side code" (emphasis mine). Does most server-side code involve dense graphs? As a point of logic, optimizing to make 95% of cases a lot less "painful" would be worth a tradeoff of a bit more pain in the already difficult 5%. Thus if Swift fits that description it stands to reason that it could displace server-side Java. [1] (Although one could argue those are exactly the scenarios where reasoning more carefully about your object graph is helpful.)
- fpoling 10y agoWorking with cycles under reference counting is not impossible. The key is good memory reporting tools. Yes, sometimes even with tools identifying a memory leak through cycle with reference counting can be painful. However, the fix itself is typically very local and easy to test. Compare that with a C#/Go/Java application after it turned out that GC-induced latency is unacceptable. The fix could be a massive rewrite that makes the resulting code maintenance nightmare. And if in the case of Java there are at least some knobs to tune the GC that could be enough to fix the problem, with Go or C# the code rewrite is the only solution. As for local object pools, they are nice as long as their size is smaller than CPU cache. Without the control on the pull size it is very to easy to accumulate a lot of local garbage that it starts to hurt the performance.
- johncolanduoni 10y agoThe problem with both approaches is how binary they are. GC latency is an issue if the active part of the heap gets too big and/or any strategies you have for dealing with short lived garbage (e.g. generations) get overwhelmed. The simple solution is to give flexibility in how allocations are done. C# does this a bit but makes you pick per type, instead of per instance. Go goes a bit further, but it still only leaves you with the options of stack allocated/containment and GC. What would be ideal is having stack allocated, ARC, and GC all be options in the same program. Another advantage of the task-focused approach is that you can optionally collect the arena if it has gotten too big without halting the whole program (just the running task). In this case, you may have latency on one request but it need not effect latency on other requests. > As for local object pools, they are nice as long as their size is smaller than CPU cache. Without the control on the pull size it is very to easy to accumulate a lot of local garbage that it starts to hurt the performance. How is this different from the ARC case? If your working set fits in the cache, great, if it doesn't, then you'll have problems. If your allocation/release patterns are such that the allocator keeps handing you the same exact pieces of memory, then you're basically doing stack allocation. Not to mention that RC doesn't help the cache-unfriendliness that comes from heap fragmentation.
- fpoling 10y ago