如何在Core Data中高效插入和获取UUID

时间:2012-07-05 02:52:50

标签: ios sqlite core-data uuid

我正在寻找一种在Core Data中存储和搜索UUID的有效方法。这些UUID由分布式系统中的许多iOS设备生成。这些设备中的每一个可以存储大约20-50k UUID。

很明显,将UUID作为String存储在Core Data中会损害索引的效率。但经过一系列的研究后,我发现将UUID作为二进制数据存储在核心数据中(并对其进行索引)可能效率低于将其存储为String

由于SQLit中不支持类似BINARY或VARBINARY的数据类型。我想Core Data中的任何二进制数据类型的数据都存储在SQLit中的BLOB中。由于BLOB可能是要索引的最慢数据类型,因此会对性能产生不良影响。

所以任何人都可以帮忙回答,有没有更有效的方法将UUID存储在Core Data中?

2 个答案:

答案 0 :(得分:39)

将它们存储为ASCII字符串,并将该字段设为索引。

修改

Egads,我碰巧正在做些什么,并且碰到了这个。多么可耻的回答。那天我一定有点心情。如果可以,我会删除它并继续前进。但是,这是不可能的,所以我将提供一个更新的片段。

首先,了解什么是“高效”的唯一方法是测量,考虑程序时间和空间以及源代码复杂性和程序员的工作量。

幸运的是,这个很容易。

我写了一个非常简单的OSX应用程序。该模型由单个属性组成:identifier

如果您不将属性标记为索引,则无关紧要。创建商店需要花费更多的时间,但它会使查询更快。

另请注意,为二进制属性创建谓词与为字符串创建谓词完全相同:

fetchRequest.predicate =
    [NSPredicate predicateWithFormat:@"identifier == %@", identifier];

应用程序非常简单。首先,它创建N个对象,并为标识符属性分配UUID。它每500个对象保存一次MOC。然后,我们将所有标识符存储到一个数组中并随机地将它们混洗。然后将整个CD堆栈完全拆除,将其全部从内存中删除。

接下来,我们再次构建堆栈,然后迭代标识符,并进行简单的提取。构造fetch对象,使用简单的谓词来获取该对象。所有这些都是在autoreleasepool中完成的,以使每次获取尽可能保持原始状态(我承认将与CD缓存进行一些交互)。这不是那么重要,因为我们只是在比较不同的技术。

二进制标识符是UUID的16字节。

UUID String是一个36字节的字符串,是调用[uuid UUIDString]的结果,它看起来像这样(B85E91F3-4A0A-4ABB-A049-83B2A8E6085E)。

Base64 String是一个24字节的字符串,base-64编码16字节UUID二进制数据的结果,对于相同的UUID,它看起来像这样(uF6R80oKSrugSYOyqOYIXg ==)。

Count是该次运行的对象数。

SQLite大小是实际sqlite文件的大小。

WAL大小是WAL(预写日志记录)文件的大小 - 只是FYI ......

Create是创建数据库的秒数,包括保存。

查询是查询每个对象的秒数。

Data Type     | Count (N) | SQLite Size | WAL Size  | Create  | Query
--------------+-----------+-------------+-----------+---------+---------
Binary        |   100,000 |   5,758,976 | 5,055,272 |  2.6013 |  9.2669
Binary        | 1,000,000 |  58,003,456 | 4,783,352 | 59.0179 | 96.1862
UUID String   |   100,000 |  10,481,664 | 4,148,872 |  3.6233 |  9.9160
UUID String   | 1,000,000 | 104,947,712 | 5,792,752 | 68.5746 | 93.7264
Base64 String |   100,000 |   7,741,440 | 5,603,232 |  3.0207 |  9.2446
Base64 String | 1,000,000 |  77,848,576 | 4,931,672 | 63.4510 | 94.5147

这里要注意的第一件事是实际的数据库大小远远大于存储的字节数(1,600,000和16,000,000) - 这是数据库所期望的。额外存储量将有点相对于实际对象的大小...这个只存储标识符,因此开销的百分比会更高。)

第二,关于速度问题,作为参考,做同样的1,000,000对象查询,但是使用fetch中的object-id花了大约82秒(注意它与调用existingObjectWithID:error:之间的明显区别高达0.3065秒)。

您应该剖析自己的数据库,包括在运行代码上明智地使用工具。我想如果我进行多次运行,数字会有所不同,但它们非常接近,因此不需要进行此分析。

但是,根据这些数字,我们来看看代码执行的效率测量。

  • 正如所料,存储原始UUID二进制数据在空间方面更有效。
  • 创建时间非常接近(差异似乎取决于创建字符串的时间和所需的额外存储空间)。
  • 查询时间看起来几乎相同,二进制字符串看起来有点慢。我认为这是最初的问题 - 对二进制属性进行查询。

Binary赢得了很多空间,它可以被认为是创建时间和查询时间的紧密关系。如果我们只考虑那些,存储二进制数据是明显的赢家。

源代码复杂性和程序员时间如何?

好吧,如果您使用的是iOS和OSX的现代版本,那么几乎没有任何区别,特别是对于NSUUID上的简单类别。

但是,您需要考虑一个因素,并且在数据库中使用数据非常容易。存储二进制数据时,很难在数据上获得良好的视觉效果。

因此,如果由于某种原因,您希望数据库中的数据以更有效的方式存储给人类,那么将其存储为字符串是更好的选择。因此,您可能需要考虑base64编码(或其他一些编码 - 但请记住它已经采用base-256编码)。

FWIW,这是一个示例类别,可以更方便地访问UUID作为NSData和base64字符串:

- (NSData*)data
{
    uuid_t rawuuid;
    [self getUUIDBytes:rawuuid];
    return [NSData dataWithBytes:rawuuid length:sizeof(rawuuid)];
}

- (NSString*)base64String
{
    uuid_t rawuuid;
    [self getUUIDBytes:rawuuid];
    NSData *data = [NSData dataWithBytesNoCopy:rawuuid length:sizeof(rawuuid) freeWhenDone:NO];
    return [data base64EncodedStringWithOptions:0];
}

- (instancetype)initWithBase64String:(NSString*)string
{
    NSData *data = [[NSData alloc] initWithBase64EncodedString:string options:0];
    if (data.length == sizeof(uuid_t)) {
        return [self initWithUUIDBytes:data.bytes];
    }
    return self = nil;
}

- (instancetype)initWithString:(NSString *)string
{
    if ((self = [self initWithUUIDString:string]) == nil) {
        self = [self initWithBase64String:string];
    }
    return self;
}

答案 1 :(得分:0)

由于该帖子似乎相当受欢迎,因此值得注意的是,自2012年以来情况有所变化。

您现在可以使用NSUUIDA / UUID属性类型(UUIDAttributeType),而不必手动将其映射到字符串或二进制数据(iOS 11中已添加)。 UUID将自动以二进制形式存储,根据另一个答案,这是在CoreData中存储UUID最快,最优化的方法。

WWDC17: What's New in Core Data

[20:21]我们添加了分别由UUID和URL值类支持的NSUUIDA属性类型和NSURL属性类型。