Skip to content

Commit d0432af

Browse files
fulloclaude
andcommitted
Add examples/ with before/after SCI optimization demos
Three self-contained examples with anti-pattern → fix pairs: 01-string-processing: concatenation in loop → array + implode (-10% SCI) 02-database-simulation: N+1 queries → batch + hash-map (-98% SCI) 03-json-api: repeated decode/encode → single-pass aggregation (-15% SCI) Each example includes: - before.php (inefficient) and after.php (optimized) - Pre-generated results/ with all 4 report formats (JSONL, log, HTML, trend) - run-all.sh to regenerate reports on any machine The HTML dashboards contain SVG timeline charts and sparklines showing the SCI improvement visually. Zero external dependencies. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent a213b45 commit d0432af

20 files changed

Lines changed: 1240 additions & 0 deletions
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* String Processing — AFTER optimization.
7+
*
8+
* Fix: collect parts in an array, implode once at the end.
9+
* Each array append is O(1) amortized. The final implode() does a single
10+
* allocation for the complete string. Also computes summary stats in the
11+
* same loop instead of iterating twice.
12+
*
13+
* @author fullo <https://github.com/fullo>
14+
* @license MIT
15+
* @version 1.0
16+
*/
17+
18+
// Same 5,000 user records
19+
$users = [];
20+
for ($i = 0; $i < 5000; $i++) {
21+
$users[] = [
22+
'id' => $i + 1,
23+
'name' => 'User ' . str_pad((string) ($i + 1), 4, '0', STR_PAD_LEFT),
24+
'email' => 'user' . ($i + 1) . '@example.com',
25+
'score' => mt_rand(0, 10000) / 100,
26+
'active' => $i % 7 !== 0,
27+
];
28+
}
29+
30+
// ── Optimization 1: array of parts instead of concatenation ──
31+
// Each $parts[] = '...' is O(1). implode() at the end does one allocation.
32+
33+
$parts = [];
34+
$parts[] = '<!DOCTYPE html><html><head><title>User Report</title>';
35+
$parts[] = '<style>table{border-collapse:collapse}td,th{border:1px solid #ccc;padding:4px 8px}';
36+
$parts[] = 'tr:nth-child(even){background:#f9f9f9}.inactive{color:#999}</style></head><body>';
37+
$parts[] = '<h1>User Report</h1>';
38+
$parts[] = '<p>Generated: ' . date('Y-m-d H:i:s') . '</p>';
39+
$parts[] = '<table><thead><tr><th>ID</th><th>Name</th><th>Email</th><th>Score</th><th>Status</th></tr></thead><tbody>';
40+
41+
// ── Optimization 2: compute summary in the same loop ──
42+
// Avoids a second iteration over 5,000 records.
43+
$activeCount = 0;
44+
$totalScore = 0.0;
45+
46+
foreach ($users as $user) {
47+
$class = $user['active'] ? '' : ' class="inactive"';
48+
$status = $user['active'] ? 'Active' : 'Inactive';
49+
50+
// ── Optimization 3: sprintf for row — single string operation ──
51+
$parts[] = sprintf(
52+
'<tr%s><td>%d</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>',
53+
$class,
54+
$user['id'],
55+
htmlspecialchars($user['name']),
56+
htmlspecialchars($user['email']),
57+
number_format($user['score'], 2),
58+
$status,
59+
);
60+
61+
// Summary computed inline — no second loop
62+
if ($user['active']) {
63+
$activeCount++;
64+
}
65+
$totalScore += $user['score'];
66+
}
67+
68+
$parts[] = '</tbody></table>';
69+
$parts[] = '<p>Active: ' . $activeCount . ' / ' . count($users) . '</p>';
70+
$parts[] = '<p>Average score: ' . number_format($totalScore / count($users), 2) . '</p>';
71+
$parts[] = '</body></html>';
72+
73+
// ── Single allocation for the complete string ──
74+
$html = implode('', $parts);
75+
76+
echo 'Report generated: ' . strlen($html) . " bytes\n";
77+
echo 'Users: ' . count($users) . "\n";
78+
echo 'Active: ' . $activeCount . "\n";
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* String Processing — BEFORE optimization.
7+
*
8+
* Anti-pattern: string concatenation inside a loop.
9+
* Each .= on a large string forces PHP to reallocate and copy the entire
10+
* buffer, resulting in O(n²) memory copies.
11+
*
12+
* This simulates building an HTML report from 5,000 records — a common
13+
* task in admin panels, CSV exports, and email digest generators.
14+
*
15+
* @author fullo <https://github.com/fullo>
16+
* @license MIT
17+
* @version 1.0
18+
*/
19+
20+
// Simulate 5,000 user records
21+
$users = [];
22+
for ($i = 0; $i < 5000; $i++) {
23+
$users[] = [
24+
'id' => $i + 1,
25+
'name' => 'User ' . str_pad((string) ($i + 1), 4, '0', STR_PAD_LEFT),
26+
'email' => 'user' . ($i + 1) . '@example.com',
27+
'score' => mt_rand(0, 10000) / 100,
28+
'active' => $i % 7 !== 0,
29+
];
30+
}
31+
32+
// ── Anti-pattern: concatenation in loop ──
33+
// Each .= copies the entire $html string, which grows with every iteration.
34+
// For 5,000 rows, this means ~12.5 million characters copied cumulatively.
35+
36+
$html = '<!DOCTYPE html><html><head><title>User Report</title>';
37+
$html .= '<style>table{border-collapse:collapse}td,th{border:1px solid #ccc;padding:4px 8px}';
38+
$html .= 'tr:nth-child(even){background:#f9f9f9}.inactive{color:#999}</style></head><body>';
39+
$html .= '<h1>User Report</h1>';
40+
$html .= '<p>Generated: ' . date('Y-m-d H:i:s') . '</p>';
41+
$html .= '<table><thead><tr><th>ID</th><th>Name</th><th>Email</th><th>Score</th><th>Status</th></tr></thead><tbody>';
42+
43+
foreach ($users as $user) {
44+
$class = $user['active'] ? '' : ' class="inactive"';
45+
$status = $user['active'] ? 'Active' : 'Inactive';
46+
$scoreFormatted = number_format($user['score'], 2);
47+
48+
// Each concatenation copies the entire $html string
49+
$html .= '<tr' . $class . '>';
50+
$html .= '<td>' . $user['id'] . '</td>';
51+
$html .= '<td>' . htmlspecialchars($user['name']) . '</td>';
52+
$html .= '<td>' . htmlspecialchars($user['email']) . '</td>';
53+
$html .= '<td>' . $scoreFormatted . '</td>';
54+
$html .= '<td>' . $status . '</td>';
55+
$html .= '</tr>';
56+
}
57+
58+
$html .= '</tbody></table>';
59+
60+
// Summary: count active/inactive
61+
$activeCount = 0;
62+
$totalScore = 0.0;
63+
foreach ($users as $user) {
64+
if ($user['active']) {
65+
$activeCount++;
66+
}
67+
$totalScore += $user['score'];
68+
}
69+
70+
$html .= '<p>Active: ' . $activeCount . ' / ' . count($users) . '</p>';
71+
$html .= '<p>Average score: ' . number_format($totalScore / count($users), 2) . '</p>';
72+
$html .= '</body></html>';
73+
74+
echo 'Report generated: ' . strlen($html) . " bytes\n";
75+
echo 'Users: ' . count($users) . "\n";
76+
echo 'Active: ' . $activeCount . "\n";
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>SCI Profiler Dashboard</title>
7+
<style>
8+
* { margin: 0; padding: 0; box-sizing: border-box; }
9+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #f5f5f5; color: #333; padding: 2rem; max-width: 1200px; margin: 0 auto; }
10+
h1 { color: #2d6a4f; margin-bottom: 0.3rem; }
11+
h2 { color: #2d6a4f; margin: 1.5rem 0 0.8rem; font-size: 1.1rem; }
12+
.meta { color: #666; font-size: 0.85rem; margin-bottom: 1.5rem; }
13+
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 1rem; margin-bottom: 1.5rem; }
14+
.card { background: #fff; border-radius: 8px; padding: 1rem; box-shadow: 0 1px 3px rgba(0,0,0,.1); }
15+
.card .label { font-size: 0.75rem; color: #666; text-transform: uppercase; letter-spacing: 0.03em; }
16+
.card .value { font-size: 1.6rem; font-weight: 700; color: #2d6a4f; }
17+
.card .unit { font-size: 0.8rem; color: #888; }
18+
table { width: 100%; border-collapse: collapse; background: #fff; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,.1); margin-bottom: 1.5rem; }
19+
th { background: #2d6a4f; color: #fff; padding: 0.6rem 0.8rem; text-align: left; font-size: 0.8rem; }
20+
td { padding: 0.5rem 0.8rem; border-bottom: 1px solid #eee; font-size: 0.82rem; }
21+
td.num { text-align: right; font-variant-numeric: tabular-nums; }
22+
tr:hover td { background: #f0faf4; }
23+
td small { color: #999; font-size: 0.75rem; }
24+
.badge { font-size: 0.7rem; padding: 1px 5px; border-radius: 3px; font-weight: 600; }
25+
.badge.ok { background: #d4edda; color: #155724; }
26+
.badge.redir { background: #fff3cd; color: #856404; }
27+
.badge.err { background: #f8d7da; color: #721c24; }
28+
.delta { font-size: 0.8rem; font-weight: 600; }
29+
.delta.better { color: #28a745; }
30+
.delta.worse { color: #dc3545; }
31+
.delta.stable { color: #6c757d; }
32+
.config-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 0.5rem; background: #fff; border-radius: 8px; padding: 1rem; box-shadow: 0 1px 3px rgba(0,0,0,.1); margin-bottom: 1.5rem; }
33+
.config-item .ck { font-size: 0.75rem; color: #888; text-transform: uppercase; }
34+
.config-item .cv { font-size: 0.95rem; color: #333; font-weight: 500; }
35+
.timeline-box { background: #fff; border-radius: 8px; padding: 1rem; box-shadow: 0 1px 3px rgba(0,0,0,.1); margin-bottom: 1.5rem; }
36+
.timeline-box svg { width: 100%; height: auto; }
37+
.sparkline { vertical-align: middle; }
38+
.lastprev { font-size: 0.78rem; white-space: nowrap; }
39+
.lastprev .arrow { font-weight: 700; }
40+
</style>
41+
</head>
42+
<body>
43+
<h1>SCI Profiler Dashboard</h1>
44+
<p class="meta">Machine: Example: 01-string-processing | Generated: 2026-03-18T23:40:08+00:00</p>
45+
46+
<div class="cards">
47+
<div class="card">
48+
<div class="label">Total Requests</div>
49+
<div class="value">6</div>
50+
</div>
51+
<div class="card">
52+
<div class="label">Avg SCI Score</div>
53+
<div class="value">0.029902166666667</div>
54+
<div class="unit">mgCO2eq / request</div>
55+
</div>
56+
<div class="card">
57+
<div class="label">Total Emissions</div>
58+
<div class="value">0.179413</div>
59+
<div class="unit">mgCO2eq</div>
60+
</div>
61+
<div class="card">
62+
<div class="label">Avg Response Time</div>
63+
<div class="value">4.4776666666667</div>
64+
<div class="unit">ms</div>
65+
</div>
66+
</div>
67+
68+
<h2>SCI Timeline (Last 6)</h2><div class="timeline-box"><svg viewBox="0 0 800 150" xmlns="http://www.w3.org/2000/svg"><line x1="60" y1="102.5" x2="790" y2="102.5" stroke="#eee" stroke-width="1"/><line x1="60" y1="75.0" x2="790" y2="75.0" stroke="#eee" stroke-width="1"/><line x1="60" y1="47.5" x2="790" y2="47.5" stroke="#eee" stroke-width="1"/><polygon points="60.0,20.0 206.0,72.6 352.0,66.0 498.0,45.7 644.0,93.5 790.0,130.0 790.0,130.0 60.0,130.0" fill="rgba(45,106,79,0.1)" /><polyline points="60.0,20.0 206.0,72.6 352.0,66.0 498.0,45.7 644.0,93.5 790.0,130.0" fill="none" stroke="#2d6a4f" stroke-width="2" stroke-linejoin="round" /><text x="55" y="24.0" font-size="10" fill="#888" text-anchor="end">0.0338</text><text x="55" y="134.0" font-size="10" fill="#888" text-anchor="end">0.0255</text><text x="60" y="148" font-size="9" fill="#aaa">2026-03-18T23:40</text><text x="790" y="148" font-size="9" fill="#aaa" text-anchor="end">2026-03-18T23:40</text></svg></div>
69+
70+
<h2>Measurement Parameters</h2><div class="config-grid"><div class="config-item"><div class="ck">Device Power (E)</div><div class="cv">18 W</div></div><div class="config-item"><div class="ck">Grid Carbon Intensity (I)</div><div class="cv">332 gCO2eq/kWh</div></div><div class="config-item"><div class="ck">Embodied Carbon (M)</div><div class="cv">211,000 gCO2eq</div></div><div class="config-item"><div class="ck">Device Lifetime</div><div class="cv">11,680 hours</div></div></div>
71+
72+
<h2>Per-Script Summary</h2>
73+
<table>
74+
<thead>
75+
<tr>
76+
<th>Script</th>
77+
<th>Runs</th>
78+
<th>Avg SCI (mgCO2eq)</th>
79+
<th>Avg Time (ms)</th>
80+
<th>Peak Mem (MB)</th>
81+
<th>Sparkline</th>
82+
<th>Last vs Prev</th>
83+
<th>Trend</th>
84+
</tr>
85+
</thead>
86+
<tbody><tr><td>01-string-processing/before.php</td><td class="num">3</td><td class="num">0.0313</td><td class="num">4.69</td><td class="num">6.0</td><td><svg class="sparkline" width="120" height="24" viewBox="0 0 120 24"><polyline points="0.0,2.0 60.0,22.0 120.0,19.5" fill="none" stroke="#2d6a4f" stroke-width="1.5" stroke-linejoin="round" /></svg></td><td><span class="lastprev">0.0298 → 0.0303 <span class="arrow" style="color:#6c757d">═ 1.7%</span></span></td><td></td></tr><tr><td>01-string-processing/after.php</td><td class="num">3</td><td class="num">0.0285</td><td class="num">4.27</td><td class="num">6.0</td><td><svg class="sparkline" width="120" height="24" viewBox="0 0 120 24"><polyline points="0.0,2.0 60.0,13.3 120.0,22.0" fill="none" stroke="#2d6a4f" stroke-width="1.5" stroke-linejoin="round" /></svg></td><td><span class="lastprev">0.0282 → 0.0255 <span class="arrow" style="color:#28a745">▼ -9.8%</span></span></td><td></td></tr></tbody>
87+
</table>
88+
89+
<h2>Recent Requests</h2>
90+
<table>
91+
<thead>
92+
<tr>
93+
<th>Timestamp</th>
94+
<th>Method</th>
95+
<th>URI</th>
96+
<th>Time (ms)</th>
97+
<th>SCI (mgCO2eq)</th>
98+
<th>Peak (MB)</th>
99+
</tr>
100+
</thead>
101+
<tbody><tr><td>2026-03-18T23:40:08</td><td>CLI</td><td>/Users/fullo/Development/sci-profiler-php/examples/01-string-processing/after.php</td><td class="num">3.81</td><td class="num">0.0255</td><td class="num">6</td></tr><tr><td>2026-03-18T23:40:08</td><td>CLI</td><td>/Users/fullo/Development/sci-profiler-php/examples/01-string-processing/after.php</td><td class="num">4.23</td><td class="num">0.0282 <span class="delta worse"></span></td><td class="num">6</td></tr><tr><td>2026-03-18T23:40:08</td><td>CLI</td><td>/Users/fullo/Development/sci-profiler-php/examples/01-string-processing/after.php</td><td class="num">4.77</td><td class="num">0.0318 <span class="delta worse"></span></td><td class="num">6</td></tr><tr><td>2026-03-18T23:40:08</td><td>CLI</td><td>/Users/fullo/Development/sci-profiler-php/examples/01-string-processing/before.php</td><td class="num">4.54</td><td class="num">0.0303</td><td class="num">6</td></tr><tr><td>2026-03-18T23:40:08</td><td>CLI</td><td>/Users/fullo/Development/sci-profiler-php/examples/01-string-processing/before.php</td><td class="num">4.46</td><td class="num">0.0298</td><td class="num">6</td></tr><tr><td>2026-03-18T23:40:08</td><td>CLI</td><td>/Users/fullo/Development/sci-profiler-php/examples/01-string-processing/before.php</td><td class="num">5.06</td><td class="num">0.0338 <span class="delta worse"></span></td><td class="num">6</td></tr></tbody>
102+
</table>
103+
</body>
104+
</html>

0 commit comments

Comments
 (0)