Reputation: 13334
Using the standard pattern for a singleton in Objective-C, ARC still automatically generates retain and release calls around every use of the singleton, even though we know that the object will never be deallocated. In performance-sensitive code, these ARC-generated calls cause a significant amount of extra overhead. Is there any way to tell the compiler not to generate retain/release code for a singleton?
In my case, I'm writing a performance-sensitive parser of Japanese text. As part of my parsing, I often need access to an NSCharacterSet of kanji characters, which I have defined in a category on NSCharacterSet.
+ (id)kanjiCharacterSet
{
static NSCharacterSet* kanjiCharacterSet = nil;
static dispatch_once_t onceToken;
dispatch_once( &onceToken, ^
{
NSRange range = { .location = 0x4e00, .length = 0x9faf - 0x4e00 };
kanjiCharacterSet = [NSCharacterSet characterSetWithRange:range];
} );
return kanjiCharacterSet;
}
One of the most common places that I access this character set is to check for the existence of a kanji character in a string. This code lives in a category on NSString.
- (BOOL)containsKanji
{
return [self rangeOfCharacterFromSet:[NSCharacterSet kanjiCharacterSet]].location != NSNotFound;
}
When I run this through a profiler, about 40% of the entire time spent in [NSString containsKanji]
is in retain/release code. As for [NSCharacterSet kanjiCharacterSet]
, excluding the first call where we actually generate the character set, about 80% of each call is spent in retain/release code.
If I set breakpoints in objc_retain
, obj_release
, and objc_autorelease
, I can see that one retain and one autorelease are being added to the return statement from [NSCharacterSet kanjiCharacterSet]
, one retain is being added in [NSString containsKanji]
when it receives the value returned from [NSCharacterSet kanjiCharacterSet]
, and one release is being added when we return from [NSString containsKanji]
. It seems like all of these calls are unnecessary for a singleton. Is there any way to tell the compiler not to generate these calls?
I've found one workaround, which is to add a static variable to [NSString containsKanji]
to store the character set in, but I'd love to find a more general solution.
Upvotes: 2
Views: 214
Reputation: 52538
In your method which calls kanjiCharacterSet repeatedly, cache a copy. Obviously that kind of thing should only be done when it is performance critical.
- (BOOL)containsKanji
{
static NSCharacterSet* cachedKanji = nil;
static dispatch_once_t onceToken;
dispatch_once (&onceToken, ^{
cachedKanji = [NSCharacterSet kanjiCharacterSet];
});
return [self rangeOfCharacterFromSet: cachedKanji].location != NSNotFound;
}
Upvotes: 1
Reputation: 13334
I found a partial solution by adding __attribute__((ns_returns_retained))
to the method declaration:
+ (id)kanjiCharacterSet __attribute__((ns_returns_retained));
According to the documentation, this forces the compiler to treat the method the same way it would treat a method that starts with alloc
, copy
, init
, mutableCopy
, or new
. Instead of four retain/release related calls, it only generates two: a single retain in [NSCharacterSet kanjiCharacterSet]
and a single release in the calling code.
Upvotes: 1
Reputation: 9533
You don’t say what percentage of the total time is in -containsKanji, which is the only relevant metric for whether you should optimise that routine. If it’s, like, 1% of your total time, then it doesn’t matter if it spends all its time retaining or spinning or doing the dishes.
If it is in fact your big time sync, you should stop using objects in that method, a bit...it’s the big advantage of Objective-C, that we get to write low-level code in the middle of our high-level code.
In your case, since Kanji is from 0x4e00 to 0x9faf, I’d just write something like:
- (BOOL)containsKanji;
{
const NSUInteger bufferSize = 256;
unichar buffer[bufferSize];
for (NSUInteger characterIndex = 0; characterIndex < self.length; characterIndex += bufferSize) {
const NSUInteger charactersToFetch = MIN(bufferSize, self.length - characterIndex);
[self getCharacters:buffer range:(NSRange){characterIndex, charactersToFetch}];
for (NSUInteger checkCharacterIndex = 0; checkCharacterIndex < charactersToFetch; checkCharacterIndex++) {
unichar characterToCheck = buffer[characterIndex + checkCharacterIndex];
if (characterToCheck >= 0x4e00 && characterToCheck <= 0x9faf)
return YES;
}
}
return NO;
}
Note I haven’t compiled this code, so you might need to fix typos and such.
Upvotes: 1