Change ITEMS_ARRAY_CHUNK_THRESHOLD to chunk up a big array into small arrays.
14 |
15 | Here are some performance metrics on my Macbook Pro running Yosemite 2.3GHz Intel i7 and 8GB memory.
16 | Count\Mechanism sizeWithAttributes NSAttributedString NSLayoutManager
17 | 1000 0.057 0.031 0.007
18 | 10000 0.329 0.325 0.064
19 | 100000 3.06 3.14 0.689
20 | 1000000 29.5 31.3 7.06
21 |
22 | NOTE: The NSLayoutManager mechanism creates an NSTextStorage object for every string. These objects are very heavyweight and memory intensive. By just creating 1 and re-using them however causes layout to happen for every string and the measurement time goes up to 40 seconds for a million strings.
23 |
24 | UPDATE
25 | Using CoreText is the way to go. For the above table here is the performance metric with and without multithreading.
26 | Count\Mechanism Core Text NO Multithreading Core Text Multithreading
27 | 1000 0.018 0.02
28 | 10000 0.023 0.023
29 | 100000 0.161 0.061
30 | 1000000 1.46 0.447
31 |
--------------------------------------------------------------------------------
/cocoa-string-size-performance/cocoa_string_size_performance.1:
--------------------------------------------------------------------------------
1 | .\"Modified from man(1) of FreeBSD, the NetBSD mdoc.template, and mdoc.samples.
2 | .\"See Also:
3 | .\"man mdoc.samples for a complete listing of options
4 | .\"man mdoc for the short list of editing options
5 | .\"/usr/share/misc/mdoc.template
6 | .Dd 5/29/15 \" DATE
7 | .Dt cocoa-string-size-performance 1 \" Program name and manual section number
8 | .Os Darwin
9 | .Sh NAME \" Section Header - required - don't modify
10 | .Nm cocoa-string-size-performance,
11 | .\" The following lines are read in generating the apropos(man -k) database. Use only key
12 | .\" words here as the database is built based on the words here and in the .ND line.
13 | .Nm Other_name_for_same_program(),
14 | .Nm Yet another name for the same program.
15 | .\" Use .Nm macro to designate other names for the documented program.
16 | .Nd This line parsed for whatis database.
17 | .Sh SYNOPSIS \" Section Header - required - don't modify
18 | .Nm
19 | .Op Fl abcd \" [-abcd]
20 | .Op Fl a Ar path \" [-a path]
21 | .Op Ar file \" [file]
22 | .Op Ar \" [file ...]
23 | .Ar arg0 \" Underlined argument - use .Ar anywhere to underline
24 | arg2 ... \" Arguments
25 | .Sh DESCRIPTION \" Section Header - required - don't modify
26 | Use the .Nm macro to refer to your program throughout the man page like such:
27 | .Nm
28 | Underlining is accomplished with the .Ar macro like this:
29 | .Ar underlined text .
30 | .Pp \" Inserts a space
31 | A list of items with descriptions:
32 | .Bl -tag -width -indent \" Begins a tagged list
33 | .It item a \" Each item preceded by .It macro
34 | Description of item a
35 | .It item b
36 | Description of item b
37 | .El \" Ends the list
38 | .Pp
39 | A list of flags and their descriptions:
40 | .Bl -tag -width -indent \" Differs from above in tag removed
41 | .It Fl a \"-a flag as a list item
42 | Description of -a flag
43 | .It Fl b
44 | Description of -b flag
45 | .El \" Ends the list
46 | .Pp
47 | .\" .Sh ENVIRONMENT \" May not be needed
48 | .\" .Bl -tag -width "ENV_VAR_1" -indent \" ENV_VAR_1 is width of the string ENV_VAR_1
49 | .\" .It Ev ENV_VAR_1
50 | .\" Description of ENV_VAR_1
51 | .\" .It Ev ENV_VAR_2
52 | .\" Description of ENV_VAR_2
53 | .\" .El
54 | .Sh FILES \" File used or created by the topic of the man page
55 | .Bl -tag -width "/Users/joeuser/Library/really_long_file_name" -compact
56 | .It Pa /usr/share/file_name
57 | FILE_1 description
58 | .It Pa /Users/joeuser/Library/really_long_file_name
59 | FILE_2 description
60 | .El \" Ends the list
61 | .\" .Sh DIAGNOSTICS \" May not be needed
62 | .\" .Bl -diag
63 | .\" .It Diagnostic Tag
64 | .\" Diagnostic informtion here.
65 | .\" .It Diagnostic Tag
66 | .\" Diagnostic informtion here.
67 | .\" .El
68 | .Sh SEE ALSO
69 | .\" List links in ascending order by section, alphabetically within a section.
70 | .\" Please do not reference files that do not exist without filing a bug report
71 | .Xr a 1 ,
72 | .Xr b 1 ,
73 | .Xr c 1 ,
74 | .Xr a 2 ,
75 | .Xr b 2 ,
76 | .Xr a 3 ,
77 | .Xr b 3
78 | .\" .Sh BUGS \" Document known, unremedied bugs
79 | .\" .Sh HISTORY \" Document history if command behaves in a unique manner
--------------------------------------------------------------------------------
/cocoa-string-size-performance.xcodeproj/xcuserdata/Abhi.xcuserdatad/xcschemes/cocoa-string-size-performance.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
51 |
52 |
58 |
59 |
60 |
61 |
62 |
63 |
69 |
70 |
76 |
77 |
78 |
79 |
81 |
82 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/cocoa-string-size-performance/main.m:
--------------------------------------------------------------------------------
1 | //
2 | // main.m
3 | // cocoa-string-size-performance
4 | //
5 | // Created by Abhi on 5/29/15.
6 | // Copyright (c) 2015 ___Abhishek Moothedath___. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | #define ITEMS_COUNT 10000
12 | #define ITEMS_ARRAY_CHUNK_THRESHOLD 1000
13 |
14 |
15 | NS_INLINE NSArray* splitIntoChunks(NSArray *items, NSUInteger chunkSize)
16 | {
17 | NSMutableArray *chunks = [NSMutableArray new];
18 |
19 | for(NSUInteger i = 0; i < items.count; i++)
20 | {
21 | NSUInteger chunkIndex = i / chunkSize;
22 | if(chunks.count <= chunkIndex)
23 | {
24 | NSMutableArray *chunk = [NSMutableArray new];
25 | [chunks addObject:chunk];
26 | }
27 | NSMutableArray *chunk = [chunks objectAtIndex:chunkIndex];
28 | [chunk addObject:[items objectAtIndex:i]];
29 | }
30 |
31 | return chunks;
32 | }
33 |
34 | NS_INLINE void performAnalysisUsingSizeWithAttributes(NSArray* items)
35 | {
36 | double width = 0, timeTaken = 0;
37 | NSDate *startDate = [NSDate dateWithTimeIntervalSinceNow:0];
38 | for(NSString *item in items)
39 | {
40 | width = MAX(width, [item sizeWithAttributes:nil].width);
41 | }
42 | timeTaken = -[startDate timeIntervalSinceNow];
43 | NSLog(@"-----------------------Using [NSString sizeWithAttributes:]------------------\n");
44 | NSLog(@"Time to measure the width(%f) of %li strings is %f\n\n\n",width, items.count, timeTaken);
45 | }
46 |
47 | NS_INLINE void performAnalysisUsingNSAttributedString(NSArray* items)
48 | {
49 | double width = 0, timeTaken = 0;
50 | NSDate *startDate = [NSDate dateWithTimeIntervalSinceNow:0];
51 | for(NSString *item in items)
52 | {
53 | NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:item];
54 | width = MAX(width, attributedString.size.width);
55 | }
56 | timeTaken = -[startDate timeIntervalSinceNow];
57 | NSLog(@"-----------------------Using [NSAttributedString size]------------------\n");
58 | NSLog(@"Time taken to measure the width(%f) of %li strings is %f\n\n\n",width, items.count, timeTaken);
59 | }
60 |
61 | static void performAnalysisUsingNSLayoutManager(NSArray* items)
62 | {
63 | double width = 0, timeTaken = 0;
64 | NSDate *startDate = [NSDate dateWithTimeIntervalSinceNow:0];
65 |
66 | NSLayoutManager *layoutManager = [NSLayoutManager new];
67 | NSTextContainer *textContainer = [NSTextContainer new];
68 | [textContainer setLineFragmentPadding:0];
69 | [layoutManager addTextContainer:textContainer];
70 |
71 | NSMutableArray *textStorageObjects = [NSMutableArray new];
72 | for(NSString *item in items)
73 | {
74 | @autoreleasepool {
75 | NSTextStorage *textStorage = [[NSTextStorage alloc] initWithString:item];
76 | [textStorage addLayoutManager:layoutManager];
77 | [textStorageObjects addObject:textStorage];
78 | }
79 | }
80 | timeTaken = -[startDate timeIntervalSinceNow];
81 | NSLog(@"-----------------------Using NSLayoutManager------------------\n");
82 | NSLog(@"Time taken to create %li NSTextStorage objects is %f",items.count, timeTaken);
83 |
84 | startDate = [NSDate dateWithTimeIntervalSinceNow:0];
85 | [layoutManager glyphRangeForTextContainer:textContainer];
86 | width = [layoutManager usedRectForTextContainer:textContainer].size.width;
87 | timeTaken = -[startDate timeIntervalSinceNow];
88 | NSLog(@"Time taken to measure the width(%f) of %li strings is %f\n\n\n",width, items.count, timeTaken);
89 | }
90 |
91 | static void performUsingCoreText(NSArray *items)
92 | {
93 | NSArray *chunks = splitIntoChunks(items, ITEMS_ARRAY_CHUNK_THRESHOLD);
94 |
95 | __block double width = 0, timeTaken;
96 | NSFont *font = [NSFont fontWithName:@"Helvetica" size:12];
97 | NSDictionary *attributes = @{NSFontAttributeName: font};
98 | CGPathRef path = CGPathCreateWithRect(CGRectMake(0, 0, CGFLOAT_MAX, CGFLOAT_MAX), NULL);
99 |
100 | dispatch_group_t group = dispatch_group_create();
101 | dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
102 |
103 | NSDate *startDate = [NSDate dateWithTimeIntervalSinceNow:0];
104 | for(NSArray *chunk in chunks)
105 | {
106 | dispatch_group_async(group, queue, ^{
107 | NSMutableAttributedString *mutableString = [[NSMutableAttributedString alloc]initWithString:[chunk componentsJoinedByString:@"\n"]
108 | attributes:attributes];
109 | [mutableString appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n"]];
110 | CFAttributedStringRef stringRef = (__bridge CFAttributedStringRef)mutableString;
111 | CTFramesetterRef framesetterRef = CTFramesetterCreateWithAttributedString(stringRef);
112 | CTFrameRef frameRef = CTFramesetterCreateFrame(framesetterRef, CFRangeMake(0, mutableString.length), path, NULL);
113 |
114 | NSArray *lines = (__bridge NSArray*)CTFrameGetLines(frameRef);
115 |
116 | for(id item in lines)
117 | {
118 | CTLineRef line = (__bridge CTLineRef)item;
119 | double lineWidth = CTLineGetTypographicBounds(line, NULL, NULL, NULL);
120 | width = MAX(width, lineWidth);
121 | }
122 | });
123 | }
124 | dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
125 |
126 | timeTaken = -[startDate timeIntervalSinceNow];
127 | NSLog(@"-----------------------Using Core Text------------------\n");
128 | NSLog(@"Time taken to measure the width(%f) of %li strings is %f",width, items.count, timeTaken);
129 | }
130 |
131 | static NSString* randomStringWithLength(int len)
132 | {
133 | NSString *letters = @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
134 | NSMutableString *randomString = [NSMutableString stringWithCapacity: len];
135 |
136 | for (int i=0; i