Skip to content

Commit c55a8f9

Browse files
committed
add live origins
1 parent 8aa7431 commit c55a8f9

File tree

1 file changed

+165
-65
lines changed

1 file changed

+165
-65
lines changed

clang/test/Analysis/LifetimeSafety/benchmark.py

Lines changed: 165 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -99,28 +99,86 @@ def generate_cpp_merge_test(n: int) -> str:
9999
return cpp_code
100100

101101

102-
def analyze_trace_file(trace_path: str) -> tuple[float, float]:
102+
def generate_cpp_nested_loop_test(n: int) -> str:
103103
"""
104-
Parses the -ftime-trace JSON output to find durations.
104+
Generates C++ code with N levels of nested loops.
105+
This pattern tests how analysis performance scales with loop nesting depth,
106+
which is a key factor in the complexity of dataflow analyses on structured
107+
control flow.
105108
106-
Returns:
107-
A tuple of (lifetime_analysis_duration_us, total_clang_duration_us).
109+
Example (n=3):
110+
struct MyObj { int id; ~MyObj() {} };
111+
void nested_loops_3() {
112+
MyObj* p = nullptr;
113+
for(int i0=0; i0<2; ++i0) {
114+
MyObj s0;
115+
p = &s0;
116+
for(int i1=0; i1<2; ++i1) {
117+
MyObj s1;
118+
p = &s1;
119+
for(int i2=0; i2<2; ++i2) {
120+
MyObj s2;
121+
p = &s2;
122+
}
123+
}
124+
}
125+
}
126+
"""
127+
if n <= 0:
128+
return "// Nesting depth must be positive."
129+
130+
cpp_code = "struct MyObj { int id; ~MyObj() {} };\n\n"
131+
cpp_code += f"void nested_loops_{n}() {{\n"
132+
cpp_code += " MyObj* p = nullptr;\n"
133+
134+
for i in range(n):
135+
indent = " " * (i + 1)
136+
cpp_code += f"{indent}for(int i{i}=0; i{i}<2; ++i{i}) {{\n"
137+
cpp_code += f"{indent} MyObj s{i}; p = &s{i};\n"
138+
139+
for i in range(n - 1, -1, -1):
140+
indent = " " * (i + 1)
141+
cpp_code += f"{indent}}}\n"
142+
143+
cpp_code += "}\n"
144+
cpp_code += f"\nint main() {{ nested_loops_{n}(); return 0; }}\n"
145+
return cpp_code
146+
147+
148+
def analyze_trace_file(trace_path: str) -> dict:
108149
"""
109-
lifetime_duration = 0.0
110-
total_duration = 0.0
150+
Parses the -ftime-trace JSON output to find durations for the lifetime
151+
analysis and its sub-phases.
152+
Returns a dictionary of durations in microseconds.
153+
"""
154+
durations = {
155+
"lifetime_us": 0.0,
156+
"total_us": 0.0,
157+
"fact_gen_us": 0.0,
158+
"loan_prop_us": 0.0,
159+
"expired_loans_us": 0.0,
160+
"live_origins_us": 0.0,
161+
}
162+
event_name_map = {
163+
"LifetimeSafetyAnalysis": "lifetime_us",
164+
"ExecuteCompiler": "total_us",
165+
"FactGenerator": "fact_gen_us",
166+
"LoanPropagation": "loan_prop_us",
167+
"ExpiredLoans": "expired_loans_us",
168+
"LiveOrigins": "live_origins_us",
169+
}
111170
try:
112171
with open(trace_path, "r") as f:
113172
trace_data = json.load(f)
114173
for event in trace_data.get("traceEvents", []):
115-
if event.get("name") == "LifetimeSafetyAnalysis":
116-
lifetime_duration += float(event.get("dur", 0))
117-
if event.get("name") == "ExecuteCompiler":
118-
total_duration += float(event.get("dur", 0))
119-
174+
event_name = event.get("name")
175+
if event_name in event_name_map:
176+
key = event_name_map[event_name]
177+
durations[key] += float(event.get("dur", 0))
120178
except (IOError, json.JSONDecodeError) as e:
121179
print(f"Error reading or parsing trace file {trace_path}: {e}", file=sys.stderr)
122-
return 0.0, 0.0
123-
return lifetime_duration, total_duration
180+
return {key: 0.0 for key in durations}
181+
return durations
124182

125183

126184
def power_law(n, c, k):
@@ -135,8 +193,29 @@ def human_readable_time(ms: float) -> str:
135193
return f"{ms:.2f} ms"
136194

137195

