mirror of
https://github.com/forcedotcom/afv-library.git
synced 2026-08-05 14:54:50 +08:00
328 lines
15 KiB
OpenEdge ABL
328 lines
15 KiB
OpenEdge ABL
|
|
/**
|
|||
|
|
* Apex Benchmarking Template
|
|||
|
|
*
|
|||
|
|
* Reliable performance testing using established Apex benchmarking techniques.
|
|||
|
|
* Run in Anonymous Apex for consistent results.
|
|||
|
|
*
|
|||
|
|
* Features:
|
|||
|
|
* - Warm-up phase to normalize JIT compilation
|
|||
|
|
* - Multiple iterations for statistical accuracy
|
|||
|
|
* - Side-by-side comparison of two approaches
|
|||
|
|
* - Automatic winner determination
|
|||
|
|
*
|
|||
|
|
* See: references/benchmarking-guide.md for methodology and benchmark data
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|||
|
|
// SIMPLE BENCHMARK (Copy into Anonymous Apex)
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
// Quick single-operation benchmark
|
|||
|
|
Long startTime = System.currentTimeMillis();
|
|||
|
|
|
|||
|
|
for (Integer i = 0; i < 10000; i++) {
|
|||
|
|
// Your operation here
|
|||
|
|
String s = 'test'.toUpperCase();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Long duration = System.currentTimeMillis() - startTime;
|
|||
|
|
System.debug('Duration: ' + duration + 'ms');
|
|||
|
|
System.debug('Per iteration: ' + (duration / 10000.0) + 'ms');
|
|||
|
|
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|||
|
|
// COMPARISON BENCHMARK CLASS
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Compare two implementations side-by-side
|
|||
|
|
*
|
|||
|
|
* Usage in Anonymous Apex:
|
|||
|
|
* BenchmarkComparison.run();
|
|||
|
|
*/
|
|||
|
|
public class BenchmarkComparison {
|
|||
|
|
|
|||
|
|
private static final Integer ITERATIONS = 10000;
|
|||
|
|
private static final Integer WARMUP_ITERATIONS = 100;
|
|||
|
|
|
|||
|
|
public static void run() {
|
|||
|
|
System.debug('═══════════════════════════════════════════════════════');
|
|||
|
|
System.debug('BENCHMARK: [Description Here]');
|
|||
|
|
System.debug('Iterations: ' + ITERATIONS);
|
|||
|
|
System.debug('═══════════════════════════════════════════════════════');
|
|||
|
|
|
|||
|
|
// Warm-up phase (normalizes JIT compilation effects)
|
|||
|
|
System.debug('\n🔥 Warm-up phase...');
|
|||
|
|
for (Integer i = 0; i < WARMUP_ITERATIONS; i++) {
|
|||
|
|
methodA();
|
|||
|
|
methodB();
|
|||
|
|
}
|
|||
|
|
System.debug(' Warm-up complete');
|
|||
|
|
|
|||
|
|
// Benchmark Method A
|
|||
|
|
System.debug('\n📊 Running Method A...');
|
|||
|
|
Long startA = System.currentTimeMillis();
|
|||
|
|
for (Integer i = 0; i < ITERATIONS; i++) {
|
|||
|
|
methodA();
|
|||
|
|
}
|
|||
|
|
Long durationA = System.currentTimeMillis() - startA;
|
|||
|
|
|
|||
|
|
// Benchmark Method B
|
|||
|
|
System.debug('📊 Running Method B...');
|
|||
|
|
Long startB = System.currentTimeMillis();
|
|||
|
|
for (Integer i = 0; i < ITERATIONS; i++) {
|
|||
|
|
methodB();
|
|||
|
|
}
|
|||
|
|
Long durationB = System.currentTimeMillis() - startB;
|
|||
|
|
|
|||
|
|
// Results
|
|||
|
|
System.debug('\n═══════════════════════════════════════════════════════');
|
|||
|
|
System.debug('RESULTS');
|
|||
|
|
System.debug('═══════════════════════════════════════════════════════');
|
|||
|
|
System.debug('Method A: ' + durationA + 'ms (' + (durationA / (Decimal)ITERATIONS) + 'ms per iteration)');
|
|||
|
|
System.debug('Method B: ' + durationB + 'ms (' + (durationB / (Decimal)ITERATIONS) + 'ms per iteration)');
|
|||
|
|
System.debug('───────────────────────────────────────────────────────');
|
|||
|
|
|
|||
|
|
Long difference = Math.abs(durationA - durationB);
|
|||
|
|
String winner = durationA < durationB ? 'Method A' : 'Method B';
|
|||
|
|
Decimal improvement = durationA < durationB
|
|||
|
|
? (durationB / (Decimal)durationA)
|
|||
|
|
: (durationA / (Decimal)durationB);
|
|||
|
|
|
|||
|
|
System.debug('🏆 Winner: ' + winner);
|
|||
|
|
System.debug(' Difference: ' + difference + 'ms');
|
|||
|
|
System.debug(' Improvement: ' + improvement.setScale(1) + 'x faster');
|
|||
|
|
|
|||
|
|
// Governor limits status
|
|||
|
|
System.debug('\n📈 Governor Limits Used:');
|
|||
|
|
System.debug(' CPU Time: ' + Limits.getCpuTime() + '/' + Limits.getLimitCpuTime() + 'ms');
|
|||
|
|
System.debug(' Heap Size: ' + Limits.getHeapSize() + '/' + Limits.getLimitHeapSize() + ' bytes');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════
|
|||
|
|
// METHODS TO COMPARE (Replace with your implementations)
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
private static void methodA() {
|
|||
|
|
// Implementation A - e.g., String concatenation
|
|||
|
|
String result = '';
|
|||
|
|
for (Integer i = 0; i < 10; i++) {
|
|||
|
|
result += 'item' + i + ',';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static void methodB() {
|
|||
|
|
// Implementation B - e.g., String.join()
|
|||
|
|
List<String> items = new List<String>();
|
|||
|
|
for (Integer i = 0; i < 10; i++) {
|
|||
|
|
items.add('item' + i);
|
|||
|
|
}
|
|||
|
|
String result = String.join(items, ',');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|||
|
|
// DATA STRUCTURE BENCHMARK
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Compare List vs Set vs Map lookup performance
|
|||
|
|
*
|
|||
|
|
* Expected Results:
|
|||
|
|
* List.contains(): O(n) - slow
|
|||
|
|
* Set.contains(): O(1) - fast
|
|||
|
|
* Map.containsKey(): O(1) - fast
|
|||
|
|
*/
|
|||
|
|
public class DataStructureBenchmark {
|
|||
|
|
|
|||
|
|
private static final Integer DATA_SIZE = 1000;
|
|||
|
|
private static final Integer LOOKUPS = 5000;
|
|||
|
|
|
|||
|
|
public static void run() {
|
|||
|
|
// Setup test data
|
|||
|
|
List<String> testList = new List<String>();
|
|||
|
|
Set<String> testSet = new Set<String>();
|
|||
|
|
Map<String, Boolean> testMap = new Map<String, Boolean>();
|
|||
|
|
|
|||
|
|
for (Integer i = 0; i < DATA_SIZE; i++) {
|
|||
|
|
String key = 'key_' + i;
|
|||
|
|
testList.add(key);
|
|||
|
|
testSet.add(key);
|
|||
|
|
testMap.put(key, true);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Random lookup keys (mix of existing and non-existing)
|
|||
|
|
List<String> lookupKeys = new List<String>();
|
|||
|
|
for (Integer i = 0; i < LOOKUPS; i++) {
|
|||
|
|
lookupKeys.add('key_' + Math.mod(i * 7, DATA_SIZE * 2));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
System.debug('═══════════════════════════════════════════════════════');
|
|||
|
|
System.debug('DATA STRUCTURE LOOKUP BENCHMARK');
|
|||
|
|
System.debug('Data Size: ' + DATA_SIZE + ' | Lookups: ' + LOOKUPS);
|
|||
|
|
System.debug('═══════════════════════════════════════════════════════');
|
|||
|
|
|
|||
|
|
// List.contains()
|
|||
|
|
Long startList = System.currentTimeMillis();
|
|||
|
|
for (String key : lookupKeys) {
|
|||
|
|
Boolean found = testList.contains(key);
|
|||
|
|
}
|
|||
|
|
Long durationList = System.currentTimeMillis() - startList;
|
|||
|
|
|
|||
|
|
// Set.contains()
|
|||
|
|
Long startSet = System.currentTimeMillis();
|
|||
|
|
for (String key : lookupKeys) {
|
|||
|
|
Boolean found = testSet.contains(key);
|
|||
|
|
}
|
|||
|
|
Long durationSet = System.currentTimeMillis() - startSet;
|
|||
|
|
|
|||
|
|
// Map.containsKey()
|
|||
|
|
Long startMap = System.currentTimeMillis();
|
|||
|
|
for (String key : lookupKeys) {
|
|||
|
|
Boolean found = testMap.containsKey(key);
|
|||
|
|
}
|
|||
|
|
Long durationMap = System.currentTimeMillis() - startMap;
|
|||
|
|
|
|||
|
|
// Results
|
|||
|
|
System.debug('\nRESULTS:');
|
|||
|
|
System.debug('List.contains(): ' + durationList + 'ms');
|
|||
|
|
System.debug('Set.contains(): ' + durationSet + 'ms');
|
|||
|
|
System.debug('Map.containsKey(): ' + durationMap + 'ms');
|
|||
|
|
|
|||
|
|
if (durationList > 0) {
|
|||
|
|
System.debug('\nSet is ' + (durationList / Math.max(1, durationSet)) + 'x faster than List');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|||
|
|
// LOOP STYLE BENCHMARK
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Compare different loop constructs
|
|||
|
|
*
|
|||
|
|
* Expected Results (from documented Apex loop benchmarks):
|
|||
|
|
* While loop: ~0.4s (fastest)
|
|||
|
|
* Cached iterator: ~0.8s
|
|||
|
|
* For loop (index): ~1.4s
|
|||
|
|
* Enhanced for-each: ~2.4s
|
|||
|
|
*/
|
|||
|
|
public class LoopBenchmark {
|
|||
|
|
|
|||
|
|
private static final Integer ITERATIONS = 10000;
|
|||
|
|
|
|||
|
|
public static void run() {
|
|||
|
|
// Create test data
|
|||
|
|
List<Integer> numbers = new List<Integer>();
|
|||
|
|
for (Integer i = 0; i < 1000; i++) {
|
|||
|
|
numbers.add(i);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
System.debug('═══════════════════════════════════════════════════════');
|
|||
|
|
System.debug('LOOP STYLE BENCHMARK');
|
|||
|
|
System.debug('Outer Iterations: ' + ITERATIONS + ' | List Size: ' + numbers.size());
|
|||
|
|
System.debug('═══════════════════════════════════════════════════════');
|
|||
|
|
|
|||
|
|
// While loop with iterator
|
|||
|
|
Long startWhile = System.currentTimeMillis();
|
|||
|
|
for (Integer outer = 0; outer < ITERATIONS; outer++) {
|
|||
|
|
Iterator<Integer> iter = numbers.iterator();
|
|||
|
|
while (iter.hasNext()) {
|
|||
|
|
Integer num = iter.next();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
Long durationWhile = System.currentTimeMillis() - startWhile;
|
|||
|
|
|
|||
|
|
// Traditional for loop
|
|||
|
|
Long startFor = System.currentTimeMillis();
|
|||
|
|
for (Integer outer = 0; outer < ITERATIONS; outer++) {
|
|||
|
|
for (Integer i = 0; i < numbers.size(); i++) {
|
|||
|
|
Integer num = numbers[i];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
Long durationFor = System.currentTimeMillis() - startFor;
|
|||
|
|
|
|||
|
|
// Enhanced for-each
|
|||
|
|
Long startEnhanced = System.currentTimeMillis();
|
|||
|
|
for (Integer outer = 0; outer < ITERATIONS; outer++) {
|
|||
|
|
for (Integer num : numbers) {
|
|||
|
|
// Just iterate
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
Long durationEnhanced = System.currentTimeMillis() - startEnhanced;
|
|||
|
|
|
|||
|
|
// Results
|
|||
|
|
System.debug('\nRESULTS:');
|
|||
|
|
System.debug('While loop: ' + durationWhile + 'ms');
|
|||
|
|
System.debug('For loop: ' + durationFor + 'ms');
|
|||
|
|
System.debug('Enhanced for: ' + durationEnhanced + 'ms');
|
|||
|
|
|
|||
|
|
System.debug('\n📊 Analysis:');
|
|||
|
|
Long fastest = Math.min(durationWhile, Math.min(durationFor, durationEnhanced));
|
|||
|
|
if (durationWhile == fastest) {
|
|||
|
|
System.debug('🏆 While loop is fastest');
|
|||
|
|
} else if (durationFor == fastest) {
|
|||
|
|
System.debug('🏆 For loop is fastest');
|
|||
|
|
} else {
|
|||
|
|
System.debug('🏆 Enhanced for is fastest');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|||
|
|
// STRING BENCHMARK (demonstrates 22x improvement)
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* String concatenation vs String.join()
|
|||
|
|
*
|
|||
|
|
* Expected Results (from documented Apex benchmarks):
|
|||
|
|
* Concatenation: 11,767ms for 1,750 items (CPU LIMIT)
|
|||
|
|
* String.join(): 539ms for 7,500 items (still running)
|
|||
|
|
* Improvement: ~22x faster
|
|||
|
|
*/
|
|||
|
|
public class StringBenchmark {
|
|||
|
|
|
|||
|
|
public static void run() {
|
|||
|
|
System.debug('═══════════════════════════════════════════════════════');
|
|||
|
|
System.debug('STRING BUILDING BENCHMARK');
|
|||
|
|
System.debug('═══════════════════════════════════════════════════════');
|
|||
|
|
|
|||
|
|
// Test with safe number of items
|
|||
|
|
Integer itemCount = 500;
|
|||
|
|
|
|||
|
|
// Method 1: String concatenation (BAD)
|
|||
|
|
Long startConcat = System.currentTimeMillis();
|
|||
|
|
String resultConcat = '';
|
|||
|
|
for (Integer i = 0; i < itemCount; i++) {
|
|||
|
|
resultConcat += 'Item_' + i + '_Name\n';
|
|||
|
|
}
|
|||
|
|
Long durationConcat = System.currentTimeMillis() - startConcat;
|
|||
|
|
|
|||
|
|
// Method 2: String.join() (GOOD)
|
|||
|
|
Long startJoin = System.currentTimeMillis();
|
|||
|
|
List<String> items = new List<String>();
|
|||
|
|
for (Integer i = 0; i < itemCount; i++) {
|
|||
|
|
items.add('Item_' + i + '_Name');
|
|||
|
|
}
|
|||
|
|
String resultJoin = String.join(items, '\n');
|
|||
|
|
Long durationJoin = System.currentTimeMillis() - startJoin;
|
|||
|
|
|
|||
|
|
// Results
|
|||
|
|
System.debug('\nRESULTS (' + itemCount + ' items):');
|
|||
|
|
System.debug('String +=: ' + durationConcat + 'ms');
|
|||
|
|
System.debug('String.join(): ' + durationJoin + 'ms');
|
|||
|
|
|
|||
|
|
if (durationJoin > 0 && durationConcat > durationJoin) {
|
|||
|
|
System.debug('\n🏆 String.join() is ' + (durationConcat / durationJoin) + 'x faster!');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
System.debug('\n⚠️ WARNING: With larger datasets, concatenation hits CPU limit!');
|
|||
|
|
System.debug(' Documented benchmark: 11,767ms for 1,750 items (concat) vs 539ms for 7,500 items (join)');
|
|||
|
|
}
|
|||
|
|
}
|