Careeroza
Measuring Performance
Measure First
"Premature optimization is the root of all evil." Most slowness in real apps comes from a few places: database queries (N+1, missing indexes), network calls, and bad algorithms (O(n²) loops). Find them with measurements, not guesses.
| Tool | Use |
|---|---|
| JMH (Java Microbenchmark Harness) | correct micro-benchmarks (handles JIT warm-up, dead-code elimination) |
| Java Flight Recorder + Mission Control | CPU hot spots, allocations, locks in production |
| async-profiler | flame graphs with very low overhead |
| Spring Boot Actuator + Micrometer | request latency percentiles (p95, p99) |
| APM (New Relic, Datadog, Elastic) | slow endpoints, slow SQL in production |
A JMH Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Warmup(iterations = 3) @Measurement(iterations = 5) @Fork(1)
@State(Scope.Benchmark)
public class ConcatBenchmark {
@Param({"100", "1000"}) int n;
@Benchmark
public String plusInLoop() {
String s = "";
for (int i = 0; i < n; i++) s += i;
return s; // return the result so the JIT cannot remove the work
}
@Benchmark
public String stringBuilder() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < n; i++) sb.append(i);
return sb.toString();
}
}Example
java -jar target/benchmarks.jar ConcatBenchmarkExample output
Benchmark (n) Mode Cnt Score Error Units ConcatBenchmark.plusInLoop 100 avgt 5 2.913 ± 0.120 us/op ConcatBenchmark.plusInLoop 1000 avgt 5 187.402 ± 6.551 us/op ConcatBenchmark.stringBuilder 100 avgt 5 0.611 ± 0.020 us/op ConcatBenchmark.stringBuilder 1000 avgt 5 6.884 ± 0.233 us/op
Big-O Still Matters
Example
Checking membership in a List is O(n); in a HashSet it is O(1). With 20,000 ids, the list version does about 200 million comparisons; the set version about 20,000 lookups. (The program counts work instead of printing timings, so the output is stable.)
import java.util.*;
import java.util.stream.*;
public class Main {
public static void main(String[] args) {
List<Integer> blockedList = IntStream.range(0, 20_000).map(i -> i * 2).boxed().toList();
Set<Integer> blockedSet = new HashSet<>(blockedList);
List<Integer> incoming = IntStream.range(0, 20_000).boxed().toList();
long listHits = incoming.stream().filter(blockedList::contains).count(); // O(n * m)
long setHits = incoming.stream().filter(blockedSet::contains).count(); // O(n)
System.out.println("Blocked (list): " + listHits);
System.out.println("Blocked (set): " + setHits);
System.out.println("Worst-case comparisons with list: " + (long) incoming.size() * blockedList.size());
System.out.println("Hash lookups with set: " + incoming.size());
}
}Output
Blocked (list): 10000 Blocked (set): 10000 Worst-case comparisons with list: 400000000 Hash lookups with set: 20000