-
Notifications
You must be signed in to change notification settings - Fork 208
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Java - Move method references for ParsableFactory to model types to reduce size #3152
Comments
I'm not sure I follow the change here, could you please provide examples? |
For
and on all places where
Do the same for all other I also tried reducing the size on the side of
|
So this final HashMap<String, ParsableFactory<? extends Parsable>> errorMapping = new HashMap<String, ParsableFactory<? extends Parsable>>();
errorMapping.put("4XX", ODataError.factory());
errorMapping.put("5XX", ODataError.factory()); instead of final HashMap<String, ParsableFactory<? extends Parsable>> errorMapping = new HashMap<String, ParsableFactory<? extends Parsable>>();
errorMapping.put("4XX", ODataError::createFromDiscriminatorValue);
errorMapping.put("5XX", ODataError::createFromDiscriminatorValue); I'm not sure how that saves byte code since we're introducing a new method in between? |
The Java compiler adds what's called bootstrap methods for these method references. These methods use some very large signatures, which will end up in de constant pool of the class. A bootstrap method will be reused when the same method reference is used multiple times and some of the data is shared between multiple bootstrap methods, so the biggest gain is if you remove all method references from a class. It may even be beneficial to move all method references to a single class, but I haven't tried that. To get an impression on what's in a class file, you can use |
This is what those bootstrap methods actually look like in the byte code (as you can see, the signatures are quite large):
|
Thanks for the additional information.
We have on average at least 3 of those per operation:
Which we could reduce down to 1 of those per referenced type
Which means we'd reduce by at least 2 (error mappings) x 20k operations x 500 bytes ~= 20MB for Graph v1. (currently sitting at 43MB). This sounds like a lot so I'd like to make sure:
|
You are mostly correct. Identical method references are shared, so most operations have 2 (1 for errors and 1 for the response). Multiple operations in 1 file still have 2. Every unique method reference is indeed about 500 bytes. This very simple test compiles to 1332 bytes: public class Test {
public static void main(String[] args) throws Exception {
java.util.List.of(1, 2, 3).stream().map(Object::toString).toList();
}
} When I think the most efficient way by far would be to generate 1 class with all factory methods for public class ParsableFactories {
public static com.microsoft.kiota.serialization.ParsableFactory<X> xFactory() {
return X::createFromDiscriminatorValue;
}
} I count 17265 unique (per file) references to ::createFromDiscriminatorValue over 9471 files and 3594 createFromDiscriminatorValue methods. Given the size of the signatures, I would say the first reference would add about 650 bytes and any subsequent reference about 250. This would give a total size of all references at this moment of 8104650 bytes. After moving them to a single class, this class will be about 1.4MB (taking 400 bytes per method). The calls to the factory methods will probably take about 100 bytes each (per unique call), adding back another 1.7MB. The total savings will then be about 5MB in class size, which will be about 2MB in jar file size. |
One thing you need to take into account with this solution is the maximum size of the constant pool of a class: 65535 entries. A quick test shows that every method will probably take 9 entries, which will limit this to a maximum of about 7000 types. This is still twice as much as needed for graph, but something you might want to keep in the back of your mind. |
Update: the jar file only decreased 2MB with the changes we've implemented above This is consistent with the early estimates you had provided, moving the factory references would most likely result in a 5-10% reduction ~2 to 4MB. While appreciable, this is not going to be enough to allow supporting both a sync and an async API. |
Good to hear this helped to reduce the size of the SDKs. I must say I was a bit surprised myself to find out that method references take so much space. |
Assigning to @ramsessanchez as we're looking at wrapping Java up. To recap, in the models we want an additional method: // notice the unique name to avoid conflict in inheritance
public static com.microsoft.kiota.serialization.ParsableFactory<ODataError> oDataErrorfactory() {
return ODataError::createFromDiscriminatorValue;
} and in the request builders we want the following changes final HashMap<String, ParsableFactory<? extends Parsable>> errorMapping = new HashMap<String, ParsableFactory<? extends Parsable>>();
-errorMapping.put("4XX", ODataError::createFromDiscriminatorValue);
-errorMapping.put("5XX", ODataError::createFromDiscriminatorValue);
+errorMapping.put("4XX", ODataError.oDataErrorfactory());
+errorMapping.put("5XX", ODataError.oDataErrorfactory());
-return this.requestAdapter.sendAsync(requestInfo, MessageCollectionResponse::createFromDiscriminatorValue, errorMapping);
+return this.requestAdapter.sendAsync(requestInfo, MessageCollectionResponse.messageCollectionResponseFactory(), errorMapping); And before this change gets merged in, we want to validate we achieved a size decrease of the jar (should be about 2MB) |
Also for reference, the previous version of the Graph SDK (different generator), is only 11MB. This new version supports much more API surface and pattern of course, and we expect it to get smaller with that change as well as because we'll be stripping the completable futures. |
With the recent code reduction changes we've made, we've been able to reduce the full service library from 111MB down to 46MB. (and a few hundred operations were added during that time, plus new capabilities) |
Each unique method reference seems to add about 500 bytes to a class file. Using, for example, ODataError::createFromDiscriminatorValue in almost every request builder will cost a lot. Moving these to the model types allows for reuse from different request builder classes. A quick count showed that this might save as much as 5 to 10% on the total size of the jar.
The text was updated successfully, but these errors were encountered: