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:
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
in case want play around, here github project.
here ideas haven't tried.
use core text directly. other apis built on top of it.
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
)regarding “high memory footprint”: use local autorelease pool blocks reduce peak memory footprint.
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
Post a Comment