Skip to content

Commit

Permalink
Improve the performance of -[NSObject valueForKeyPath:]. (#1005)
Browse files Browse the repository at this point in the history
* Improve the performance of _NSKVCSplitKeypath by using CF functions.
* Do not create NSInvocations for quick get on nil values.

References #904.
  • Loading branch information
DHowett authored Sep 20, 2016
1 parent 812d663 commit 2b8e783
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 73 deletions.
15 changes: 6 additions & 9 deletions Frameworks/Foundation/NSKVOSwizzling.mm
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#import "NSMethodSignatureInternal.h"
#import <objc/encoding.h>
#import <objc/runtime.h>
#import "type_encoding_cases.h"

#include <memory>

Expand Down Expand Up @@ -282,6 +283,10 @@ static void notifyingVariadicSetImpl(id self, SEL _cmd, ...) {
}
#pragma endregion

#define KVO_SET_IMPL_CASE(type, name, capitalizedName, encodingChar) \
case encodingChar: \
newImpl = reinterpret_cast<IMP>(&notifyingSetImpl<type>); \
break;
// invariant: rawKey has already been capitalized
static void _NSKVOEnsureSimpleKeyWillNotify(id object, NSString* key, const char* rawKey) {
auto sel = KVCSetterForPropertyName(object, rawKey);
Expand All @@ -298,15 +303,7 @@ static void _NSKVOEnsureSimpleKeyWillNotify(id object, NSString* key, const char
IMP newImpl = NULL;

switch (valueType[0]) {
#define APPLY_TYPE(type, name, capitalizedName, encodingChar) \
case encodingChar: \
newImpl = reinterpret_cast<IMP>(&notifyingSetImpl<type>); \
break;

APPLY_TYPE(id, object, Object, '@')
APPLY_TYPE(Class, class, Class, '#')
#include "type_encoding_cases.h"
#undef APPLY_TYPE
OBJC_APPLY_ALL_TYPE_ENCODINGS(KVO_SET_IMPL_CASE);
case '{':
case '[': {
size_t valueSize = objc_sizeof_type(valueType);
Expand Down
56 changes: 25 additions & 31 deletions Frameworks/Foundation/NSKeyValueCoding.mm
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#import "NSValueTransformers.h"
#import "Starboard.h"
#import "StubReturn.h"
#import "type_encoding_cases.h"
#import <_NSKeyValueCodingAggregateFunctions.h>

#import <unicode/utf8.h>
Expand Down Expand Up @@ -54,22 +55,12 @@
NSString* const NSUnionOfSetsKeyValueOperator = @"NSUnionOfSetsKeyValueOperator";

NSString* _NSKVCSplitKeypath(NSString* keyPath, NSString* __autoreleasing* pRemainder) {
NSData* utf8String = [keyPath dataUsingEncoding:NSUTF8StringEncoding];
const char* buffer = static_cast<const char*>(utf8String.bytes);
NSInteger length = utf8String.length;
int i = 0;
UChar32 currentCharacter = 0;
while (i < length) {
U8_NEXT(buffer, i, length, currentCharacter);
if (currentCharacter == '.') {
*pRemainder = [[[NSString alloc] initWithBytesNoCopy:const_cast<char*>(buffer + i)
length:length - i
encoding:NSUTF8StringEncoding
freeWhenDone:NO] autorelease];
return
[[[NSString alloc] initWithBytesNoCopy:const_cast<char*>(buffer) length:i - 1 encoding:NSUTF8StringEncoding freeWhenDone:NO]
autorelease];
}
static woc::unique_cf<CFCharacterSetRef> dot{CFCharacterSetCreateWithCharactersInRange(nullptr, (CFRange){'.', 1})};
CFRange result;
CFIndex length = CFStringGetLength((CFStringRef)keyPath);
if (length > 0 && CFStringFindCharacterFromSet((CFStringRef)keyPath, dot.get(), (CFRange){0, length}, 0, &result)) {
*pRemainder = [(NSString*)CFStringCreateWithSubstring(nullptr, (CFStringRef)keyPath, (CFRange){result.location + 1, length - (result.location + 1)}) autorelease];
return [(NSString*)CFStringCreateWithSubstring(nullptr, (CFStringRef)keyPath, (CFRange){0, result.location}) autorelease];
}
*pRemainder = nil;
return keyPath;
Expand Down Expand Up @@ -172,6 +163,10 @@ static id quickGet(id self, SEL getter) {
return ((id (*)(id, SEL))objc_msgSend)(self, getter);
}

#define QUICK_GET_CASE(type, name, capitalizedName, encodingChar) \
case encodingChar: \
*ret = quickGet<type>(self, getter); \
return true;
bool KVCGetViaAccessor(NSObject* self, SEL getter, id* ret) {
if (!getter) {
return false;
Expand All @@ -185,13 +180,10 @@ bool KVCGetViaAccessor(NSObject* self, SEL getter, id* ret) {
return false;
}

if (0
#define APPLY_TYPE(type, name, capitalizedName, encodingChar) || (valueType[0] == encodingChar && (*ret = quickGet<type>(self, getter)))
APPLY_TYPE(id, object, Object, '@') APPLY_TYPE(Class, class, Class, '#')
#include "type_encoding_cases.h"
#undef APPLY_TYPE
) {
return true;
switch (valueType[0]) {
OBJC_APPLY_NUMERIC_TYPE_ENCODINGS(QUICK_GET_CASE);
QUICK_GET_CASE(id, object, Object, '@');
QUICK_GET_CASE(Class, class, Class, '#');
}

NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:sig];
Expand Down Expand Up @@ -406,6 +398,12 @@ static bool quickSet(id self, SEL setter, id value, const char* valueType) {
return true;
}

#define QUICK_SET_CASE(type, name, capitalizedName, encodingChar) \
case encodingChar: \
if (quickSet<type>(self, setter, value, valueType)) { \
return true; \
} \
break;
bool KVCSetViaAccessor(NSObject* self, SEL setter, id value) {
if (!setter) {
return false;
Expand All @@ -417,14 +415,10 @@ bool KVCSetViaAccessor(NSObject* self, SEL setter, id value) {
if (sig && [sig numberOfArguments] == 3) {
const char* valueType = [sig getArgumentTypeAtIndex:2];

if (0
#define APPLY_TYPE(type, name, capitalizedName, encodingChar) \
|| (valueType[0] == encodingChar && quickSet<type>(self, setter, value, valueType))
APPLY_TYPE(id, object, Object, '@') APPLY_TYPE(Class, class, Class, '#')
#include "type_encoding_cases.h"
#undef APPLY_TYPE
) {
return true;
switch (valueType[0]) {
OBJC_APPLY_NUMERIC_TYPE_ENCODINGS(QUICK_SET_CASE);
QUICK_SET_CASE(id, object, Object, '@');
QUICK_SET_CASE(Class, class, Class, '#');
}

uint8_t* data = static_cast<uint8_t*>(_alloca(objc_sizeof_type(valueType)));
Expand Down
89 changes: 56 additions & 33 deletions Frameworks/include/type_encoding_cases.h
Original file line number Diff line number Diff line change
@@ -1,35 +1,58 @@
/**
* type_encoding_cases.h - expects the APPLY_TYPE macro to be defined. This
* macro is invoked once for every type and its Objective-C name. Use this
* file when implementing things like the -unsignedIntValue family of methods.
* For this case, the macro will be invoked with unsigned int as the type and
* unsignedInt as the name.
*/
#ifndef APPLY_TYPE
#error Define APPLY_TYPE(type, name, capitalizedName, encodingChar) before including this file
//******************************************************************************
//
// Copyright (c) Microsoft. All rights reserved.
//
// This code is licensed under the MIT License (MIT).
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
//******************************************************************************

#pragma once

// Each OBJC_APPLY_*_TYPE_ENCODINGS macro expects a single argument: the name
// of a macro to apply for every type encoding. That macro should take the form
// #define name(ctype, objectiveCName, CapitalizedObjectiveCName, typeEncodingCharacter)

#if defined(_Bool)
#define OBJC_APPLY_BOOL_TYPE_ENCODING(_APPLY_TYPE_MACRO) \
_APPLY_TYPE_MACRO(_Bool, bool, Bool, 'B')
#else
#define OBJC_APPLY_BOOL_TYPE_ENCODING(_APPLY_TYPE_MACRO) // Nothing
#endif
APPLY_TYPE(double, double, Double, 'd')
APPLY_TYPE(float, float, Float, 'f')
APPLY_TYPE(signed char, char, Char, 'c')
APPLY_TYPE(int, int, Int, 'i')
APPLY_TYPE(short, short, Short, 's')
APPLY_TYPE(long, long, Long, 'l')
APPLY_TYPE(long long, longLong, LongLong, 'q')
//APPLY_TYPE(__int128, int128, Int128, 't')
APPLY_TYPE(unsigned char, unsignedChar, UnsignedChar, 'C')
APPLY_TYPE(unsigned short, unsignedShort, UnsignedShort, 'S')
APPLY_TYPE(unsigned int, unsignedInt, UnsignedInt, 'I')
APPLY_TYPE(unsigned long, unsignedLong, UnsignedLong, 'L')
APPLY_TYPE(unsigned long long, unsignedLongLong, UnsignedLongLong, 'Q')

#define OBJC_APPLY_NUMERIC_TYPE_ENCODINGS(_APPLY_TYPE_MACRO) \
_APPLY_TYPE_MACRO(double, double, Double, 'd') \
_APPLY_TYPE_MACRO(float, float, Float, 'f') \
_APPLY_TYPE_MACRO(signed char, char, Char, 'c') \
_APPLY_TYPE_MACRO(int, int, Int, 'i') \
_APPLY_TYPE_MACRO(short, short, Short, 's') \
_APPLY_TYPE_MACRO(long, long, Long, 'l') \
_APPLY_TYPE_MACRO(long long, longLong, LongLong, 'q') \
_APPLY_TYPE_MACRO(unsigned char, unsignedChar, UnsignedChar, 'C') \
_APPLY_TYPE_MACRO(unsigned short, unsignedShort, UnsignedShort, 'S') \
_APPLY_TYPE_MACRO(unsigned int, unsignedInt, UnsignedInt, 'I') \
_APPLY_TYPE_MACRO(unsigned long, unsignedLong, UnsignedLong, 'L') \
_APPLY_TYPE_MACRO(unsigned long long, unsignedLongLong, UnsignedLongLong, 'Q') \
OBJC_APPLY_BOOL_TYPE_ENCODING(_APPLY_TYPE_MACRO)

//APPLY_TYPE(__int128, int128, Int128, 't') \
//APPLY_TYPE(unsigned __int128, unsignedInt128, UnsignedInt128, 'T')
#ifdef NON_INTEGER_TYPES
#undef NON_INTEGER_TYPES
APPLY_TYPE(_Bool, bool, Bool, 'B')
#ifndef SKIP_ID
APPLY_TYPE(id, object, Object, '@')
#endif
APPLY_TYPE(Class, class, Class, '#')
APPLY_TYPE(SEL, selector, Selector, ':')
APPLY_TYPE(char*, cString, CString, '*')
#endif
#undef APPLY_TYPE

#define OBJC_APPLY_OBJECTIVEC_TYPE_ENCODINGS(_APPLY_TYPE_MACRO) \
_APPLY_TYPE_MACRO(id, object, Object, '@') \
_APPLY_TYPE_MACRO(Class, class, Class, '#') \
_APPLY_TYPE_MACRO(SEL, selector, Selector, ':')
#define OBJC_APPLY_POINTER_TYPE_ENCODINGS(_APPLY_TYPE_MACRO) \
_APPLY_TYPE_MACRO(char*, cString, CString, '*')

#define OBJC_APPLY_ALL_TYPE_ENCODINGS(_APPLY_TYPE_MACRO) \
OBJC_APPLY_NUMERIC_TYPE_ENCODINGS(_APPLY_TYPE_MACRO) \
OBJC_APPLY_OBJECTIVEC_TYPE_ENCODINGS(_APPLY_TYPE_MACRO) \
OBJC_APPLY_POINTER_TYPE_ENCODINGS(_APPLY_TYPE_MACRO)

0 comments on commit 2b8e783

Please sign in to comment.