Optimize startup be reducing selector lookup.

Move away from looking up selectors at proto initialization time to doing optimized string compares at implementation resolving time.

PiperOrigin-RevId: 623183331
pull/16444/head
Dave MacLachlan 2024-04-09 09:00:58 -07:00 committed by Copybara-Service
parent e22539bdd8
commit b375d010bf
4 changed files with 269 additions and 125 deletions

View File

@ -51,40 +51,6 @@ static const char kParentClassValueKey = 0;
static const char kClassNameSuffixKey = 0;
static const char kFileDescriptorCacheKey = 0;
// Utility function to generate selectors on the fly.
static SEL SelFromStrings(const char *prefix, const char *middle, const char *suffix,
BOOL takesArg) {
if (prefix == NULL && suffix == NULL && !takesArg) {
return sel_getUid(middle);
}
const size_t prefixLen = prefix != NULL ? strlen(prefix) : 0;
const size_t middleLen = strlen(middle);
const size_t suffixLen = suffix != NULL ? strlen(suffix) : 0;
size_t totalLen = prefixLen + middleLen + suffixLen + 1; // include space for null on end.
if (takesArg) {
totalLen += 1;
}
char buffer[totalLen];
if (prefix != NULL) {
memcpy(buffer, prefix, prefixLen);
memcpy(buffer + prefixLen, middle, middleLen);
buffer[prefixLen] = (char)toupper(buffer[prefixLen]);
} else {
memcpy(buffer, middle, middleLen);
}
if (suffix != NULL) {
memcpy(buffer + prefixLen + middleLen, suffix, suffixLen);
}
if (takesArg) {
buffer[totalLen - 2] = ':';
}
// Always null terminate it.
buffer[totalLen - 1] = 0;
SEL result = sel_getUid(buffer);
return result;
}
static NSArray *NewFieldsArrayForHasIndex(int hasIndex, NSArray *allMessageFields)
__attribute__((ns_returns_retained));
@ -594,8 +560,6 @@ static NSArray *NewFieldsArrayForHasIndex(int hasIndex, NSArray *allMessageField
for (GPBFieldDescriptor *fieldDesc in fields) {
fieldDesc->containingOneof_ = self;
}
caseSel_ = SelFromStrings(NULL, name, "OneOfCase", NO);
}
return self;
}
@ -682,27 +646,9 @@ uint32_t GPBFieldAlternateTag(GPBFieldDescriptor *self) {
coreDesc = description;
}
description_ = coreDesc;
getSel_ = sel_getUid(coreDesc->name);
setSel_ = SelFromStrings("set", coreDesc->name, NULL, YES);
GPBDataType dataType = coreDesc->dataType;
BOOL isMessage = GPBDataTypeIsMessage(dataType);
BOOL isMapOrArray = GPBFieldIsMapOrArray(self);
if (isMapOrArray) {
// map<>/repeated fields get a *Count property (inplace of a has*) to
// support checking if there are any entries without triggering
// autocreation.
hasOrCountSel_ = SelFromStrings(NULL, coreDesc->name, "_Count", NO);
} else {
// It is a single field; it gets has/setHas selectors if...
// - not in a oneof (negative has index)
// - not clearing on zero
if ((coreDesc->hasIndex >= 0) && ((coreDesc->flags & GPBFieldClearHasIvarOnZero) == 0)) {
hasOrCountSel_ = SelFromStrings("has", coreDesc->name, NULL, NO);
setHasSel_ = SelFromStrings("setHas", coreDesc->name, NULL, YES);
}
}
// Extra type specific data.
if (isMessage) {
@ -719,6 +665,7 @@ uint32_t GPBFieldAlternateTag(GPBFieldDescriptor *self) {
}
// Non map<>/repeated fields can have defaults in proto2 syntax.
BOOL isMapOrArray = GPBFieldIsMapOrArray(self);
if (!isMapOrArray && includesDefault) {
defaultValue_ = ((GPBMessageFieldDescriptionWithDefault *)description)->defaultValue;
if (dataType == GPBDataTypeBytes) {

View File

@ -80,6 +80,10 @@ typedef struct GPBFileDescription {
// Describes a single field in a protobuf as it is represented as an ivar.
typedef struct GPBMessageFieldDescription {
// Name of ivar.
// Note that we looked into using a SEL here instead (which really is just a C string)
// but there is not a way to initialize an SEL with a constant (@selector is not constant) so the
// additional code generated to initialize the value is actually bigger in size than just using a
// C identifier for large apps.
const char *name;
union {
// className is deprecated and will be removed in favor of clazz.
@ -235,7 +239,6 @@ typedef NS_OPTIONS(uint32_t, GPBDescriptorInitializationFlags) {
@package
const char *name_;
NSArray *fields_;
SEL caseSel_;
}
// name must be long lived.
- (instancetype)initWithName:(const char *)name fields:(NSArray *)fields;
@ -245,11 +248,6 @@ typedef NS_OPTIONS(uint32_t, GPBDescriptorInitializationFlags) {
@package
GPBMessageFieldDescription *description_;
GPB_UNSAFE_UNRETAINED GPBOneofDescriptor *containingOneof_;
SEL getSel_;
SEL setSel_;
SEL hasOrCountSel_; // *Count for map<>/repeated fields, has* otherwise.
SEL setHasSel_;
}
@end

View File

@ -3216,92 +3216,290 @@ static void ResolveIvarSet(__unsafe_unretained GPBFieldDescriptor *field,
}
}
// Highly optimized routines for determining selector types.
// Meant to only be used by GPBMessage when resolving selectors in
// `+ (BOOL)resolveInstanceMethod:(SEL)sel`.
// These routines are intended to make negative decisions as fast as possible.
GPB_INLINE char GPBFastToUpper(char c) { return (c >= 'a' && c <= 'z') ? (c - 'a' + 'A') : c; }
GPB_INLINE BOOL GPBIsGetSelForField(const char *selName, GPBFieldDescriptor *descriptor) {
// Does 'selName' == '<name>'?
// selName and <name> have to be at least two characters long (i.e. ('a', '\0')" is the shortest
// selector you can have).
return (selName[0] == descriptor->description_->name[0]) &&
(selName[1] == descriptor->description_->name[1]) &&
(strcmp(selName + 1, descriptor->description_->name + 1) == 0);
}
GPB_INLINE BOOL GPBIsSetSelForField(const char *selName, size_t selNameLength,
GPBFieldDescriptor *descriptor) {
// Does 'selName' == 'set<Name>:'?
// Do fastest compares up front
const size_t kSetLength = strlen("set");
// kSetLength is 3 and one for the colon.
if (selNameLength <= kSetLength + 1) {
return NO;
}
if (selName[kSetLength] != GPBFastToUpper(descriptor->description_->name[0])) {
return NO;
}
// NB we check for "set" and the colon later in this routine because we have already checked for
// starting with "s" and ending with ":" in `+resolveInstanceMethod:` before we get here.
if (selName[0] != 's' || selName[1] != 'e' || selName[2] != 't') {
return NO;
}
if (selName[selNameLength - 1] != ':') {
return NO;
}
// Slow path.
size_t nameLength = strlen(descriptor->description_->name);
size_t setSelLength = nameLength + kSetLength + 1;
if (selNameLength != setSelLength) {
return NO;
}
if (strncmp(&selName[kSetLength + 1], descriptor->description_->name + 1, nameLength - 1) != 0) {
return NO;
}
return YES;
}
GPB_INLINE BOOL GPBFieldHasHas(GPBFieldDescriptor *descriptor) {
// It gets has/setHas selectors if...
// - not in a oneof (negative has index)
// - not clearing on zero
return (descriptor->description_->hasIndex >= 0) &&
((descriptor->description_->flags & GPBFieldClearHasIvarOnZero) == 0);
}
GPB_INLINE BOOL GPBIsHasSelForField(const char *selName, size_t selNameLength,
GPBFieldDescriptor *descriptor) {
// Does 'selName' == 'has<Name>'?
// Do fastest compares up front.
const size_t kHasLength = strlen("has");
if (selNameLength <= kHasLength) {
return NO;
}
if (selName[0] != 'h' || selName[1] != 'a' || selName[2] != 's') {
return NO;
}
if (selName[kHasLength] != GPBFastToUpper(descriptor->description_->name[0])) {
return NO;
}
if (!GPBFieldHasHas(descriptor)) {
return NO;
}
// Slow path.
size_t nameLength = strlen(descriptor->description_->name);
size_t setSelLength = nameLength + kHasLength;
if (selNameLength != setSelLength) {
return NO;
}
if (strncmp(&selName[kHasLength + 1], descriptor->description_->name + 1, nameLength - 1) != 0) {
return NO;
}
return YES;
}
GPB_INLINE BOOL GPBIsCountSelForField(const char *selName, size_t selNameLength,
GPBFieldDescriptor *descriptor) {
// Does 'selName' == '<name>_Count'?
// Do fastest compares up front.
if (selName[0] != descriptor->description_->name[0]) {
return NO;
}
const size_t kCountLength = strlen("_Count");
if (selNameLength <= kCountLength) {
return NO;
}
if (selName[selNameLength - kCountLength] != '_') {
return NO;
}
// Slow path.
size_t nameLength = strlen(descriptor->description_->name);
size_t setSelLength = nameLength + kCountLength;
if (selNameLength != setSelLength) {
return NO;
}
if (strncmp(selName, descriptor->description_->name, nameLength) != 0) {
return NO;
}
if (strncmp(&selName[nameLength], "_Count", kCountLength) != 0) {
return NO;
}
return YES;
}
GPB_INLINE BOOL GPBIsSetHasSelForField(const char *selName, size_t selNameLength,
GPBFieldDescriptor *descriptor) {
// Does 'selName' == 'setHas<Name>:'?
// Do fastest compares up front.
const size_t kSetHasLength = strlen("setHas");
// kSetHasLength is 6 and one for the colon.
if (selNameLength <= kSetHasLength + 1) {
return NO;
}
if (selName[selNameLength - 1] != ':') {
return NO;
}
if (selName[kSetHasLength] != GPBFastToUpper(descriptor->description_->name[0])) {
return NO;
}
if (selName[0] != 's' || selName[1] != 'e' || selName[2] != 't' || selName[3] != 'H' ||
selName[4] != 'a' || selName[5] != 's') {
return NO;
}
if (!GPBFieldHasHas(descriptor)) {
return NO;
}
// Slow path.
size_t nameLength = strlen(descriptor->description_->name);
size_t setHasSelLength = nameLength + kSetHasLength + 1;
if (selNameLength != setHasSelLength) {
return NO;
}
if (strncmp(&selName[kSetHasLength + 1], descriptor->description_->name + 1, nameLength - 1) !=
0) {
return NO;
}
return YES;
}
GPB_INLINE BOOL GPBIsCaseOfSelForOneOf(const char *selName, size_t selNameLength,
GPBOneofDescriptor *descriptor) {
// Does 'selName' == '<name>OneOfCase'?
// Do fastest compares up front.
if (selName[0] != descriptor->name_[0]) {
return NO;
}
const size_t kOneOfCaseLength = strlen("OneOfCase");
if (selNameLength <= kOneOfCaseLength) {
return NO;
}
if (selName[selNameLength - kOneOfCaseLength] != 'O') {
return NO;
}
// Slow path.
size_t nameLength = strlen(descriptor->name_);
size_t setSelLength = nameLength + kOneOfCaseLength;
if (selNameLength != setSelLength) {
return NO;
}
if (strncmp(&selName[nameLength], "OneOfCase", kOneOfCaseLength) != 0) {
return NO;
}
if (strncmp(selName, descriptor->name_, nameLength) != 0) {
return NO;
}
return YES;
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
const GPBDescriptor *descriptor = [self descriptor];
if (!descriptor) {
return [super resolveInstanceMethod:sel];
}
// NOTE: hasOrCountSel_/setHasSel_ will be NULL if the field for the given
// message should not have has support (done in GPBDescriptor.m), so there is
// no need for checks here to see if has*/setHas* are allowed.
ResolveIvarAccessorMethodResult result = {NULL, NULL};
// See comment about __unsafe_unretained on ResolveIvarGet.
for (__unsafe_unretained GPBFieldDescriptor *field in descriptor->fields_) {
BOOL isMapOrArray = GPBFieldIsMapOrArray(field);
if (!isMapOrArray) {
// Single fields.
if (sel == field->getSel_) {
ResolveIvarGet(field, &result);
const char *selName = sel_getName(sel);
const size_t selNameLength = strlen(selName);
// A setter has a leading 's' and a trailing ':' (e.g. 'setFoo:' or 'setHasFoo:').
BOOL couldBeSetter = selName[0] == 's' && selName[selNameLength - 1] == ':';
if (couldBeSetter) {
// See comment about __unsafe_unretained on ResolveIvarGet.
for (__unsafe_unretained GPBFieldDescriptor *field in descriptor->fields_) {
BOOL isMapOrArray = GPBFieldIsMapOrArray(field);
if (GPBIsSetSelForField(selName, selNameLength, field)) {
if (isMapOrArray) {
// Local for syntax so the block can directly capture it and not the
// full lookup.
result.impToAdd = imp_implementationWithBlock(^(id obj, id value) {
GPBSetObjectIvarWithFieldPrivate(obj, field, value);
});
result.encodingSelector = @selector(setArray:);
} else {
ResolveIvarSet(field, &result);
}
break;
} else if (sel == field->setSel_) {
ResolveIvarSet(field, &result);
break;
} else if (sel == field->hasOrCountSel_) {
int32_t index = GPBFieldHasIndex(field);
uint32_t fieldNum = GPBFieldNumber(field);
result.impToAdd = imp_implementationWithBlock(^(id obj) {
return GPBGetHasIvar(obj, index, fieldNum);
});
result.encodingSelector = @selector(getBool);
break;
} else if (sel == field->setHasSel_) {
} else if (!isMapOrArray && GPBIsSetHasSelForField(selName, selNameLength, field)) {
result.impToAdd = imp_implementationWithBlock(^(id obj, BOOL value) {
if (value) {
[NSException raise:NSInvalidArgumentException
format:@"%@: %@ can only be set to NO (to clear field).", [obj class],
NSStringFromSelector(field->setHasSel_)];
NSStringFromSelector(sel)];
}
GPBClearMessageField(obj, field);
});
result.encodingSelector = @selector(setBool:);
break;
} else {
GPBOneofDescriptor *oneof = field->containingOneof_;
if (oneof && (sel == oneof->caseSel_)) {
}
}
} else {
// See comment about __unsafe_unretained on ResolveIvarGet.
for (__unsafe_unretained GPBFieldDescriptor *field in descriptor->fields_) {
BOOL isMapOrArray = GPBFieldIsMapOrArray(field);
if (GPBIsGetSelForField(selName, field)) {
if (isMapOrArray) {
if (field.fieldType == GPBFieldTypeRepeated) {
result.impToAdd = imp_implementationWithBlock(^(id obj) {
return GetArrayIvarWithField(obj, field);
});
} else {
result.impToAdd = imp_implementationWithBlock(^(id obj) {
return GetMapIvarWithField(obj, field);
});
}
result.encodingSelector = @selector(getArray);
} else {
ResolveIvarGet(field, &result);
}
break;
}
if (!isMapOrArray) {
if (GPBIsHasSelForField(selName, selNameLength, field)) {
int32_t index = GPBFieldHasIndex(field);
uint32_t fieldNum = GPBFieldNumber(field);
result.impToAdd = imp_implementationWithBlock(^(id obj) {
return GPBGetHasOneof(obj, index);
return GPBGetHasIvar(obj, index, fieldNum);
});
result.encodingSelector = @selector(getEnum);
result.encodingSelector = @selector(getBool);
break;
} else {
GPBOneofDescriptor *oneof = field->containingOneof_;
if (oneof && GPBIsCaseOfSelForOneOf(selName, selNameLength, oneof)) {
int32_t index = GPBFieldHasIndex(field);
result.impToAdd = imp_implementationWithBlock(^(id obj) {
return GPBGetHasOneof(obj, index);
});
result.encodingSelector = @selector(getEnum);
break;
}
}
} else {
if (GPBIsCountSelForField(selName, selNameLength, field)) {
result.impToAdd = imp_implementationWithBlock(^(id obj) {
// Type doesn't matter, all *Array and *Dictionary types support
// -count.
NSArray *arrayOrMap = GPBGetObjectIvarWithFieldNoAutocreate(obj, field);
return [arrayOrMap count];
});
result.encodingSelector = @selector(getArrayCount);
break;
}
}
} else {
// map<>/repeated fields.
if (sel == field->getSel_) {
if (field.fieldType == GPBFieldTypeRepeated) {
result.impToAdd = imp_implementationWithBlock(^(id obj) {
return GetArrayIvarWithField(obj, field);
});
} else {
result.impToAdd = imp_implementationWithBlock(^(id obj) {
return GetMapIvarWithField(obj, field);
});
}
result.encodingSelector = @selector(getArray);
break;
} else if (sel == field->setSel_) {
// Local for syntax so the block can directly capture it and not the
// full lookup.
result.impToAdd = imp_implementationWithBlock(^(id obj, id value) {
GPBSetObjectIvarWithFieldPrivate(obj, field, value);
});
result.encodingSelector = @selector(setArray:);
break;
} else if (sel == field->hasOrCountSel_) {
result.impToAdd = imp_implementationWithBlock(^(id obj) {
// Type doesn't matter, all *Array and *Dictionary types support
// -count.
NSArray *arrayOrMap = GPBGetObjectIvarWithFieldNoAutocreate(obj, field);
return [arrayOrMap count];
});
result.encodingSelector = @selector(getArrayCount);
break;
}
}
}
if (result.impToAdd) {
const char *encoding = GPBMessageEncodingForSelector(result.encodingSelector, YES);
Class msgClass = descriptor.messageClass;

View File

@ -539,16 +539,17 @@ void GPBSetRetainedObjectIvarWithFieldPrivate(GPBMessage *self, GPBFieldDescript
// field, and fall back on the default value. The warning below will only
// appear in debug, but the could should be changed so the intention is
// clear.
NSString *hasSel = NSStringFromSelector(field->hasOrCountSel_);
NSString *propName = field.name;
NSString *className = self.descriptor.name;
NSString *firstLetterCapitalizedName = [[[className substringToIndex:1] uppercaseString]
stringByAppendingString:[className substringFromIndex:1]];
NSLog(@"warning: '%@.%@ = nil;' is not clearly defined for fields with "
@"default values. Please use '%@.%@ = %@' if you want to set it to "
@"empty, or call '%@.%@ = NO' to reset it to it's default value of "
@"empty, or call '%@.has%@ = NO' to reset it to it's default value of "
@"'%@'. Defaulting to resetting default value.",
className, propName, className, propName,
(fieldType == GPBDataTypeString) ? @"@\"\"" : @"GPBEmptyNSData()", className, hasSel,
field.defaultValue.valueString);
(fieldType == GPBDataTypeString) ? @"@\"\"" : @"GPBEmptyNSData()", className,
firstLetterCapitalizedName, field.defaultValue.valueString);
// Note: valueString, depending on the type, it could easily be
// valueData/valueMessage.
}