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
Optimize memory allocations in StringUtils#cleanPath #26316
Conversation
The list will contain at most pathElements.length elements, and in most cases exactly this number of elements. Directly set this as initial capacity.
Introduce an optimized method to join a collection of strings. Iterating the list is cheaper than resizing (allocate + copy) the backing array of the StringBuilder used to collect the elements and delimiters.
Optimize the optimization: if the path did not contain any components that required cleaning, return the (normalized) path as-is. No need to concatenate the prefix and the trailing path.
Is there still anything required from my part? Should I provide benchmarks to prove the optimization? Is something else blocking this change? |
This commit applies several optimizations to StringUtils#cleanPath and related methods: * pre-size pathElements deque in StringUtils#cleanPath with pathElements.length elements, since this this is the maximum size and the most likely case. * optimize StringUtils#collectionToDelimitedString to calculate the size of the resulting String and avoid array auto-resizing in the StringBuilder. * If the path did not contain any components that required cleaning, return the (normalized) path as-is. No need to concatenate the prefix and the trailing path. See gh-26316
Closed with cc026fc |
Thanks @knittl - this is now merged. |
@bclozel thanks for merging, polishing, and benchmarks! I wonder how big the overhead of converting each collection item to a string when pre-computing the string builder size for non-string collections (in cc026fc)? For string collections, it is obviously just a no-op, but for generic collections of a different type stringifying each of its elements twice might incur quite some memory pressure and runtime overhead (depending on the toString complexity)? One of the reasons the method was duplicated was to have a specialization for string collections. One idea I had was to pre-compute all string representations, copy them to a new collection, then use this collection to populate the string builder. With your JMH benchmarks (thanks!), copying the collection seems to incur quite some overhead itself. |
This commit applies several optimizations to StringUtils#cleanPath and related methods: * pre-size pathElements deque in StringUtils#cleanPath with pathElements.length elements, since this this is the maximum size and the most likely case. * optimize StringUtils#collectionToDelimitedString to calculate the size of the resulting String and avoid array auto-resizing in the StringBuilder. * If the path did not contain any components that required cleaning, return the (normalized) path as-is. No need to concatenate the prefix and the trailing path. See spring-projectsgh-26316
Investigating once again slow Spring Boot start times, I played around with Java Mission Control profiler and found out that
StringUtils#cleanPath
alone is responsible for 7% of all CPU time spent during setup of the application context. Looking at the implementation I was able to identify some potential hotspots (e.g. LinkedList). The application is currently using Spring Framework 5.1.10 and I was happy to find that some of these issues have already been fixed in more recent versions of Spring.However there are still a few more opportunities to avoid memory allocations and copying of data:
return "" + somestring;
is slower thanreturn somestring;
. Obviously it is more expensive memory-wise due to the construction of the newly resulting string.This PR relates to and builds on #24674, #25650, #25552, #25553, and others.