Hello, apologies for the slow response, been busy as well. Thanks everyone for your input, it's been helpful understanding the general mindset of approaches and learning how things work.
Eric Miles: > If you want to try some smaller optimizations in the bytecode generation then > you’ll start to get some perspective on how easy/hard it can be to translate > AST into bytecode. > There is room for improvement in our bytecode, for sure. It just happens to > be the part of the code that is most full of pitfalls and weirdness and > requires a very steady hand and a lot of testing and sometimes trial and > error. I've been playing around with some ideas in the classgen and it indeed looks quite tricky... some of the compilation artefacts start making more sense now, I'll bear your word of caution in mind when working on it. Before actually looking at it, I was half-hoping there was some kind of special "Optimization phase" for providing more context for the classgen, although it seems like the optimizing statement writer is somewhat used for that purpose? I think I need to spend more time poking around the code to properly understand how it works. Jochen Theodorou: > InvokerHelper normally contains methods for the users of the language to > help the with mostly invocation related issues like invoking a method in > a dynamic way or such. There is more in there, but usually we only add > if really really needed. ScriptBytecodeAdapter (SBA) is for helper code > directly called from the bytecode and tailored to the needs of those > calls only. Good to know, in that case I'll go back to using SBA. > VMPlugin should not call into SBA or InvokerHelper. Should I be copying over code in that case? Or should it be calling DGM instead? > I actually now wonder why you do Arrays.stream(values).toList(); in the > Java16 version. Would List.of be better if all elements are not null? If > yes, then why not check them? And why go with List#of and catch-NPE > instead of stream before? Stream#toList ends up creating the same kind of object (with its JVM @Stable optimisations) as List#of (see java.util.stream.ReferencePipeline#toList). The difference is the former allows nulls, while the latter iterates over each parameter and throws an NPE if any are null. Using the try-catch approach is slower because in the worst-case scenario it would be performing lots of null checks only to give up and use Collections#unmodifiableList, which the JVM can't optimise as easily due to being backed by a mutable collection. > In the end though I > second Miles and wonder if it is worth it. Java 9's immutable collections can have meaningful performance advantages for grabbing data (akin to records) and avoiding redundant copying, although in retrospect, I agree that my current approach is a bit over the top in the context of the generated toList()/toMap() record methods. Paul King: > what do you think of this simplified PR: > https://github.com/apache/groovy/pull/2047 > > It could be made to know about emptyList(), singletonList() etc., but I am > not sure the added complexity adds much value. Admittedly, I got carried away with optimising for every scenario. Technically my approach maximises the chances of using the special immutable collections and minimising GC pressure (all empty lists reference the same field for example) - both at compile-time and runtime - however it's a lot more code than just simply wrapping it in an unmodifiable list. I'm happy with your simplified PR if you'd like to go ahead with that. Later on, I could re-evaluate my approach to be less micro-optimised and/or more generalised so that it could benefit other parts of the Groovy language to better justify its complexity. Best regards, Oscar ________________________________ From: Jochen Theodorou <blackd...@gmx.org> Sent: 27 January 2024 11:38 To: dev@groovy.apache.org <dev@groovy.apache.org> Subject: Re: Questions regarding implementation approach for GROOVY-11294 Hi Oscar, sorry for replying so late, I was very busy this week. On 22.01.24 20:55, Oscar N wrote: > Indeed, that is the crux of my earlier questions. I've got it working at > runtime, however I'm unsure of which approach to take for compile-time. > > Adding new `ScriptBytecodeAdapter`/`InvokerHelper` methods that call a > `VMPlugin` method allows for optimising other scenarios, such as > `DefaultGroovyMethods.asImmutable(List)`. InvokerHelper normally contains methods for the users of the language to help the with mostly invocation related issues like invoking a method in a dynamic way or such. There is more in there, but usually we only add if really really needed. ScriptBytecodeAdapter (SBA) is for helper code directly called from the bytecode and tailored to the needs of those calls only. Used mostly if too complex to compile directly to bytecode or if further helper structures like the VMPlugin are required. Here again the rule is trying to avoid using it, but the VMPlugin is not for direct calling, so if you do like you did in your started implementation, then going through SBA looks to me like the right way. DefaultGroovyMethods are methods made available by the runtime for method calls. DefaultGroovyMethods.asImmutable(List) would mean List#asImmutable() ah yes.. and VMPlugin should not call into SBA or InvokerHelper. > You can find my current implementation here: PaintNinja/groovy at > GROOVY-11294-record-immutable-tolist (github.com) > <https://emea01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2FPaintNinja%2Fgroovy%2Ftree%2FGROOVY-11294-record-immutable-tolist&data=05%7C02%7C%7C9674845022244ec5045e08dc1f2c8ad6%7C84df9e7fe9f640afb435aaaaaaaaaaaa%7C1%7C0%7C638419523376485393%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C0%7C%7C%7C&sdata=rcYmeVpSJw1X5UfUGmSGFfxBPfM8yoRQsZp62jk3nqQ%3D&reserved=0<https://github.com/PaintNinja/groovy/tree/GROOVY-11294-record-immutable-tolist>> well.. I suggested some changes above already. In the end though I second Miles and wonder if it is worth it. > As for compile-time optimisations, I'm not very familiar with Groovy's > compiler internals. What would be the most suitable place to do JVM > version-specific optimisations? For bytecode generation we ask the supported version in the compiler config. For the runtime VMPlugin is the right place. > Note that the exact type of immutable list used also varies on the > nullity of items and the length of the array, so for optimal performance > it's unfortunately not as simple as one method for all lists on one Java > version and another method on a different version. I actually now wonder why you do Arrays.stream(values).toList(); in the Java16 version. Would List.of be better if all elements are not null? If yes, then why not check them? And why go with List#of and catch-NPE instead of stream before? bye Jochen