Avoid rebuilding metadata maps when computing the changelog

Changelog.computeDifferences() called
ConfigurationMetadataRepository.getAllProperties() from inside its main
loop. The default repository implementation builds a new map each time
that method is called, so the new version's properties were rebuilt once
per property of the old version. The ids that had already been seen were
also tracked in an ArrayList, turning each membership check into a linear
scan.

Read the properties of each repository once and use the old properties
map itself to detect the properties that have been added.

Computing the changelog between Spring Boot 3.4.0 and 3.5.0 (2,498 and
2,555 properties) drops from 203ms to 0.5ms and the generator's peak
memory from 302MB to 91MB. The generated asciidoc is byte for byte
identical.

See gh-51655

Signed-off-by: Junggi Kim <kimjg2477@gmail.com>
This commit is contained in:
Junggi Kim
2026-09-10 16:35:18 +02:00
committed by Stéphane Nicoll
parent 69ad59c413
commit 9a09c2842b
@@ -18,6 +18,7 @@ package org.springframework.boot.configurationmetadata.changelog;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty;
import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepository;
@@ -34,6 +35,7 @@ import org.springframework.boot.configurationmetadata.Deprecation.Level;
* @author Andy Wilkinson
* @author Phillip Webb
* @author Yoobin Yoon
* @author Junggi Kim
*/
record Changelog(String oldVersionNumber, String newVersionNumber, List<Difference> differences) {
@@ -44,19 +46,18 @@ record Changelog(String oldVersionNumber, String newVersionNumber, List<Differen
static List<Difference> computeDifferences(ConfigurationMetadataRepository oldMetadata,
ConfigurationMetadataRepository newMetadata) {
List<String> seenIds = new ArrayList<>();
Map<String, ConfigurationMetadataProperty> oldProperties = oldMetadata.getAllProperties();
Map<String, ConfigurationMetadataProperty> newProperties = newMetadata.getAllProperties();
List<Difference> differences = new ArrayList<>();
for (ConfigurationMetadataProperty oldProperty : oldMetadata.getAllProperties().values()) {
String id = oldProperty.getId();
seenIds.add(id);
ConfigurationMetadataProperty newProperty = newMetadata.getAllProperties().get(id);
for (ConfigurationMetadataProperty oldProperty : oldProperties.values()) {
ConfigurationMetadataProperty newProperty = newProperties.get(oldProperty.getId());
Difference difference = Difference.compute(oldProperty, newProperty);
if (difference != null) {
differences.add(difference);
}
}
for (ConfigurationMetadataProperty newProperty : newMetadata.getAllProperties().values()) {
if (!seenIds.contains(newProperty.getId())) {
for (ConfigurationMetadataProperty newProperty : newProperties.values()) {
if (!oldProperties.containsKey(newProperty.getId())) {
if (!newProperty.isDeprecated()) {
differences.add(new Difference(DifferenceType.ADDED, null, newProperty));
}