IPhone Software Optimization: A Live Example

    Programming on the iOS platform (the one that was recently called the iPhone OS) is a strange combination of joy from fruitful work and the agony of swimming against the tide. Each developer has their own opinion on which of these components prevails. Personally, I like this lesson, so it seemed to me appropriate to share my impressions of the process of working on the next project.

    In late March, I was asked to write a mobile version of Bookmate for the iPhone. The design of most of the application was already ready in the form of a thick PSD, on the server side the work was in full swing, but I had to, as they say, “just” write the client part in Objective-C.

    In this article, we will talk about the first container with a rake that attacked us. If you play Starcraft, an analogy with zerg, which suddenly climbed out of all the cracks in typically incredible amounts, would be more appropriate.

    Architecture


    In a nutshell, Bookmate is a server on which books consisting of a set of HTML files are stored. The main task of the Bookmate client (in common people, “readers”) is to display and process this HTML. Why HTML? Unlike, for example, PDF, it is quite easy to reformat it when changing the font size, which is absolutely necessary in a mobile application on a tiny screen. On the other hand, since HTML does not have the usual concept of a page, strictly speaking, it is not very suitable for displaying books in a traditional page-by-page form. To correctly transfer a line that does not fit on the screen to the next page, you need to know its vertical indentation on the page. To do this, the HTML engine must first process the entire document and calculate the sizes and coordinates of all blocks in the document according to the stylesheet. After that, you can do

    Colleagues from Bookmate at that time had already written a library in JavaScript that does all this beautifully and quickly.

    Hello iPhone


    As the base test platform, we chose iPhone 3G, because it’s both very popular in Russia and the oldest iPhone model I have preserved. One of the test books was the publication “Models for Assembly” by Julio Cortazar in a single file of 890 KB in size.

    The first prototype was a UIWebView, a JavaScript library, and some (albeit significant) amount of interlayer code between them in Objective-C.

    iPhone 3G is a very, very smart phone. In places. Honestly, I hardly ever tried to open such heavy sites in Mobile Safari, and I’m not sure that such sites are found in nature. Poor iPhone! For those 40-plus seconds that it took him to open the “Model for Assembly”, he managed to close all other applications due to lack of memory and scream to me that now the memory will run out altogether, and to moan about a difficult fate, and bask is not able to change anything.

    But at that moment we had no time for jokes. The first zerg from the avant-garde clutched at the foot, and the earth trembled under the horde, still invisible, but already terrible.

    There was essentially nothing to optimize. For a good half of these 40 seconds, WebKit was engaged in parsing, building the DOM, recalculating geometry, and devouring memory (for lack of other terms). The latter in itself is a serious problem in iOS due to the inability to use the disk for virtual memory, and in our case it also led to the need to urgently free up memory due to background programs (such as Mail and Phone), which also takes time. JavaScript iterating over the entire DOM just finished off WebKit.

    Such performance, obviously, did not suit anyone. It was obvious that the JavaScript on the iPhone is not fast enough for our purposes, and that we can not afford 20 MB of memory.

    And what to do with it?


    To better understand the scale of the disaster, you need to break a little to describe the context in which it occurs. WebKit is an open source library . But in iOS, it refers to private APIs , the use of which is prohibited by Apple. Even if it were possible to build WebKit for iOS yourself and include it in the program as a static library, UIKit already links to WebKit, which inevitably leads to collisions of characters; even if you rename the entire WebKit and avoid collisions, UIWebView does not provide any access to the WebKit components on which it is built: see above on using the private API. It turns out that we cannot get to the DOM from Objective-C directly, without JavaScript. So, as the one-eyed blind tortoise said recently, “Well, everybody has come.”

    All that remains is to forget about WebKit: parse HTML using libxml and draw the DOM manually. That is, you need to write your HTML engine. I could only agree to this for the sake of experiment, in order to understand whether this makes sense in terms of performance. In the end, I saw the source code of WebKit and am aware of the futility of trying to rewrite it alone, while making it much faster. On the other hand, we do not use the capabilities of WebKit even by 10%. If you write your own highly specialized engine, it will be ten times lighter than any modern browser. Oh, where ours did not disappear!

    libxml


    At the annual contest in hell for programmers for the worst APIs, libxml again got the main prize. I may find fault, but the source code is much clearer than the documentation . A typical example: In all seriousness, and that’s all ?! Everything that is written here is clear from the names of the function and argument. What exactly does this function do? Why is it needed? When to use it? Which, excuse me, dunce wrote this? Sit down, libxml, deuce. In fairness, it is worth saying that the library itself works well enough to be used by almost everyone. And in life it is much simpler. And there is on every iPhone.
    Function: htmlCtxtReset
    void htmlCtxtReset(htmlParserCtxtPtr ctxt)
    Reset a parser context
    ctxt: an HTML parser context





    First results


    To lose as little time as possible on dead-end development, I started from the slowest section. This is a calculation of the sizes of blocks of text, which boils down to summing the sizes of glyphs . The results of performance measurements were not without surprises.
    • UIWebView + JavaScript - 38 seconds, 20 MB of memory. This is our first option after all the optimizations.
    • NSString, -sizeWithFont: - 14 seconds, 8 MB of memory. Since it makes no sense to repeatedly calculate the size of the same glyphs in the same font, the results are cached. 35% of the time is taken by the method call - [NSString sizeWithFont:].
    • Core Text for iPhone OS 3.1.3 - more than 70 seconds. Core Text is a private API in iPhone OS up to version 3.2, I just wanted to compare its speed, because It is amazingly easy to use and very fast on a poppy tool.

    Armed with numbers, I wrote a letter to those. support for developers (the so-called DTS , Developer Technical Support, which is generally a paid service). At the same time, I asked why the underlying function CGFontGetGlyphsForUnichars () - [NSString sizeWithFont:] refers to the private API.

    That's why I respect Apple, it’s for the attitude towards the developers, when the latter do not whine on the entire Internet, but ask questions “according to the established form”. Two days later, I received a detailed answer to my questions, from which it followed:
    • access to WebKit is not provided for by party policies;
    • Core Text under iPhone OS prior to version 3.2 is a private API due to being too slow; starting with version 3.2, it should be significantly faster than UIWebView + JavaScript;
    • CGFontGetGlyphsForUnichars () is a private API because of its inadequate design; on the Internet you can find its analogues and make sure that this is so;
    • in general, I am on the right track, for they themselves do not know the other;
    • you have to choose between convenience - [NSString sizeWithFont:] and Core Graphics speed.


    To the cause!


    The algorithm for laying out the text sounds pretty simple, if you do not go into details. First you need to break the text into fragments with the same style, for example, if in the middle of a block of text a phrase in italics appears, such a block is divided into three style fragments. Then you need to understand in what places the text can be transferred to a new line - these are, as a rule, punctuation between words or hyphenation within words. We call them candidates for transfer. Then we take the text fragments between the hyphenation candidates, one by one, consider their length, and put them on the line until their total length exceeds the length of the line. Similarly with pages: we add lines until the next line creeps out beyond the height of the page.

    An impressive set of objects is obtained: each block of text (for example, a paragraph) consists of an array of lines containing the so-called. glyph runs - fragments of the text of one style. These same glyph runs we draw as a whole as they are.

    At this point, interesting opportunities for optimization appear, and the code quickly turns into spaghetti. As we move down to the lowest-level APIs, it becomes clear that there is not enough linguistic education. On the horizon there are all new writing systems with which this code does not work. For example, because of the direction of the letter, as in Arabic or Hebrew, or because of the absence of spaces between words, as in Thai.

    As a result, realizing that it was impossible to grasp the immense on the first attempt, I had to postpone support for languages ​​with letters from right to left and languages ​​such as Thai, which I know too little about. For the same reason, the implementation of aligning the text on both edges is postponed - it requires an effective hyphenation system, which, in turn, needs to know what language each word is in.

    However, in terms of performance, significant progress has been made compared to where we started. Now Cortazar’s masterpiece appears on the screen in less than 6 (!) Seconds. It was a really hard month. I had to temporarily reflash my brain, otherwise he flatly refused to work with HTML and CSS. All this time my wife listened from my corner only grunting and mothers. It was not sweet for the client either: firstly, no one was counting on an extra month, and secondly, the start of work on the project was discouraging (“a good start, what will happen next?”), Thirdly, there were no guarantees, that this bunch of plywood can even take off. However, who does not take risks, he walks. And we wanted so much in the sky!

    Bookmate is now available on the App Store(the link opens in iTunes; if for some reason it does not work, there is an alternative one that opens in the browser).

    Epilogue


    About Steve Jobs tell such a story . In 1983, he convinced engineers to optimize the Mac’s boot time with these words: “How many people will use the Mac? Million? No more. I bet that in a few years five million people will turn on their macs at least once a day. Lets say you can reduce boot time by 10 seconds. Multiply this by five million users, you get 50 million seconds, every single day. For a year, this is probably dozens of lives. If you make it download 10 seconds faster, you saved a dozen lives. Well, is it worth it? ” I think yes.

    Also popular now: