Energy System Guide

Joule's defining feature is compile-time energy budget verification. This guide explains how it works and how to use it.

Why Energy Budgets?

Computing consumes enormous amounts of energy, and most of it is invisible. Cloud providers report aggregate billing units. Industry benchmarks report averages. Nobody tells you what a single sort, a single allocation, or a single network call actually costs in joules.

Joule makes that cost visible. Every function can declare its energy budget, and the compiler enforces it at compile time.

Basic Usage

Annotate a function with #[energy_budget]:

#[energy_budget(max_joules = 0.0001)]  // 100 microjoules
fn add(x: i32, y: i32) -> i32 {
    x + y
}

Compile with energy checking enabled:

joulec program.joule -o program --energy-check

If the function's estimated energy exceeds the declared budget, compilation fails with a diagnostic:

error: energy budget exceeded in function 'process_data'
  --> program.joule:15:1
   |
15 | fn process_data(input: Vec<f64>) -> f64 {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = estimated: 0.00035 J (confidence: 85%)
   = budget:    0.00010 J
   = exceeded by 250%

Budget Types

Energy Budget (Joules)

The primary budget type. Limits total energy consumption:

#[energy_budget(max_joules = 0.0005)]
fn fibonacci(n: i32) -> i32 {
    // ...
}

Power Budget (Watts)

Limits average power draw. Useful for sustained workloads:

#[energy_budget(max_watts = 15.0)]
fn render_frame(scene: Scene) -> Image {
    // ...
}

Thermal Budget (Temperature Delta)

Limits the temperature increase caused by the function. Prevents thermal throttling:

#[energy_budget(max_temp_delta = 5.0)]  // max 5 degrees Celsius rise
fn heavy_compute(data: Vec<f64>) -> f64 {
    // ...
}

Thermal-Aware Functions

The #[thermal_aware] attribute marks functions that should adapt to thermal conditions:

#[energy_budget(max_joules = 0.0002)]
#[thermal_aware]
fn adaptive_compute(n: i32) -> i32 {
    let result = n * n;
    result + 1
}

Combining Budgets

You can declare multiple budget constraints:

#[energy_budget(max_joules = 0.001, max_watts = 20.0, max_temp_delta = 3.0)]
fn critical_path(data: Vec<f64>) -> f64 {
    // ...
}

How the Estimator Works

The compiler uses static analysis to estimate energy consumption without running your code. Here's what it considers:

Instruction Costs

Every operation has a calibrated energy cost in picojoules:

OperationApproximate CostCycles
Integer add/sub0.05 pJ1
Integer multiply0.35 pJ3
Integer divide3.5 pJ10
Float add/sub0.35 pJ3
Float multiply0.35 pJ3
Float divide3.5 pJ10
Float sqrt5.25 pJ15
L1 cache load0.5 pJ4
L2 cache load3.0 pJ12
L3 cache load10.0 pJ40
DRAM load/store200.0 pJ200
Branch (taken)0.1 pJ1
Branch misprediction1.5 pJ15
SIMD f32x8 multiply1.5 pJ3
Half-precision (f16/bf16) op0.4 pJ1
SmallVec inline push0.5 pJ1
SmallVec heap spill45.0 pJ~100
SIMD vector op (any width)2.0 pJ3
Atomic read-modify-write8.0 pJ20
Rc/Arc clone/drop3.0 pJ5
Arena bump alloc1.0 pJ2
Arena reset (free all)0.5 pJ1
BitSet/BitVec word op0.3 pJ1
Decimal (128-bit) arithmetic5.0 pJ15
Deque push/pop2.0 pJ5
Intern hash lookup10.0 pJ30
Complex arithmetic1.6 pJ4
Instant::now() (clock read)15.0 pJ50
BTreeMap/BTreeSet traversal12.0 pJ40

Loop Analysis

For loops with known bounds, the estimator multiplies the loop body cost by the iteration count. For unbounded loops (while with runtime conditions), it uses a configurable default (100 iterations) and reduces the confidence score.

Branch Analysis

For if/else and match expressions, the estimator computes the cost of each branch and averages them, since it can't know which branch will execute at compile time. This reduces the confidence score.

Confidence Score

Every estimate comes with a confidence score from 0.0 to 1.0:

  • 1.0 -- Straight-line code, no loops or branches. Estimate is precise.
  • 0.85-0.95 -- Code with branches. Estimate is an average.
  • 0.5-0.85 -- Code with unbounded loops. Estimate depends on assumed iteration count.
  • < 0.5 -- Complex code with nested unbounded loops. Estimate is rough.

The confidence score is shown in diagnostic output so you can judge the reliability of the estimate.

Power Estimation

Power (watts) is derived from energy and estimated execution time:

Power = Energy / Time
Time = Estimated Cycles / CPU Frequency (3.0 GHz reference)

Thermal Estimation

Temperature delta is derived from power using a simplified thermal model:

Delta_T = Power * Thermal_Resistance (0.4 K/W typical)

Three-Tier Energy Measurement

Joule measures energy at three levels, providing increasing precision:

Tier 1: Static Estimation (Compile Time)

The compiler estimates energy from code structure alone, using the instruction cost model described above. This is available for all programs, everywhere, with zero runtime overhead.

  • No hardware access required
  • Works at compile time
  • Confidence score indicates reliability
  • Used for #[energy_budget] verification

Tier 2: CPU Performance Counters (Runtime)

On supported platforms, Joule reads hardware performance counters (RAPL on Intel/AMD) to measure actual CPU energy consumption during execution.

  • Requires Linux with perf_event or macOS with powermetrics
  • Per-function and per-scope measurements
  • Joule-level precision (not just watt-hours)

Tier 3: Accelerator Energy (Runtime)

For GPU and accelerator workloads, Joule queries vendor-specific energy APIs. See Accelerator Energy Measurement for details.

  • NVIDIA GPUs via NVML
  • AMD GPUs via ROCm SMI
  • Intel GPUs/accelerators via Level Zero
  • Google TPUs via TPU runtime
  • AWS Inferentia/Trainium via Neuron SDK
  • Groq LPUs via HLML
  • Cerebras and SambaNova via vendor APIs

JSON Output Mode

For programmatic consumption, set the environment variable JOULE_ENERGY_JSON=1:

JOULE_ENERGY_JSON=1 joulec program.joule --emit c --energy-check -o program.c

This outputs energy reports as structured JSON:

{
  "functions": [
    {
      "name": "process_data",
      "file": "program.joule",
      "line": 15,
      "energy_joules": 0.00035,
      "power_watts": 12.5,
      "confidence": 0.85,
      "budget_joules": 0.0001,
      "status": "exceeded",
      "breakdown": {
        "compute_pj": 280000,
        "memory_pj": 70000,
        "branch_pj": 500
      }
    }
  ],
  "total_energy_joules": 0.00042,
  "device": "cpu"
}

When accelerator energy is available, the JSON includes per-device breakdowns:

{
  "devices": [
    { "type": "cpu", "energy_joules": 0.00042 },
    { "type": "gpu", "vendor": "nvidia", "energy_joules": 0.0031, "api": "nvml" }
  ],
  "total_energy_joules": 0.00352
}

Practical Guidelines

Start Generous, Then Tighten

Begin with a generous budget, measure, then reduce:

// Start here
#[energy_budget(max_joules = 0.01)]

// After profiling, tighten
#[energy_budget(max_joules = 0.001)]

// Production target
#[energy_budget(max_joules = 0.0005)]

Budget Hot Loops Carefully

The estimator assumes 100 iterations for unbounded loops. If your loop runs 10,000 times, the estimate will be 100x too low. Consider refactoring into bounded loops or adjusting your budget accordingly.

Use Confidence Scores

If the compiler reports low confidence (< 0.7), the estimate may be significantly off. Review the function for unbounded loops and complex branching.

Transitive Energy Budgets

Energy budgets are enforced across call boundaries. A function calling another budgeted function includes the callee's energy in its own estimate:

#[energy_budget(max_joules = 0.0001)]
fn helper() -> i32 { 42 }

#[energy_budget(max_joules = 0.0005)]
fn main_work() -> i32 {
    // The compiler accounts for helper's energy within main_work's budget
    helper() + helper()
}

Profile-Guided Refinement

For the most accurate energy estimates, use profile-guided optimization:

# Phase 1: instrument and run
joulec program.joule --profile-generate -o program
./program

# Phase 2: compile with profile data
joulec program.joule --profile-use profile.json --energy-check -o program

The profile data provides actual loop trip counts and branch frequencies, dramatically improving estimate accuracy.

Feedback

Questions about the energy system? Visit joule-lang.org or open an issue on GitHub.