196+
def calculate_complexity(n_data, y_data) -> tuple[float | None, float | None]:
197+
"""
198+
Calculates the exponent 'k' for the power law fit y = c * n^k.
199+
Returns a tuple of (k, k_standard_error).
200+
"""
201+
try:
202+
if len(n_data) < 3 or np.all(y_data < 1e-6) or np.var(y_data) < 1e-6:
203+
return None, None
204+
205+
non_zero_indices = y_data > 0
206+
if np.sum(non_zero_indices) < 3:
207+
return None, None
208+
209+
n_fit, y_fit = n_data[non_zero_indices], y_data[non_zero_indices]
210+
popt, pcov = curve_fit(power_law, n_fit, y_fit, p0=[0, 1], maxfev=5000)
211+
k_stderr = np.sqrt(np.diag(pcov))[1]
212+
return popt[1], k_stderr
213+
except (RuntimeError, ValueError):
214+
return None, None
215+
216+
138217
def generate_markdown_report(results: dict) -> str:
139-
"""Generates a Markdown-formatted report from the benchmark results."""
218+
"""Generates a concise, Markdown-formatted report from the benchmark results."""
140219
report = []
141220
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S %Z")
142221
report.append(f"# Lifetime Analysis Performance Report")
@@ -146,54 +225,54 @@ def generate_markdown_report(results: dict) -> str:
146225
for test_name, data in results.items():
147226
title = data["title"]
148227
report.append(f"## Test Case: {title}")
149-
report.append("")
228+
report.append("\n**Timing Results:**\n")
150229

151230
# Table header
152-
report.append("| N | Analysis Time | Total Clang Time |")
153-
report.append("|:----|--------------:|-----------------:|")
231+
report.append(
232+
"| N (Input Size) | Total Time | Analysis Time (%) | Fact Generator (%) | Loan Propagation (%) | Expired Loans (%) | Live Origins (%) |"
233+
)
234+
report.append(
235+
"|:---------------|-----------:|------------------:|-------------------:|---------------------:|------------------:|------------------:|"
236+
)
154237

155238
# Table rows
156239
n_data = np.array(data["n"])
157-
analysis_data = np.array(data["lifetime_ms"])
158-
total_data = np.array(data["total_ms"])
240+
total_ms_data = np.array(data["total_ms"])
159241
for i in range(len(n_data)):
160-
analysis_str = human_readable_time(analysis_data[i])
161-
total_str = human_readable_time(total_data[i])
162-
report.append(f"| {n_data[i]:<3} | {analysis_str:>13} | {total_str:>16} |")
163-
164-
report.append("")
165-
166-
# Complexity analysis
167-
report.append(f"**Complexity Analysis:**")
168-
try:
169-
# Curve fitting requires at least 3 points
170-
if len(n_data) < 3:
171-
raise ValueError("Not enough data points to perform curve fitting.")
172-
173-
popt, pcov = curve_fit(
174-
power_law, n_data, analysis_data, p0=[0, 2], maxfev=5000
175-
)
176-
_, k = popt
177-
178-
# Confidence Interval for k
179-
alpha = 0.05 # 95% confidence
180-
dof = max(0, len(n_data) - len(popt)) # degrees of freedom
181-
t_val = t.ppf(1.0 - alpha / 2.0, dof)
182-
# Standard error of the parameters
183-
perr = np.sqrt(np.diag(pcov))
184-
k_stderr = perr[1]
185-
k_ci_lower = k - t_val * k_stderr
186-
k_ci_upper = k + t_val * k_stderr
187-
188-
report.append(
189-
f"- The performance for this case scales approx. as **O(n<sup>{k:.2f}</sup>)**."
190-
)
191-
report.append(
192-
f"- **95% Confidence interval for exponent:** `[{k_ci_lower:.2f}, {k_ci_upper:.2f}]`."
193-
)
242+
total_t = total_ms_data[i]
243+
if total_t < 1e-6:
244+
total_t = 1.0 # Avoid division by zero
245+
246+
row = [
247+
f"| {n_data[i]:<14} |",
248+
f"{human_readable_time(total_t):>10} |",
249+
f"{data['lifetime_ms'][i] / total_t * 100:>17.2f}% |",
250+
f"{data['fact_gen_ms'][i] / total_t * 100:>18.2f}% |",
251+
f"{data['loan_prop_ms'][i] / total_t * 100:>20.2f}% |",
252+
f"{data['expired_loans_ms'][i] / total_t * 100:>17.2f}% |",
253+
f"{data['live_origins_ms'][i] / total_t * 100:>17.2f}% |",
254+
]
255+
report.append(" ".join(row))
256+
257+
report.append("\n**Complexity Analysis:**\n")
258+
report.append("| Analysis Phase | Complexity O(n<sup>k</sup>) |")
259+
report.append("|:------------------|:--------------------------|")
260+
261+
analysis_phases = {
262+
"Total Analysis": data["lifetime_ms"],
263+
"FactGenerator": data["fact_gen_ms"],
264+
"LoanPropagation": data["loan_prop_ms"],
265+
"ExpiredLoans": data["expired_loans_ms"],
266+
"LiveOrigins": data["live_origins_ms"],
267+
}
194268

195-
except (RuntimeError, ValueError) as e:
196-
report.append(f"- Could not determine a best-fit curve for the data: {e}")
269+
for phase_name, y_data in analysis_phases.items():
270+
k, delta = calculate_complexity(n_data, np.array(y_data))
271+
if k is not None and delta is not None:
272+
complexity_str = f"O(n<sup>{k:.2f}</sup> &pm; {delta:.2f})"
273+
else:
274+
complexity_str = "(Negligible)"
275+
report.append(f"| {phase_name:<17} | {complexity_str:<25} |")
197276

198277
report.append("\n---\n")
199278

@@ -202,7 +281,7 @@ def generate_markdown_report(results: dict) -> str:
202281

203282
def run_single_test(
204283
clang_binary: str, output_dir: str, test_name: str, generator_func, n: int
205-
) -> tuple[float, float]:
284+
) -> dict:
206285
"""Generates, compiles, and benchmarks a single test case."""
207286
print(f"--- Running Test: {test_name.capitalize()} with N={n} ---")
208287

@@ -231,11 +310,12 @@ def run_single_test(
231310
if result.returncode != 0:
232311
print(f"Compilation failed for N={n}!", file=sys.stderr)
233312
print(result.stderr, file=sys.stderr)
234-
return 0.0, 0.0
313+
return {}
235314

236-
lifetime_us, total_us = analyze_trace_file(trace_file)
237-
238-
return lifetime_us / 1000.0, total_us / 1000.0
315+
durations_us = analyze_trace_file(trace_file)
316+
return {
317+
key.replace("_us", "_ms"): value / 1000.0 for key, value in durations_us.items()
318+
}
239319

240320

241321
if __name__ == "__main__":
@@ -270,6 +350,12 @@ def run_single_test(
270350
"generator_func": generate_cpp_merge_test,
271351
"n_values": [10, 50, 100, 200, 400, 800],
272352
},
353+
{
354+
"name": "nested_loops",
355+
"title": "Deeply Nested Loops",
356+
"generator_func": generate_cpp_nested_loop_test,
357+
"n_values": [10, 50, 100, 200, 400, 800],
358+
},
273359
]
274360

275361
results = {}
@@ -282,21 +368,30 @@ def run_single_test(
282368
"n": [],
283369
"lifetime_ms": [],
284370
"total_ms": [],
371+
"fact_gen_ms": [],
372+
"loan_prop_ms": [],
373+
"expired_loans_ms": [],
374+
"live_origins_ms": [],
285375
}
286376
for n in config["n_values"]:
287-
lifetime_ms, total_ms = run_single_test(
377+
durations_ms = run_single_test(
288378
args.clang_binary,
289379
args.output_dir,
290380
test_name,
291381
config["generator_func"],
292382
n,
293383
)
294-
if total_ms > 0:
384+
if durations_ms:
295385
results[test_name]["n"].append(n)
296-
results[test_name]["lifetime_ms"].append(lifetime_ms)
297-
results[test_name]["total_ms"].append(total_ms)
386+
for key, value in durations_ms.items():
387+
results[test_name][key].append(value)
388+
298389
print(
299-
f" Total: {human_readable_time(total_ms)} | Analysis: {human_readable_time(lifetime_ms)}"
390+
f" Total Analysis: {human_readable_time(durations_ms['lifetime_ms'])} | "
391+
f"FactGen: {human_readable_time(durations_ms['fact_gen_ms'])} | "
392+
f"LoanProp: {human_readable_time(durations_ms['loan_prop_ms'])} | "
393+
f"ExpiredLoans: {human_readable_time(durations_ms['expired_loans_ms'])} | "
394+
f"LiveOrigins: {human_readable_time(durations_ms['live_origins_ms'])}"
300395
)
301396

302397
print("\n\n" + "=" * 80)
@@ -305,3 +400,8 @@ def run_single_test(
305400

306401
markdown_report = generate_markdown_report(results)
307402
print(markdown_report)
403+
404+
report_filename = os.path.join(args.output_dir, "performance_report.md")
405+
with open(report_filename, "w") as f:
406+
f.write(markdown_report)
407+
print(f"Report saved to: {report_filename}")

0 commit comments

Comments
 (0)