Analyzing and Optimizing iOS dyld BuildingClosure to Reduce Startup Time
The DeWu Tech article dissects iOS’s dyld BuildingClosure stage, shows how hash‑collision‑laden selector strings inflate its runtime, and demonstrates that renaming the colliding selectors reduces perfect‑hash iterations, cutting the phase’s duration by roughly 80% and noticeably speeding up app startup.
The article from DeWu Tech investigates the mysterious BuildingClosure phase of iOS application launch, explains why it heavily influences startup latency, and presents a concrete optimization that cut the phase’s duration by about 80%.
BuildingClosure belongs to the System Interface Initialization stage generated by dyld . It was introduced in iOS 13 (dyld3) to cache symbol‑lookup, rebase and bind information, thereby shortening subsequent launches. Because it runs inside dyld, developers cannot directly modify its logic.
Instrument measurements showed that BuildingClosure consumed nearly one‑third of total launch time. A particular function’s cost jumped from ~480 ms to ~1200 ms on a PC‑based dyld trace, but after targeted changes the cost dropped to ~110 ms.
Deep profiling identified the hotspot as dyld4::PrebuiltObjC::generateHashTables(RuntimeState&) , specifically the call to objc::PerfectHash::make_perfect , which builds a perfect hash table for selector strings. The relevant source fragment is:
void PrebuiltObjC::generateHashTables(RuntimeState& state) {
// Write out the class table
writeObjCDataStructHashTable(state, PrebuiltObjC::ObjCStructKind::classes, objcImages, classesHashTable, duplicateSharedCacheClassMap, classMap);
// Write out the protocol table
writeObjCDataStructHashTable(state, PrebuiltObjC::ObjCStructKind::protocols, objcImages, protocolsHashTable, duplicateSharedCacheClassMap, protocolMap);
// If we have closure selectors, we need to make a hash table for them.
if (!closureSelectorStrings.empty()) {
objc::PerfectHash phash;
objc::PerfectHash::make_perfect(closureSelectorStrings, phash);
size_t size = ObjCStringTable::size(phash);
selectorsHashTable.resize(size);
//printf("Selector table size: %lld\n", size);
selectorStringTable = (ObjCStringTable*)selectorsHashTable.begin();
selectorStringTable->write(phash, closureSelectorMap.array());
}
}The perfect‑hash algorithm iterates until it finds a collision‑free mapping. In the older build it succeeded after 31 iterations; the problematic build required 92 iterations, indicating many hash collisions among selector strings.
By locating the colliding selector names (printed from inittab_ts ) and renaming them to avoid collisions, the loop count dropped dramatically and the BuildingClosure duration fell back to the optimized range, yielding a noticeable overall launch‑time improvement.
In summary, hash collisions inside the BuildingClosure phase can cause a steep increase in startup latency. Detecting and eliminating those collisions—without changing project configuration, build scripts, or compiler settings—provides a reliable way to accelerate iOS app launches.
DeWu Technology
A platform for sharing and discussing tech knowledge, guiding you toward the cloud of technology.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.