9
9
from scipy .optimize import curve_fit
10
10
from scipy .stats import t
11
11
12
+
12
13
def generate_cpp_cycle_test (n : int ) -> str :
13
14
"""
14
15
Generates a C++ code snippet with a specified number of pointers in a cycle.
@@ -34,6 +35,7 @@ def generate_cpp_cycle_test(n: int) -> str:
34
35
cpp_code += f"\n int main() {{ long_cycle_{ n } (false); return 0; }}\n "
35
36
return cpp_code
36
37
38
+
37
39
def generate_cpp_merge_test (n : int ) -> str :
38
40
"""
39
41
Generates a C++ code snippet with N independent conditional assignments.
@@ -55,6 +57,7 @@ def generate_cpp_merge_test(n: int) -> str:
55
57
cpp_code += f"\n int main() {{ conditional_merges_{ n } (false); return 0; }}\n "
56
58
return cpp_code
57
59
60
+
58
61
def analyze_trace_file (trace_path : str ) -> tuple [float , float ]:
59
62
"""
60
63
Parses the -ftime-trace JSON output to find durations.
@@ -65,29 +68,32 @@ def analyze_trace_file(trace_path: str) -> tuple[float, float]:
65
68
lifetime_duration = 0.0
66
69
total_duration = 0.0
67
70
try :
68
- with open (trace_path , 'r' ) as f :
71
+ with open (trace_path , "r" ) as f :
69
72
trace_data = json .load (f )
70
- for event in trace_data .get (' traceEvents' , []):
71
- if event .get (' name' ) == ' LifetimeAnalysis' :
72
- lifetime_duration += float (event .get (' dur' , 0 ))
73
- if event .get (' name' ) == ' ExecuteCompiler' :
74
- total_duration += float (event .get (' dur' , 0 ))
73
+ for event in trace_data .get (" traceEvents" , []):
74
+ if event .get (" name" ) == " LifetimeAnalysis" :
75
+ lifetime_duration += float (event .get (" dur" , 0 ))
76
+ if event .get (" name" ) == " ExecuteCompiler" :
77
+ total_duration += float (event .get (" dur" , 0 ))
75
78
76
79
except (IOError , json .JSONDecodeError ) as e :
77
80
print (f"Error reading or parsing trace file { trace_path } : { e } " , file = sys .stderr )
78
81
return 0.0 , 0.0
79
82
return lifetime_duration , total_duration
80
83
84
+
81
85
def power_law (n , c , k ):
82
86
"""Represents the power law function: y = c * n^k"""
83
87
return c * np .power (n , k )
84
88
89
+
85
90
def human_readable_time (ms : float ) -> str :
86
91
"""Converts milliseconds to a human-readable string (ms or s)."""
87
92
if ms >= 1000 :
88
93
return f"{ ms / 1000 :.2f} s"
89
94
return f"{ ms :.2f} ms"
90
95
96
+
91
97
def generate_markdown_report (results : dict ) -> str :
92
98
"""Generates a Markdown-formatted report from the benchmark results."""
93
99
report = []
@@ -97,7 +103,7 @@ def generate_markdown_report(results: dict) -> str:
97
103
report .append ("\n ---\n " )
98
104
99
105
for test_type , data in results .items ():
100
- title = ' Pointer Cycle in Loop' if test_type == ' cycle' else ' CFG Merges'
106
+ title = " Pointer Cycle in Loop" if test_type == " cycle" else " CFG Merges"
101
107
report .append (f"## Test Case: { title } " )
102
108
report .append ("" )
103
109
@@ -106,9 +112,9 @@ def generate_markdown_report(results: dict) -> str:
106
112
report .append ("|:----|--------------:|-----------------:|" )
107
113
108
114
# Table rows
109
- n_data = np .array (data ['n' ])
110
- analysis_data = np .array (data [' lifetime_ms' ])
111
- total_data = np .array (data [' total_ms' ])
115
+ n_data = np .array (data ["n" ])
116
+ analysis_data = np .array (data [" lifetime_ms" ])
117
+ total_data = np .array (data [" total_ms" ])
112
118
for i in range (len (n_data )):
113
119
analysis_str = human_readable_time (analysis_data [i ])
114
120
total_str = human_readable_time (total_data [i ])
@@ -119,28 +125,36 @@ def generate_markdown_report(results: dict) -> str:
119
125
# Complexity analysis
120
126
report .append (f"**Complexity Analysis:**" )
121
127
try :
122
- popt , pcov = curve_fit (power_law , n_data , analysis_data , p0 = [0 , 2 ], maxfev = 5000 )
128
+ popt , pcov = curve_fit (
129
+ power_law , n_data , analysis_data , p0 = [0 , 2 ], maxfev = 5000
130
+ )
123
131
_ , k = popt
124
-
132
+
125
133
# R-squared calculation
126
134
residuals = analysis_data - power_law (n_data , * popt )
127
135
ss_res = np .sum (residuals ** 2 )
128
- ss_tot = np .sum ((analysis_data - np .mean (analysis_data ))** 2 )
136
+ ss_tot = np .sum ((analysis_data - np .mean (analysis_data )) ** 2 )
129
137
r_squared = 1 - (ss_res / ss_tot )
130
-
138
+
131
139
# Confidence Interval for k
132
140
alpha = 0.05 # 95% confidence
133
- dof = max (0 , len (n_data ) - len (popt )) # degrees of freedom
134
- t_val = t .ppf (1.0 - alpha / 2. , dof )
141
+ dof = max (0 , len (n_data ) - len (popt )) # degrees of freedom
142
+ t_val = t .ppf (1.0 - alpha / 2.0 , dof )
135
143
# Standard error of the parameters
136
144
perr = np .sqrt (np .diag (pcov ))
137
145
k_stderr = perr [1 ]
138
146
k_ci_lower = k - t_val * k_stderr
139
147
k_ci_upper = k + t_val * k_stderr
140
148
141
- report .append (f"- The performance of the analysis for this case scales approximately as **O(n<sup>{ k :.2f} </sup>)**." )
142
- report .append (f"- **Goodness of Fit (R²):** `{ r_squared :.4f} ` (closer to 1.0 is better)." )
143
- report .append (f"- **95% Confidence Interval for exponent 'k':** `[{ k_ci_lower :.2f} , { k_ci_upper :.2f} ]`." )
149
+ report .append (
150
+ f"- The performance of the analysis for this case scales approximately as **O(n<sup>{ k :.2f} </sup>)**."
151
+ )
152
+ report .append (
153
+ f"- **Goodness of Fit (R²):** `{ r_squared :.4f} ` (closer to 1.0 is better)."
154
+ )
155
+ report .append (
156
+ f"- **95% Confidence Interval for exponent 'k':** `[{ k_ci_lower :.2f} , { k_ci_upper :.2f} ]`."
157
+ )
144
158
145
159
except RuntimeError :
146
160
report .append ("- Could not determine a best-fit curve for the data." )
@@ -149,67 +163,80 @@ def generate_markdown_report(results: dict) -> str:
149
163
150
164
return "\n " .join (report )
151
165
166
+
152
167
def run_single_test (clang_binary : str , test_type : str , n : int ) -> tuple [float , float ]:
153
168
"""Generates, compiles, and benchmarks a single test case."""
154
169
print (f"--- Running Test: { test_type .capitalize ()} with N={ n } ---" )
155
-
170
+
156
171
generated_code = ""
157
- if test_type == ' cycle' :
172
+ if test_type == " cycle" :
158
173
generated_code = generate_cpp_cycle_test (n )
159
- else : # merge
174
+ else : # merge
160
175
generated_code = generate_cpp_merge_test (n )
161
176
162
- with tempfile .NamedTemporaryFile (mode = 'w+' , suffix = ' .cpp' , delete = False ) as tmp_cpp :
177
+ with tempfile .NamedTemporaryFile (mode = "w+" , suffix = " .cpp" , delete = False ) as tmp_cpp :
163
178
tmp_cpp .write (generated_code )
164
179
source_file = tmp_cpp .name
165
-
166
- trace_file = os .path .splitext (source_file )[0 ] + ' .json'
180
+
181
+ trace_file = os .path .splitext (source_file )[0 ] + " .json"
167
182
168
183
clang_command = [
169
- clang_binary , '-c' , '-o' , '/dev/null' , '-ftime-trace=' + trace_file ,
170
- '-Wexperimental-lifetime-safety' , '-std=c++17' , source_file
184
+ clang_binary ,
185
+ "-c" ,
186
+ "-o" ,
187
+ "/dev/null" ,
188
+ "-ftime-trace=" + trace_file ,
189
+ "-Wexperimental-lifetime-safety" ,
190
+ "-std=c++17" ,
191
+ source_file ,
171
192
]
172
193
173
194
result = subprocess .run (clang_command , capture_output = True , text = True )
174
-
195
+
175
196
if result .returncode != 0 :
176
197
print (f"Compilation failed for N={ n } !" , file = sys .stderr )
177
198
print (result .stderr , file = sys .stderr )
178
199
os .remove (source_file )
179
200
return 0.0 , 0.0
180
-
201
+
181
202
lifetime_us , total_us = analyze_trace_file (trace_file )
182
203
os .remove (source_file )
183
204
os .remove (trace_file )
184
-
205
+
185
206
return lifetime_us / 1000.0 , total_us / 1000.0
186
207
208
+
187
209
if __name__ == "__main__" :
188
- parser = argparse .ArgumentParser (description = "Generate, compile, and benchmark C++ test cases for Clang's lifetime analysis." )
189
- parser .add_argument ("--clang-binary" , type = str , required = True , help = "Path to the Clang executable." )
190
-
210
+ parser = argparse .ArgumentParser (
211
+ description = "Generate, compile, and benchmark C++ test cases for Clang's lifetime analysis."
212
+ )
213
+ parser .add_argument (
214
+ "--clang-binary" , type = str , required = True , help = "Path to the Clang executable."
215
+ )
216
+
191
217
args = parser .parse_args ()
192
218
193
219
n_values = [10 , 25 , 50 , 75 , 100 , 150 , 200 ]
194
220
results = {
195
- ' cycle' : {'n' : [], ' lifetime_ms' : [], ' total_ms' : []},
196
- ' merge' : {'n' : [], ' lifetime_ms' : [], ' total_ms' : []}
221
+ " cycle" : {"n" : [], " lifetime_ms" : [], " total_ms" : []},
222
+ " merge" : {"n" : [], " lifetime_ms" : [], " total_ms" : []},
197
223
}
198
224
199
225
print ("Running performance benchmarks..." )
200
- for test_type in [' cycle' , ' merge' ]:
226
+ for test_type in [" cycle" , " merge" ]:
201
227
for n in n_values :
202
228
lifetime_ms , total_ms = run_single_test (args .clang_binary , test_type , n )
203
229
if total_ms > 0 :
204
- results [test_type ]['n' ].append (n )
205
- results [test_type ]['lifetime_ms' ].append (lifetime_ms )
206
- results [test_type ]['total_ms' ].append (total_ms )
207
- print (f" Total: { human_readable_time (total_ms )} | Analysis: { human_readable_time (lifetime_ms )} " )
208
-
209
- print ("\n \n " + "=" * 80 )
230
+ results [test_type ]["n" ].append (n )
231
+ results [test_type ]["lifetime_ms" ].append (lifetime_ms )
232
+ results [test_type ]["total_ms" ].append (total_ms )
233
+ print (
234
+ f" Total: { human_readable_time (total_ms )} | Analysis: { human_readable_time (lifetime_ms )} "
235
+ )
236
+
237
+ print ("\n \n " + "=" * 80 )
210
238
print ("Generating Markdown Report..." )
211
- print ("=" * 80 + "\n " )
212
-
239
+ print ("=" * 80 + "\n " )
240
+
213
241
markdown_report = generate_markdown_report (results )
214
242
print (markdown_report )
215
-
0 commit comments