Performance Tuning SQLPO...

20 views
Skip to first unread message

Andrew

unread,
Apr 24, 2009, 5:26:25 PM4/24/09
to sqlitepersistentobjects-user
Gentlefolk,

I've been looking at performance of SQLPO in my app. As a result, I
have made three changes. If these are acceptable to the group, I would
like to get them committed to the SQLPO main tree.

The first is simple: I there is a memory leak in the code I
downloaded from the Google group,
sqlitepersistentobjects_02_26_2009_snapshot.zip. It occurs in -
resolveClassMethod: and it is the allocation of methodBeingCalled. It
is never released. My fix was to allocate it with a convenience
method. My object allocation traces do not show this object building
up in the autorelease pool. Of course, if someone would like to follow
the logic of this complex method, you could probably place the release
at the optimal place.

The second two fixes follow the same theme: that objects which have
no changing values are allocated multiple times on every call.
Converting this code to use statics dramatically reduced memory
pressure on the autorelease pool, 1.8MB -> 800kB, and increased
performance by eliminating redundant work. One change in NSDate,
creating a single NSDateFormatter with a static formatting string was
particularly dramatic. It reduced one of the largest number of object
allocations in my app to exactly one instance. The second solution was
more difficult because it is class specific. Hence, the static
variable needs to be saved in our class and not in SQLPO. The method
in question is: +propertiesWithEncodedTypes. It maintains a
NSDictionary that is unique to each class. There are other places
SQLPO that may profit from this conversion.

So how do I proceed sharing this code with the rest of you?

Anon,
Andrew

Paulo Roberto Schwertner

unread,
Apr 28, 2009, 8:34:28 AM4/28/09
to sqlitepersiste...@googlegroups.com
Dear Andrew,

I am interested in your modifications, but I'm not part of the development team at Google Code.
Can you share for a while this code or methods that you changed in the group?

Regards,

--
Paulo Roberto Schwertner


2009/4/24 Andrew <andrew...@gmail.com>

Andrew

unread,
Apr 28, 2009, 11:26:18 AM4/28/09
to sqlitepersistentobjects-user


On Apr 28, 7:34 am, Paulo Roberto Schwertner
<paulo.schwert...@gmail.com> wrote:
> Can you share for a while this code or methods that you changed in the
> group?


See below. You're welcome to use these patches. I release them into
the public domain. Since this is a mostly abandoned project while we
all wait for v3.0 of iPhone OS, perhaps you should consider helping
getting them into the trunk. I have never used the change management
system.


Anon,
Andrew




In SQLitePersistentObject.h:

// As a performance enhancement, this method should be overridden to
cache the dictionary of properties in your class.
+ (NSDictionary *) propertiesWithEncodedTypes;
+ (NSMutableDictionary *) propertiesWithEncodedTypes_; // private.


Here is how I overrode the method in my class.


+ (NSDictionary *) propertiesWithEncodedTypes {

static NSDictionary *classProperties = nil;

if (classProperties == nil) {

classProperties = [NSDictionary dictionaryWithDictionary: [[self
class] propertiesWithEncodedTypes_]];
[classProperties retain]; // Because it is stored in a static, we
must retain it.

}

return classProperties;

} // propertiesWithEncodedTypes


In SQLitePersistentObject.m:

+(NSDictionary *) propertiesWithEncodedTypes {

// Call the standard routine unless overridden.
return [[self class] propertiesWithEncodedTypes_];

} // propertiesWithEncodedTypes


+(NSMutableDictionary *) propertiesWithEncodedTypes_
{
// Recurse up the classes, but stop at NSObject. Each class only
reports its own properties, not those inherited from its superclass
NSMutableDictionary *theProps;

if ([self superclass] != [NSObject class])
theProps = (NSMutableDictionary *)[[self superclass]
propertiesWithEncodedTypes_];
else
theProps = [NSMutableDictionary dictionary];

unsigned int outCount;

#ifndef TARGET_OS_COCOTRON
objc_property_t *propList = class_copyPropertyList([self class],
&outCount);
#else
NSArray *propList = [[self class] getPropertiesList];
outCount = [propList count];
#endif
int i;

// Loop through properties and add declarations for the create
for (i=0; i < outCount; i++)
{
#ifndef TARGET_OS_COCOTRON
objc_property_t oneProp = propList[i];
NSString *propName = [NSString stringWithUTF8String:property_getName
(oneProp)];
NSString *attrs = [NSString stringWithUTF8String:
property_getAttributes(oneProp)];
// Read only attributes are assumed to be derived or calculated
// See http://developer.apple.com/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/chapter_8_section_3.html
if ([attrs rangeOfString:@",R,"].location == NSNotFound)
{
NSArray *attrParts = [attrs componentsSeparatedByString:@","];
if (attrParts != nil)
{
if ([attrParts count] > 0)
{
NSString *propType = [[attrParts objectAtIndex:0]
substringFromIndex:1];
[theProps setObject:propType forKey:propName];
}
}
}
#else
NSArray *oneProp = [propList objectAtIndex:i];
NSString *propName = [oneProp objectAtIndex:0];
NSString *attrs = [oneProp objectAtIndex:1];
[theProps setObject:attrs forKey:propName];
#endif
}

#ifndef TARGET_OS_COCOTRON
free( propList );
#endif

return theProps;
} // propertiesWithEncodedTypes_


+ (BOOL)resolveClassMethod:(SEL)theMethod
{
@synchronized(self)
{


const char *methodName = sel_getName(theMethod);

// swap comments on the below two lines - awd
// NSString *methodBeingCalled = [[NSString alloc]
initWithUTF8String:methodName];
NSString *methodBeingCalled = [NSString
stringWithUTF8String:methodName];

if ([methodBeingCalled hasPrefix:@"findBy"])
{
NSRange theRange = NSMakeRange(6, [methodBeingCalled length] - 7);
NSString *property = [[methodBeingCalled
substringWithRange:theRange] stringByLowercasingFirstLetter];
NSDictionary *properties = [self propertiesWithEncodedTypes];
NSLog(@"Property: %@", property);
if ([[properties allKeys] containsObject:property])
{
SEL newMethodSelector = sel_registerName([methodBeingCalled
UTF8String]);

// Hardcore juju here, this is not documented anywhere in the
runtime (at least no
// anywhere easy to find for a dope like me), but if you want to
add a class method
// to a class, you have to get the metaclass object and add the
clas to that. If you
// add the method
#ifndef TARGET_OS_COCOTRON
Class selfMetaClass = objc_getMetaClass([[self className]
UTF8String]);
return (class_addMethod(selfMetaClass, newMethodSelector, (IMP)
findByMethodImp, "@@:@")) ? YES : [super
resolveClassMethod:theMethod];
#else
if(class_getClassMethod([self class], newMethodSelector) != NULL)
{
return [super resolveClassMethod:theMethod];
} else {
BOOL isNewMethod = YES;
Class selfMetaClass = objc_getMetaClass([[self className]
UTF8String]);


struct objc_method *newMethod = calloc(sizeof(struct
objc_method), 1);
struct objc_method_list *methodList = calloc(sizeof(struct
objc_method_list)+sizeof(struct objc_method), 1);

newMethod->method_name = newMethodSelector;
newMethod->method_types = "@@:@";
newMethod->method_imp = (IMP) findByMethodImp;

methodList->method_next = NULL;
methodList->method_count = 1;
memcpy(methodList->method_list, newMethod, sizeof(struct
objc_method));
free(newMethod);
class_addMethods(selfMetaClass, methodList);

assert(isNewMethod);
return YES;
}
#endif
}
else
return [super resolveClassMethod:theMethod];
}
// TODO: This is due for some heavy refactoring - too much copy &
paste going on...
else if ([methodBeingCalled rangeOfString:@"Of"].location !=
NSNotFound)
{
NSRange rangeOfOf = [methodBeingCalled rangeOfString:@"Of"];
NSString *operation = [methodBeingCalled
substringToIndex:rangeOfOf.location];
if ([operation isEqualToString:@"sum"] || [operation
isEqualToString:@"avg"]
|| [operation isEqualToString:@"average"] || [operation
isEqualToString:@"min"]
|| [operation isEqualToString:@"max"] || [operation
isEqualToString:@"count"])
{
NSRange criteriaRange = [methodBeingCalled
rangeOfString:@"WithCriteria"];
if (criteriaRange.location == NSNotFound)
{
// Do for all
NSRange theRange = NSMakeRange(rangeOfOf.location +
rangeOfOf.length, [methodBeingCalled length] - (rangeOfOf.location +
rangeOfOf.length));
NSString *property = [[methodBeingCalled
substringWithRange:theRange] stringByLowercasingFirstLetter];
NSDictionary *properties = [self propertiesWithEncodedTypes];
if ([[properties allKeys] containsObject:property])
{
SEL newMethodSelector = sel_registerName([methodBeingCalled
UTF8String]);
Class selfMetaClass = objc_getMetaClass([[self className]
UTF8String]);
return (class_addMethod(selfMetaClass, newMethodSelector, (IMP)
aggregateMethodImp, "@@:")) ? YES : [super
resolveClassMethod:theMethod];
}
}
else
{
// do with criteria
NSRange theRange = NSMakeRange(rangeOfOf.location +
rangeOfOf.length, [methodBeingCalled length] - criteriaRange.length -
(rangeOfOf.length + rangeOfOf.location) - 1);
NSString *property = [[methodBeingCalled
substringWithRange:theRange] stringByLowercasingFirstLetter];
NSDictionary *properties = [self propertiesWithEncodedTypes];
if ([[properties allKeys] containsObject:property])
{
SEL newMethodSelector = sel_registerName([methodBeingCalled
UTF8String]);
Class selfMetaClass = objc_getMetaClass([[self className]
UTF8String]);
return (class_addMethod(selfMetaClass, newMethodSelector, (IMP)
aggregateMethodWithCriteriaImp, "@@:@")) ? YES : [super
resolveClassMethod:theMethod];
}
}
}
}
return [super resolveClassMethod:theMethod];
}
return NO;
}


In NSDateSQLitePersistence.m:
In particular, I've been making the smallest changes possible. This
static could also be used by the following method.

#if 1 // Create a static date formatter. - awd
+ (id)objectWithSqlColumnRepresentation:(NSString *)columnData;
{
#ifdef TARGET_OS_COCOTRON
NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc]
initWithDateFormat:@"%Y-%m-%d %H:%M:%S.%F" allowNaturalLanguage:NO]
autorelease];
NSDate *d;
BOOL cvt = [dateFormatter getObjectValue:&d forString:columnData
errorDescription:nil];
assert(cvt);
return d;
#else
static NSDateFormatter *dateFormatter = nil;

if (dateFormatter == nil) {

// We're not autoreleasing this because it lives in a static.
dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss.SSSS"];

}

return [dateFormatter dateFromString:columnData];

#endif
}

#else

+ (id)objectWithSqlColumnRepresentation:(NSString *)columnData;
{
#ifdef TARGET_OS_COCOTRON
NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc]
initWithDateFormat:@"%Y-%m-%d %H:%M:%S.%F" allowNaturalLanguage:NO]
autorelease];
NSDate *d;
BOOL cvt = [dateFormatter getObjectValue:&d forString:columnData
errorDescription:nil];
assert(cvt);
return d;
#else
NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init]
autorelease];
[dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss.SSSS"];
return [dateFormatter dateFromString:columnData];
#endif
}
#endif

Reply all
Reply to author
Forward
0 new messages