【问题标题】:Simple Core Data fetch is very slow简单的核心数据获取非常慢
【发布时间】:2011-11-08 04:57:20
【问题描述】:

我的 iPhone 应用程序有一个实体 Words,其属性为 wordlengthlanguage。两者都被索引:

我将 cdatamodel 和数据库复制到一个单独的导入器应用程序中,在该应用程序中预先填充了大约 40 万个不同语言的单词。我通过查看 SQLite 文件验证了导入,然后将预填充的数据库复制回 iPhone 项目。

首先我认为(简单的)谓词是问题所在。但是即使从 fetch 请求中删除了谓词,执行也需要很长时间:

2011-09-01 09:26:38.945 MyApp[3474:3c07] Start
2011-09-01 09:26:58.120 MyApp[3474:3c07] End

我的代码如下所示:

// Get word
NSLog(@"Start");
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Words" inManagedObjectContext:appDelegate.managedObjectContext];
[fetchRequest setEntity:entity];
            
NSError *error = nil;
NSArray *fetchedObjects = [appDelegate.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
    //... error handling code
}
    
[fetchRequest release];
NSLog(@"End");
return fetchedObjects;

数据库中的条目数量对 Core Data 来说是个问题吗?


编辑: 正如 gcbrueckmann 和 jrturton 指出的那样,设置fetchBatchSize 是一个好点。但是获取时间仍然不尽人意:

  • 带有谓词集的 2 秒:

    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"length == %d AND language BEGINSWITH %@", wordLength, lng]; [fetchRequest setPredicate:predicate];

  • 批量大小设置为 7 秒:

    [fetchRequest setFetchBatchSize:1];

  • 1 秒同时设置谓词和批量大小

还有另一个瓶颈吗?

【问题讨论】:

  • 在您的谓词中是语言可能比长度更具限制性,有时谓词检查的顺序也可以加快速度。例如,在这种情况下,如果 60% 的单词符合您的长度标准,但只有 40% 符合语言标准,最好先检查语言。另一件事可能是,如果您需要更快的速度,那就是预先加载它,然后在内存中过滤一个数组,但不确定您的 iphone 应用程序是否可以处理。
  • 在这种情况下,第一个查询是比较整数(索引会使这非常快),第二个是字符串比较(即使是索引字符串也不会很快) - 我会如果重新排序查询有帮助,您会感到惊讶。但是,请尝试一下 - 我很想看看它是否有帮助!
  • 哦,我忘了说:我已经尝试过交换谓词的顺序,它不会加快获取速度。

标签: iphone performance core-data


【解决方案1】:

由于您没有以任何方式限制结果集,因此一次获取 400,000 个对象肯定会成为 Core Data 的负担。有几种方法可以提高性能:

更改 fetch 请求的 fetchBatchSize 会限制 fetch 一次保留在内存中的对象数量。此功能对您的应用程序完全透明,因此绝对值得一试。

如果您不需要完全成熟的对象,您可以考虑将获取请求的resultType 更改为更合适的值。尤其是如果您只对对象的某些值感兴趣,使用NSDictionaryResultType 是个好主意。

最后,fetchLimitfetchOffset 属性允许您限制结果范围,如果您想自己管理批处理。如果您对每个结果对象的处理使用大量内存,这是一个好主意,因为您可以将每个批次包装在 NSAutoreleasePool 中(只是不要试图为每个结果对象创建一个自动释放池)。

我猜是 1 秒。可能与您的情况一样快——即使您使用普通的 Sqlite 数据库。我能想到的唯一进一步优化是每种语言使用一个表(而不是将所有语言的单词放入一个表中)。当然,这仅适用于 Sqlite,除非您为所有语言定义单独的实体,即。 e.将您的Words 实体保持原样并使其抽象化。然后添加子实体,如EnglishWord 等。来自不同实体的对象存储在单独的表中。因此,结合 fetchBatchSizepredicate 参数,这应该类似于 Sqlite 方法,所有语言都有单独的表。

【讨论】:

  • fetchBatchSize 绝对是个好点。但不幸的是,抓住一个单词仍然需要 2 秒。
  • 在您的情况下,是否可以选择使用基本的 Sqlite 数据库?看起来现有的对象没有被修改,所以 Core Data 可能不会比普通的 Sqlite 有(m)任何优势。 400,000 确实是 iPhone 上的一个大型数据集。可以选择每种语言有一个表吗?
  • 是的,我已经考虑过切换回普通 SQLite,但我认为仍然可能存在我看不到的瓶颈。
  • 来自Apple 的警告:“使用 SQLite 持久存储时要小心实体继承。从另一个实体继承的所有实体都将存在于 SQLite 的同一个表中。这个因素在SQLite 持久存储的设计可能会产生性能问题。”
【解决方案2】:

这会将整个 400k 数据库提取到内存中,这看起来确实很多。你可以调查 NSFetchRequest 的

setFetchBatchSize

阻止框架为您的获取请求中的所有内容返回完整对象的方法,假设您不需要在第一个实例中从存储中获取每个返回的对象。

【讨论】:

    【解决方案3】:

    您正在 BEGINSWITH - 这不是一个非常快的操作!但是,语言数量有限,因此 emum 可能会有所帮助。

    有一个 language_id 字段,它是一个索引整数,并在您的谓词中使用它。您仍然可以存储语言名称并将其作为获取对象的一部分返回,只是不要搜索它:)


    PS 您可以通过添加“-com.apple.CoreData.SQLDebug 1”作为启动时传递的参数来打开 SQL 调试(在您的方案中进行配置)——这可能有助于您了解 SQL 在幕后所做的事情。

    (详情请参阅this question

    【讨论】:

    • language BEGINSWITH %@ 耗时 600 毫秒(平均); language == %@ 耗时 350 毫秒(平均)!
    • 我相信沿着这些思路,我也看到它说做语言比较 >= %@ 比使用 BEGINSWITH 更快。我想说这是在 2010 年 WWDC 核心数据视频中。
    • 如果这仍然是字符串相等,那么如果您转换为比较整数,您将获得更快的速度;) SQL 中的字符串索引仅考虑字符串中的一定数量的字符,而索引整数是理想的! - dev.mysql.com/doc/refman/5.0/en/create-index.html
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多