On Aug 12, 2009, at 11:15 AM, Andy Lee wrote:

On Aug 12, 2009, at 11:37 AM, Christopher Kane wrote:
Don't pass the observer as the context: argument; pretty much anything else is better.

Out of curiosity -- why would this affect performance and why is this a bad idea (other than performance)? Isn't the context: treated as an opaque pointer by KVO?

--Andy


Well, as anyone can see from sampling the test program, a lot of time is being spent in hash table management, and as I can tell through experience, there are a lot of probe collisions happening. You are adding many observers into the KVO subsystem, and perhaps KVO is keeping hash tables of these registrations.

                [modelObject addObserver:observer
                              forKeyPath:@"title"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
                                 context:observer];

Now here is where we enter the realm of the hypothetical. Suppose that the hashing is based on some combination of the observer, key path, options, and context, which would be reasonable. Suppose (and I checked and this isn't the case, but this is easy to understand), that the hash code computation goes something like this:

NSUInteger computeHash(...args...) {
return (NSUInteger)((uintptr_t)observer - (uintptr_t)context + [keyPath hash] - (uintptr_t)options);
}

Now, the keyPath and the options are the same for every registration, so they aren't contributing anything [in the test program] to make the hash codes for each registration different. If the observer and context are the same, obviously in my contrived example, they are cancelling each other out. Thus my -hash would always return the same value for all the registrations, which is terrible for hash table performance.

Now I can see that what is going on is not actually full degenerate linear probing, so the interaction between observer and context must be more subtle in the computation of any hash code. How did I know that context being the observer was the issue? Try it! Just changing it to NULL makes an enormous difference. So there must be some sort of interaction there. In this contrived example, the observers and modelObjects are likely to be fairly linearly allocated from out of the heap, and probably all pretty close together, which might be one effect, and so the bit pattern of the "contexts", which can only be used as a raw pointer/integer value in any hash code computation, would be relatively similar.

However, using the address of static data would be better than NULL, to avoid the likelihood of collisions with other possible KVO registrants on that object.

I don't know any reason for not passing observer as the context: other than it seems to avoid this performance corner-case. In the context (no pun intended) of the observeValueForKeyPath:... method, of course, you don't *need* to pass observer as the context because the observer is already "self".


Chris Kane
Cocoa Frameworks, Apple



Passing NULL (just, for example) flattens the curve quite a bit. If you have nothing better to pass in, put this in your code and pass the address of it:

static char _xyzzy_ = 0;


Chris Kane
Cocoa Frameworks, Apple


On Aug 3, 2009, at 6:51 AM, Andreas Känner wrote:

Hi,

If you add more and more key/value observers with addObserver:forKeyPath:options:context: this call takes longer and longer in a non-linear way.

I posted a bug report today (ID: 7112953) but maybe someone already has a workaround. Here is my sample code:

#include <Foundation/Foundation.h>

@interface ModelObject : NSObject
{
  NSString* title;
}
@property (readwrite, copy) NSString* title;
@end

@implementation ModelObject
@synthesize title;
@end

@interface Observer : NSObject
{}
@end

@implementation Observer
@end

void addObservers(NSUInteger count)
{
        // Create model objects and observers:

NSMutableArray* modelObjects = [NSMutableArray arrayWithCapacity:count]; NSMutableArray* observers = [NSMutableArray arrayWithCapacity:count];
        for(NSUInteger i=0; i<count; i++)
        {
          [modelObjects addObject:[[ModelObject alloc] init]];
             [observers    addObject:[[Observer alloc] init]];
        }

        // Register observers:

        NSDate* startDate = [NSDate date];
        for(NSUInteger i=0; i<count; i++)
        {
                ModelObject* modelObject = [modelObjects objectAtIndex:i];
                Observer* observer       = [observers    objectAtIndex:i];
                [modelObject addObserver:observer
                              forKeyPath:@"title"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
                                 context:observer];

        }
NSLog(@"time to add %d observers: %f s", count, -[startDate timeIntervalSinceNow]);
}



int main (int argc, const char * argv[])
{
        NSAutoreleasePool* pool = [NSAutoreleasePool new];

        addObservers(10);
        addObservers(100);
        addObservers(1000);
        addObservers(10000);
        [pool release];
}

In my test case an observer does always observe only one object and each observed object is only observed by one observer. I think this is a common use case.

I have results for Snow Leopard too, but I think I'm not allowed to post them on this mailing list. Here are the results for Leopard:

I tested this with different build settings (with/without GC and 32/64bit)

Machine: iMac 2.4 GHz Intel Core 2 Duo

Leopard (10.5.7/9J61):

32Bit:

With GC:

time to add    10 observers: 0.001008 s
time to add   100 observers: 0.001796 s
time to add  1000 observers: 0.174864 s
time to add 10000 observers: 10.746330 s

Without GC:

time to add    10 observers: 0.000920 s
time to add   100 observers: 0.000956 s
time to add  1000 observers: 0.056894 s
time to add 10000 observers: 4.615175 s

64Bit:

With GC:

time to add    10 observers: 0.001340 s
time to add   100 observers: 0.001533 s
time to add  1000 observers: 0.125700 s
time to add 10000 observers: 7.545702 s

Without GC:

time to add    10 observers: 0.001058 s
time to add   100 observers: 0.000831 s
time to add  1000 observers: 0.053189 s
time to add 10000 observers: 4.006754 s

Notes:

If you know a workaround for this bottleneck please drop me an email.


_______________________________________________

Cocoa-dev mailing list (Cocoa-dev@lists.apple.com)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
http://lists.apple.com/mailman/options/cocoa-dev/ckane%40apple.com

This email sent to ck...@apple.com

_______________________________________________

Cocoa-dev mailing list (Cocoa-dev@lists.apple.com)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
http://lists.apple.com/mailman/options/cocoa-dev/aglee%40mac.com

This email sent to ag...@mac.com


_______________________________________________

Cocoa-dev mailing list (Cocoa-dev@lists.apple.com)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
http://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com

This email sent to arch...@mail-archive.com

Reply via email to