Description
Despite having JMH (Java Microbenchmark Harness) as a test dependency, there are no performance benchmarks implemented or automated in the CI/CD pipeline. This means performance regressions can silently creep into the codebase without detection.
Current State
✅ JMH Dependency Present
<!-- pom.xml lines 414-426 -->
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.37</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.37</version>
<scope>test</scope>
</dependency>
❌ No Benchmarks Implemented
find src/test/java -name "*Benchmark*.java" -o -name "*Perf*.java"
# Returns: (no results)
❌ No Performance Testing in CI/CD
```.github/workflows/main.yml` contains:
- Unit tests ✅
- Coverage checks ✅
- Static analysis ✅
- Mutation testing (PIT) configured ✅
- Performance benchmarks ❌
Why Performance Testing Matters
1. This is a Foundation Library
jcommons is a dependency for Solenopsis SOAP framework:
- Used in hot paths (SOAP API calls)
- String utilities called frequently
- Performance issues multiply across all consumers
- One slow utility affects entire framework
2. Critical Performance Paths
StringUtil.concat() and variants:
- Used for building strings in loops
- Could cause O(n²) issues with large datasets
- StringBuilder reuse impacts allocation rate
SoapUtil header operations:
- Called on every SOAP request
- Performance directly impacts API throughput
Serialization methods (deprecated but still in use):
- Object serialization/deserialization
- Compression operations
- Critical for applications still using these
3. No Performance SLAs
Without benchmarks:
- Don't know if changes cause regressions
- Can't track performance over time
- No baseline to measure against
- Can't make informed optimization decisions
4. Subtle Regressions Go Undetected
Examples of silent performance issues:
- Accidentally adding synchronization
- Changing from ArrayList to LinkedList
- Adding extra String concatenations
- Inefficient regex patterns
- Excessive object allocation
Impact
- Severity: Medium
- Type: Quality Assurance / Performance
- No performance regression detection
- Unknown performance characteristics
- Can't prove performance improvements
- No data-driven optimization
Recommended Fix
1. Create Performance Benchmarks
Create src/test/java/org/flossware/jcommons/benchmarks/:
StringUtilBenchmark.java:
package org.flossware.jcommons.benchmarks;
import org.flossware.jcommons.util.StringUtil;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
@Fork(value = 1, warmups = 1)
@Warmup(iterations = 3, time = 1)
@Measurement(iterations = 5, time = 1)
public class StringUtilBenchmark {
private String[] smallArray;
private String[] largeArray;
@Setup
public void setup() {
smallArray = new String[]{"a", "b", "c"};
largeArray = new String[100];
for (int i = 0; i < 100; i++) {
largeArray[i] = "string" + i;
}
}
@Benchmark
public String concatSmallArray() {
return StringUtil.concat(smallArray);
}
@Benchmark
public String concatLargeArray() {
return StringUtil.concat(largeArray);
}
@Benchmark
public String concatWithSeparator() {
return StringUtil.concatWithSeparator(", ", smallArray);
}
@Benchmark
public String requireNonBlank() {
return StringUtil.requireNonBlank("test string");
}
public static void main(String[] args) throws Exception {
Options opt = new OptionsBuilder()
.include(StringUtilBenchmark.class.getSimpleName())
.build();
new Runner(opt).run();
}
}
SoapUtilBenchmark.java:
package org.flossware.jcommons.benchmarks;
import jakarta.xml.ws.Service;
import org.flossware.jcommons.util.SoapUtil;
import org.openjdk.jmh.annotations.*;
import javax.xml.namespace.QName;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
@Fork(value = 1, warmups = 1)
@Warmup(iterations = 3)
@Measurement(iterations = 5)
public class SoapUtilBenchmark {
private QName qname;
@Setup
public void setup() {
qname = new QName("http://test.namespace", "TestService");
}
@Benchmark
public void getSoapFactory() {
try {
SoapUtil.getSoapFactory();
} catch (Exception e) {
// Expected in benchmark environment
}
}
// Add more SOAP-related benchmarks
}
2. Add Performance Profile to pom.xml
<profiles>
<profile>
<id>benchmark</id>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>run-benchmarks</id>
<phase>test</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>java</executable>
<arguments>
<argument>-classpath</argument>
<classpath/>
<argument>org.openjdk.jmh.Main</argument>
<argument>.*</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
3. Add CI/CD Performance Testing
Add to .github/workflows/performance.yml:
name: Performance Benchmarks
on:
pull_request:
branches: [ main ]
schedule:
# Run weekly on Sunday at 2am
- cron: '0 2 * * 0'
jobs:
benchmark:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- name: Setup JDK 17
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
cache: 'maven'
- name: Run Benchmarks
run: mvn clean test -Pbenchmark
- name: Store benchmark results
uses: benchmark-action/github-action-benchmark@v1
with:
tool: 'jmh'
output-file-path: target/jmh-result.json
github-token: ${{ secrets.GITHUB_TOKEN }}
auto-push: true
# Alert if performance degrades by 200%
alert-threshold: '200%'
comment-on-alert: true
fail-on-alert: false
4. Document Performance Standards
Add to CONTRIBUTING.md:
## Performance Testing
### Running Benchmarks Locally
\`\`\`bash
# Run all benchmarks
mvn clean test -Pbenchmark
# Run specific benchmark
mvn clean test -Pbenchmark -Dbenchmark=StringUtilBenchmark
\`\`\`
### Performance Standards
- String concatenation: < 100ns per operation
- Object validation: < 50ns per call
- SOAP header setting: < 1μs per call
### When to Add Benchmarks
- New utility methods expected to be called frequently
- Changes to hot-path code
- Optimization attempts (prove improvement)
- After performance bug reports
5. Critical Methods to Benchmark
High Priority:
- StringUtil.concat() - Used in tight loops
- StringUtil.concatWithSeparator() - String building
- StringUtil.requireNonBlank() - Validation overhead
- SoapUtil.setHeader() - Per-request overhead
- SoapUtil.setUrl() - Configuration cost
- ArrayUtil.ensureArray() - Validation overhead
- Objects.requireNonNull() patterns - Validation cost
Medium Priority:
8. FileUtil.newInputStream() - I/O initialization
9. PropertyUtil.fromInputStream() - Config loading
10. LoggerUtil.log() - Logging overhead
Low Priority (but track):
11. Deprecated serialization methods (establish baseline before removal)
12. Reflection utilities (ClassUtil, MethodUtil)
Benefits
1. Catch Regressions Early
Before: StringUtil.concat() - 45ns/op
After: StringUtil.concat() - 180ns/op ❌ REGRESSION DETECTED
2. Prove Optimizations Work
Before: requireNonBlank() - 120ns/op
After: requireNonBlank() - 35ns/op ✅ 71% IMPROVEMENT
3. Data-Driven Decisions
- "Should we cache the Logger?" → Run benchmark
- "Is StringBuilder faster here?" → Measure it
- "Does this allocation matter?" → Quantify it
4. Performance Documentation
- Benchmark results serve as performance specs
- Users know what to expect
- Can compare alternatives
5. Competitive Analysis
- Compare against Apache Commons equivalents
- Justify library choice with data
- Know trade-offs
Alternative: Manual Performance Testing
If automated benchmarks are too complex initially:
- Document expected performance in JavaDoc:
/**
* Concatenates objects to a string.
*
* <p>Performance: O(n) where n is the number of objects.
* Typical throughput: ~20M ops/sec on modern hardware.
*
* @param objs objects to concatenate
* @return concatenated string
*/
public static String concat(Object... objs) {
- Create manual benchmark classes (not automated):
// src/test/java/org/flossware/jcommons/manual/StringUtilPerformance.java
public class StringUtilPerformance {
public static void main(String[] args) {
// Manual timing code
}
}
- Document in README:
## Performance Characteristics
- String concatenation: 45ns per operation
- Validation methods: < 50ns overhead
- Tested on: JDK 17, Ubuntu 22.04, Intel i7
But automated benchmarks are strongly recommended for production quality.
Related Tools
Consider also:
- JProfiler - For production profiling
- Async-profiler - Low-overhead profiling
- JFR (Java Flight Recorder) - Built-in profiling
Description
Despite having JMH (Java Microbenchmark Harness) as a test dependency, there are no performance benchmarks implemented or automated in the CI/CD pipeline. This means performance regressions can silently creep into the codebase without detection.
Current State
✅ JMH Dependency Present
❌ No Benchmarks Implemented
❌ No Performance Testing in CI/CD
```.github/workflows/main.yml` contains:
Why Performance Testing Matters
1. This is a Foundation Library
jcommons is a dependency for Solenopsis SOAP framework:
2. Critical Performance Paths
StringUtil.concat() and variants:
SoapUtil header operations:
Serialization methods (deprecated but still in use):
3. No Performance SLAs
Without benchmarks:
4. Subtle Regressions Go Undetected
Examples of silent performance issues:
Impact
Recommended Fix
1. Create Performance Benchmarks
Create
src/test/java/org/flossware/jcommons/benchmarks/:StringUtilBenchmark.java:
SoapUtilBenchmark.java:
2. Add Performance Profile to pom.xml
3. Add CI/CD Performance Testing
Add to
.github/workflows/performance.yml:4. Document Performance Standards
Add to
CONTRIBUTING.md:5. Critical Methods to Benchmark
High Priority:
Medium Priority:
8. FileUtil.newInputStream() - I/O initialization
9. PropertyUtil.fromInputStream() - Config loading
10. LoggerUtil.log() - Logging overhead
Low Priority (but track):
11. Deprecated serialization methods (establish baseline before removal)
12. Reflection utilities (ClassUtil, MethodUtil)
Benefits
1. Catch Regressions Early
2. Prove Optimizations Work
3. Data-Driven Decisions
4. Performance Documentation
5. Competitive Analysis
Alternative: Manual Performance Testing
If automated benchmarks are too complex initially:
But automated benchmarks are strongly recommended for production quality.
Related Tools
Consider also: