Skip to content

Commit 6740b0e

Browse files
Use DependencyGraph in integration sorting
This updates the SmithyIntegration sorting to use the generic DependencyGraph. It also adds some benchmarking configurations, which show a minor speed bump for highly dependent integrations and a major speed bump for highly independent integrations.
1 parent e93ced8 commit 6740b0e

File tree

3 files changed

+233
-57
lines changed

3 files changed

+233
-57
lines changed

smithy-codegen-core/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*/
55
plugins {
66
id("smithy.module-conventions")
7+
id("smithy.profiling-conventions")
78
}
89

910
description = "This module provides a code generation framework for generating clients, " +
@@ -14,6 +15,7 @@ extra["moduleName"] = "software.amazon.smithy.codegen.core"
1415

1516
dependencies {
1617
api(project(":smithy-utils"))
18+
jmh(project(":smithy-utils"))
1719
api(project(":smithy-model"))
1820
api(project(":smithy-build"))
1921
}
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,225 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
15
package software.amazon.smithy.codegen.core.jmh;
26

7+
import java.util.ArrayList;
8+
import java.util.Collections;
9+
import java.util.Comparator;
10+
import java.util.LinkedHashMap;
11+
import java.util.List;
12+
import java.util.Map;
13+
import java.util.concurrent.TimeUnit;
14+
import org.openjdk.jmh.annotations.Benchmark;
15+
import org.openjdk.jmh.annotations.BenchmarkMode;
16+
import org.openjdk.jmh.annotations.Fork;
17+
import org.openjdk.jmh.annotations.Measurement;
18+
import org.openjdk.jmh.annotations.Mode;
19+
import org.openjdk.jmh.annotations.Scope;
20+
import org.openjdk.jmh.annotations.Setup;
21+
import org.openjdk.jmh.annotations.State;
22+
import org.openjdk.jmh.annotations.Warmup;
23+
import software.amazon.smithy.codegen.core.CodegenContext;
24+
import software.amazon.smithy.codegen.core.ImportContainer;
25+
import software.amazon.smithy.codegen.core.SmithyIntegration;
26+
import software.amazon.smithy.codegen.core.Symbol;
27+
import software.amazon.smithy.codegen.core.SymbolWriter;
28+
import software.amazon.smithy.utils.DependencyGraph;
29+
30+
@Warmup(iterations = 3)
31+
@Measurement(iterations = 3, timeUnit = TimeUnit.MICROSECONDS)
32+
@BenchmarkMode(Mode.AverageTime)
33+
@Fork(1)
334
public class SmithyIntegrations {
35+
36+
@State(Scope.Thread)
37+
public static class SmithyIntegrationsState {
38+
public Map<String, SmithyIntegration<?, ?, ?>> independentIntegrations10;
39+
public Map<String, SmithyIntegration<?, ?, ?>> dependentIntegrations10;
40+
public Map<String, SmithyIntegration<?, ?, ?>> independentIntegrations100;
41+
public Map<String, SmithyIntegration<?, ?, ?>> dependentIntegrations100;
42+
public Map<String, SmithyIntegration<?, ?, ?>> independentIntegrations1000;
43+
public Map<String, SmithyIntegration<?, ?, ?>> dependentIntegrations1000;
44+
45+
@Setup
46+
public void setup() {
47+
independentIntegrations10 = new LinkedHashMap<>(10);
48+
independentIntegrations100 = new LinkedHashMap<>(100);
49+
independentIntegrations1000 = new LinkedHashMap<>(1000);
50+
for (int i = 0; i < 1000; i++) {
51+
String name = "integration" + i;
52+
TestIntegration integration = new TestIntegration(name);
53+
if (i < 10) {
54+
independentIntegrations10.put(name, integration);
55+
}
56+
if (i < 100) {
57+
independentIntegrations100.put(name, integration);
58+
}
59+
independentIntegrations1000.put(name, integration);
60+
}
61+
independentIntegrations100 = Collections.unmodifiableMap(independentIntegrations100);
62+
63+
dependentIntegrations10 = new LinkedHashMap<>(10);
64+
dependentIntegrations100 = new LinkedHashMap<>(100);
65+
dependentIntegrations1000 = new LinkedHashMap<>(1000);
66+
for (int i = 0; i < 1000; i++) {
67+
String name = "integration" + i;
68+
String next = "integration" + (i + 1);
69+
String afterNext = "integration" + (i + 2);
70+
71+
72+
if (i < 10) {
73+
List<String> dependencies = new ArrayList<>();
74+
if (i < 9) {
75+
dependencies.add(next);
76+
}
77+
if (i < 8) {
78+
dependencies.add(afterNext);
79+
}
80+
dependentIntegrations10.put(name, new TestIntegration(name, (byte) 0, Collections.emptyList(), dependencies));
81+
}
82+
83+
if (i < 100) {
84+
List<String> dependencies = new ArrayList<>();
85+
if (i < 99) {
86+
dependencies.add(next);
87+
}
88+
if (i < 98) {
89+
dependencies.add(afterNext);
90+
}
91+
dependentIntegrations100.put(name, new TestIntegration(name, (byte) 0, Collections.emptyList(), dependencies));
92+
}
93+
94+
List<String> dependencies = new ArrayList<>();
95+
if (i < 999) {
96+
dependencies.add(next);
97+
}
98+
if (i < 998) {
99+
dependencies.add(afterNext);
100+
}
101+
dependentIntegrations1000.put(name,
102+
new TestIntegration(name, (byte) 0, Collections.emptyList(), dependencies));
103+
}
104+
}
105+
}
106+
107+
@Benchmark
108+
public List<SmithyIntegration<?, ?, ?>> sortIndependentIntegrations10(SmithyIntegrationsState state) {
109+
return SmithyIntegration.sort(state.independentIntegrations10.values());
110+
}
111+
112+
@Benchmark
113+
public List<SmithyIntegration<?, ?, ?>> sortIndependentIntegrations100(SmithyIntegrationsState state) {
114+
return SmithyIntegration.sort(state.independentIntegrations100.values());
115+
}
116+
117+
@Benchmark
118+
public List<SmithyIntegration<?, ?, ?>> sortIndependentIntegrations1000(SmithyIntegrationsState state) {
119+
return SmithyIntegration.sort(state.independentIntegrations1000.values());
120+
}
121+
122+
@Benchmark
123+
public List<SmithyIntegration<?, ?, ?>> sortDependentIntegrations10(SmithyIntegrationsState state) {
124+
return SmithyIntegration.sort(state.dependentIntegrations10.values());
125+
}
126+
127+
@Benchmark
128+
public List<SmithyIntegration<?, ?, ?>> sortDependentIntegrations100(SmithyIntegrationsState state) {
129+
return SmithyIntegration.sort(state.dependentIntegrations100.values());
130+
}
131+
132+
@Benchmark
133+
public List<SmithyIntegration<?, ?, ?>> sortDependentIntegrations1000(SmithyIntegrationsState state) {
134+
return SmithyIntegration.sort(state.dependentIntegrations1000.values());
135+
}
136+
137+
private static class IntegrationComparator implements Comparator<String> {
138+
139+
private final Map<String, SmithyIntegration<?, ?, ?>> lookup;
140+
141+
IntegrationComparator(Map<String, SmithyIntegration<?, ?, ?>> lookup) {
142+
this.lookup = lookup;
143+
}
144+
145+
@Override
146+
public int compare(String o1, String o2) {
147+
SmithyIntegration<?, ?, ?> left = lookup.get(o1);
148+
SmithyIntegration<?, ?, ?> right = lookup.get(o2);
149+
if (left == null || right == null) {
150+
return 0;
151+
}
152+
return Byte.compare(left.priority(), right.priority());
153+
}
154+
}
155+
156+
private static class TestImportContainer implements ImportContainer {
157+
@Override
158+
public void importSymbol(Symbol symbol, String alias) {
159+
160+
}
161+
}
162+
163+
private static class TestSymbolWriter extends SymbolWriter<TestSymbolWriter, TestImportContainer> {
164+
public TestSymbolWriter(TestImportContainer importContainer) {
165+
super(importContainer);
166+
}
167+
}
168+
169+
private static class TestSettings {}
170+
171+
private static class TestIntegration implements SmithyIntegration<
172+
TestSettings,
173+
TestSymbolWriter,
174+
CodegenContext<TestSettings, TestSymbolWriter, TestIntegration>> {
175+
176+
private final String name;
177+
private final byte priority;
178+
private final List<String> runBefore;
179+
private final List<String> runAfter;
180+
181+
TestIntegration(String name) {
182+
this(name, (byte) 0);
183+
}
184+
185+
TestIntegration(String name, byte priority) {
186+
this(name, priority, Collections.emptyList());
187+
}
188+
189+
TestIntegration(String name, byte priority, List<String> runBefore) {
190+
this(name, priority, runBefore, Collections.emptyList());
191+
}
192+
193+
TestIntegration(String name, byte priority, List<String> runBefore, List<String> runAfter) {
194+
this.name = name;
195+
this.priority = priority;
196+
this.runBefore = runBefore;
197+
this.runAfter = runAfter;
198+
}
199+
200+
@Override
201+
public String name() {
202+
return name;
203+
}
204+
205+
@Override
206+
public byte priority() {
207+
return priority;
208+
}
209+
210+
@Override
211+
public List<String> runBefore() {
212+
return runBefore;
213+
}
214+
215+
@Override
216+
public List<String> runAfter() {
217+
return runAfter;
218+
}
219+
220+
@Override
221+
public String toString() {
222+
return name();
223+
}
224+
}
4225
}

smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/IntegrationTopologicalSort.java

Lines changed: 10 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -5,65 +5,37 @@
55
package software.amazon.smithy.codegen.core;
66

77
import java.util.ArrayList;
8-
import java.util.Collections;
9-
import java.util.HashMap;
108
import java.util.LinkedHashMap;
11-
import java.util.LinkedHashSet;
129
import java.util.List;
1310
import java.util.Map;
14-
import java.util.PriorityQueue;
15-
import java.util.Queue;
16-
import java.util.Set;
1711
import java.util.logging.Logger;
12+
import software.amazon.smithy.utils.DependencyGraph;
1813

1914
final class IntegrationTopologicalSort<I extends SmithyIntegration<?, ?, ?>> {
2015

2116
private static final Logger LOGGER = Logger.getLogger(IntegrationTopologicalSort.class.getName());
2217

2318
private final Map<String, I> integrationLookup = new LinkedHashMap<>();
24-
private final Map<String, Integer> insertionOrder = new HashMap<>();
25-
private final Map<String, Set<String>> forwardDependencies = new LinkedHashMap<>();
26-
private final Map<String, Set<String>> reverseDependencies = new LinkedHashMap<>();
27-
28-
private final Queue<String> satisfied = new PriorityQueue<>((left, right) -> {
29-
I leftIntegration = integrationLookup.get(left);
30-
I rightIntegration = integrationLookup.get(right);
31-
// Priority order is used to sort first.
32-
int byteResult = Byte.compare(rightIntegration.priority(), leftIntegration.priority());
33-
// If priority is a tie, then sort based on insertion order of integrations.
34-
// This makes the order deterministic.
35-
return byteResult == 0
36-
? Integer.compare(insertionOrder.get(left), insertionOrder.get(right))
37-
: byteResult;
38-
});
19+
private final DependencyGraph<String> dependencyGraph = new DependencyGraph<>();
3920

4021
IntegrationTopologicalSort(Iterable<I> integrations) {
41-
// Validate name conflicts and register integrations with the lookup table + insertion order table.
4222
for (I integration : integrations) {
4323
addIntegration(integration);
4424
}
4525

4626
// Validate missing dependencies and add found dependencies.
4727
for (I integration : integrations) {
4828
for (String before : getValidatedDependencies("before", integration.name(), integration.runBefore())) {
49-
addDependency(before, integration.name());
29+
dependencyGraph.addDependency(before, integration.name());
5030
}
5131
for (String after : getValidatedDependencies("after", integration.name(), integration.runAfter())) {
52-
addDependency(integration.name(), after);
53-
}
54-
}
55-
56-
// Offer satisfied dependencies.
57-
for (I integration : integrations) {
58-
if (!forwardDependencies.containsKey(integration.name())) {
59-
satisfied.offer(integration.name());
32+
dependencyGraph.addDependency(integration.name(), after);
6033
}
6134
}
6235
}
6336

6437
private void addIntegration(I integration) {
6538
I previous = this.integrationLookup.put(integration.name(), integration);
66-
insertionOrder.put(integration.name(), insertionOrder.size());
6739
if (previous != null) {
6840
throw new IllegalArgumentException(String.format(
6941
"Conflicting SmithyIntegration names detected for '%s': %s and %s",
@@ -91,33 +63,14 @@ private List<String> getValidatedDependencies(String descriptor, String what, Li
9163
}
9264
}
9365

94-
private void addDependency(String what, String dependsOn) {
95-
forwardDependencies.computeIfAbsent(what, n -> new LinkedHashSet<>()).add(dependsOn);
96-
reverseDependencies.computeIfAbsent(dependsOn, n -> new LinkedHashSet<>()).add(what);
97-
}
98-
9966
List<I> sort() {
100-
List<I> result = new ArrayList<>();
101-
102-
while (!satisfied.isEmpty()) {
103-
String current = satisfied.poll();
104-
forwardDependencies.remove(current);
105-
result.add(integrationLookup.get(current));
106-
107-
for (String dependent : reverseDependencies.getOrDefault(current, Collections.emptySet())) {
108-
Set<String> dependentDependencies = forwardDependencies.get(dependent);
109-
dependentDependencies.remove(current);
110-
if (dependentDependencies.isEmpty()) {
111-
satisfied.offer(dependent);
112-
}
113-
}
67+
List<I> result = new ArrayList<>(dependencyGraph.size());
68+
List<String> sortedNames = dependencyGraph.toSortedList((left, right) -> {
69+
return Byte.compare(integrationLookup.get(left).priority(), integrationLookup.get(right).priority());
70+
});
71+
for (String name : sortedNames) {
72+
result.add(integrationLookup.get(name));
11473
}
115-
116-
if (!forwardDependencies.isEmpty()) {
117-
throw new IllegalArgumentException("SmithyIntegration cycles detected among "
118-
+ forwardDependencies.keySet());
119-
}
120-
12174
return result;
12275
}
12376
}

0 commit comments

Comments
 (0)