性能:核心数据关系在分配后出现故障
我有一个代表 iOS 4+ 上的电视指南的核心数据模型,有 3 个类:
Channel
(BBC 1)Program
(Top Gear)Broadcast
(周一晚上 8 点在 BBC 1 上的 Top Gear)
我有大约 40 个频道、8000 个节目和 6000 个广播,我想微调导入过程,以便不需要一分钟运行。
导入频道和节目很容易,因为它们是独立的对象。然而,广播与频道和节目具有关系(一对多),并且频道和节目与广播具有逆关系(多对1)。为了加快速度,我有一个故障通道和节目的内存字典,仅预取其 Web 服务标识符:我创建一个广播并浏览两个字典以获取相应的通道和节目,而无需往返数据库。
但是,当我将节目或频道分配给广播时,频道和节目的反向关系访问会立即触发两个对象的故障,导致大幅减速(6000 * 2 请求)和随之而来的内存压力,如核心数据故障中所示仪器报告。我尝试在频道和节目上预取broadcasts
关系,但该关系仍然出现故障。
你知道为什么逆向关系会被访问并责怪他们的父母吗?保存关系时如何避免从数据库中读取数据?
更新:示例代码,我的 Broadcast
实例的分配/更新方法。 dictionary
变量来自 Web 服务,channels
和 programs
包含由 Web 服务标识符索引的故障通道和程序对象。故障发生在 self.program = program
和 self.channel = channel
行上。
- (BOOL)assignWithDictionary:(NSDictionary *)dictionary channels:(NSDictionary *)channels programs:(NSDictionary *)programs {
// Add channel relationship
NSNumber *channelIdentifier = [dictionary objectForKey:@"channel_id"];
if (self.channel == nil || ![self.channel.identifier isEqualToNumber:channelIdentifier]) {
Channel *channel = [channels objectForKey:channelIdentifier];
if (channel == nil) {
NSLog(@"Broadcast %@ has invalid channel: %@", identifier, channelIdentifier);
return NO;
}
self.channel = channel;
}
// Same to add a program relationship
// ...
}
我的获取请求是获取频道或节目列表:
- (NSDictionary *)itemsForEntity:(NSEntityDescription *)entity {
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
NSError *error = nil;
NSArray *itemsArray = nil;
request.entity = entity;
request.relationshipKeyPathsForPrefetching = [NSArray arrayWithObject:@"broadcasts", nil];
request.propertiesToFetch = [NSArray arrayWithObjects:@"identifier", @"version", nil];
itemsArray = [self.context executeFetchRequest:request error:&error];
NSAssert1(error == nil, @"Could not fetch the items from the database: %@", error);
{
NSMutableDictionary *items = [NSMutableDictionary dictionaryWithCapacity:itemsArray.count];
for (NSManagedObject *item in itemsArray) {
[items setObject:item forKey:[item valueForKey:@"identifier"]];
}
return [NSDictionary dictionaryWithDictionary:items];
}
}
I have a Core Data model representing a TV guide on iOS 4+, with 3 classes:
Channel
(BBC 1)Program
(Top Gear)Broadcast
(Top Gear on BBC 1 on Monday at 8pm)
I have about 40 channels, 8000 programs and 6000 broadcasts, and I would like to fine-tune the import process so that it doesn't take up to a minute to run.
Importing the channels and programs is easy because these are independent objects. A broadcast however has a relationship to a channel and to a program (1-to-many), and both channels and programs have inverse relationships to the broadcasts (many-to-1). To speed things up I have an in-memory dictionary of fault channels and programs that have only their Web Service identifier prefetched: I create a broadcast and look through both dictionaries to get the corresponding channel and program without a round-trip to the database.
But when I assign a program or a channel to a broadcast, the channel and program's inverse relationships access trigger a fault of both objects right away, causing a massive slowdown (6000 * 2 requests) and consequent memory pressure as shown in the Core Data Faults Instruments report. I tried pre-fetching the broadcasts
relationship on both channels and programs, but the relationship still gets faulted.
Do you have any idea why the inverse relationships get accessed and fault their parents? How do I avoid reading from the database when saving a relationship?
UPDATE: Sample code, my assign / update method for a Broadcast
instance. The dictionary
variable comes from the Web Service and channels
and programs
contain the fault channels and programs objects indexed by Web Service identifier. Faulting occurs on the self.program = program
and self.channel = channel
lines.
- (BOOL)assignWithDictionary:(NSDictionary *)dictionary channels:(NSDictionary *)channels programs:(NSDictionary *)programs {
// Add channel relationship
NSNumber *channelIdentifier = [dictionary objectForKey:@"channel_id"];
if (self.channel == nil || ![self.channel.identifier isEqualToNumber:channelIdentifier]) {
Channel *channel = [channels objectForKey:channelIdentifier];
if (channel == nil) {
NSLog(@"Broadcast %@ has invalid channel: %@", identifier, channelIdentifier);
return NO;
}
self.channel = channel;
}
// Same to add a program relationship
// ...
}
And my fetch request to get the channels or the programs list:
- (NSDictionary *)itemsForEntity:(NSEntityDescription *)entity {
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
NSError *error = nil;
NSArray *itemsArray = nil;
request.entity = entity;
request.relationshipKeyPathsForPrefetching = [NSArray arrayWithObject:@"broadcasts", nil];
request.propertiesToFetch = [NSArray arrayWithObjects:@"identifier", @"version", nil];
itemsArray = [self.context executeFetchRequest:request error:&error];
NSAssert1(error == nil, @"Could not fetch the items from the database: %@", error);
{
NSMutableDictionary *items = [NSMutableDictionary dictionaryWithCapacity:itemsArray.count];
for (NSManagedObject *item in itemsArray) {
[items setObject:item forKey:[item valueForKey:@"identifier"]];
}
return [NSDictionary dictionaryWithDictionary:items];
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
不太确定您在这里要做什么,但是...
第一件事是您不能仅使用错误来更改属性。错误只是占位符,允许您测量/计算对象图和行走关系。如果您确实更改了关系,它将引发故障,导致相关对象加载。
如果您尝试仅使用错误来设置特定的
Channel
、Program
和Broadcast
对象之间的关系,这是行不通的。我不明白你的
itemsForEntity:
方法。它将获取传递的实体的每个现有托管对象,然后将在字典中返回这些对象。这将导致大量的内存开销,尤其是在Program
对象有 8,000 个的情况下。除非您将获取返回设置为字典,否则您不能使用
propertiesToFetch
,但您没有这样做。如果需要设置关系,无论如何都不能使用字典返回类型。当您想要的只是某些属性中保存的数据时,可以使用这两种方法。它不是操作对象图关系的工具。仅当您知道将访问现有关系时,设置
relationshipKeyPathsForPrefetching
才能加快速度。当您首先设置关系时,它没有帮助,例如,如果broadcasts
关系中没有现有对象,或者您要添加或删除Broadcast
对象,预取broadcasts
keypath对你没有任何帮助。我不确定我是否足够了解您的数据模型,但我认为您的处理方式是错误的。在我看来,您试图将
identifier
用作 SQL 数据库中的主键,但这样做会适得其反。在核心数据中,关系将对象链接在一起,而不是共享属性和值。通常,如果您有两个或多个对象具有相同的属性名称和相同的值,那么在大多数情况下您的数据模型设计得很差。
Not exactly sure what you are trying to do here but...
The first thing is that you can't alter properties using just faults. Faults are just placeholders to allow you to measure/count the object graph and walk relationships. If you actually alter a relationship it will fire the fault causing the related objects to load.
If you are trying to set relationships between specific
Channel
,Program
andBroadcast
objects using just faults, that won't work.Your
itemsForEntity:
method I don't understand. It will fetch every existing managed object of the entity passed and then it will return those objects in a dictionary. That will cause a massive memory overhead especially in the case of theProgram
objects of which there are 8,000.You can't use
propertiesToFetch
unless you set the fetch return to dictionary, which you don't. You can't use a dictionary return type anyway if you need to set relationships. You use both these when all you want is the data held in certain attributes. It's not a tool for manipulating the object graph's relationships.Setting the
relationshipKeyPathsForPrefetching
only speeds things up if you know you will be accessing an existing relationship. It doesn't help when you are setting the relationships up in the first place e.g. if there is no existing objects in thebroadcasts
relationships or you are adding or removingBroadcast
objects, prefetching thebroadcasts
keypath does nothing for you.I'm not sure I understand your data model well enough but I think you are going about this the wrong way. It looks to me like your trying to use the
identifier
like a primary key in a SQL database and that is counterproductive. In Core Data, a relationship links to objects together, not a shared attribute and value.As a rule, if you have two or more objects with the same attribute name with the same value, then you have a poorly designed data model in most cases.