gnodet opened a new issue, #12667: URL: https://github.com/apache/maven/issues/12667
## Summary JFR profiling of Maven 4 RC5/RC6 on a [4,383-module generated reactor project](https://github.com/maven-turbo-reactor/maven-multiproject-generator) revealed several performance regressions compared to Maven 3. A `clean install -DskipTests` that takes **~1:15** on Maven 3.9.16 was taking **~2:45** on Maven 4 RC6. After a series of targeted optimizations across 4 repositories, Maven 4 now completes the same build in **~1:18** — at parity with Maven 3. ## Benchmark Results Test machine: Apple M4 Pro, JDK 21. Project: 4,383-module diamond-graph reactor, `clean install -DskipTests -q`. | Configuration | Wall time | vs RC6 | |---|---|---| | Maven 3.9.16 | **1:14 – 1:18** | — | | Maven 4 RC6 (unpatched) | **2:45** | baseline | | + PathConflictResolver default | **1:45** | -36% | | + resolver optimizations | **1:22** | -50% | | + all optimizations | **1:18** | **-53%** | ## Hotspots & Fixes Each optimization was identified via JFR CPU profiling. The fixes are grouped by repository and listed in order of impact. ### 1. Conflict Resolver: O(N²) → O(N) — [apache/maven#12662](https://github.com/apache/maven/pull/12662) **JFR: `ClassicConflictResolver.gatherConflictItems` — 46.7% CPU** The `ClassicConflictResolver` performs O(N×M) recursive DFS in `gatherConflictItems()`. Switching the default from `classic` to `path` (`PathConflictResolver`, available since resolver 2.0.11, same resolution results) reduces this to O(N). Single biggest win: **2:45 → 1:45**. ### 2. TransitiveDependencyManager — [apache/maven-resolver#2014](https://github.com/apache/maven-resolver/pull/2014) **JFR: `deriveChildManager` + `Key.equals` + `MMap.done` — 28% CPU combined** `TransitiveDependencyManager` (Maven 4 default, `deriveUntil=Integer.MAX_VALUE`) creates a new child manager at every graph node. Four layered fixes: - **Instance self-reuse** — when no new management data is collected (common for transitive POMs without `<dependencyManagement>`), return `this` instead of a new instance. Restores pool cache transparency. - **Key coordinate caching** — cache `groupId`/`artifactId`/`extension`/`classifier` strings eagerly to avoid `RelocatedArtifact` virtual dispatch per `equals()`. - **Cons-list parent pointer** — replace O(depth)-copied `ArrayList<AbstractDependencyManager>` with a single `parent` reference. O(1) derive, cascading hashCode, identity-shortcircuit equals. - **Varargs elimination** — replace `Objects.hash(Object...)` with manual `31*h + field.hashCode()` chains throughout Key, constructor, Holder, and MMap to eliminate ~10 temporary `Object[]` allocations per derive call. - **MEMO_CACHE_SIZE 4 → 16** — self-reuse causes long-lived managers to serve children from many BFS wavefronts. - **PathConflictResolver O(1) cycle detection** — each `Path` node maintains a `Set<String>` of all conflict IDs from root, making `hasConflictIdOnPathToRoot` a `Set.contains()` instead of O(depth) parent walk. ### 3. InstallMojo / DeployMojo: O(N²) → O(N) — [apache/maven-install-plugin#427](https://github.com/apache/maven-install-plugin/pull/427), [apache/maven-deploy-plugin#684](https://github.com/apache/maven-deploy-plugin/pull/684) **JFR: `PluginContainer.getPluginsAsMap` — 5.6% CPU** Both `InstallMojo.execute()` and `DeployMojo.execute()` scan the entire reactor project list on every module invocation to check which projects use the plugin. In a 4,383-module build this is ~19.2M filter evaluations. Fixed by caching the filtered list in the first reactor project's plugin context on first invocation. ### 4. Reactor Sort: O(N² log N) → O(N log N) — [apache/maven#12652](https://github.com/apache/maven/pull/12652) **JFR: `MavenProject.equals` — 23.5% CPU** `DefaultGraphBuilder` uses `result.sort(comparing(sortedProjects::indexOf))` where `ArrayList.indexOf()` is O(n), causing O(N² log N) total `MavenProject.equals()` calls (~230M for 4,383 modules). Replaced with a `HashMap<MavenProject, Integer>` index for O(1) lookups. Also includes: - **`DefaultModelObjectPool`** (~8% CPU) — cache `getPooledTypes()` set at construction, inline `Objects.hash()` varargs in `PoolKey`, add hashCode fast-rejection in `equals()` - **`PhaseComparator`** (~2% CPU) — pre-build `HashMap<String, Integer>` for O(1) phase lookups ### 5. Model Building Pipeline — [apache/maven#12653](https://github.com/apache/maven/pull/12653) **JFR: `DefaultModelObjectPool.PoolKey` + `Dependency` builders — 37% + 18% CPU** - Add Builder getters and `*ToBuilder` merger variants to generated model classes - Defer `Dependency.build()` in `DefaultDependencyManagementInjector` — accumulate as Builder objects, build once at end - Optimize `computeLocations()` — replace `Stream.concat().collect()` with `HashMap.putAll()` + `Map.copyOf()` - Precompute `locationsHashCode` at build time for fast inequality checks ### 6. PrintWriter Lock Contention — [apache/maven#12654](https://github.com/apache/maven/pull/12654) **JFR: 1,470ms blocked time on `PrintWriter.println()`** `AsyncDrainWriter` wraps the logging `Consumer<String>` with a lock-free `ConcurrentLinkedQueue` + non-blocking drain via `ReentrantLock.tryLock()`. Eliminates all contention during parallel model building (`-T1C`). ### 7. Location Tracking Wire-up — [apache/maven#12655](https://github.com/apache/maven/pull/12655) **JFR: 97 `InputLocation.of()` allocations per POM** `ModelBuilderRequest.isLocationTracking()` existed but was only checked in one place. Now wired through `XmlReaderRequest` → `DefaultModelXmlFactory` → `MavenStaxReader` so the parser actually skips all `InputLocation.of()` calls when tracking is disabled. ## All PRs | Repository | PR | Status | Description | |---|---|---|---| | apache/maven | [#12662](https://github.com/apache/maven/pull/12662) | ✅ Ready | Enable PathConflictResolver by default | | apache/maven | [#12652](https://github.com/apache/maven/pull/12652) | ✅ Ready | Optimize reactor sort, model pool, phase comparator | | apache/maven | [#12653](https://github.com/apache/maven/pull/12653) | ✅ Ready | Optimize model building pipeline | | apache/maven | [#12654](https://github.com/apache/maven/pull/12654) | ✅ Ready | AsyncDrainWriter — lock contention elimination | | apache/maven | [#12655](https://github.com/apache/maven/pull/12655) | ✅ Ready | Wire location tracking to XML parser | | apache/maven-resolver | [#2014](https://github.com/apache/maven-resolver/pull/2014) | ✅ Ready | TransitiveDependencyManager performance | | apache/maven-install-plugin | [#427](https://github.com/apache/maven-install-plugin/pull/427) | ✅ Ready | Cache projectsUsingPlugin — O(N²) → O(N) | | apache/maven-deploy-plugin | [#684](https://github.com/apache/maven-deploy-plugin/pull/684) | ✅ Ready | Cache projectsWithDeployExecution — O(N²) → O(N) | ## Reproducing ```bash # Generate the test project git clone https://github.com/maven-turbo-reactor/maven-multiproject-generator cd maven-multiproject-generator && ./generate.sh # Build with gnodet's bench branch that includes all resolver + maven patches git clone -b bench/resolver-2.0.22 https://github.com/gnodet/maven cd maven && mvn install -DskipTests -q # Benchmark cd generated && time path/to/patched-maven/bin/mvn clean install -DskipTests -q ``` See also: [benchmark gist](https://gist.github.com/gnodet/5d324a772d2be64bdb8c0b73a35e4354) -- 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]
