使用 SQLite 后端修复谓词 NSFetchedResultsController/NSFetchRequest 性能?
我有一系列 NSFetchedResultsControllers 为一些表视图提供支持,但它们在设备上的性能非常糟糕,大约需要几秒。由于它全部在主线程上运行,因此它在启动时阻止我的应用程序,这不太好。
我调查后发现谓词是问题所在:
NSPredicate *somePredicate = [NSPredicate predicateWithFormat:@"ANY somethings == %@", something];
[fetchRequest setPredicate:somePredicate];
即获取实体(称为“事物”)与实体“事物”具有多对多关系。该谓词是一个过滤器,它将结果限制为仅与特定“某物”有关系的事物。
当我删除谓词进行测试时,获取时间(初始的 PerformFetch: 调用)从 4 秒下降到(对于某些极端情况)大约 100 毫秒或更短,这是可以接受的。不过,我对此感到困扰,因为它否定了我希望通过 Core Data 和 NSFRC 获得的很多好处,否则这似乎是一个强大的工具。
所以,我的问题是,如何优化这种性能?我使用谓词是否错误?我应该以某种方式修改模型/架构吗?还有什么其他方法可以解决这个问题?这种性能下降是可以预料的吗? (大约有数百个 <1KB 对象。)
编辑详细信息:
这是代码:
[fetchRequest setFetchLimit:200];
NSLog(@"before fetch");
BOOL success = [frc performFetch:&error];
if (!success) {
NSLog(@"Fetch request error: %@", error);
}
NSLog(@"after fetch");
更新的日志(之前,我遇到了一些应用程序效率低下的情况,从而降低了这里的性能。这些是更新的日志在我当前的环境下,这应该是最接近最佳的):
2010-02-05 12:45:22.138 Special Ppl[429:207] before fetch
2010-02-05 12:45:22.144 Special Ppl[429:207] CoreData: sql: SELECT DISTINCT 0, t0.Z_PK, t0.Z_OPT, <model fields> FROM ZTHING t0 LEFT OUTER JOIN Z_1THINGS t1 ON t0.Z_PK = t1.Z_2THINGS WHERE t1.Z_1SOMETHINGS = ? ORDER BY t0.ZID DESC LIMIT 200
2010-02-05 12:45:22.663 Special Ppl[429:207] CoreData: annotation: sql connection fetch time: 0.5094s
2010-02-05 12:45:22.668 Special Ppl[429:207] CoreData: annotation: total fetch execution time: 0.5240s for 198 rows.
2010-02-05 12:45:22.706 Special Ppl[429:207] after fetch
如果我在没有谓词的情况下执行相同的获取(通过注释掉问题开头的两行):
2010-02-05 12:44:10.398 Special Ppl[414:207] before fetch
2010-02-05 12:44:10.405 Special Ppl[414:207] CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, <model fields> FROM ZTHING t0 ORDER BY t0.ZID DESC LIMIT 200
2010-02-05 12:44:10.426 Special Ppl[414:207] CoreData: annotation: sql connection fetch time: 0.0125s
2010-02-05 12:44:10.431 Special Ppl[414:207] CoreData: annotation: total fetch execution time: 0.0262s for 200 rows.
2010-02-05 12:44:10.457 Special Ppl[414:207] after fetch
时间上有 20 倍的差异。 500ms 并不是那么好,而且似乎没有办法在后台线程中做到这一点,或者我能想到的其他优化方法。 (除了进入二进制存储,这不再是问题,所以我可能会这样做。对于上述 200 个对象谓词查询,二进制存储性能始终约为 100 毫秒。)
(我之前在这里嵌套了另一个问题,现在我将其搬走了 )。
I have a series of NSFetchedResultsControllers powering some table views, and their performance on device was abysmal, on the order of seconds. Since it all runs on main thread, it's blocking my app at startup, which is not great.
I investigated and turns out the predicate is the problem:
NSPredicate *somePredicate = [NSPredicate predicateWithFormat:@"ANY somethings == %@", something];
[fetchRequest setPredicate:somePredicate];
I.e the fetch entity, call it "things", has a many-to-many relation with entity "something". This predicate is a filter that limits the results to only things that have a relation with a particular "something".
When I removed the predicate for testing, fetch time (the initial performFetch: call) dropped (for some extreme cases) from 4 seconds to around 100ms or less, which is acceptable. I am troubled by this, though, as it negates a lot of the benefit I was hoping to gain with Core Data and NSFRC, which otherwise seems like a powerful tool.
So, my question is, how can I optimize this performance? Am I using the predicate wrong? Should I modify the model/schema somehow? And what other ways there are to fix this? Is this kind of degraded performance to be expected? (There are on the order of hundreds of <1KB objects.)
EDIT WITH DETAILS:
Here's the code:
[fetchRequest setFetchLimit:200];
NSLog(@"before fetch");
BOOL success = [frc performFetch:&error];
if (!success) {
NSLog(@"Fetch request error: %@", error);
}
NSLog(@"after fetch");
Updated logs (previously, I had some application inefficiencies degrading the performance here. These are the updated logs that should be as close to optimal as you can get under my current environment):
2010-02-05 12:45:22.138 Special Ppl[429:207] before fetch
2010-02-05 12:45:22.144 Special Ppl[429:207] CoreData: sql: SELECT DISTINCT 0, t0.Z_PK, t0.Z_OPT, <model fields> FROM ZTHING t0 LEFT OUTER JOIN Z_1THINGS t1 ON t0.Z_PK = t1.Z_2THINGS WHERE t1.Z_1SOMETHINGS = ? ORDER BY t0.ZID DESC LIMIT 200
2010-02-05 12:45:22.663 Special Ppl[429:207] CoreData: annotation: sql connection fetch time: 0.5094s
2010-02-05 12:45:22.668 Special Ppl[429:207] CoreData: annotation: total fetch execution time: 0.5240s for 198 rows.
2010-02-05 12:45:22.706 Special Ppl[429:207] after fetch
If I do the same fetch without predicate (by commenting out the two lines in the beginning of the question):
2010-02-05 12:44:10.398 Special Ppl[414:207] before fetch
2010-02-05 12:44:10.405 Special Ppl[414:207] CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, <model fields> FROM ZTHING t0 ORDER BY t0.ZID DESC LIMIT 200
2010-02-05 12:44:10.426 Special Ppl[414:207] CoreData: annotation: sql connection fetch time: 0.0125s
2010-02-05 12:44:10.431 Special Ppl[414:207] CoreData: annotation: total fetch execution time: 0.0262s for 200 rows.
2010-02-05 12:44:10.457 Special Ppl[414:207] after fetch
20-fold difference in times. 500ms is not that great, and there does not seem to be a way to do it in background thread or otherwise optimize that I can think of. (Apart from going to a binary store where this becomes a non-issue, so I might do that. Binary store performance is consistently ~100ms for the above 200-object predicated query.)
(I nested another question here previously, which I now moved away).
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
对于我们许多试图在 iPhone 上使用 Core Data 来处理更大的数据库或更复杂的模式的人来说,这是一个持续存在的问题。我的个人资料中有一些与此相关的不同问题的链接——在全文搜索的背景下。
并非所有应用程序仅通过将谓词应用于一对多关系就变得很慢,因此还有更多内容。请参阅 苹果的绩效文件。设置 -com.apple.CoreData.SQLDebug 1.
检查是否有索引(如果适用)。确保没有调用任何阻止 sqlite 有效连接或使用索引的 sql 函数(例如格式/类型转换)。
如果您的实体中有很多行并且有很多属性,则可能会出现内存问题。考虑将一个实体拆分为一个较小的实体,其中包含一些用于搜索的属性,并在需要时提取其余的属性。
如果您的谓词比一对多关系部分更复杂,那么您可以尝试分两步进行搜索。例如,SQL 可能会低效地连接然后过滤,而不是过滤然后连接。但这样做会破坏 NSFetchedResultsController。尽管如此,它还是可以让我们对这个问题有所了解。
我不会做类似将 ObjectID 打包到属性中来创建您自己的多外键引用之类的事情。听起来像是一个可怕的黑客行为,肯定会阻止您认真使用 Core Data……但 Core Data 的优点有很多。
尝试删除或添加反向关系。我发现在一种情况下,如果没有反向关系,我的查询运行得更快。缺点是删除反向关系会使我的数据库变得臃肿。
让我们知道您发现了什么。
This is an on-going problem for many of us trying to use Core Data on the iPhone for larger databases or more complex schemas. My profile has links to a couple different SO questions related to this -- in the context of full text searching.
Not all apps are dog slow just by applying a predicate to a to-many relationship, so there's more to it. See Apple's performance document. Set -com.apple.CoreData.SQLDebug 1.
Check that there's an index, if appropriate. Make sure there are no sql functions (such as format/type conversions) being called that keep sqlite from efficiently joining or using an index.
If you have a lot of rows and there are many attributes in your entity there may be memory issues. Consider splitting a single entity into a smaller one with a few attributes used for your search and pull in the rest when needed.
If your predicate is more complex than the to-many relationship part, then you might experiment with searching in two steps. E.g. the SQL might be inefficiently joining and then filtering instead of filtering and then joining. But doing this would break NSFetchedResultsController. Still, it might shed some light on the trouble.
I would not do something like pack ObjectIDs into an attribute to make your own to-many foreign key reference. Sounds like a horrible hack surely to dissuade you from using Core Data seriously... yet the advantages of Core Data are numerous.
Experiment with removing or adding the reverse relationship. I found in one case that my query ran much faster without the reverse relationship. The downside was that removing the reverse relationship bloated my database.
Let us know what you find.
对我来说,实际的解决方案是切换到性能更好的 Core Data 二进制存储。我没有调查是否可以根据我当前的应用程序情况提高 Core Data SQLite 性能。
The practical solution to this for me was to just switch to Core Data binary store that has much better performance. I did not investigate whether I can improve Core Data SQLite performance with my current app situation.
二进制故事会将整个数据集加载到内存中,这将导致某些设备上的内存问题。相反,您应该重写您的谓词。
由于您的关系是双面的(对?),您可以从另一端获取它,并且在这种情况下您可能根本不需要
NSFetchedResultsController
。假设您有:MyWidgetEntity <--->> SomethingEntity
因为您已经拥有对 SomethingEntity 实例的引用,所以您只需通过以下方式询问它的小部件:
在您有的情况下:
MyWidgetEntity <<-->>> SomethingEntity
您可以通过以下方式访问所有关联的小部件:
不需要 NSFetchedResultsController ,因为您可以将此集合转换为数组并对其进行排序。
A binary story will load the entire data set into memory which will cause a memory issue on some devices. Instead you should re-write your predicate.
Since your relationships are double-sided (right?) you can come at it from the other side and you probably don't need a
NSFetchedResultsController
at all for this situation. Presume you have:MyWidgetEntity <--->> SomethingEntity
Since you already have a reference to an instance of SomethingEntity you just ask it for it's widget via:
In the situation where you have:
MyWidgetEntity <<-->> SomethingEntity
You can get access to all of the associated widgets via:
No
NSFetchedResultsController
is needed because you can turn this set into an array and sort it.