humancode.us

Measuring the performance of mergeable libraries in iOS apps

January 2, 2024

I was curious about what the use of mergeable libraries meant for a typical app that may have a lot of library dependencies, so I devised a test which would measure the performance of merged frameworks versus old-school dynamic frameworks.

To read more about how mergeable libraries work, read this post.

The results

Let’s start with the results. These were taken on an iPhone 14 Pro using Instruments.

Graph. X axis shows number of frameworks from 0–100. Y axis shows time to UIKit initialization. First series: “individual frameworks”. The line goes from around 40 ms at zero frameworks, to about 60 ms at one, and rising linearly to just over 600 ms with 100. Second series: “merged frameworks”. The line hovers around 50 ms, and stays flat and rises slightly to just under 100 ms at 100. Third series: “merged frameworks with intermediate non-merged framework”. The line remains about as flat as the second series, but at a slightly higher plateau around 80 ms. The line rises steadily at 70 to reach about 180 ms at 100. Fourth series: static libraries. The line stays flat at around 30–50 ms.

And here is a close-up of the region around the origin:

Close-up of the previous graph around the origin

As you can see, the difference is dramatic. Virtually all of the O(n) cost of dynamic library loading is gone, even when an intermediate merged framework is introduced. It’s almost like the dynamic libraries have been turned into static ones, and that is not far from the truth.

Read more…

All about mergeable libraries

January 2, 2024

Mergeable Libraries is a technology introduced in WWDC23 that allows you to “absorb” and reexport dynamic libraries into a higher-level dynamic library, or directly into your app’s binary.

To see the performance implications of using mergeable libraries in your app, see this post.

How mergeable libraries work

The mergeable libraries feature allows you to copy the contents of dependent1 dynamic libraries one level up while maintaining the illusion in code that the original dependent libraries still existed where they were, when using API such as dlopen and Bundle.

Because the contents of dependent libraries are copied into the binary that links them, unresolved symbols in that binary can be statically resolved against the library’s contents which are now stored within the same binary. This is how mergeable libraries improve load times: it allows the linker to turn load-time resolution of symbols into link-time resolution, giving you dynamic-library semantics with near-static-library performance.

  1. I use the term “dependent libraries” to refer to a library that is linked by another binary, i.e. the child nodes in a link dependency tree. I know this term can be confusing, but this is the terminology Apple uses in their docs. 

Read more…

Welcome progress, no matter how small

December 22, 2023

Remember to welcome progress, no matter how small, no matter how late.

It’s easy for someone who’s been shouting warnings from the rooftops to be frustrated that people are only now beginning to listen to them, and then make such small and entirely-insufficient changes based on that “new” information.

But that is what progress looks like. People can’t pay attention to everything, because they have their entire lives to contend with. So when they start to pay attention to your message, it is vital that you don’t turn against them for doing so, but instead nurture their interest and direct their next steps.

Yes, you’ve seen it coming. You’ve been experiencing it. You know what needs to be done to fix the issue. But not everyone does. And when someone begins to show signs of acquiring a sliver of your insight, celebrate it.

That is what progress looks like.

Go small to go big

November 29, 2023

When I’m in the zone coding, especially if it’s a moderately complex program that I understand well, I get hyperfocused. It’s really hard for me to put the project down. The idea for the next building block, the next simplifying refactor, the next feature burn in my brain, and I need to get it out and into code. It is challenging when I have other important tasks to do, people to take care of, or if I have to eat or sleep.

The danger with obsession is of course to rathole—to push yourself beyond your optimal cognitive load, to hyper-optimize, to push a feature too far, too early. I spent a whole day this past weekend implementing a major refactor that ended up being entirely wrong-headed, and I had to git reset --hard and admit defeat. My current implementation is much like the original, just slightly more finessed.

One way I address this (which I forgot to do this past weekend) is to take a grand idea and apply it to something as small as possible. Want to rearchitect a whole class cluster? Try modifying one method in one class. Want to refactor 10 files? Do it with one. Want to rewrite a function that is too large from scratch? Extricate one if branch instead. Want to add a utility library? Start with one function in an empty file.

For me, it turns out that what drives my burning obsession is a constant desire for a taste of progress. Turns out that small progress is far easier to achieve than large progress, so breaking down a grand vision into its smallest possible manifestation allows me to make some progress quickly and satisfy my thirst, and also cheaply test whether the improvement was worth it in the end—and if not, the amount of work discarded would also be small.

Making the smallest possible change that implements your grand vision has a wonderful side effect of making each of your commits do only one small thing. The smallest implementation of your refactor could literally be “Rename foo to bar”, which would make a fine commit message: clear and descriptive. “Reparent Foo class cluster and protocols” on the other hand, will probably be a huge mess of a diff. It’s better to have a commit log of 20 tiny isolated changes than one big one.

So I’m committed to focusing on small progress from now on. When it’s time to step away for meals, to socialize, or to take a walk, I would be satisfied to see that I’ve pushed five small changes to the repo, representing five steps toward a grand plan—a plan that I can now revise with the hindsight of testing if those first five steps were going in the right direction. Because paradoxically, small steps let you make big changes faster.

Dealing with a veiled emotional rejection

September 17, 2023

When someone keeps asking you for more data, documentation, or justification for your change request, it’s because they don’t want to accept the change at an emotional level. They are cloaking their reluctance in a veneer of intellectual superiority.

An actually neutral party would make an attempt to appreciate your point of view, then show you a path to their acceptance, and not continually challenge your request.

When you see this coming from some party, admit plainly (in private, if it helps them save face) that you have a philosophical disagreement with them, and ask them to engage honestly at that level. Do not play the game of attempting to satisfy their incessant demands. There is no end to them.