Energy Optimization Walkthrough
This walkthrough takes a real Python program through the full Joule energy analysis and optimization pipeline — from first scan to CI-ready energy budgets.
Step 1: Start with Real Code
Here's a data processing program with several common energy anti-patterns:
def find_duplicates(items, reference):
"""Find items that appear in both lists."""
duplicates = []
for item in items:
for ref in reference: # nested loop: O(n*m)
if item == ref:
duplicates.append(item)
return duplicates
def build_report(records):
"""Build a text report from records."""
report = ""
for i in range(len(records)): # len() in loop, string concat
report += "Record " + str(i) + ": " + str(records[i]) + "\n"
return report
def process_batch(data):
"""Filter and transform a data batch."""
results = []
for item in data:
temp = [] # allocation inside loop
temp.append(item * 2)
if temp[0] > 10:
results.append(temp[0])
return results
def search_all(items, targets):
"""Check if all targets exist in items."""
found = 0
for t in targets:
for item in items: # linear scan for each target
if item == t:
found = found + 1
# no break — scans entire list even after finding match
return found
def main():
data = []
for i in range(500):
data.append(i)
reference = []
for i in range(250, 750):
reference.append(i)
dups = find_duplicates(data, reference)
report = build_report(data)
processed = process_batch(data)
count = search_all(data, reference)
print(len(dups))
print(len(processed))
print(count)
main()
Step 2: Run Baseline Analysis
$ joulec --lift python anti_patterns.py --energy-report baseline.json
Energy Analysis: anti_patterns.py
find_duplicates 285.00 nJ (confidence: 0.50)
build_report 72.50 nJ (confidence: 0.55)
process_batch 18.30 nJ (confidence: 0.60)
search_all 285.00 nJ (confidence: 0.50)
main 12.40 nJ (confidence: 0.75)
Total: 673.20 nJ
Step 3: Read the Recommendations
Recommendations:
!!! [ALGORITHM] find_duplicates — O(n^2) nested loop for membership test
Suggestion: convert reference to a set for O(1) lookups
Estimated savings: 50x
!!! [ALGORITHM] search_all — O(n^2) nested loop for membership test
Suggestion: convert items to a set for O(1) lookups
Estimated savings: 50x
!! [STRING] build_report — string concatenation in loop
Suggestion: use "".join() to build string in one allocation
Estimated savings: 8x
!! [LOOP] search_all — no early exit after finding match
Suggestion: add break after match to avoid scanning remaining elements
Estimated savings: 2x (average case)
! [REDUNDANCY] build_report — len(records) called in loop range
Suggestion: compute len() once before the loop
Estimated savings: 1.2x
! [ALLOCATION] process_batch — list allocation inside loop body
Suggestion: reuse buffer or eliminate temporary list
Estimated savings: 3x
. [REDUNDANCY] build_report — str() conversion could use f-string
Suggestion: use f"Record {i}: {records[i]}" for cleaner concatenation
Estimated savings: 1.1x
Severity markers: !!! Critical, !! High, ! Medium, . Low
Step 4: Fix Critical Issues First
Fix #1: Hash set for find_duplicates
def find_duplicates(items, reference):
ref_set = set(reference) # O(m) one-time cost
duplicates = []
for item in items:
if item in ref_set: # O(1) per lookup
duplicates.append(item)
return duplicates
$ joulec --lift python fixed_v1.py
find_duplicates 8.20 nJ (confidence: 0.70) # was 285.00 nJ — 34x reduction
Fix #2: Hash set for search_all + early tracking
def search_all(items, targets):
item_set = set(items)
found = 0
for t in targets:
if t in item_set:
found = found + 1
return found
$ joulec --lift python fixed_v2.py
search_all 6.80 nJ (confidence: 0.75) # was 285.00 nJ — 42x reduction
Fix #3: String builder for build_report
def build_report(records):
n = len(records)
parts = []
for i in range(n):
parts.append(f"Record {i}: {records[i]}")
report = "\n".join(parts) + "\n"
return report
$ joulec --lift python fixed_v3.py
build_report 9.50 nJ (confidence: 0.75) # was 72.50 nJ — 7.6x reduction
Fix #4: Eliminate temporary allocation in process_batch
def process_batch(data):
results = []
for item in data:
doubled = item * 2
if doubled > 10:
results.append(doubled)
return results
$ joulec --lift python fixed_v4.py
process_batch 6.10 nJ (confidence: 0.75) # was 18.30 nJ — 3x reduction
Step 5: Run Optimized Baseline
After all four fixes:
$ joulec --lift python optimized.py --energy-report optimized.json
Energy Analysis: optimized.py
find_duplicates 8.20 nJ (confidence: 0.70)
build_report 9.50 nJ (confidence: 0.75)
process_batch 6.10 nJ (confidence: 0.75)
search_all 6.80 nJ (confidence: 0.75)
main 12.40 nJ (confidence: 0.75)
Total: 43.00 nJ
No recommendations — all detected anti-patterns have been resolved.
Step 6: Apply Automated Optimization
The --energy-optimize flag applies four compiler passes on top of your fixes:
$ joulec --energy-optimize --lift-run python optimized.py
Energy Optimization Report:
Pass 1 (Thermal-Aware Selection): 2 instructions adapted
Pass 2 (Branch Optimization): 3 branches reordered
Pass 3 (Loop Unrolling): 1 loop unrolled (trip count 4)
Pass 4 (DRAM Layout Analysis): no suggestions
Optimized energy: 38.70 nJ (10.0% reduction from automated passes)
Step 7: Compare Results
| Function | Before | After Fixes | After Optimization | Reduction |
|---|---|---|---|---|
| find_duplicates | 285.00 nJ | 8.20 nJ | 7.40 nJ | 97.4% |
| build_report | 72.50 nJ | 9.50 nJ | 8.90 nJ | 87.7% |
| process_batch | 18.30 nJ | 6.10 nJ | 5.50 nJ | 69.9% |
| search_all | 285.00 nJ | 6.80 nJ | 6.10 nJ | 97.9% |
| main | 12.40 nJ | 12.40 nJ | 10.80 nJ | 12.9% |
| Total | 673.20 nJ | 43.00 nJ | 38.70 nJ | 94.3% |
The manual fixes account for 93.6% of the savings. The automated passes add another 10% on top.
Step 8: Set an Energy Budget for CI
# Set budget at 50 nJ — optimized version passes
$ joulec --lift python optimized.py --energy-budget 50nJ
# Exit code: 0 (within budget)
# The original version would fail
$ joulec --lift python anti_patterns.py --energy-budget 50nJ
# Exit code: 1 (budget exceeded: 673.20 nJ > 50.00 nJ)
GitHub Actions Integration
- name: Energy budget check
run: |
joulec --lift python src/core.py --energy-budget 100nJ
joulec --lift python src/utils.py --energy-budget 50nJ
The build fails if any file exceeds its budget, catching energy regressions before merge.
Step 9: Generate Reports for Dashboards
$ joulec --lift python optimized.py --energy-report report.json
The JSON report includes per-function energy, confidence scores, and any remaining recommendations. Feed this into Grafana, Datadog, or any monitoring system to track energy consumption across releases.
Key Takeaways
- Start with
--liftto get a baseline without running the code - Fix critical recommendations first — algorithmic changes (O(n^2) → O(n)) yield the biggest savings
- Use
--energy-optimizefor automated passes on top of manual fixes - Set
--energy-budgetin CI to prevent regressions - Generate
--energy-reportJSON for tracking trends over time