Skip to content

Commit 0e3c685

Browse files
Reduce memory usage in AST parent map generation by partially reverting quadratic slowdown mitigation
The use of parent maps (hasParent(), hasAncestor(), etc.) in Clang AST matchers is no longer guaranteed to avoid quadratic slowdown, but will only do so in pathological cases.
1 parent c017cdf commit 0e3c685

File tree

1 file changed

+65
-8
lines changed

1 file changed

+65
-8
lines changed

clang/lib/AST/ParentMapContext.cpp

Lines changed: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,21 +65,78 @@ class ParentMapContext::ParentMap {
6565
public:
6666
ParentVector() = default;
6767
explicit ParentVector(size_t N, const DynTypedNode &Value) {
68-
Items.reserve(N);
68+
SortedAndUnsortedItems.reserve(N);
6969
for (; N > 0; --N)
7070
push_back(Value);
7171
}
7272
bool contains(const DynTypedNode &Value) {
73-
return Seen.contains(Value);
73+
const auto SortBoundary = SortedAndUnsortedItems.begin() + NumSorted;
74+
bool Found = std::binary_search(SortedAndUnsortedItems.begin(),
75+
SortBoundary, Value);
76+
Budget += llvm::bit_width(
77+
static_cast<size_t>(SortBoundary - SortedAndUnsortedItems.begin()));
78+
if (!Found) {
79+
auto FoundIt =
80+
std::find(SortBoundary, SortedAndUnsortedItems.end(), Value);
81+
Budget += FoundIt - SortBoundary;
82+
Found |= FoundIt != SortedAndUnsortedItems.end();
83+
}
84+
SortIfWorthwhile();
85+
return Found;
7486
}
7587
void push_back(const DynTypedNode &Value) {
76-
if (!Value.getMemoizationData() || Seen.insert(Value).second)
77-
Items.push_back(Value);
88+
++Budget;
89+
if (!Value.getMemoizationData() || !contains(Value)) {
90+
SortedAndUnsortedItems.push_back(Value);
91+
if (SortedAndUnsortedItems.back() < SortedAndUnsortedItems[NumSorted]) {
92+
// Keep the minimum element in the middle to quickly tell us if
93+
// merging will be necessary
94+
using std::swap;
95+
swap(SortedAndUnsortedItems.back(),
96+
SortedAndUnsortedItems[NumSorted]);
97+
}
98+
}
99+
VerifyInvariant();
78100
}
79-
llvm::ArrayRef<DynTypedNode> view() const { return Items; }
101+
llvm::ArrayRef<DynTypedNode> view() {
102+
++Budget;
103+
return SortedAndUnsortedItems;
104+
}
105+
80106
private:
81-
llvm::SmallVector<DynTypedNode, 2> Items;
82-
llvm::SmallDenseSet<DynTypedNode, 2> Seen;
107+
void SortIfWorthwhile() {
108+
VerifyInvariant();
109+
auto SortBoundary = SortedAndUnsortedItems.begin() + NumSorted;
110+
if (SortBoundary != SortedAndUnsortedItems.end()) {
111+
const size_t NumUnsorted = SortedAndUnsortedItems.end() - SortBoundary;
112+
const size_t SortingCost = NumUnsorted * llvm::bit_width(NumUnsorted);
113+
const bool NeedMerge = SortBoundary != SortedAndUnsortedItems.begin();
114+
// Assume that the naive implementation would copy these elements.
115+
// This is just an estimate; it's OK if it's wrong.
116+
const size_t MergeCost = SortedAndUnsortedItems.size() + NumUnsorted;
117+
if (Budget >= (NeedMerge ? MergeCost : 0) + SortingCost) {
118+
std::sort(SortBoundary, SortedAndUnsortedItems.end());
119+
if (NeedMerge) {
120+
std::inplace_merge(SortedAndUnsortedItems.begin(), SortBoundary,
121+
SortedAndUnsortedItems.end());
122+
}
123+
Budget = 0;
124+
NumSorted = SortedAndUnsortedItems.size();
125+
}
126+
}
127+
}
128+
129+
void VerifyInvariant() const {
130+
assert(
131+
!(NumSorted < SortedAndUnsortedItems.size() &&
132+
SortedAndUnsortedItems.back() <
133+
SortedAndUnsortedItems[NumSorted]) &&
134+
"the boundary item must always be the minimum of the unsorted items");
135+
}
136+
137+
llvm::SmallVector<DynTypedNode, 2> SortedAndUnsortedItems;
138+
size_t NumSorted = 0;
139+
int64_t Budget = 0;
83140
};
84141

85142
/// Maps from a node to its parents. This is used for nodes that have
@@ -117,7 +174,7 @@ class ParentMapContext::ParentMap {
117174
if (I == Map.end()) {
118175
return llvm::ArrayRef<DynTypedNode>();
119176
}
120-
if (const auto *V = dyn_cast<ParentVector *>(I->second)) {
177+
if (auto *V = dyn_cast<ParentVector *>(I->second)) {
121178
return V->view();
122179
}
123180
return getSingleDynTypedNodeFromParentMap(I->second);

0 commit comments

Comments
 (0)