Skip to content

Allow method inlining of ObjectUtils.nullSafeEquals() [SPR-14349] #18921

Closed
@spring-projects-issues

Description

@spring-projects-issues

Christoph Dreis opened SPR-14349 and commented

Hey,

while doing a benchmark in our project with the JVM options

-XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining

I noticed that ObjectUtils.nullSafeEquals() is a hot method, but is unfortunately too big for the VM to inline it. At least with the defaults of 325 bytecodes as it currently shows up with 337 bytes.

...
 @ 8   org.springframework.util.ObjectUtils::nullSafeEquals (337 bytes)   hot method too big
...

I made a small improvement by simply extracting the more uncommon use-case of checking for array equality, which reduces the method to 55 bytes and allows the inlining of the method. As a small side-benefit it also reduces the cyclomatic complexity of the method itself.

...
@ 8   com.dreis.benchmark.ObjectUtilsInlinedEquals::nullSafeEquals (55 bytes)   inline (hot)
...

I also created a small microbenchmark that looks like this:

@BenchmarkMode(Mode.Throughput)
@State(Scope.Thread)
public class ObjectUtilsBenchmark {

	@State(Scope.Thread)
	public static class TestState {
		public String[] firstStringArray = new String[] {"a", "b", "c"};
		public String[] secondStringArray = new String[] {"a", "b", "c"};
		public List<String> firstStringCollection = Arrays.asList("a", "b", "c");
		public List<String> secondStringCollection = Arrays.asList("a", "b", "c");
	}

	@Benchmark
	public boolean testStringArray(TestState testState) {
		return ObjectUtils.nullSafeEquals(testState.firstStringArray, testState.secondStringArray);
	}

	@Benchmark
	public boolean testStringCollection(TestState testState) {
		return ObjectUtils.nullSafeEquals(testState.firstStringCollection, testState.secondStringCollection);
	}

	@Benchmark
	public boolean testStringArrayInlined(TestState testState) {
		return ObjectUtilsInlinedEquals.nullSafeEquals(testState.firstStringArray, testState.secondStringArray);
	}

	@Benchmark
	public boolean testStringCollectionInlined(TestState testState) {
		return ObjectUtilsInlinedEquals.nullSafeEquals(testState.firstStringCollection, testState.secondStringCollection);
	}

}

The results of this microbenchmark look roughly like this.

Benchmark                                          Mode  Cnt          Score        Error  Units
ObjectUtilsBenchmark.testStringArray              thrpt   20  127900470,357 ± 985253,526  ops/s
ObjectUtilsBenchmark.testStringArrayInlined       thrpt   20  149902422,075 ± 905479,738  ops/s
ObjectUtilsBenchmark.testStringCollection         thrpt   20   61584045,911 ± 345039,219  ops/s
ObjectUtilsBenchmark.testStringCollectionInlined  thrpt   20   70381357,642 ± 751583,252  ops/s

I created a PR with my changes at #1076 and would be very happy if this gets accepted.

Keep up the great work!

Cheers,
Christoph


Affects: 3.2.17, 4.2.6

Referenced from: pull request #1076, and commits ca12e13, 14ab980, 71df9ce

Backported to: 4.2.7, 3.2.18

Metadata

Metadata

Assignees

Labels

in: coreIssues in core modules (aop, beans, core, context, expression)status: backportedAn issue that has been backported to maintenance branchestype: enhancementA general enhancement

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions