[lld][MachO] Avoid quadratic iteration over already-folded symbols during ICF (#213339)
`ConcatInputSection::foldIdentical()` clears the folded functions `originalUnwindEntry`. However, it cleared every symbol: with N members, the repeated clearing is **O(N²)**. This is redundant: we should only remove the incoming `copy->symbols`. This patch moves the removing loop ahead and adds more clear comments on why we need to skip the first element. Also added a new `lld/test/MachO/icf-scale-same-class.s` with 500K identical functions as a stress test, which would've taken minutes to link, and less than a second with the patch. Testing on real-world app (IRPGO instrumentation + ICF) find that we achieved a 19x speed up (1:14:42 -> 3:55)
This commit is contained in:
parent
278261413b
commit
84afdb4771
2 changed files with 47 additions and 6 deletions
|
|
@ -204,16 +204,20 @@ void ConcatInputSection::foldIdentical(ConcatInputSection *copy,
|
||||||
for (auto ©Sym : copy->symbols)
|
for (auto ©Sym : copy->symbols)
|
||||||
copySym->identicalCodeFoldingKind = foldKind;
|
copySym->identicalCodeFoldingKind = foldKind;
|
||||||
|
|
||||||
symbols.insert(symbols.end(), copy->symbols.begin(), copy->symbols.end());
|
if (copy->symbols.empty())
|
||||||
copy->symbols.clear();
|
|
||||||
|
|
||||||
// Remove duplicate compact unwind info for symbols at the same address.
|
|
||||||
if (symbols.empty())
|
|
||||||
return;
|
return;
|
||||||
for (auto it = symbols.begin() + 1; it != symbols.end(); ++it) {
|
auto *it = copy->symbols.begin();
|
||||||
|
// The first symbol in the merged section (symbols.front()) must keep its
|
||||||
|
// unwind entry. If this section is empty, the copy's first symbol becomes the
|
||||||
|
// new front, so we skip clearing it.
|
||||||
|
if (symbols.empty())
|
||||||
|
++it;
|
||||||
|
for (; it != copy->symbols.end(); ++it) {
|
||||||
assert((*it)->value == 0);
|
assert((*it)->value == 0);
|
||||||
(*it)->originalUnwindEntry = nullptr;
|
(*it)->originalUnwindEntry = nullptr;
|
||||||
}
|
}
|
||||||
|
symbols.insert(symbols.end(), copy->symbols.begin(), copy->symbols.end());
|
||||||
|
copy->symbols.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConcatInputSection::writeTo(uint8_t *buf) {
|
void ConcatInputSection::writeTo(uint8_t *buf) {
|
||||||
|
|
|
||||||
37
lld/test/MachO/icf-scale-same-class.s
Normal file
37
lld/test/MachO/icf-scale-same-class.s
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
# REQUIRES: x86
|
||||||
|
# RUN: rm -rf %t*
|
||||||
|
|
||||||
|
# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %s -o %t.o
|
||||||
|
# RUN: %lld -lSystem --icf=all -o %t %t.o
|
||||||
|
# RUN: llvm-objdump --macho --section-headers %t | FileCheck %s --check-prefix=SECT
|
||||||
|
# RUN: llvm-objdump --macho --syms %t | FileCheck %s --check-prefix=SYMS
|
||||||
|
|
||||||
|
## Every body folds into one, so __text holds a single 6-byte body plus _main
|
||||||
|
## rather than the 3 MiB (0x30000d) the unfolded copies occupy.
|
||||||
|
# SECT: Idx Name Size
|
||||||
|
# SECT-NEXT: 0 __text 00000009
|
||||||
|
|
||||||
|
## The sentinels bracket the group, so checking that the first and last members
|
||||||
|
## of the class share an address covers the whole run of folds.
|
||||||
|
# SYMS-DAG: [[#%.16x,F:]] g F __TEXT,__text _f_first
|
||||||
|
# SYMS-DAG: [[#F]] g F __TEXT,__text _f_last
|
||||||
|
|
||||||
|
.subsections_via_symbols
|
||||||
|
.text
|
||||||
|
.p2align 2
|
||||||
|
|
||||||
|
.globl _f_first
|
||||||
|
_f_first:; movl $7, %eax; ret
|
||||||
|
|
||||||
|
## 64 Ki identical function landing in one equivalence class.
|
||||||
|
.rept 65536
|
||||||
|
.globl _f\+
|
||||||
|
_f\+:; movl $$7, %eax; ret
|
||||||
|
.endr
|
||||||
|
|
||||||
|
.globl _f_last
|
||||||
|
_f_last:; movl $7, %eax; ret
|
||||||
|
|
||||||
|
.globl _main
|
||||||
|
_main:
|
||||||
|
ret
|
||||||
Loading…
Reference in a new issue