From b67a381fbec96ba116467a34db5e9d954442095f Mon Sep 17 00:00:00 2001 From: shawyeok Date: Mon, 2 Aug 2021 11:55:04 +0800 Subject: [PATCH 1/2] Improve performance of CompositePropertySource#getPropertyNames Create LinkedHashSet with a initialCapacity, prevent under the hood table resize cost in continuous add operations. Reduce bootstrap time in the case of large properties. See gh-27236 --- .../env/CompositePropertySourceBenchmark.java | 79 +++++++++++++++++++ .../core/env/CompositePropertySource.java | 13 ++- 2 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 spring-core/src/jmh/java/org/springframework/core/env/CompositePropertySourceBenchmark.java diff --git a/spring-core/src/jmh/java/org/springframework/core/env/CompositePropertySourceBenchmark.java b/spring-core/src/jmh/java/org/springframework/core/env/CompositePropertySourceBenchmark.java new file mode 100644 index 00000000000..73d3d18f523 --- /dev/null +++ b/spring-core/src/jmh/java/org/springframework/core/env/CompositePropertySourceBenchmark.java @@ -0,0 +1,79 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.core.env; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.lang3.RandomStringUtils; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +/** + * Benchmarks for {@link CompositePropertySource}. + * + * @author Yike Xiao + */ +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@Warmup(iterations = 5, time = 2) +@Measurement(iterations = 5, time = 2) +public class CompositePropertySourceBenchmark { + + @Benchmark + public void getPropertyNames(BenchmarkState state, Blackhole blackhole) { + blackhole.consume(state.composite.getPropertyNames()); + } + + @State(Scope.Benchmark) + public static class BenchmarkState { + + static final Object VALUE = new Object(); + + CompositePropertySource composite; + + @Param({"2", "5", "10"}) + int numberOfPropertySource; + + @Param({"10", "100", "1000"}) + int numberOfPropertyNamesPerSource; + + @Setup(Level.Trial) + public void setUp() { + this.composite = new CompositePropertySource("benchmark"); + for (int i = 0; i < this.numberOfPropertySource; i++) { + Map map = new HashMap<>(this.numberOfPropertyNamesPerSource); + for (int j = 0; j < this.numberOfPropertyNamesPerSource; j++) { + map.put(RandomStringUtils.randomAlphanumeric(5, 50), VALUE); + } + PropertySource propertySource = new MapPropertySource("propertySource" + i, map); + this.composite.addPropertySource(propertySource); + } + } + } +} diff --git a/spring-core/src/main/java/org/springframework/core/env/CompositePropertySource.java b/spring-core/src/main/java/org/springframework/core/env/CompositePropertySource.java index 6224a95f339..efc59f67a9d 100644 --- a/spring-core/src/main/java/org/springframework/core/env/CompositePropertySource.java +++ b/spring-core/src/main/java/org/springframework/core/env/CompositePropertySource.java @@ -78,15 +78,22 @@ public class CompositePropertySource extends EnumerablePropertySource { @Override public String[] getPropertyNames() { - Set names = new LinkedHashSet<>(); + List namesList = new ArrayList<>(this.propertySources.size()); + int total = 0; for (PropertySource propertySource : this.propertySources) { if (!(propertySource instanceof EnumerablePropertySource enumerablePropertySource)) { throw new IllegalStateException( "Failed to enumerate property names due to non-enumerable property source: " + propertySource); } - names.addAll(Arrays.asList(enumerablePropertySource.getPropertyNames())); + String[] names = enumerablePropertySource.getPropertyNames(); + namesList.add(names); + total += names.length; } - return StringUtils.toStringArray(names); + Set allNames = new LinkedHashSet<>(total); + for (String[] names : namesList) { + allNames.addAll(Arrays.asList(names)); + } + return StringUtils.toStringArray(allNames); } From 5c691960a2129612b905f9dcc0452014a06d14ad Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Sat, 26 Aug 2023 17:45:25 +0200 Subject: [PATCH 2/2] Polish "Improve performance of CompositePropertySource#getPropertyNames" See gh-27236 --- .../env/CompositePropertySourceBenchmark.java | 18 ++++++++++++------ .../core/env/CompositePropertySource.java | 6 ++---- .../core/env/CompositePropertySourceTests.java | 10 ++++++++++ 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/spring-core/src/jmh/java/org/springframework/core/env/CompositePropertySourceBenchmark.java b/spring-core/src/jmh/java/org/springframework/core/env/CompositePropertySourceBenchmark.java index 73d3d18f523..3d5eae2bdf5 100644 --- a/spring-core/src/jmh/java/org/springframework/core/env/CompositePropertySourceBenchmark.java +++ b/spring-core/src/jmh/java/org/springframework/core/env/CompositePropertySourceBenchmark.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,6 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; -import org.apache.commons.lang3.RandomStringUtils; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Level; @@ -34,6 +33,9 @@ import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.infra.Blackhole; +import org.springframework.util.AlternativeJdkIdGenerator; +import org.springframework.util.IdGenerator; + /** * Benchmarks for {@link CompositePropertySource}. * @@ -53,14 +55,16 @@ public class CompositePropertySourceBenchmark { @State(Scope.Benchmark) public static class BenchmarkState { - static final Object VALUE = new Object(); + private static final IdGenerator ID_GENERATOR = new AlternativeJdkIdGenerator(); + + private static final Object VALUE = new Object(); CompositePropertySource composite; - @Param({"2", "5", "10"}) + @Param({ "2", "5", "10" }) int numberOfPropertySource; - @Param({"10", "100", "1000"}) + @Param({ "10", "100", "1000" }) int numberOfPropertyNamesPerSource; @Setup(Level.Trial) @@ -69,11 +73,13 @@ public class CompositePropertySourceBenchmark { for (int i = 0; i < this.numberOfPropertySource; i++) { Map map = new HashMap<>(this.numberOfPropertyNamesPerSource); for (int j = 0; j < this.numberOfPropertyNamesPerSource; j++) { - map.put(RandomStringUtils.randomAlphanumeric(5, 50), VALUE); + map.put(ID_GENERATOR.generateId().toString(), VALUE); } PropertySource propertySource = new MapPropertySource("propertySource" + i, map); this.composite.addPropertySource(propertySource); } } + } + } diff --git a/spring-core/src/main/java/org/springframework/core/env/CompositePropertySource.java b/spring-core/src/main/java/org/springframework/core/env/CompositePropertySource.java index efc59f67a9d..7345ff4dff9 100644 --- a/spring-core/src/main/java/org/springframework/core/env/CompositePropertySource.java +++ b/spring-core/src/main/java/org/springframework/core/env/CompositePropertySource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -90,9 +90,7 @@ public class CompositePropertySource extends EnumerablePropertySource { total += names.length; } Set allNames = new LinkedHashSet<>(total); - for (String[] names : namesList) { - allNames.addAll(Arrays.asList(names)); - } + namesList.forEach(names -> allNames.addAll(Arrays.asList(names))); return StringUtils.toStringArray(allNames); } diff --git a/spring-core/src/test/java/org/springframework/core/env/CompositePropertySourceTests.java b/spring-core/src/test/java/org/springframework/core/env/CompositePropertySourceTests.java index d7bafe37643..fba8bd2aa5d 100644 --- a/spring-core/src/test/java/org/springframework/core/env/CompositePropertySourceTests.java +++ b/spring-core/src/test/java/org/springframework/core/env/CompositePropertySourceTests.java @@ -17,6 +17,7 @@ package org.springframework.core.env; import java.util.Collections; +import java.util.Map; import org.junit.jupiter.api.Test; @@ -46,4 +47,13 @@ class CompositePropertySourceTests { assertThat(((i1 < i2) && (i2 < i3))).as("Bad order: " + s).isTrue(); } + @Test + void getPropertyNamesRemovesDuplicates() { + CompositePropertySource composite = new CompositePropertySource("c"); + composite.addPropertySource(new MapPropertySource("p1", Map.of("p1.property", "value"))); + composite.addPropertySource(new MapPropertySource("p2", + Map.of("p2.property1", "value", "p1.property", "value", "p2.property2", "value"))); + assertThat(composite.getPropertyNames()).containsOnly("p1.property", "p2.property1", "p2.property2"); + } + }