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

5
核心数据在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秒钟即可完成。 - Mike
4个回答

10

几点建议。如果获取21,500行需要5秒钟,那么你可能在使用旧设备,比如3G或原始iPhone。这些设备的内存和I/O性能很慢。你需要非常小心地处理数据,避免将所有数据读入内存并进行不必要的I/O操作。你可能会发现-setFetchBatchSize特别有用。如果你在3GS上运行,10-20千行是可管理的,但需要小心。如果你在iPad或iPhone4上运行,这应该不是什么问题。

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

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

-com.apple.CoreData.SQLDebug 1

运行应用程序,它会记录生成的SQL到控制台。你应该会看到一些以t0.uuid == ?结尾的东西。

你可以将该SQL select语句输入SQLite的explain查询工具中运行。在模拟器中对db文件运行/usr/bin/sqlite3,然后执行

.explain ON explain query plan copythatsqllinehere

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

这真是令人惊讶。如果我想逐个获取存储中的每个对象,那将需要近4.5小时!

我想你可以用最痛苦的方式之一做到这一点。或者你可以使用-setFetchBatchSize:,快速地迭代处理对象的批次。

此外,请记住,每次获取都会与数据库进行I/O以保持与任何其他线程保存的内容同步。获取不是某种神奇的字典查找。执行最小单元的I/O所需的时间有一个下限。你需要将每个单独的I/O请求分摊来获得最佳性能。你将需要权衡读取过多内存而造成的问题。

如果您继续遇到问题,请在bugreport.apple.com上报告错误。

  • Ben

这不是旧款iPhone,而是3GS。我也尝试在新的iPhone 4上使用它,但性能并没有好多少。我创建自己的UUID正是出于这个原因 - 我需要与一个使用自己的UUID引用数据的外部系统进行接口,并且我需要根据这些UUID获取数据。 - Mike
我将尝试您的建议来检查这些查询。我认为索引没有被使用或者没有被正确地使用。提取时间似乎与表的大小大致呈线性关系。如果表确实被索引了,它应该执行二分搜索(或基于树的搜索),并且应该与表的大小的对数成比例。如果我的表未被索引,那么很可能是一个错误,我会向苹果报告此问题。 - Mike
1
4.5小时的评论只是速度慢的一个例子。由于我是按独立ID获取,所以没有批量获取的方法,因此setFetchBatchSize是无用的。现在我正在做的是获取整个表并构建自己的字典,其中包含每个对象的UUID。获取应该是一种神奇的字典查找。至少在现代数据库中是这样的。对于中等大小的表,下限不应该在第二个范围内。即使数据库没有将表索引缓存在内存中,性能也不应随着表的大小呈线性变化。 - Mike
看起来索引没问题。问题可能是我的所有对象都继承自一个抽象对象。苹果似乎通过将所有数据放入单个表中来实现实体继承,因此我的所有数据都在一个巨大的表中,有近100列! - Mike
如果您需要将传入的JSON行与存储中的所有条目进行比较,可以按ID对传入的行进行排序,然后使用谓词从存储中获取一批实体,该谓词也按ID对它们进行排序。当您循环遍历传入的行时,它们将与批处理获取的实体的顺序匹配。使用偏移量获取下一个批次。如果您只需要匹配特定数量的记录,则可以执行单个获取操作,使用IN谓词仅选择您收到的行中的ID。 - Scott Ahten

2
这不会回答你的问题,但可能会让你思考一些问题。在iPhone上仅使用SQLite时,我对性能感到非常失望。我处理约8000个条目,如果返回所有内容,需要大约两分钟来插入/排序等操作。

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

简而言之,Core Data使用闪存的越少,性能就越好,我认为很难有更好的方法来提高它。


1
我也遇到过这些问题,我已经尽可能多地在内存中工作,然后一次性执行所有的插入操作来解决大部分问题。此外,我会尽可能晚地保存核心数据,所以最后一次保存似乎比更频繁的保存效率更高。唯一无法解决的是提取性能问题。 - Mike

2
我认为问题在于对于大多数数据库来说,比较字符串的速度比比较数字要慢得多。
您可以尝试为NSManagedObject添加一个新属性(列),aNumber,它是一个数字,其值是从其UUID生成的。
然后,像这样构建您的查询:“aNumber == XXX AND uuid == UUID”。
这可以使数据库首先比较数字,只有在数字相同时才需要比较字符串。
或者,您可以尝试索引UUID。

1
使用Core Data的技巧在于仅从存储中检索并保存实际需要的数据。我无法想象在像iPhone这样的设备上如何编辑/重新排序/处理21500行数据。 有几种方法可以提高CoreData性能: - 设置FetchBatchSize - 使用基元方法 - 仅加载所需的属性
我记得WWDC视频比较了SQLite和CoreData性能,CD是明显的赢家。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接