objective c - Performance of measuring text width in AppKit -


is there way in appkit measure width of large number of nsstring objects(say million) fast? have tried 3 different ways this:

  • [nsstring sizewithattributes:]
  • [nsattributedstring size]
  • nslayoutmanager (get text width instead of height)

    here performance metrics

    count\mechanism    sizewithattributes    nsattributedstring    nslayoutmanager
    1000               0.057                 0.031                 0.007
    10000              0.329                 0.325                 0.064
    100000             3.06                  3.14                  0.689
    1000000            29.5                  31.3                  7.06



    nslayoutmanager way go, problem being

  • high memory footprint(more 1gb according profiler) because of creation of heavyweight nstextstorage objects.
  • high creation time. of time taken during creation of above strings, dealbreaker in itself.(subsequently measuring nstextstorage objects have glyphs created , laid out takes 0.0002 seconds).
  • 7 seconds still too slow trying do. there faster way? measure million strings in second?

    in case want play around, here github project.

  • here ideas haven't tried.

    1. use core text directly. other apis built on top of it.

    2. parallelize. modern macs (and modern ios devices) have multiple cores. divide string array several subarrays. each subarray, submit block global gcd queue. in block, create necessary core text or nslayoutmanager objects , measure strings in subarray. both apis can used safely way. (core text) (nslayoutmanager)

    3. regarding “high memory footprint”: use local autorelease pool blocks reduce peak memory footprint.

    4. regarding “all of time taken during creation of above strings, dealbreaker in itself”: saying time spent in these lines:

      double random = (double)arc4random_uniform(1000) / 1000; nsstring *randomnumber = [nsstring stringwithformat:@"%f", random]; 

      formatting floating-point number expensive. real use case? if want format random rational of form n/1000 0 ≤ n < 1000, there faster ways. also, in many fonts, digits have same width, it's easy typeset columns of numbers. if pick such font, can avoid measuring strings in first place.

    update

    here's fastest code i've come using core text. dispatched version twice fast single-threaded version on core i7 macbook pro. fork of project here.

    static cgfloat maxwidthofstringsusingctframesetter(nsarray *strings, nsrange range) {     nsstring *bigstring = [[strings subarraywithrange:range] componentsjoinedbystring:@"\n"];     nsattributedstring *richtext = [[nsattributedstring alloc] initwithstring:bigstring attributes:@{ nsfontattributename: (__bridge nsfont *)font }];     cgpathref path = cgpathcreatewithrect(cgrectmake(0, 0, cgfloat_max, cgfloat_max), null);     cgfloat width = 0.0;     ctframesetterref setter = ctframesettercreatewithattributedstring((__bridge cfattributedstringref)richtext);     ctframeref frame = ctframesettercreateframe(setter, cfrangemake(0, bigstring.length), path, null);     nsarray *lines = (__bridge nsarray *)ctframegetlines(frame);     (id item in lines) {         ctlineref line = (__bridge ctlineref)item;         width = max(width, ctlinegettypographicbounds(line, null, null, null));     }     cfrelease(frame);     cfrelease(setter);     cfrelease(path);     return (cgfloat)width; }  static void test_ctframesetter() {     runtest(__func__, ^{         return maxwidthofstringsusingctframesetter(teststrings, nsmakerange(0, teststrings.count));     }); }  static void test_ctframesetter_dispatched() {     runtest(__func__, ^{         dispatch_queue_t gatherqueue = dispatch_queue_create("test_ctframesetter_dispatched result-gathering queue", nil);         dispatch_queue_t runqueue = dispatch_get_global_queue(qos_class_utility, 0);         dispatch_group_t group = dispatch_group_create();          __block cgfloat gatheredwidth = 0.0;          const size_t parallelism = 16;         const size_t totalcount = teststrings.count;         // force unsigned long 64-bit math avoid overflow large totalcounts.         (unsigned long = 0; < parallelism; ++i) {             nsuinteger start = (totalcount * i) / parallelism;             nsuinteger end = (totalcount * (i + 1)) / parallelism;             nsrange range = nsmakerange(start, end - start);             dispatch_group_async(group, runqueue, ^{                 double width = maxwidthofstringsusingctframesetter(teststrings, range);                 dispatch_sync(gatherqueue, ^{                     gatheredwidth = max(gatheredwidth, width);                 });             });         }          dispatch_group_wait(group, dispatch_time_forever);          return gatheredwidth;     }); } 

    Comments

    Popular posts from this blog

    Java 3D LWJGL collision -

    spring - SubProtocolWebSocketHandler - No handlers -

    methods - python can't use function in submodule -