Skip to content

Use sized array for List.toArray() #12153

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

Closed
wants to merge 1 commit into from
Closed

Conversation

izeye
Copy link
Contributor

@izeye izeye commented Feb 21, 2018

This PR changes to use sized array for List.toArray().

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Feb 21, 2018
@@ -92,7 +92,7 @@ public void shouldCreateInstancesForReactiveAndImperativeRepositories() {
CouchbaseReactiveRepositoriesAutoConfiguration.class }) {
names.add(type.getName());
}
return names.toArray(new String[0]);
return names.toArray(new String[names.size()]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we write it using method reference as return names.toArray(String[]::new) ?

Copy link
Member

@wilkinsona wilkinsona Feb 21, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I don't think so. AFAIK, you can't create an array using that syntax. Even if you could, it would presumably give you a 0-length array which would defeat the purpose of the change that @izeye is proposing.

Copy link
Contributor

@rajadilipkolli rajadilipkolli Feb 21, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wilkinsona You are correct, I mistook it

@joshiste
Copy link
Contributor

joshiste commented Feb 21, 2018

Afaik a sized array is not always the fastest solution. When using a sized array the VM needs to initialize it with all zeroes, so it will be inited twice. When using a unsized array the VM can omit the second first init as it knows that all positions are already empty will be set. So it depends on the array size if the double init or the additional instantiation is faster...

@izeye
Copy link
Contributor Author

izeye commented Feb 21, 2018

@joshiste Thanks for the feedback! I didn't intend to gain better performance via this change but tried to align them with the other parts in codebase and simply avoid unnecessary empty array creation. By the way I'm curious on the double initialization part from your comment. Could you explain more on it as I don't understand where the double initialization is coming from?

@wilkinsona
Copy link
Member

wilkinsona commented Feb 21, 2018

FWIW, I don't understand either. If the passed in array is too small, a new array is initialized reflectively. Once armed with an appropriate array, System.arraycopy is used to populate it. The only difference when the passed in array is used is that, if its length is greater than the list's size, array[size] is nulled out. I'd be surprised if the evaluation of the extra if condition and setting a single element to null is slower than reflectively creating a new array.

@joshiste
Copy link
Contributor

joshiste commented Feb 21, 2018

I've just done a simple JMH on this. https://gist.github.com/joshiste/9c49661be53b752b36042b05581dbf20

Results:
image
(please note the logarithmic scale)

Benchmark               (size)   Mode  Cnt         Score         Error  Units
ArrayInit.doubledSize        1  thrpt   20  38500301.375 ±  477791.966  ops/s
ArrayInit.doubledSize       10  thrpt   20  29310356.815 ±  277465.983  ops/s
ArrayInit.doubledSize      100  thrpt   20   5571228.302 ±  129209.792  ops/s
ArrayInit.doubledSize     1000  thrpt   20    473345.508 ±   12074.610  ops/s
ArrayInit.doubledSize    10000  thrpt   20     46048.324 ±     837.223  ops/s
ArrayInit.doubledSize   100000  thrpt   20      2568.249 ±      48.178  ops/s
ArrayInit.doubledSize  1000000  thrpt   20       183.246 ±       0.854  ops/s
ArrayInit.halfSize           1  thrpt   20  26512697.710 ±  379465.736  ops/s
ArrayInit.halfSize          10  thrpt   20  27783698.499 ±  451057.759  ops/s
ArrayInit.halfSize         100  thrpt   20   6168023.668 ±  131382.264  ops/s
ArrayInit.halfSize        1000  thrpt   20    556418.365 ±   27003.580  ops/s
ArrayInit.halfSize       10000  thrpt   20     52891.933 ±     577.345  ops/s
ArrayInit.halfSize      100000  thrpt   20      2958.170 ±      96.353  ops/s
ArrayInit.halfSize     1000000  thrpt   20       185.678 ±       5.977  ops/s
ArrayInit.perfectFit         1  thrpt   20  37522113.175 ±  623601.483  ops/s
ArrayInit.perfectFit        10  thrpt   20  28695350.569 ± 1579416.465  ops/s
ArrayInit.perfectFit       100  thrpt   20   6009344.886 ±  350704.834  ops/s
ArrayInit.perfectFit      1000  thrpt   20    533221.149 ±   23782.529  ops/s
ArrayInit.perfectFit     10000  thrpt   20     49193.196 ±    2090.297  ops/s
ArrayInit.perfectFit    100000  thrpt   20      2759.403 ±      69.535  ops/s
ArrayInit.perfectFit   1000000  thrpt   20       191.008 ±       1.925  ops/s
ArrayInit.zeroSize           1  thrpt   20  76837570.300 ± 6328017.558  ops/s
ArrayInit.zeroSize          10  thrpt   20  46996171.285 ±  506615.172  ops/s
ArrayInit.zeroSize         100  thrpt   20   7102430.863 ±   87709.353  ops/s
ArrayInit.zeroSize        1000  thrpt   20    638110.741 ±    7825.890  ops/s
ArrayInit.zeroSize       10000  thrpt   20     56099.741 ±     449.873  ops/s
ArrayInit.zeroSize      100000  thrpt   20      2995.854 ±     140.905  ops/s
ArrayInit.zeroSize     1000000  thrpt   20       190.813 ±       1.512  ops/s

@joshiste
Copy link
Contributor

joshiste commented Feb 21, 2018

Imho this proves (for smaller array sizes) that calling toArray(new String[0]) yields the highest throughput.

@izeye
Copy link
Contributor Author

izeye commented Feb 21, 2018

@joshiste Thanks for the feedback! I found a few articles backing your feedback like this one: https://shipilev.net/blog/2016/arrays-wisdom-ancients/

I didn't read them thoroughly yet but based on the articles the opposite direction of this PR looks right. I'll look into it soon.

@wilkinsona
Copy link
Member

Very interesting (and surprising). Thanks, @joshiste. I just stumbled across the article that @izeye linked to as well.

@philwebb
Copy link
Member

I always preferred the syntax of the zero-array better. I've raised #12160 to switch to it.

@philwebb philwebb closed this Feb 21, 2018
@philwebb philwebb added status: declined A suggestion or change that we don't feel we should currently apply and removed status: waiting-for-triage An issue we've not yet triaged labels Feb 21, 2018
@joshiste
Copy link
Contributor

joshiste commented Feb 21, 2018

My explanation wasn't quite correct...

If the toArray() method instantiates the array itself it can completely omit the initialisation of the array as it knows that it will set the complete content.
If you instantiate the array beforehand, the JVM will zero init the array, because it doesn't know that it will be completly set afterwards... So using a correct sized array results in a initialisation which won't happen when using zero sized arrays

@izeye
Copy link
Contributor Author

izeye commented Feb 22, 2018

I did read the above mentioned article closely and I think I understand the benchmark result better now which looks counter-intuitive at first glance. I'd recommend anyone interested in this topic to read the article.

@joshiste Thanks for enlightening me on this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: declined A suggestion or change that we don't feel we should currently apply
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants