Core Data faulting and bindings: recursive KVO notifications?

2008-03-27 Thread Dennis Lorson

Hi,

I have a Core Data (single-doc) application in which I have a medium- 
complex object graph.


In this application, a main entity (Image) forms the master part (they  
are displayed in a list), and this entity has several properties.
I use an array controller to manage the Image selection, and I have a  
detail view (Inspector, if you will) displaying the properties.


The problem I'm having arises when selecting all images at once  
(Command+A), if they are fairly large in number (in my store: ~2000).   
This takes an enormous time that is definitely > O(n).
Sampler reveals nothing particular because processing time is  
generally divided between many lower-level CD operations (string  
creation, etc.)
Upon inspecting the issue closer, it seems that the stack reaches a  
large depth (trace > 20.000).  Of course, this is some kind of  
recursion.  The stack recursive pattern is:


#0  0x0002cba2 in -[ImageArrayController  
observeValueForKeyPath:ofObject:change:context:] at  
ImageArrayController.m:345

#1  0x91abb58e in NSKVONotify
#2  0x91abb58e in NSKVONotify
#3	0x91b6fdec in -[NSObject(NSKeyValueObservingPrivate)  
_didChangeValuesForKeys:]

#4  0x937d290d in _PFFaultHandlerFulfillFault
#5  0x9380bf3a in -[NSFaultHandler fulfillFault:withContext:]
#6  0x937d4102 in _sharedIMPL_pvfk_core
#7  0x937d4c3c in -[NSManagedObject valueForKey:]
#8  0x91a8c39a in -[NSObject(NSKeyValueCoding) valueForKeyPath:]
#9  0x91a8c300 in -[NSObject(NSKeyValueCoding) valueForKeyPath:]
#10 0x94de7516 in -[NSArrayController _singleValueForKeyPath:]
#11	0x0002cced in -[ImageArrayController _singleValueForKeyPath:] at  
ImageArrayController.m:370

#12 0x91a8c300 in -[NSObject(NSKeyValueCoding) valueForKeyPath:]
#13	0x94daae48 in -[NSBinder  
_valueForKeyPath:ofObject:mode:raisesForNotApplicableKeys:]
#14	0x94daaa50 in -[NSBinder  
valueForBinding:resolveMarkersToPlaceholders:]

#15 0x94ddff8c in -[NSValueBinder _referenceBindingValue]
#16	0x94ddfd57 in -[NSValueBinder  
_adjustObject:mode:observedController:observedKeyPath:context:editableState:adjustState 
:]
#17	0x94ddf9e0 in -[NSValueBinder  
_observeValueForKeyPath:ofObject:context:]
#18	0x94de10c1 in -[NSTextValueBinder  
_observeValueForKeyPath:ofObject:context:]

#19 0x91abb58e in NSKVONotify
#20	0x91a4be45 in -[NSObject(NSKeyValueObservingPrivate)  
_notifyObserversForKeyPath:change:]

#21 0x94bcd15e in -[NSController _notifyObserversForKeyPath:change:]
#22	0x94dea920 in -[NSController  
observeValueForKeyPath:ofObject:change:context:]
#23	0x94dea479 in -[NSArrayController  
observeValueForKeyPath:ofObject:change:context:]
#24	0x0002cca4 in -[ImageArrayController  
observeValueForKeyPath:ofObject:change:context:] at  
ImageArrayController.m:361

#25 0x91abb58e in NSKVONotify
#26 0x91abb58e in NSKVONotify



In which [ImageArrayController observeValue:::] is invoked every time  
with the same keypath : @"instrument.magnification", but with a  
different image as observable object.  Instrument is a NSManagedObject  
with float property magnification, and each image has one instrument  
(the inverse relationship is 1..n).


The way I interpret this (it could be wrong, I don't have unlimited  
insight in private APIs)


- the array controller observes a change in keypath  
instrument.magnification of object imageA, and calls super.  This is  
because observers are bound to the selection key of the controller.
- a NSTextField that is bound to  
controller.selection.instrument.magnification receives the  
notification. (through NSTextValueBinder)

- the text field asks for the value of the property.
- a Core Data fault fires for the magnification property.  I assume  
that here, only the properties of Instrument are loaded.  Is this  
correct?
- some object (I assume the Instrument?) invokes [self  
_didChangeValuesForKeys:] because of the fulfilled fault
- since the array controller observes the key, observeValue:::  is  
invoked again with keyPath instrument.magnification, HOWEVER the image  
object is not the same!


This recurses further with all images in the controller selection  
until the whole object graph is in-memory.  It is much slower (>1  
minute on a MBP with 2000 elements in the controller set) than loading  
the data in a normal manner, probably because of spread faulting and  
the large stack depth.


My question now is, why does this recursiveness occur?  Each  
instrument has one image relationship in the test scenario, so why is  
the same keypath triggered for another image object, if the trigger is  
caused by a property of an instrument that is only related to one of  
the two images?


Thanks for any advice,


Dennis
___

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 Subs

Re: Core Data faulting and bindings: recursive KVO notifications?

2008-03-28 Thread Dennis Lorson


On 28 Mar 2008, at 20:24, Ben Trumbull wrote:




The problem I'm having arises when selecting all images at once
(Command+A), if they are fairly large in number (in my store: ~2000).
This takes an enormous time that is definitely > O(n).


Command-A should be instantaneous, even for selections 10x larger.  
The typical performance problems I've seen with array controller  
selections are (1) complex KVO observer actions cascade (2) tripping  
faults unnecessarily (3) using expensive array controller options.


(1) Complex KVO observer actions need to be carefully constrained.  
If they simply grow organically, then it's pretty easy to fall into  
the trap of observers creating side effects that trigger other  
observers that cascade to yet more observers.  This (a) makes your  
code impossible to understand, since no one ever intentionally  
designed their app that way from the beginning, and (b) sucks for  
performance.


This seems like an easy mistake indeed.  However, I checked my code  
for this when originally debugging the issue, and while it is  
difficult to eliminate all cases, no custom KVO observer triggers  
other observers as for my subclass implementation -- and if they do,  
they relate to disjunct entity objects that should not trigger others.


To investigate this further, I created a very simple Core Data app.  I  
did the following:

- Create an Employee entity
- add a few (10) properties to it:  plain string values.
- create a standard master-detail interface in IB: a tableview and  
some textfields.
- add an arraycontroller, set the entity to Employee, bind the  
managedObjectContext
- bind a tablecolumn value to arrangedObjects.someEmployeeProperty of  
the controller
- bind the textfields to selection.otherEmployeeProperties of the  
controller


When plain starting the app with a prepopulated store (10.000  
Employees with all their properties set) and selecting all, the app  
crashes after a few seconds due to stack overflow.


Again the same pattern as I described before can be observed.
I think I can come up with an explanation now:

- the selection.property1, selection.property2,... values change due  
to the table selection change.
- the first observer (random textfield, lets assume the one bound to  
selection.property1) gets notified
- through the controller, the property is asked from the model object  
employee1

- Core Data fault fires, employee1 is fully fetched
- since employee1.property1 has a changed value (fault->real value),  
it notifies all its observers.
- the same textfield receives this notification, and I can only assume  
that IT NOW ASKS THE NEXT EMPLOYEE for the keyPath, as it tries to  
assemble its multiple values set from the different employees.property1


- employee2.property1 is now asked for its value, faulted, sends  
notification, textfield asks for employee 3.


etc...until stack overflow.
The test project can be found on http://users.telenet.be/dlo/CDPerformance.zip

The good news is that the issue is entirely avoidable by batch  
faulting using the 10.5 [NSFetchRequest setReturnsObjectsAsFaults:NO]  
and executing the fetch on all objects in the selection.


But still, I have two questions:

- Am I overlooking something obvious?  If it is the correct approach,  
someone else must have experienced this in a larger master-detail set,  
right?
- While I understand the constraints the CD API imposes, shouldn't  
this be labeled as a bug?  I'm not sure here, if so, I'll file it with  
bugreporter.



It's often easier to understand, and much faster, to use  
NSNotificationCenter to defer and coalesce observations for a batch  
operation like operating upon 2000 objects.  One way to achieve this  
with the array controller is to remove all the objects from the  
controller, do your batch operation, and then add them back.


I agree, it's something I apply when importing large sets of data  
(stacks of 1000 image objects at once along with 20+ metadata  
properties per image).  Not doing it causes the observers to go crazy  
trying to refresh their data, although not recursively this time.


(2) Firing faults unnecessarily is the canonical performance issue  
for Core Data developers.  Our SQL logging (- 
com.apple.CoreData.SyntaxColoredLogging 1 - 
com.apple.CoreData.SQLDebug 1) and our Instruments template can help  
you find this, and identify which entities you're faulting  
excessively.
If you need that data, it's much (10-100x) better to batch fetch the  
data instead.  This is described in the Core Data Programming Guide  
under Performance.


While I'm certain to perform some optimization here, the delay I was  
having was almost fully due to the issue above.
It is indeed very useful, but in my case mostly to avoid the critical  
stack overflow.  I will not be having 100.000+ stores so the excessive  
faulting has still little impact.


Regarding the call into _didChangeValuesForKeys:, the API contract  
for KVO requires Core Data call -will

Re: Core Data faulting and bindings: recursive KVO notifications?

2008-03-29 Thread Dennis Lorson

On 29 Mar 2008, at 06:08, Ben Trumbull wrote:



On Mar 28, 2008, at 7:38 PM, Dennis Lorson wrote:



On 28 Mar 2008, at 20:24, Ben Trumbull wrote:




The problem I'm having arises when selecting all images at once
(Command+A), if they are fairly large in number (in my store:  
~2000).

This takes an enormous time that is definitely > O(n).


Command-A should be instantaneous, even for selections 10x larger.  
The typical performance problems I've seen with array controller  
selections are (1) complex KVO observer actions cascade (2)  
tripping faults unnecessarily (3) using expensive array controller  
options.


(1) Complex KVO observer actions need to be carefully constrained.  
If they simply grow organically, then it's pretty easy to fall  
into the trap of observers creating side effects that trigger  
other observers that cascade to yet more observers.  This (a)  
makes your code impossible to understand, since no one ever  
intentionally designed their app that way from the beginning, and  
(b) sucks for performance.


This seems like an easy mistake indeed.  However, I checked my code  
for this when originally debugging the issue, and while it is  
difficult to eliminate all cases, no custom KVO observer triggers  
other observers as for my subclass implementation -- and if they  
do, they relate to disjunct entity objects that should not trigger  
others.


You've bound the text fields to the selection poorly.  That's the  
easiest and best fix.  Other workarounds for your edification below.


Well, it might be far from ideal for CD performance, but that doesn't  
mean it's bound poorly.  It is done exactly to do what we need it to  
do, otherwise this binding would have been the first to be targeted  
for rewrite.


I really do require the app to be able to show the user the values of  
about 1000 items at once, e.g. for a stack of related items.  If they  
are the same for every item, it is important that that value be  
visible to the user.
And this is one thing Bindings as it is, cannot do properly:  The  
creation of all observation info (one dictionary for each bound  
property of each item in the selection) when selecting is just too  
much overhead.  Plus you easily encounter the aforementioned stack  
overflow.





The good news is that the issue is entirely avoidable by batch  
faulting using the 10.5 [NSFetchRequest  
setReturnsObjectsAsFaults:NO] and executing the fetch on all  
objects in the selection.


Yes.  You can also do something like this to ignore KVO  
notifications around faulting:






OK, I'll try this.




But still, I have two questions:

- Am I overlooking something obvious?  If it is the correct  
approach, someone else must have experienced this in a larger  
master-detail set, right?


Yes, you should disable "Allows Editing Multiple Values Selection"  
when you expect selection to include more than a handful of objects.



This is as said a compromise I cannot make...
Properties should be propagatable to 1000 objects at once for my  
users, as they work equally with individual images and up till 1000- 
image stacks.  I guess this calls for an extra layer of indirection,  
with a real separate entity representing the image stack and  
propagating the changes, without having 1000s of observers-observed  
object relationships.




If it's actually recursing through the entire graph, then it's  
probably a bug in your observer method.  If it's looping, then it  
could simply be toggling off a few array controller options will  
address the issue.


You should file a bug with bugreport.apple.com, and include the  
entire stack trace.  Without the rest of it, it's hard for me to  
say if this is the expected stack depth or not.  It may be a  
problem with one of your custom observation methods not coexisting  
happily with faulting, but it's a bit hard to tell from this  
excerpt.


As you can verify from the test project, I don't use any custom KVO  
logic.  Still, the crash should occur almost every time, after a  
variable amount of time (seconds range).


Yes, thanks.  Sample projects are terrific.  So this project suffers  
from both the KVO faulting issue, and a poorly bound text field.


Binding to .selection.a is going to call -valueForKeyPath:@"a" on  
the array that's selected, which is 10,000 objects.  Since the text  
field can only show one of those values, this is kinda pointless.   
This is the source of the recursion.  Selection is based on the  
entire array, which doesn't play well with KVO notifications during  
faulting.


I understand what happens, but fetching those 10.000 values is exactly  
I expect and need to happen, because there is a real chance that they  
are the same, and then "Multiple Values" is very confusing to the user.
The fetching itself can actually be very fast with CD, be it not  
completely instantaneous.  

Re: Core Data faulting and bindings: recursive KVO notifications?

2008-03-29 Thread Dennis Lorson


On 29 Mar 2008, at 06:29, Ron Lue-Sang wrote:

There's also the controller attribute "Always Use Multi Value  
Marker" which will keep the controller from even looking at the  
property values of the selected objects as long as more than one  
object is selected in the controller.


Of course, this won't help if your textfield is editable and bound  
to the controller's selection.foo keypath and the user tries to edit  
the value.


Well, as said, in many cases I really need the compound value, in the  
likely case that the property is the same for the whole selection.   
However, for a complete library the compound value is useless.  So I  
might have to tweak this Multiple Value behavior.


The user will rarely have to edit values, it is mostly (though not  
strictly) a consulting UI.  If he does, it is no more than once per  
object/property.  In that case, I can take the overhead if necessary.

___

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 [EMAIL PROTECTED]