gnodet commented on PR #1908:
URL: https://github.com/apache/maven-resolver/pull/1908#issuecomment-4652055593
Nice benchmark! A few observations and a suggestion for a more
representative topology.
## Minor issues in the current code
1. **`makeDependencyNode(String, String, String, String scope)` silently
drops `scope`** — it delegates to the 3-arg overload which then calls the 5-arg
one with `null` classifier and hardcoded `"compile"`. The `scope` parameter is
never used. Either remove that overload or fix the delegation chain.
2. **`DUMPER_SOUT` is declared but never used** — leftover from debugging.
3. **Assertions in `@Benchmark` hot path** — `assertSame`, `assertEquals`,
`assertNotNull` in `uniqueSnake`/`uniqueSnakeWithRootCycle` add overhead to
every benchmark invocation. Fine for correctness validation but they do inflate
the measurements slightly.
## The "diamond" topology would better stress conflict resolution
The current `uniqueSnake` topology has **zero real conflicts** — every
artifact is unique, so both resolvers spend most of their time on bookkeeping
with nothing to actually resolve. This actually understates PCR's advantage:
PCR's O(N) vs CCR's O(N²) comes from CCR re-walking the whole graph once per
conflict group, so real conflicts are needed to expose the difference.
A diamond (or wide diamond fan) is the classic Maven conflict topology — the
same artifact pulled in at different versions via two or more paths:
```
root
/ \
left right
| \ / |
| \/ |
| shared-1.0
| \
| shared-2.0 ← conflict! classic "nearest wins" scenario
...
```
Here's a `diamondFan(depth, width)` topology to add — `width` independent
chains all pulling in the same shared artifact at conflicting versions:
```java
/**
* A "diamond fan": {@code width} independent chains of length {@code
depth}, all converging
* on the same shared artifact at two different versions (simulating a real
version conflict).
* This is the topology where CCR's O(N²) re-walk per conflict group shows
up most clearly.
*
* Graph shape (width=3, depth=2):
* root
* ├── chain-0-0 → chain-0-1 → shared:1.0
* ├── chain-1-0 → chain-1-1 → shared:2.0 (conflict: 1.0 vs 2.0)
* └── chain-2-0 → chain-2-1 → shared:1.0
*/
private static void diamondFan(ConflictResolver conflictResolver, int depth,
int width)
throws RepositoryException {
DependencyNode root = makeDependencyNode("group-id", "root", "1.0");
List<DependencyNode> chains = new ArrayList<>(width);
for (int w = 0; w < width; w++) {
DependencyNode chainHead = makeDependencyNode("group-id", "chain-" +
w + "-0", "1.0");
DependencyNode last = chainHead;
for (int d = 1; d < depth; d++) {
DependencyNode dep = makeDependencyNode("group-id", "chain-" + w
+ "-" + d, "1.0");
last.setChildren(mutableList(dep));
last = dep;
}
// All chains converge on the same artifact at alternating versions
— real conflict
String version = (w % 2 == 0) ? "1.0" : "2.0";
DependencyNode shared = makeDependencyNode("group-id", "shared",
version);
last.setChildren(mutableList(shared));
chains.add(chainHead);
}
root.setChildren(chains);
DependencyNode result = transform(conflictResolver, root);
assertNotNull(result);
// After conflict resolution, all paths to "shared" must agree on one
version
assertEquals(1, countDistinctVersions(result, "shared"));
}
private static int countDistinctVersions(DependencyNode node, String
artifactId) {
// BFS/DFS to count distinct versions of the given artifactId in the
resolved graph
java.util.Set<String> versions = new java.util.HashSet<>();
collectVersions(node, artifactId, versions);
return versions.size();
}
private static void collectVersions(DependencyNode node, String artifactId,
java.util.Set<String> versions) {
if (node.getArtifact() != null &&
artifactId.equals(node.getArtifact().getArtifactId())
&& node.getData().get(ConflictResolver.NODE_DATA_WINNER) ==
null) {
versions.add(node.getArtifact().getVersion());
}
for (DependencyNode child : node.getChildren()) {
collectVersions(child, artifactId, versions);
}
}
```
Then add benchmark methods:
```java
@Benchmark
public void diamondFan_10x5_path() throws RepositoryException {
diamondFan(path, 5, 10);
}
@Benchmark
public void diamondFan_10x5_classic() throws RepositoryException {
diamondFan(classic, 5, 10);
}
@Benchmark
public void diamondFan_20x10_path() throws RepositoryException {
diamondFan(path, 10, 20);
}
@Benchmark
public void diamondFan_20x10_classic() throws RepositoryException {
diamondFan(classic, 10, 20);
}
```
My expectation is that `diamondFan` will show a **larger** PCR advantage
than `uniqueSnake`, because CCR re-walks the entire graph for the `shared`
conflict group (finding it at the tip of every chain), while PCR already has it
pre-indexed in its partition map from the initial build pass.
Would be interesting to see your numbers on this one!
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]