Skip to content

Commit 250a92f

Browse files
authored
[LifetimeSafety] Avoid adding already present items in sets/maps (llvm#159582)
Optimize lifetime safety analysis performance - Added early return optimization in `join` function for ImmutableSet when sets are identical - Improved ImmutableMap join logic to avoid unnecessary operations when values are equal I was under the impression that ImmutableSets/Maps would not modify the underlying if already existing elements are added to the container (and was hoping for structural equality in this aspect). It looks like the current implementation of `ImmutableSet` would perform addition nevertheless thereby creating (presumably `O(log(N))` tree nodes. This change considerably brings down compile times for some edge cases which happened to be present in the LLVM codebase. Now it is actually possible to compile LLVM in under 20 min with the lifetime analysis. The compile time hit is still significant but not as bad as before this change where it was not possible to compile LLVM without severely limiting analysis' scope (giving up on CFG with > 3000 blocks). Fixes llvm#157420 <details> <summary>Report (Before)</summary> </details> <details> <summary>Report (After)</summary> # Lifetime Analysis Performance Report > Generated on: 2025-09-18 14:28:00 --- ## Test Case: Pointer Cycle in Loop **Timing Results:** | N (Input Size) | Total Time | Analysis Time (%) | Fact Generator (%) | Loan Propagation (%) | Expired Loans (%) | |:---------------|-----------:|------------------:|-------------------:|---------------------:|------------------:| | 25 | 53.76 ms | 85.58% | 0.00% | 85.46% | 0.00% | | 50 | 605.35 ms | 98.39% | 0.00% | 98.37% | 0.00% | | 75 | 2.89 s | 99.62% | 0.00% | 99.61% | 0.00% | | 100 | 8.62 s | 99.80% | 0.00% | 99.80% | 0.00% | **Complexity Analysis:** | Analysis Phase | Complexity O(n<sup>k</sup>) | |:------------------|:--------------------------| | Total Analysis | O(n<sup>3.82</sup> &pm; 0.01) | | FactGenerator | (Negligible) | | LoanPropagation | O(n<sup>3.82</sup> &pm; 0.01) | | ExpiredLoans | (Negligible) | --- ## Test Case: CFG Merges **Timing Results:** | N (Input Size) | Total Time | Analysis Time (%) | Fact Generator (%) | Loan Propagation (%) | Expired Loans (%) | |:---------------|-----------:|------------------:|-------------------:|---------------------:|------------------:| | 400 | 66.02 ms | 58.61% | 1.04% | 56.53% | 1.02% | | 1000 | 319.24 ms | 81.31% | 0.63% | 80.04% | 0.64% | | 2000 | 1.43 s | 92.00% | 0.40% | 91.32% | 0.28% | | 5000 | 9.35 s | 97.01% | 0.25% | 96.63% | 0.12% | **Complexity Analysis:** | Analysis Phase | Complexity O(n<sup>k</sup>) | |:------------------|:--------------------------| | Total Analysis | O(n<sup>2.12</sup> &pm; 0.02) | | FactGenerator | O(n<sup>1.54</sup> &pm; 0.02) | | LoanPropagation | O(n<sup>2.12</sup> &pm; 0.03) | | ExpiredLoans | O(n<sup>1.13</sup> &pm; 0.03) | --- ## Test Case: Deeply Nested Loops **Timing Results:** | N (Input Size) | Total Time | Analysis Time (%) | Fact Generator (%) | Loan Propagation (%) | Expired Loans (%) | |:---------------|-----------:|------------------:|-------------------:|---------------------:|------------------:| | 50 | 137.30 ms | 90.72% | 0.00% | 90.42% | 0.00% | | 100 | 1.09 s | 98.13% | 0.00% | 98.02% | 0.09% | | 150 | 4.06 s | 99.24% | 0.00% | 99.18% | 0.05% | | 200 | 10.44 s | 99.66% | 0.00% | 99.63% | 0.03% | **Complexity Analysis:** | Analysis Phase | Complexity O(n<sup>k</sup>) | |:------------------|:--------------------------| | Total Analysis | O(n<sup>3.29</sup> &pm; 0.01) | | FactGenerator | (Negligible) | | LoanPropagation | O(n<sup>3.29</sup> &pm; 0.01) | | ExpiredLoans | O(n<sup>1.42</sup> &pm; 0.19) | --- </details>
1 parent a513b70 commit 250a92f

File tree

2 files changed

+14
-9
lines changed

2 files changed

+14
-9
lines changed

clang/lib/Analysis/LifetimeSafety.cpp

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -910,10 +910,13 @@ template <typename T>
910910
static llvm::ImmutableSet<T> join(llvm::ImmutableSet<T> A,
911911
llvm::ImmutableSet<T> B,
912912
typename llvm::ImmutableSet<T>::Factory &F) {
913+
if (A == B)
914+
return A;
913915
if (A.getHeight() < B.getHeight())
914916
std::swap(A, B);
915917
for (const T &E : B)
916-
A = F.add(A, E);
918+
if (!A.contains(E))
919+
A = F.add(A, E);
917920
return A;
918921
}
919922

@@ -947,10 +950,11 @@ join(llvm::ImmutableMap<K, V> A, llvm::ImmutableMap<K, V> B,
947950
for (const auto &Entry : B) {
948951
const K &Key = Entry.first;
949952
const V &ValB = Entry.second;
950-
if (const V *ValA = A.lookup(Key))
951-
A = F.add(A, Key, JoinValues(*ValA, ValB));
952-
else
953+
const V *ValA = A.lookup(Key);
954+
if (!ValA)
953955
A = F.add(A, Key, ValB);
956+
else if (*ValA != ValB)
957+
A = F.add(A, Key, JoinValues(*ValA, ValB));
954958
}
955959
return A;
956960
}

clang/test/Analysis/LifetimeSafety/benchmark.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ def generate_markdown_report(results: dict) -> str:
252252
report.append(" ".join(row))
253253

254254
report.append("\n**Complexity Analysis:**\n")
255-
report.append("| Analysis Phase | Complexity O(n<sup>k</sup>) |")
255+
report.append("| Analysis Phase | Complexity O(n<sup>k</sup>) | ")
256256
report.append("|:------------------|:--------------------------|")
257257

258258
analysis_phases = {
@@ -302,7 +302,7 @@ def run_single_test(
302302
source_file,
303303
]
304304

305-
result = subprocess.run(clang_command, capture_output=True, text=True)
305+
result = subprocess.run(clang_command, capture_output=True, text=True, timeout=60)
306306

307307
if result.returncode != 0:
308308
print(f"Compilation failed for N={n}!", file=sys.stderr)
@@ -334,24 +334,25 @@ def run_single_test(
334334
os.makedirs(args.output_dir, exist_ok=True)
335335
print(f"Benchmark files will be saved in: {os.path.abspath(args.output_dir)}\n")
336336

337+
# Maximize 'n' values while keeping execution time under 10s.
337338
test_configurations = [
338339
{
339340
"name": "cycle",
340341
"title": "Pointer Cycle in Loop",
341342
"generator_func": generate_cpp_cycle_test,
342-
"n_values": [10, 25, 50, 75, 100, 150],
343+
"n_values": [25, 50, 75, 100],
343344
},
344345
{
345346
"name": "merge",
346347
"title": "CFG Merges",
347348
"generator_func": generate_cpp_merge_test,
348-
"n_values": [10, 50, 100, 200, 400, 800],
349+
"n_values": [400, 1000, 2000, 5000],
349350
},
350351
{
351352
"name": "nested_loops",
352353
"title": "Deeply Nested Loops",
353354
"generator_func": generate_cpp_nested_loop_test,
354-
"n_values": [10, 50, 100, 200, 400, 800],
355+
"n_values": [50, 100, 150, 200],
355356
},
356357
]
357358

0 commit comments

Comments
 (0)