4 ms·
This kind of thing is where I really hate the big GC systems, the JVM and the CLR. Many times, a ton of efficiency could be gained with some simple tricks. For
by MichaelGG 12y ago
This kind of thing is where I really hate the big GC systems, the JVM and the CLR. Many times, a ton of efficiency could be gained with some simple tricks. For instance, "OO style" code really loves newing up objects, even if they're simple containers. In .NET, this means a heap allocation and garbage, even if the little object is immediately picked apart and never used again.
Example, the String.Split function takes an array of chars to split on. So every time split is called, normally, a new heap alloc happens for no good reason. Functions should be able to provide some simple "pure" annotation and let callers stackalloc. In fact, the CLR already supports this - it's just super cumbersome to get at. The JVM supposedly can do this in JIT, but this post seems to indicate it's not effective.
Secondly, it seems there it often a unit of work where you could allocate from an arena, and then throw it all away together. I know this is more involved, as something could leak out by accident in code. But perhaps the arena could be a hint to the GC "if nothing points into this entire arena dump it in one chunk". Maybe GCs are too fast to benefit from this.
Really, it's s bit if what Rust will accomplish with the borrow checker. I know for a fact (measured) that adding a bit of such management to .NET would be a major boon in certain high allocation scenarios.
Edit: The evangelism around these languages doesn't help. There's a big push to leave it to the JIT, that the runtime knows best. But in truth, they seem to still have fairly suboptimal codegen. Even inlining is poorly handled. For some idiotic reason, they still JIT, and have to make a time/speed tradeoff. Even if it's an program that you're going to execute repeatedly, the installer has to go out if it's way to pre compile. And even then, the pre compiler doesn't do a lot more, and MS warns people it might be worse, because the runtime knows best. I guess no credible competition leads to not putting tons of resources on things.
- voltagex_ 12y agoWhoa. I'm assuming I'm not doing enough String.Splits to worry about performance, but what's my alternative? Looping through the string myself?
- MichaelGG 12y agoThat's just one example. There's plenty of APIs that force garbage to be created for no good reason. For string.split what I've done when it was critical, is to statically allocate an array for each type of split. For other APIs, I'd create a state object for each request or piece of work that contains assorted buffers and other temp objects, then pass it around as needed. Ugly, but at high processing rates, every allocation counts.
- pmcg 12y agoThere can be two allocations when you call String.Split, one at the calling site where you create the array of delimiters to pass in, and one inside that creates the String[] to return. The parent refers to the first, which you can avoid by creating your char[] of delimiter[s] once and reusing it.
- emjaygee 12y agoSome people have built string processing libraries which operate on struct which have a string object reference and an index to the first character and a length. This lets you slice etc the strings without forcing any heap allocations.
- readerrrr 12y agoYour comment is absolutely adorable, for anyone writing C code. :-)
- viraptor 12y agoThat's actually not true for .net. The tiny local variables can be structs rather than objects. For example if structs could be used for coordinates in this change instead of full objects, they would likely never touch the heap.
- MichaelGG 12y agoExcept it is true because structs are often frowned on, and plenty of things end up as reference types. And arrays are reference types too, so things like string.split have no workaround. Also note that until v4 or so, the CLR had limitations optimizing methods using structs. But yes, the CLR has a far superior design and can avoid some of Java's pitfalls.
- eropple 12y agoStructs are frowned upon? Since when?
- pmcg 12y agoAnyone writing Minecraft in C# would use a struct for the position vector. There's no good reason to frown upon it in this case.
- bovermyer 12y agoIt's not C#, but here's Minecraft in C++ and Lua: https://github.com/minetest/minetest https://github.com/minetest/minetest
- Arnavion 12y ago>structs are often frowned on Only in the premature optimization sense, and "familiarity with new programmers" sense. There's nothing wrong with using structs. In fact, XNA and many other game programming libraries do use structs to represent position vectors, which is the context of this article. >And arrays are reference types too, so things like string.split have no workaround. If you're okay with going unsafe, arrays can be stackalloc'd.
- 12y ago
- broodbucket 12y agoTangentially related, but there's a Minecraft clone being written in Rust. Still very early stages, but pretty cool. https://github.com/PistonDevelopers/hematite https://github.com/PistonDevelopers/hematite
- kvb 12y agoYour String.Split example seems like more of an indictment of the language/library design than the GC. For instance, the language could compile uses of an array literal to a static value that's reused across invocations.
- zak_mc_kracken 12y ago> This kind of thing is where I really hate the big GC systems, the JVM and the CLR. All the evidence points to the problem being sloppy programming and nothing to do with the runtime.
- deleted 12y ago[deleted]
- Skinney 12y agoFor .NET you should really read about structs, which allocates on the stack instead of the heap, and thus avoids the GC most of the time. Oracle is working on a struct implementation for Java 9 or Java 10. Using an arena/buffer/cache in Java and .NET is not unheard of, and is a good way of avoiding allocations, and thus postponing collections. An arena will, however, increase your live memory which means that when a collection finally does happen, it will take longer. The arena will probably be promoted to the oldgen quickly though, so the collector shouldn't scan it to often. "if nothing points into this entire arena dump it in one chunk" -> is more or less how a GC works, so you get that for free. The way the GC does it, is that it scans everything that is live and considers everything not scanned as garbage, which is then collected. The beautiful part of this, is that even if you have allocated 1Gb of memory, you only pay for what is live. If you have 200mb of live memory, collection will be constant regardless of how much garbage you have, whether it is 1Gb or 2Gb of garbage. Increasing the size of memory of course makes collections happens less often. Your point regarding JIT is irrelevant in this particular case, as this case is exclusively related to the GC. We would be talking about the same potential speed improvements here with or without a JIT. That being said, you are also wrong. A JIT can produce faster code than any pre-compiler, due to there being more information available, and because a JIT can easily "undo" compiled code, allowing it to experiment with code that can fail in certain rare cases, but most of the time is vastly faster. Mono, the open source .NET implementation, can be pre-compiled using LLVM (originally a C/C++ compiler), but using the JIT produces faster code. The reason Java and .NET is "slow" for idiomatic (not higly optimized, which most code isn't) code, is due to the GC, which causes the application to freeze at certain intervals. This problem increases the larger live memory you have. A GC is probably faster overall compared to manual memory management though, the problem with GCs today is that they de-allocate (garbage collect) all at once, instead of de-allocating memory once said memory becomes garbage, which evens things out a bit. That being said, there are of course downsides to a JIT. It takes time before the JIT is "warm", which is to say it takes time before most of the code in the application is compiled. If you have applications that run for a short amount of time (I think Java's JIT compiles functions that are run 32 times or more), like shell scripts, they get no performance benefit of using a JIT. Another thing is memory use. Since a JIT compiles code as it goes, and nothing is pre-compiled, the JIT has to store compiled code in memory. So a JIT will naturally use more memory than a pre-compiled program, and a GC normally ads to this.