slow? ➡What is it doing? ➡How often does it run? ➡Would it being faster make a difference? ➡ Can it do less work? ➡ How can I keep it from getting worse? Making CocoaPods Fast – Samuel Giddins @ RubyConf Taiwan 2019 17
to make it 10% faster? 50%? 90%? ➡ How do I know which part of what it's doing is slow? Making CocoaPods Fast – Samuel Giddins @ RubyConf Taiwan 2019 18
seconds ➡ # this segment took 10 seconds ➡ $ this method was called 97645 times, averaging 23ms per call, taking up 48% of total runtime Making CocoaPods Fast – Samuel Giddins @ RubyConf Taiwan 2019 19
it may never finish under rubyprof. Sorry, I didn't think of that. — CocoaPods/CocoaPods#5180 Making CocoaPods Fast – Samuel Giddins @ RubyConf Taiwan 2019 28
your app's normal execution. ➡ Possible to miss things, it's luck whether a particular stack trace gets sampled at the right time. ➡ Can’t tell you how many times something has been called. ➡ “Here were the stack traces when I looked” ➡What was at the top of the stack at the time? Making CocoaPods Fast – Samuel Giddins @ RubyConf Taiwan 2019 32
Benchmark.measure will tell you how long it took. ➡ Can help you focus in on what you already suspect to be hotspots ➡ More likely to tell you if the distribution of calls is not unimodal ➡e.g. calling a memoized method, so only the first call will take a significant amount of time Making CocoaPods Fast – Samuel Giddins @ RubyConf Taiwan 2019 34
➡ Can output results in a format compatible with chrome://tracing ➡ Happens in-process, so you can write code that uses the timing results Making CocoaPods Fast – Samuel Giddins @ RubyConf Taiwan 2019 36
prepare resolve_dependencies download_dependencies validate_targets generate_pods_project integrate_user_project perform_post_install_actions run_podfile_post_install_hooks ] end # ... for_class Pod::Resolver do method :resolve end Making CocoaPods Fast – Samuel Giddins @ RubyConf Taiwan 2019 37
extensively last year. But knowing which to reach for at each step of the investigation would’ve saved me a bunch of time Making CocoaPods Fast – Samuel Giddins @ RubyConf Taiwan 2019 39
C, let’s recurse!” But then you end up visiting D twice! This might not sound like a big deal, but imagine 50 more nodes come after D... Making CocoaPods Fast – Samuel Giddins @ RubyConf Taiwan 2019 50
node (target, library, gem, etc.) has a list of dependencies ➡only rule is “you can’t depend upon something that depends on you” ➡ Most operations rely on what’s called the “transitive closure” of a node ➡“what are all the nodes that come after this one, no matter how far away?” Making CocoaPods Fast – Samuel Giddins @ RubyConf Taiwan 2019 53
to def recursive_predecessors vertices = Set.new visit = ->(vertex) do vertex.incoming_edges.each do |edge| vertex = edge.origin next unless vertices.add?(vertex) visit[vertex] end visit[self] vertices end Making CocoaPods Fast – Samuel Giddins @ RubyConf Taiwan 2019 54
that can happen ➡ We’re just lazily computing a value, and storing it so next time it’s needed, we can return the stored value Making CocoaPods Fast – Samuel Giddins @ RubyConf Taiwan 2019 61
for expensive_to_calculate can be nil, or false. ➡ The ||= short-circuiting won’t kick in. ➡ We’ll need to recompute the value every time. ➡ Not ideal. Making CocoaPods Fast – Samuel Giddins @ RubyConf Taiwan 2019 63
on the pattern: def expensive_to_calculate return @expensive_to_calculate if defined?(@expensive_to_calculate) @expensive_to_calculate = ... end Making CocoaPods Fast – Samuel Giddins @ RubyConf Taiwan 2019 64
a tiny little DSL to make memoization a bit easier: def self.define_build_settings_method( method_name, build_setting: false, memoized: false, sorted: false, uniqued: false, compacted: false, frozen: true, from_search_paths_aggregate_targets: false, from_pod_targets_to_link: false, &implementation) Making CocoaPods Fast – Samuel Giddins @ RubyConf Taiwan 2019 65
those values, calls #fetch on that hash, returns the value unless the hash doesnt yet contain the key. ➡ One benefit of this is that the memoized state can be easily discarded by clearing that one ivar, instead of needing to keep track of a bunch of ivars, each only used in a single method implementation. ➡ By carefully selecting the key, we’re able to memoize both what a superclass and subclass return separately, so multiple calls to super can also be memoized. Making CocoaPods Fast – Samuel Giddins @ RubyConf Taiwan 2019 66
bugs in build settings generation. Bug fixes which would've slowed installation down by over 5 minutes under the old implementation. Making CocoaPods Fast – Samuel Giddins @ RubyConf Taiwan 2019 67
of this comes down to the fact that the job CocoaPods does is inherently complicated. ➡ Some of it is our own fault, for having a system design from 7 years ago that wasn’t built to scale this far. Making CocoaPods Fast – Samuel Giddins @ RubyConf Taiwan 2019 70
have sidecar objects that hold computed values only when its safe to do so. ➡ (unless you want to get into cache invalidation bugs) Making CocoaPods Fast – Samuel Giddins @ RubyConf Taiwan 2019 72
The entire Xcode project model ➡ Built using nested hashes, making change-tracking hard. ➡ Lack of copy-on-write. Making CocoaPods Fast – Samuel Giddins @ RubyConf Taiwan 2019 75
state to leak in, makes computing stable hashes complicated because there are multiple valid representations to compute them from. Making CocoaPods Fast – Samuel Giddins @ RubyConf Taiwan 2019 76
every time means all invocations are equally slow. ➡ Fixed now, thanks to @sebastianv1! Making CocoaPods Fast – Samuel Giddins @ RubyConf Taiwan 2019 78