-
Notifications
You must be signed in to change notification settings - Fork 15k
[LifetimeSafety] Avoid adding already present items in sets/maps #159582
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[LifetimeSafety] Avoid adding already present items in sets/maps #159582
Conversation
|
@llvm/pr-subscribers-clang-analysis @llvm/pr-subscribers-clang-temporal-safety Author: Utkarsh Saxena (usx95) ChangesOptimize lifetime safety analysis performance
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 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. Fixes #157420 Full diff: https://github.com/llvm/llvm-project/pull/159582.diff 2 Files Affected:
diff --git a/clang/lib/Analysis/LifetimeSafety.cpp b/clang/lib/Analysis/LifetimeSafety.cpp
index 0dd5716d93fb6..ddcdd0e72a723 100644
--- a/clang/lib/Analysis/LifetimeSafety.cpp
+++ b/clang/lib/Analysis/LifetimeSafety.cpp
@@ -910,6 +910,8 @@ template <typename T>
static llvm::ImmutableSet<T> join(llvm::ImmutableSet<T> A,
llvm::ImmutableSet<T> B,
typename llvm::ImmutableSet<T>::Factory &F) {
+ if (A == B)
+ return A;
if (A.getHeight() < B.getHeight())
std::swap(A, B);
for (const T &E : B)
@@ -947,10 +949,11 @@ join(llvm::ImmutableMap<K, V> A, llvm::ImmutableMap<K, V> B,
for (const auto &Entry : B) {
const K &Key = Entry.first;
const V &ValB = Entry.second;
- if (const V *ValA = A.lookup(Key))
- A = F.add(A, Key, JoinValues(*ValA, ValB));
- else
+ const V *ValA = A.lookup(Key);
+ if (!ValA)
A = F.add(A, Key, ValB);
+ else if (*ValA != ValB)
+ A = F.add(A, Key, JoinValues(*ValA, ValB));
}
return A;
}
diff --git a/clang/test/Analysis/LifetimeSafety/benchmark.py b/clang/test/Analysis/LifetimeSafety/benchmark.py
index 4421fe9a81e21..2373f9984eecd 100644
--- a/clang/test/Analysis/LifetimeSafety/benchmark.py
+++ b/clang/test/Analysis/LifetimeSafety/benchmark.py
@@ -252,7 +252,7 @@ def generate_markdown_report(results: dict) -> str:
report.append(" ".join(row))
report.append("\n**Complexity Analysis:**\n")
- report.append("| Analysis Phase | Complexity O(n<sup>k</sup>) |")
+ report.append("| Analysis Phase | Complexity O(n<sup>k</sup>) | ")
report.append("|:------------------|:--------------------------|")
analysis_phases = {
@@ -302,7 +302,7 @@ def run_single_test(
source_file,
]
- result = subprocess.run(clang_command, capture_output=True, text=True)
+ result = subprocess.run(clang_command, capture_output=True, text=True, timeout=60)
if result.returncode != 0:
print(f"Compilation failed for N={n}!", file=sys.stderr)
@@ -334,24 +334,25 @@ def run_single_test(
os.makedirs(args.output_dir, exist_ok=True)
print(f"Benchmark files will be saved in: {os.path.abspath(args.output_dir)}\n")
+ # Maximize 'n' values while keeping execution time under 10s.
test_configurations = [
{
"name": "cycle",
"title": "Pointer Cycle in Loop",
"generator_func": generate_cpp_cycle_test,
- "n_values": [10, 25, 50, 75, 100, 150],
+ "n_values": [25, 50, 75, 100],
},
{
"name": "merge",
"title": "CFG Merges",
"generator_func": generate_cpp_merge_test,
- "n_values": [10, 50, 100, 200, 400, 800],
+ "n_values": [400, 1000, 2000, 5000],
},
{
"name": "nested_loops",
"title": "Deeply Nested Loops",
"generator_func": generate_cpp_nested_loop_test,
- "n_values": [10, 50, 100, 200, 400, 800],
+ "n_values": [50, 100, 150, 200],
},
]
|
|
Ugh, I wonder if this is unintentional and we should change the implementation of these data structures. I also find this behavior very surprising. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oof nice find!
| llvm::ImmutableSet<T> B, | ||
| typename llvm::ImmutableSet<T>::Factory &F) { | ||
| if (A == B) | ||
| return A; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would the add() on line 918 also possibly do more allocation than desired (and cost more than checking if contains first), if E was already in A ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added this check as well.
I can try changing the data structure. Let me do that in a separate PR. It would need an extra Value comparison which needs to be done while inserting the element and this could be a slight concern for other users. Let me give it a try. |
Seems like its a bug, based on the comment for |
|
Hmm. Atleast the documentation for add_internal suggests that this might be really unintentional |
|
Yitzi beat me to it. Yup this is definitely a bug then. |
45f4b75 to
533103b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will change the underlying data structure in the next PR. Submitting this to stop the bleeding.
| llvm::ImmutableSet<T> B, | ||
| typename llvm::ImmutableSet<T>::Factory &F) { | ||
| if (A == B) | ||
| return A; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added this check as well.
533103b to
713609d
Compare
Merge activity
|
c08ea2d to
ddfed4a
Compare
ddfed4a to
e6a9a57
Compare

Optimize lifetime safety analysis performance
joinfunction for ImmutableSet when sets are identicalI 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
ImmutableSetwould perform addition nevertheless thereby creating (presumablyO(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 #157420
Report (Before)
Report (After)
Lifetime Analysis Performance Report
Test Case: Pointer Cycle in Loop
Timing Results:
Complexity Analysis:
Test Case: CFG Merges
Timing Results:
Complexity Analysis:
Test Case: Deeply Nested Loops
Timing Results:
Complexity Analysis: