如何提高 iPhone 上的核心数据获取性能?

Posted

技术标签:

【中文标题】如何提高 iPhone 上的核心数据获取性能?【英文标题】:How can I improve core data fetch performance on the iPhone? 【发布时间】:2010-08-27 21:42:34 【问题描述】:

Core data 在 iPhone 上的表现绝对惨不忍睹。索引是完全损坏还是只是一个糟糕的实现?

我的核心数据存储(SQLite 后备存储)中有大约 21500 个单一类型的对象。对象在 UUID 上进行索引,该 UUID 是一个 NSString(例如,一个看起来像这样:“6b09e200-07b6-11df-a245-002500a30d78”)。

在 NSManagedObjectContext 中使用 executeFetchRequest 单次获取对象需要大约 0.75 秒!这是最简单的谓词“uuid == $UUID”,其中 $UUID 是一个字符串,如上例所示。

这真是令人惊讶。如果我想将商店中的每件物品一件一件地取出来,需要将近 4.5 个小时!

有什么办法可以提高这种性能,还是我应该完全放弃核心数据?

【问题讨论】:

为了比较,我尝试获取 21,500 行的整个表,并使用结果创建一个以 UUID 作为键的 NSDictionary。然后,我在每个 UUID 上逐个遍历整个字典并返回每个对象。整个过程只需大约 5 秒即可完成。 【参考方案1】:

几点。如果获取 21,500 行需要 5 秒,这听起来像是在旧设备上运行。就像 3G 或原装 iPhone。它们的内存和 I/O 性能简直太慢了。您需要非常小心地处理您的数据,以避免将其全部读入内存并执行不必要的 I/O。您可能会发现 -setFetchBatchSize 特别有用。如果您在 3GS 上运行,10-20,000 行是可以管理的,但需要小心。如果您使用的是 ipad 或 iphone4,这应该不是什么大问题。

您不需要创建自己的 UUID,除非是与外部系统(如服务器)交互。每个托管对象都有一个 objectID,它是其主键的 OOP 表示。只需传递 objectID,然后执行 @"self = %@" 或 @"self IN %@" 之类的查询,即可通过其 ID 或 ID 数组搜索对象。您还可以使用 -existingObjectWithID:error: 通过其 objectID 仅查找 1 个对象,这将比具有通用谓词的通用获取请求更快。

验证索引是否按预期使用的最佳方法是在模拟器中使用可执行参数运行应用程序

-com.apple.CoreData.SQLDebug 1

这将记录到控制台正在生成的 SQL。您应该会看到一些以 t0.uuid == 之类结尾的内容?

您可以使用该 SQL 选择语句,并通过 SQLite 的解释查询工具运行它。对模拟器中的 db 文件运行 /usr/bin/sqlite3。做

.explain ON 在此处解释查询计划复制该sqlline

它应该打印出类似的东西 0|0|TABLE ZFOO AS t0 WITH INDEX something

如果它缺少“with index”,那么您创建 Core Data 存储的方式存在问题(您确定模型被标记为索引 uuid 吗?)或者您的 fetch 请求存在其他问题。

这真是令人惊讶。如果我想将商店中的每件物品一件一件地取出来,> 需要将近 4.5 小时!

我想你可以这样做,因为这是最痛苦的方式之一。或者您可以使用 -setFetchBatchSize: 并快速迭代成批的对象。

另外,请记住,每次提取都会对数据库进行 I/O,以与任何其他线程保存的内容保持同步。获取不是一些神奇的字典查找。执行最小 I/O 单元所需的时间有一个下限。您将希望摊销单个 I/O 请求的数量以获得最佳性能。您必须在这与一次读取过多内存之间取得平衡。

如果您仍然遇到问题,请向 bugreport.apple.com 提交错误

【讨论】:

它不是旧款 iPhone,而是 3GS。我也在新的 iPhone 4 上尝试过,但性能并没有那么好。正是出于这个原因,我创建了自己的 UUID——我需要与使用自己的 UUID 来引用数据的外部系统进行交互,并且我需要根据这些 UUID 进行获取。 我会尝试您的建议来检查查询。我认为索引没有被使用或没有被正确使用。获取时间似乎与表的大小大致呈线性关系。如果表确实被索引,它应该进行二分搜索(或基于树的搜索),并且它应该与表大小的日志成正比。如果是我的表没有被索引的情况,那么很可能是一个错误,我会提交给苹果。 4.5 小时的备注是它有多慢的一个例子。由于我是按个别id取,所以没办法批量取,所以setFetchBatchSize没用。例外是现在正在做的事情,即获取整个表并构建我自己的字典,其中包含每个对象的 UUID。获取应该是一些神奇的字典查找。至少在现代数据库中。对于中型表格,下限不应在第二个范围内。即使数据库没有将表索引缓存在内存中,性能也不应该随表的大小线性变化。 索引似乎没问题。问题可能是我的所有对象都继承自一个抽象对象。 Apple 似乎通过将所有数据粘贴在一个表中来实现实体继承,因此我的所有数据都在一个包含近 100 列的巨大表中! 如果您需要将传入的 JSON 行与商店中的所有整体进行比较,您可以按 ID 对传入的行进行排序,然后使用也对它们进行排序的谓词从您的商店中获取一批实体按身份证。当您遍历传入的行时,它们将匹配批量提取中实体的顺序。为下一个获取的批次使用偏移量。如果您只需要匹配特定数量的记录,则可以使用 IN 谓词执行单次提取,该谓词仅从您收到的行中选择 ID。【参考方案2】:

这不会回答您的问题,但可能会让您有所思考。在 iPhone 上只使用 SQLite,我对性能感到非常失望。我正在处理大约 8000 个条目,如果返回全部等等,插入/排序大约需要两分钟。

玩弄它,我发现在内存中过滤/排序所需的时间比让它在 SQLite 中完成要好 100 倍,我认为这主要是由于闪存的性能。

简而言之,Core Data 使用闪存的次数越少,您将获得更好的性能,我认为没有很多方法可以让它变得更好。

【讨论】:

我也遇到过这些问题,我可以通过在内存中做尽可能多的工作然后一次完成所有插入来解决其中的大部分问题。此外,我尽可能晚地推迟保存核心数据,因此最后一次完成所有操作似乎比更频繁地保存更有效率。我无法解决的一件事是获取性能。【参考方案3】:

我认为问题是比较字符串比比较大多数数据库(如果不是全部)的数字要慢得多。

您可以尝试为您的 NSManagedObject 添加一个新属性(列)aNumber,它是一个数字,值是从它的 UUID 生成的。

然后,构建您的查询,例如“aNumber == XXX AND uuid == UUID”

这可以使数据库先比较一个数字,如果数字相同,它只需要比较一个字符串。

或者,您可以尝试索引 UUID。

【讨论】:

【参考方案4】:

使用核心数据的诀窍在于,只有实际需要的数据才会从存储中获取并保存在内存中。我无法想象我将如何在 iPhone 这样的设备上编辑/重新排序/任何 21500 行。 有几种方法可以提高 CoreData 的性能: - setFetchBatchSize - 使用原始方法 - 只加载需要的属性

我记得一个比较 SQLite 和 CoreData 性能的 WWDC 视频和 CD 显然是赢家。

【讨论】:

Core Data 只能使用二进制文件、sqlite 或内存,因为它是 iPhone 上的后备存储。显然,使用内存的核心数据会最快,但您的数据实际上并未保存到磁盘,因此在大多数情况下它没有用处。 SQLite 作为后备存储比平面二进制文件快,因此核心数据永远不会像单独的 SQLite 一样快,因为它总是必须增加一些开销。事实上,它看起来可能要慢得多。 我不确定 setFetchBatchSize 对我有什么帮助?我通常一次只获取单个数据项。我实际上并没有修改那 20,000 行表中的任何一个。它纯粹是我的应用程序所必需的预构建参考表。 (想想一本有 20,000 个单词的英语词典,您永远不会对其进行编辑,但您希望能够在其中快速查找单词)。

以上是关于如何提高 iPhone 上的核心数据获取性能?的主要内容,如果未能解决你的问题,请参考以下文章

iPhone核心数据:如何在NIBS之间传递和获取实体

选择性地获取核心数据以获得更好的性能(稍后获取大项目)

如何提高 memcpy 的性能

从 iPhone 上的 VBO 读取数据

【性能】如何优化 NAT 性能?

如何提高 PostgreSQL 在 INSERT 上的性能?