| <!DOCTYPE html> |
| <!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]--> |
| <!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]--> |
| <!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]--> |
| <!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]--><head> |
| <meta charset='utf-8'/><meta http-equiv='X-UA-Compatible' content='IE=edge'/><meta name='viewport' content='width=device-width, initial-scale=1'/><meta name='keywords' content='groovy, records, java, scala, kotlin, lombok'/><meta name='description' content='This post looks at the performance of some of the generated methods in Groovy records.'/><title>The Apache Groovy programming language - Blogs - Groovy Record Performance</title><link href='../img/favicon.ico' type='image/x-ico' rel='icon'/><link rel='stylesheet' type='text/css' href='../css/bootstrap.css'/><link rel='stylesheet' type='text/css' href='../css/font-awesome.min.css'/><link rel='stylesheet' type='text/css' href='../css/style.css'/><link rel='stylesheet' type='text/css' href='https://cdnjs.cloudflare.com/ajax/libs/prettify/r298/prettify.min.css'/> |
| </head><body> |
| <div id='fork-me'> |
| <a href='https://github.com/apache/groovy'> |
| <img style='position: fixed; top: 20px; right: -58px; border: 0; z-index: 100; transform: rotate(45deg);' src='/img/horizontal-github-ribbon.png'/> |
| </a> |
| </div><div id='st-container' class='st-container st-effect-9'> |
| <nav class='st-menu st-effect-9' id='menu-12'> |
| <h2 class='icon icon-lab'>Socialize</h2><ul> |
| <li> |
| <a href='https://groovy-lang.org/mailing-lists.html' class='icon'><span class='fa fa-envelope'></span> Discuss on the mailing-list</a> |
| </li><li> |
| <a href='https://twitter.com/ApacheGroovy' class='icon'><span class='fa fa-twitter'></span> Groovy on Twitter</a> |
| </li><li> |
| <a href='https://groovy-lang.org/events.html' class='icon'><span class='fa fa-calendar'></span> Events and conferences</a> |
| </li><li> |
| <a href='https://github.com/apache/groovy' class='icon'><span class='fa fa-github'></span> Source code on GitHub</a> |
| </li><li> |
| <a href='https://groovy-lang.org/reporting-issues.html' class='icon'><span class='fa fa-bug'></span> Report issues in Jira</a> |
| </li><li> |
| <a href='http://stackoverflow.com/questions/tagged/groovy' class='icon'><span class='fa fa-stack-overflow'></span> Stack Overflow questions</a> |
| </li><li> |
| <a href='http://groovycommunity.com/' class='icon'><span class='fa fa-slack'></span> Slack Community</a> |
| </li> |
| </ul> |
| </nav><div class='st-pusher'> |
| <div class='st-content'> |
| <div class='st-content-inner'> |
| <!--[if lt IE 7]> |
| <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p> |
| <![endif]--><div><div class='navbar navbar-default navbar-static-top' role='navigation'> |
| <div class='container'> |
| <div class='navbar-header'> |
| <button type='button' class='navbar-toggle' data-toggle='collapse' data-target='.navbar-collapse'> |
| <span class='sr-only'></span><span class='icon-bar'></span><span class='icon-bar'></span><span class='icon-bar'></span> |
| </button><a class='navbar-brand' href='../index.html'> |
| <i class='fa fa-star'></i> Apache Groovy |
| </a> |
| </div><div class='navbar-collapse collapse'> |
| <ul class='nav navbar-nav navbar-right'> |
| <li class=''><a href='https://groovy-lang.org/learn.html'>Learn</a></li><li class=''><a href='https://groovy-lang.org/documentation.html'>Documentation</a></li><li class=''><a href='/download.html'>Download</a></li><li class=''><a href='https://groovy-lang.org/support.html'>Support</a></li><li class=''><a href='/'>Contribute</a></li><li class=''><a href='https://groovy-lang.org/ecosystem.html'>Ecosystem</a></li><li class=''><a href='/blog'>Blog posts</a></li><li class=''><a href='https://groovy.apache.org/events.html'></a></li><li> |
| <a data-effect='st-effect-9' class='st-trigger' href='#'>Socialize</a> |
| </li><li class=''> |
| <a href='../search.html'> |
| <i class='fa fa-search'></i> |
| </a> |
| </li> |
| </ul> |
| </div> |
| </div> |
| </div><div id='content' class='page-1'><div class='row'><div class='row-fluid'><div class='col-lg-3'><ul class='nav-sidebar'><li><a href='./'>Blog index</a></li><li class='active'><a href='#doc'>Groovy Record Performance</a></li><li><a href='#_our_domain' class='anchor-link'>Our domain</a></li><li><a href='#_performance_of_hashcode' class='anchor-link'>Performance of <code>hashCode</code></a></li><li><a href='#_performance_of_equals' class='anchor-link'>Performance of <code>equals</code></a></li><li><a href='#_conclusion' class='anchor-link'>Conclusion</a></li></ul><br/><ul class='nav-sidebar'><li style='padding: 0.35em 0.625em; background-color: #eee'><span>Related posts</span></li><li><a href='./deep-learning-and-eclipse-collections'>Deep Learning and Eclipse Collections</a></li><li><a href='./reading-and-writing-csv-files'>Reading and Writing CSV files with Groovy</a></li><li><a href='./deck-of-cards-with-groovy'>Deck of cards with Groovy, JDK collections and Eclipse Collections</a></li><li><a href='./comparators-and-sorting-in-groovy'>Comparators and Sorting in Groovy</a></li><li><a href='./fun-with-obfuscated-groovy'>Fun with obfuscated Groovy</a></li><li><a href='./testing-your-java-with-groovy'>Testing your Java with Groovy, Spock, JUnit5, Jacoco, Jqwik and Pitest</a></li><li><a href='./groovy-records'>Groovy Records</a></li><li><a href='./using-groovy-with-apache-wayang'>Using Groovy with Apache Wayang and Apache Spark</a></li></ul></div><div class='col-lg-8 col-lg-pull-0'><a name='doc'></a><h1>Groovy Record Performance</h1><p><span>Author: <i>Paul King</i></span><br/><span>Published: 2023-05-09 11:39PM (Last updated: 2023-05-10 07:57PM)</span></p><hr/><div id="preamble"> |
| <div class="sectionbody"> |
| <div class="paragraph"> |
| <p>We highly recommend the excellent |
| <a href="https://www.youtube.com/results?search_query=%23jepcafe">JEP Café</a> |
| series on the |
| <a href="https://www.youtube.com/@java">@java</a> YouTube channel. |
| Features arriving on the JDK are likely to be features you will |
| be able to use in Groovy soon too!</p> |
| </div> |
| <div class="paragraph"> |
| <p>In JEP Café <a href="https://www.youtube.com/watch?v=1oC9ESbyvqs">Episode 8</a>, |
| <a href="https://twitter.com/JosePaumard">José Paumard</a> looks at a number of topics related to Records including the performance |
| of some of the generated methods like <code>hashCode</code> and <code>equals</code>. |
| It compares the performance of those methods for Java, Kotlin |
| data classes and Lombok’s @Data classes.</p> |
| </div> |
| <div class="paragraph"> |
| <p>Let’s do a similar comparison adding in Scala case classes |
| and Groovy records. For Groovy, we’ll cover native records |
| and emulated records (which you can use on JDK8 and above |
| if you are still stuck on older JDK versions).</p> |
| </div> |
| </div> |
| </div> |
| <div class="sect1"> |
| <h2 id="_our_domain">Our domain</h2> |
| <div class="sectionbody"> |
| <div class="paragraph"> |
| <p>We’ll use the example about 7½ minutes into the JEP Café episode. |
| It is an aggregate of five Strings |
| which together form a kind of aggregate label.</p> |
| </div> |
| <div class="sect2"> |
| <h3 id="_java_record">Java record</h3> |
| <div class="paragraph"> |
| <p>Here is the Java version:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="java">public record JavaRecordLabel(String x0, String x1, String x2, String x3, String x4) { }</code></pre> |
| </div> |
| </div> |
| </div> |
| <div class="sect2"> |
| <h3 id="_java_class_with_lomboks_data">Java class with Lombok’s @Data</h3> |
| <div class="paragraph"> |
| <p>For Lombok, the default equivalent will look like this:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="java">@Data |
| public class LombokDataLabel { |
| final String x0, x1, x2, x3, x4; |
| }</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>But to get behavior closer to records, we can configure Lombok in a different way like this:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="java">@Data |
| @EqualsAndHashCode(doNotUseGetters = true) |
| public class LombokDirectDataLabel { |
| final String x0, x1, x2, x3, x4; |
| }</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>We’ll discuss this in more detail later.</p> |
| </div> |
| </div> |
| <div class="sect2"> |
| <h3 id="_kotlin_data_class">Kotlin data class</h3> |
| <div class="paragraph"> |
| <p>Here is the Kotlin equivalent:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="kotlin">data class KotlinDataLabel ( |
| val x0: String, val x1: String, val x2: String, val x3: String, val x4: String)</code></pre> |
| </div> |
| </div> |
| </div> |
| <div class="sect2"> |
| <h3 id="_scala_case_class">Scala case class</h3> |
| <div class="paragraph"> |
| <p>Here is the Scala equivalent:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="scala">case class ScalaCaseLabel(x0: String, x1: String, x2: String, x3: String, x4: String)</code></pre> |
| </div> |
| </div> |
| </div> |
| <div class="sect2"> |
| <h3 id="_groovy_record">Groovy record</h3> |
| <div class="paragraph"> |
| <p>Here is the Groovy equivalent:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="groovy">record GroovyRecordLabel(String x0, String x1, String x2, String x3, String x4) { }</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>This will produce bytecode similar to a native Java record |
| when run on a version of the JDK supporting records. |
| On earlier JDK versions, it will produce an emulated record. |
| We are going to use JDK17 for our examples. We can force the |
| Groovy compiler to produce emulated code with JDK17 by |
| applying a record option annotation as shown here:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="groovy">@RecordOptions(mode = RecordTypeMode.EMULATE) |
| record GroovyEmulatedRecordLabel(String x0, String x1, String x2, String x3, String x4) { }</code></pre> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| <div class="sect1"> |
| <h2 id="_performance_of_hashcode">Performance of <code>hashCode</code></h2> |
| <div class="sectionbody"> |
| <div class="paragraph"> |
| <p>Our benchmark code was written in Java for all cases |
| and used <a href="https://github.com/openjdk/jmh">JMH</a>. |
| It called the <code>hashCode</code> method of a static instance |
| for each of the different cases. |
| The full source code is in a <code>record-performance</code> <a href="https://github.com/paulk-asert/record-performance/">repo</a> on GitHub.</p> |
| </div> |
| <div class="paragraph"> |
| <p>Here is an example of setting up the static instance (<code>X0</code> .. <code>X4</code> are String constants):</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="java">private static final JavaRecordLabel JAVA_RECORD_LABEL = new JavaRecordLabel(X0, X1, X2, X3, X4);</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>Here is an example of the benchmark test:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="java">@Benchmark |
| public void hashcodeJavaRecord(Blackhole bh) { |
| bh.consume(JAVA_RECORD_LABEL.hashCode()); |
| }</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>We used 3 warmup iterations and 10 benchmark iterations:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="groovy">jmh { |
| warmupIterations = 3 |
| iterations = 10 |
| fork = 1 |
| timeUnit = 'ns' |
| benchmarkMode = ['avgt'] |
| }</code></pre> |
| </div> |
| </div> |
| <div class="sect2"> |
| <h3 id="_results">Results</h3> |
| <div class="paragraph"> |
| <p>The tests were run using GitHub actions on various platforms. |
| The results show average time taken in nanoseconds, so a smaller <em>Score</em> means faster.</p> |
| </div> |
| <div class="sect3"> |
| <h4 id="_using_ubuntu_latest">Using ubuntu-latest</h4> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre>Benchmark Mode Cnt Score Error Units |
| HashCodeBenchmark.hashcodeGroovyEmulatedRecord avgt 10 3.130 ± 0.015 ns/op |
| HashCodeBenchmark.hashcodeGroovyRecord avgt 10 2.814 ± 0.003 ns/op |
| HashCodeBenchmark.hashcodeJavaRecord avgt 10 2.813 ± 0.001 ns/op |
| HashCodeBenchmark.hashcodeKotlinDataLabel avgt 10 5.213 ± 0.016 ns/op |
| HashCodeBenchmark.hashcodeLombokDirectDataLabel avgt 10 5.427 ± 0.071 ns/op |
| HashCodeBenchmark.hashcodeLombokDataLabel avgt 10 18.328 ± 0.006 ns/op |
| HashCodeBenchmark.hashcodeScalaCaseLabel avgt 10 16.901 ± 0.007 ns/op</pre> |
| </div> |
| </div> |
| </div> |
| <div class="sect3"> |
| <h4 id="_using_windows_latest">Using windows-latest</h4> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre>Benchmark Mode Cnt Score Error Units |
| HashCodeBenchmark.hashcodeGroovyEmulatedRecord avgt 10 2.948 ± 0.005 ns/op |
| HashCodeBenchmark.hashcodeGroovyRecord avgt 10 3.410 ± 0.005 ns/op |
| HashCodeBenchmark.hashcodeJavaRecord avgt 10 3.407 ± 0.004 ns/op |
| HashCodeBenchmark.hashcodeKotlinDataLabel avgt 10 6.635 ± 0.005 ns/op |
| HashCodeBenchmark.hashcodeLombokDirectDataLabel avgt 10 8.520 ± 0.017 ns/op |
| HashCodeBenchmark.hashcodeLombokDataLabel avgt 10 16.172 ± 0.026 ns/op |
| HashCodeBenchmark.hashcodeScalaCaseLabel avgt 10 19.150 ± 0.048 ns/op</pre> |
| </div> |
| </div> |
| </div> |
| <div class="sect3"> |
| <h4 id="_using_macos_latest">Using macos-latest</h4> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre>Benchmark Mode Cnt Score Error Units |
| HashCodeBenchmark.hashcodeGroovyEmulatedRecord avgt 10 2.999 ± 0.194 ns/op |
| HashCodeBenchmark.hashcodeGroovyRecord avgt 10 2.765 ± 0.741 ns/op |
| HashCodeBenchmark.hashcodeJavaRecord avgt 10 2.990 ± 0.280 ns/op |
| HashCodeBenchmark.hashcodeKotlinDataLabel avgt 10 7.103 ± 0.123 ns/op |
| HashCodeBenchmark.hashcodeLombokDirectDataLabel avgt 10 6.494 ± 0.063 ns/op |
| HashCodeBenchmark.hashcodeLombokDataLabel avgt 10 15.100 ± 0.108 ns/op |
| HashCodeBenchmark.hashcodeScalaCaseLabel avgt 10 20.327 ± 0.085 ns/op</pre> |
| </div> |
| </div> |
| </div> |
| <div class="sect3"> |
| <h4 id="_graphing_the_results">Graphing the results</h4> |
| <div class="paragraph"> |
| <p>We can already see some variance in the results across platforms. |
| So, let’s average the results across the three platforms, which |
| gives us the following chart:</p> |
| </div> |
| <div class="paragraph"> |
| <p><span class="image"><img src="img/hashcodeTimes.png" alt="hashcodeTimes"></span></p> |
| </div> |
| <div class="paragraph"> |
| <p>Next we’ll look at some of the reasons behind these differences |
| and some other considerations which can help you speed up <code>hashCode</code> |
| or justify why you might want to choose a slower version as a |
| trade-off for other useful properties.</p> |
| </div> |
| </div> |
| </div> |
| <div class="sect2"> |
| <h3 id="_discussion">Discussion</h3> |
| <div class="paragraph"> |
| <p>It is always dangerous to draw too many conclusions from microbenchmarks. |
| We don’t always know if we are comparing apples with apples, or what else |
| was running on the machine when the benchmarks were executed, or how changing |
| the benchmark slightly might alter the result. Certainly for <code>hashCode</code>, |
| the result is impacted by the number of and types of our record components. |
| Even the particular data instances (arbitrary Strings in our case) will |
| impact the speed of that method.</p> |
| </div> |
| <div class="paragraph"> |
| <p>But what does this really tell us? For Groovy users, it is good to know |
| that the <code>hashCode</code> method is as good or better than Java records. |
| That isn’t too surprising since the Groovy bytecode is almost identical |
| to the Java bytecode for most parts of records.</p> |
| </div> |
| <div class="paragraph"> |
| <p>For Lombok and the other languages, the <code>hashCode</code> method is slower but only |
| by a few (or into the 10s of) nanoseconds. |
| Do we really care about how fast this particular method is? |
| Certainly if we are storing a lot of our label instances |
| into hashed collections, it could matter, but otherwise, not so much; |
| we’ll rarely call this method directly.</p> |
| </div> |
| <div class="paragraph"> |
| <p>But speed is only one of properties we’d like in a good <code>hashCode</code> method. |
| Another is minimal collisions. We can after all return the constant <code>0</code> or <code>-1</code> |
| from our <code>hashCode</code> and that would be very fast but hopeless in terms of collisions.</p> |
| </div> |
| <div class="sect3"> |
| <h4 id="_hashing_algorithm">Hashing algorithm</h4> |
| <div class="paragraph"> |
| <p>For Scala case classes, the |
| <a href="https://en.wikipedia.org/wiki/MurmurHash">Murmur3</a> hashing algorithm is currently |
| used which is slightly slower that what Java uses but |
| <a href="https://stackoverflow.com/questions/40980193/scala-murmur-hash-vs-java-native-hash">claims</a> |
| to have improved collision resistance. |
| If you are using large collections or records with many components, this tradeoff |
| might be worth considering.</p> |
| </div> |
| <div class="paragraph"> |
| <p>You can use Scala’s algorithm directly in Groovy with a record definition like this:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="groovy">record GroovyRecordScalaMurmur3Label(String x0, String x1, String x2, String x3, String x4) { |
| int hashCode() { |
| ScalaRunTime._hashCode(new Tuple5<>(x0, x1, x2, x3, x4)) |
| } |
| }</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>And this has almost identical performance to the native Scala example from our earlier bar chart.</p> |
| </div> |
| <div class="paragraph"> |
| <p>If you want a smaller dependency than the Scala runtime jar, you could use |
| the <a href="https://guava.dev/releases/31.0-jre/api/docs/src-html/com/google/common/hash/Hashing.html#line.158">32-bit Murmur3</a> algorithm from <a href="https://github.com/google/guava">Guava</a> |
| or write your own combiner to combine hashes produced by Apache Commons Codec’s |
| <a href="https://commons.apache.org/proper/commons-codec/apidocs/org/apache/commons/codec/digest/MurmurHash3.html#hash32x86-byte:A-">Murmur3 algorithm</a> on the bytes of each String component. |
| In my tests, both of these alternatives ended up being slower than borrowing Scala’s algorithm, |
| but I didn’t try to optimise my implementation.</p> |
| </div> |
| <div class="paragraph"> |
| <p>If you want to diver deeper on this topic, check out:</p> |
| </div> |
| <div class="ulist"> |
| <ul> |
| <li> |
| <p>this great overview article about <a href="https://www.baeldung.com/java-hashmap-optimize-performance">Optimizing HashMap’s Performance</a>,</p> |
| </li> |
| <li> |
| <p>and this article on |
| <a href="https://www.javacodegeeks.com/2015/09/an-introduction-to-optimising-a-hashing-strategy.html">optimising a hashing strategy</a> and its impact on collisions,</p> |
| </li> |
| <li> |
| <p>the original |
| <a href="https://github.com/aappleby/smhasher/wiki/MurmurHash3">C++ implementation</a> of the Murmur3 algorithm.</p> |
| </li> |
| </ul> |
| </div> |
| </div> |
| <div class="sect3"> |
| <h4 id="_jdk_version_support">JDK version support</h4> |
| <div class="paragraph"> |
| <p>One difference worth pointing out is that the Groovy, Lombok and other |
| languages work on earlier JDKs. As the GitHub action workflow configuration |
| shows, the example in this blog post are tested on JDK 8, 11 and 17.</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="yaml">matrix: |
| java: [8,11,17]</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>The Java record examples are tested in JDK 17 (technically requires 16+). |
| This is good to know if you are stuck on earlier versions but should become |
| less of an issue over time.</p> |
| </div> |
| </div> |
| <div class="sect3"> |
| <h4 id="_caching">Caching</h4> |
| <div class="paragraph"> |
| <p>A nice Groovy feature provided by some of Groovy’s transforms |
| is <em>caching</em>, which is exactly what you might want to do |
| for immutable classes (like records). In fact, in Groovy, caching is turned on |
| by default for the <code>hashCode</code> and <code>toString</code> methods for <code>@Immutable</code> classes, |
| but we leave it off by default for records for Java compatibility.</p> |
| </div> |
| <div class="paragraph"> |
| <p>Let’s turn on caching for the <code>hashCode</code> method with Groovy:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="groovy">@EqualsAndHashCode(useGetters = false, cache = true) |
| record GroovyRecordWithCacheLabel(String x0, String x1, String x2, String x3, String x4) { }</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>By default, Groovy records behave like Java records. |
| By supplying the <code>@EqualsAndHashCode</code> annotation, we effectively get |
| the code for an emulated record instead of the normal record bytecode. |
| To be as close to records as possible but with caching turned on, |
| we enable <code>cache</code> and disable <code>useGetters</code>. We’ll discuss the latter |
| in more detail in the next subsection.</p> |
| </div> |
| <div class="paragraph"> |
| <p>Now, let’s change our Java and Groovy benchmark code to simulate some code that |
| uses <code>hashCode</code> multiple times. For our purposes, we’ll just sum 5 calls to <code>hashCode</code>:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="java">@Benchmark |
| public void hashcodeJavaRecord(Blackhole bh) { |
| bh.consume(JAVA_RECORD_LABEL.hashCode() |
| + JAVA_RECORD_LABEL.hashCode() |
| + JAVA_RECORD_LABEL.hashCode() |
| + JAVA_RECORD_LABEL.hashCode() |
| + JAVA_RECORD_LABEL.hashCode()); |
| }</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>And we can do the same for Groovy. Here are the results of our new benchmark:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre>Benchmark Mode Cnt Score Error Units |
| HashCodeCacheBenchmark.hashcodeGroovyCacheRecord avgt 10 4.296 ± 0.108 ns/op windows-latest |
| HashCodeCacheBenchmark.hashcodeGroovyCacheRecord avgt 10 4.787 ± 0.151 ns/op ubuntu-latest |
| HashCodeCacheBenchmark.hashcodeGroovyCacheRecord avgt 10 5.465 ± 0.045 ns/op macos-latest |
| HashCodeCacheBenchmark.hashcodeJavaRecord avgt 10 21.956 ± 0.023 ns/op windows-latest |
| HashCodeCacheBenchmark.hashcodeJavaRecord avgt 10 33.820 ± 0.750 ns/op ubuntu-latest |
| HashCodeCacheBenchmark.hashcodeJavaRecord avgt 10 32.837 ± 1.136 ns/op macos-latest</pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>As expected, the effect of caching is clearly visible. We could certainly |
| write our own caching with an explicit <code>hashCode</code> method in Java and perhaps |
| call into <code>Objects.hash</code> or similar, but it’s not as nice as having the option |
| of a declarative approach.</p> |
| </div> |
| <div class="paragraph"> |
| <p>As a side note, we could add <code>@Memoized</code> to the <code>hashCode</code> method in our earlier |
| <code>GroovyRecordScalaMurmur3Label</code> example to turn on caching when using that algorithm.</p> |
| </div> |
| </div> |
| <div class="sect3"> |
| <h4 id="_supporting_javabean_like_behavior">Supporting JavaBean-like behavior</h4> |
| <div class="paragraph"> |
| <p>One other "feature" of Java (and Groovy) records is the ability to override the |
| record component "getters". You could for instance, write a 3-String label record in Java that |
| always returns its <code>x1</code> component in uppercase:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="java">public record JavaRecordLabelUpper(String x0, String x1, String x2) { |
| public String x1() { return x1.toUpperCase(); } |
| }</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>Now using the <code>x1()</code> getter method will give you the uppercase version. |
| Just be aware though that <code>hashCode</code> (and <code>equals</code>) don’t use the getter |
| but access the field directly.</p> |
| </div> |
| <div class="paragraph"> |
| <p>So, while all the components might be equal in the following example, |
| the hashcode (and the record as a whole) won’t be equal:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="java">private static final JavaRecordLabelUpper JAVA_UPPER_1 |
| = new JavaRecordLabelUpper("a", "b", "c"); |
| private static final JavaRecordLabelUpper JAVA_UPPER_2 |
| = new JavaRecordLabelUpper("a", "B", "c"); |
| ... |
| assertEquals(JAVA_UPPER_1.x0(), JAVA_UPPER_2.x0()); |
| assertEquals(JAVA_UPPER_1.x1(), JAVA_UPPER_2.x1()); |
| assertEquals(JAVA_UPPER_1.x2(), JAVA_UPPER_2.x2()); |
| assertNotEquals(JAVA_UPPER_1.hashCode(), JAVA_UPPER_2.hashCode()); |
| assertNotEquals(JAVA_UPPER_1, JAVA_UPPER_2);</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>This is exactly as expected from the record-related parts of the JLS specification |
| and is a reasonable design decision given that records are handling the use case |
| of <em>"a simple aggregate of values"</em>. |
| Indeed, records step away from many of the JavaBean conventions, so we might expect |
| some differences, yet not using the getter might still seem strange to some folks.</p> |
| </div> |
| <div class="paragraph"> |
| <p>The JLS specification elaborates further, stating that the above <code>JavaRecordLabelUpper</code> |
| class might be considered bad style. The rationale is in terms of a record <code>r2</code> derived |
| from the components of record <code>r1</code>:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="java">R r2 = new R(r1.c1(), r1.c2(), ..., r1.cn());</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>For any well-behaved record class, <code>r1.equals(r2)</code> should be true, which |
| won’t be the case for <code>JavaRecordLabelUpper</code>.</p> |
| </div> |
| <div class="paragraph"> |
| <p>Accessing the component through its getter is slower but would preserve the above property. |
| Both the <code>LombokDataLabel</code> and <code>ScalaCaseLabel</code> implementations use the getter. |
| This accounts for some of the reduced speed of those implementations.</p> |
| </div> |
| <div class="paragraph"> |
| <p>Groovy records default to Java behavior here but allow you to use the getters |
| for <code>hashCode</code> (and <code>equals</code> and <code>toString</code>) if you so desire. It will be slower |
| but now preserves traditional JavaBean-like getter behavior.</p> |
| </div> |
| <div class="paragraph"> |
| <p>Here is what the code would look like:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="groovy">@EqualsAndHashCode |
| record GroovyRecordUpperGetter(String x0, String x1, String x2) { |
| String x1() { x1.toUpperCase() } |
| }</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>The explicit <code>@EqualsAndHashCode</code> annotation tells the compiler to provide |
| Groovy’s default generated <code>hashCode</code> bytecode which does use getters rather |
| than the special record bytecode which doesn’t. It ends up being the same hashing |
| algorithm but uses the getters to access the components.</p> |
| </div> |
| <div class="paragraph"> |
| <p>And now our tests pass (with <code>assertEquals</code> instead of <code>assertNotEquals</code>):</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="java">private static final GroovyRecordUpperGetter GROOVY_UPPER_GETTER_1 |
| = new GroovyRecordUpperGetter("a", "b", "c"); |
| private static final GroovyRecordUpperGetter GROOVY_UPPER_GETTER_2 |
| = new GroovyRecordUpperGetter("a", "B", "c"); |
| ... |
| assertEquals(GROOVY_UPPER_GETTER_1.hashCode(), GROOVY_UPPER_GETTER_2.hashCode());</code></pre> |
| </div> |
| </div> |
| </div> |
| <div class="sect3"> |
| <h4 id="_summary">Summary</h4> |
| <div class="paragraph"> |
| <p>Groovy records have good <code>hashCode</code> performance. There are times when you might want to |
| enable caching. On rare occasions, you might want to also consider swapping the hashing |
| algorithm or enabling getters, but if you need to, Groovy makes that easy too.</p> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| <div class="sect1"> |
| <h2 id="_performance_of_equals">Performance of <code>equals</code></h2> |
| <div class="sectionbody"> |
| <div class="paragraph"> |
| <p>For this benchmark, the <code>equals</code> method of a static instance |
| was called passing in a second static instance.</p> |
| </div> |
| <div class="paragraph"> |
| <p>Here is an example of our benchmark code:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="java">@Benchmark |
| public void equalsGroovyRecord(Blackhole bh) { |
| bh.consume(GROOVY_RECORD_LABEL.equals(GROOVY_RECORD_LABEL_2)); |
| }</code></pre> |
| </div> |
| </div> |
| <div class="sect2"> |
| <h3 id="_results_2">Results</h3> |
| <div class="paragraph"> |
| <p>As before, the tests were run using GitHub actions on various platforms. |
| The results show average time taken in nanoseconds, so a smaller <em>Score</em> means faster.</p> |
| </div> |
| <div class="sect3"> |
| <h4 id="_using_ubuntu_latest_2">Using ubuntu-latest</h4> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre>Benchmark Mode Cnt Score Error Units |
| EqualsBenchmark.equalsGroovyEmulatedRecord avgt 10 2.615 ± 0.005 ns/op |
| EqualsBenchmark.equalsGroovyRecord avgt 10 0.603 ± 0.001 ns/op |
| EqualsBenchmark.equalsJavaRecord avgt 10 0.686 ± 0.154 ns/op |
| EqualsBenchmark.equalsKotlinDataLabel avgt 10 3.617 ± 0.002 ns/op |
| EqualsBenchmark.equalsLombokDirectDataLabel avgt 10 3.617 ± 0.002 ns/op |
| EqualsBenchmark.equalsLombokDataLabel avgt 10 24.115 ± 0.014 ns/op |
| EqualsBenchmark.equalsScalaCaseLabel avgt 10 24.130 ± 0.045 ns/op</pre> |
| </div> |
| </div> |
| </div> |
| <div class="sect3"> |
| <h4 id="_using_windows_latest_2">Using windows-latest</h4> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre>Benchmark Mode Cnt Score Error Units |
| EqualsBenchmark.equalsGroovyEmulatedRecord avgt 10 2.216 ± 0.004 ns/op |
| EqualsBenchmark.equalsGroovyRecord avgt 10 0.511 ± 0.002 ns/op |
| EqualsBenchmark.equalsJavaRecord avgt 10 0.511 ± 0.001 ns/op |
| EqualsBenchmark.equalsKotlinDataLabel avgt 10 3.066 ± 0.004 ns/op |
| EqualsBenchmark.equalsLombokDirectDataLabel avgt 10 3.068 ± 0.005 ns/op |
| EqualsBenchmark.equalsLombokDataLabel avgt 10 21.451 ± 0.021 ns/op |
| EqualsBenchmark.equalsScalaCaseLabel avgt 10 21.442 ± 0.024 ns/op</pre> |
| </div> |
| </div> |
| </div> |
| <div class="sect3"> |
| <h4 id="_using_macos_latest_2">Using macos-latest</h4> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre>Benchmark Mode Cnt Score Error Units |
| EqualsBenchmark.equalsGroovyEmulatedRecord avgt 10 1.943 ± 0.116 ns/op |
| EqualsBenchmark.equalsGroovyRecord avgt 10 0.612 ± 0.013 ns/op |
| EqualsBenchmark.equalsJavaRecord avgt 10 0.579 ± 0.021 ns/op |
| EqualsBenchmark.equalsKotlinDataLabel avgt 10 2.727 ± 0.068 ns/op |
| EqualsBenchmark.equalsLombokDirectDataLabel avgt 10 2.734 ± 0.096 ns/op |
| EqualsBenchmark.equalsLombokDataLabel avgt 10 21.206 ± 2.789 ns/op |
| EqualsBenchmark.equalsScalaCaseLabel avgt 10 20.673 ± 0.766 ns/op</pre> |
| </div> |
| </div> |
| </div> |
| <div class="sect3"> |
| <h4 id="_graphing_the_results_2">Graphing the results</h4> |
| <div class="paragraph"> |
| <p>Like before, we’ll average the results across the three platforms:</p> |
| </div> |
| <div class="paragraph"> |
| <p><span class="image"><img src="img/equalsTimes.png" alt="equalsTimes"></span></p> |
| </div> |
| </div> |
| </div> |
| <div class="sect2"> |
| <h3 id="_discussion_2">Discussion</h3> |
| <div class="paragraph"> |
| <p>We saw for <code>hashCode</code>, that using getters retained JavaBean-like expectations |
| but with additional costs of calling that method. That impact is doubly worse |
| for <code>equals</code> since we call the getters for <code>this</code> and the instance we are comparing |
| against. This explains a significant part of the slowness for the <code>LombokDataLabel</code> |
| and <code>ScalaCaseLabel</code> implementations.</p> |
| </div> |
| <div class="paragraph"> |
| <p>Like we discussed for <code>hashCode</code>, you would not normally modify the getters |
| for records and record-like classes. But for Java and Groovy which allow you to, |
| it might come as a surprise. Groovy follows Java behavior here by default but |
| allows you to enable access via getters if you desire.</p> |
| </div> |
| <div class="paragraph"> |
| <p>The JLS has an example of a <code>SmallPoint</code> record in section |
| <a href="https://docs.oracle.com/javase/specs/jls/se20/html/jls-8.html#jls-8.10.3">8.10.3</a> |
| which is discussed as <em>bad style</em> because with Java records the last statement prints <code>false</code>. |
| If we enable getters, the last statement now prints <code>true</code> as shown in this Groovy equivalent |
| of that example:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="java">@EqualsAndHashCode |
| record SmallPoint(int x, int y) { |
| int x() { this.x < 100 ? this.x : 100 } |
| int y() { this.y < 100 ? this.y : 100 } |
| |
| static main(args) { |
| var p1 = new SmallPoint(200, 300) |
| var p2 = new SmallPoint(200, 300) |
| println p1 == p2 // prints true |
| |
| var p3 = new SmallPoint(p1.x(), p1.y()) |
| println p1 == p3 // prints true |
| } |
| }</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>Never-the-less, for this particular example, it might be better style to leave the |
| normal field access in place and provide something like a compact canonical |
| constructor to truncate the points during construction.</p> |
| </div> |
| </div> |
| </div> |
| </div> |
| <div class="sect1"> |
| <h2 id="_conclusion">Conclusion</h2> |
| <div class="sectionbody"> |
| <div class="paragraph"> |
| <p>We have looked at a few aspects of the performance of Groovy records and compared |
| them to other languages. Groovy’s default behavior piggybacks directly on Java’s |
| behavior but Groovy has many declarative options to tweak the generated code if needed.</p> |
| </div> |
| </div> |
| </div></div></div></div></div><footer id='footer'> |
| <div class='row'> |
| <div class='colset-3-footer'> |
| <div class='col-1'> |
| <h1>Groovy</h1><ul> |
| <li><a href='https://groovy-lang.org/learn.html'>Learn</a></li><li><a href='https://groovy-lang.org/documentation.html'>Documentation</a></li><li><a href='/download.html'>Download</a></li><li><a href='https://groovy-lang.org/support.html'>Support</a></li><li><a href='/'>Contribute</a></li><li><a href='https://groovy-lang.org/ecosystem.html'>Ecosystem</a></li><li><a href='/blog'>Blog posts</a></li><li><a href='https://groovy.apache.org/events.html'></a></li> |
| </ul> |
| </div><div class='col-2'> |
| <h1>About</h1><ul> |
| <li><a href='https://github.com/apache/groovy'>Source code</a></li><li><a href='https://groovy-lang.org/security.html'>Security</a></li><li><a href='https://groovy-lang.org/learn.html#books'>Books</a></li><li><a href='https://groovy-lang.org/thanks.html'>Thanks</a></li><li><a href='http://www.apache.org/foundation/sponsorship.html'>Sponsorship</a></li><li><a href='https://groovy-lang.org/faq.html'>FAQ</a></li><li><a href='https://groovy-lang.org/search.html'>Search</a></li> |
| </ul> |
| </div><div class='col-3'> |
| <h1>Socialize</h1><ul> |
| <li><a href='https://groovy-lang.org/mailing-lists.html'>Discuss on the mailing-list</a></li><li><a href='https://twitter.com/ApacheGroovy'>Groovy on Twitter</a></li><li><a href='https://groovy-lang.org/events.html'>Events and conferences</a></li><li><a href='https://github.com/apache/groovy'>Source code on GitHub</a></li><li><a href='https://groovy-lang.org/reporting-issues.html'>Report issues in Jira</a></li><li><a href='http://stackoverflow.com/questions/tagged/groovy'>Stack Overflow questions</a></li><li><a href='http://groovycommunity.com/'>Slack Community</a></li> |
| </ul> |
| </div><div class='col-right'> |
| <p> |
| The Groovy programming language is supported by the <a href='http://www.apache.org'>Apache Software Foundation</a> and the Groovy community. |
| </p><div text-align='right'> |
| <img src='../img/asf_logo.png' title='The Apache Software Foundation' alt='The Apache Software Foundation' style='width:60%'/> |
| </div><p>Apache® and the Apache feather logo are either registered trademarks or trademarks of The Apache Software Foundation.</p> |
| </div> |
| </div><div class='clearfix'>© 2003-2023 the Apache Groovy project — Groovy is Open Source: <a href='http://www.apache.org/licenses/LICENSE-2.0.html' alt='Apache 2 License'>license</a>, <a href='https://privacy.apache.org/policies/privacy-policy-public.html'>privacy policy</a>.</div> |
| </div> |
| </footer></div> |
| </div> |
| </div> |
| </div> |
| </div><script src='../js/vendor/jquery-1.10.2.min.js' defer></script><script src='../js/vendor/classie.js' defer></script><script src='../js/vendor/bootstrap.js' defer></script><script src='../js/vendor/sidebarEffects.js' defer></script><script src='../js/vendor/modernizr-2.6.2.min.js' defer></script><script src='../js/plugins.js' defer></script><script src='https://cdnjs.cloudflare.com/ajax/libs/prettify/r298/prettify.min.js'></script><script>document.addEventListener('DOMContentLoaded',prettyPrint)</script><script> |
| (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ |
| (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), |
| m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) |
| })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); |
| |
| ga('create', 'UA-257558-10', 'auto'); |
| ga('send', 'pageview'); |
| </script> |
| </body></html> |