【问题标题】:Is there a way to 'prime the pump' in FMDB so it's ready to operate quicker有没有办法在 FMDB 中“启动泵”,以便更快地运行
【发布时间】:2016-07-12 05:42:46
【问题描述】:

我们有一个 sqlite 数据库,我们的 iOS 应用程序将图像存储在 blob 列中。 我们使用 FMDB 将 blob 读取为 NSData,然后转换为 UIImage。代码如下所示。

-(UIImage*)getImageWithGuid:(NSString *)guid imageSizeKind:(ImageSizeKind)imageSizeKind
    {
        FMDatabase *db = [self openFMDatabase];
        if (!db) {
            return nil;
        }

        NSData *imageData = nil;
        NSString *query = [NSString stringWithFormat:@"SELECT Image FROM images WHERE Guid = '%@' AND MediaType = %d limit 1", guid, imageSizeKind];

        FMResultSet *rs = [db executeQuery:query];

        if ([rs next])
        {
            imageData = [rs dataForColumn:imagesTable.image];
        }

        [rs close];
        [db close];

        if (!imageData) {
            NSLog(@"Image was not found in database '%@' using sql query '%@'", [self databasePath], query);
        }

        UIImage *image = [UIImage imageWithData:imageData];

        return image;
    }

上面这个方法的调用者接收到图像然后调整它的大小。 在上面这个方法的调用者中,我有一些代码来计时获取和调整部分代码的时间,我在调试控制台中收到了以下输出...


23:31:17.084 在 4.208354 秒内获得图像
23:31:17.086 在 0.001961 秒内调整大小的图像
23:31:17.115 在 0.028943 秒内获得图像
23:31:17.117 在 0.001891 秒内调整大小的图像
23:31:17.131 在 0.013373 秒内获得图像
23:31:17.133 在 0.002036 秒内调整大小的图像
23:31:17.844 在 0.711072 秒内获得图像
23:31:17.846 调整大小的图像在 0.001634 秒内
23:31:17.880 在 0.034076 秒内获得图像
23:31:17.882 在 0.001678 秒内调整大小的图像
23:31:17.910 在 0.028255 秒内获得图像
23:31:17.912 在 0.001652 秒内调整大小的图像
23:31:17.943 在 0.031323 秒内获得图像
23:31:17.945 调整大小的图像在 0.001783 秒内
23:31:17.954 在 0.009396 秒内获得图像
23:31:17.956 调整大小的图像在 0.001982 秒内
23:31:17.986 在 0.029724 秒内获得图像
23:31:17.988 在 0.001977 秒内调整大小的图像
23:31:18.026 在 0.037283 秒内获得图像
23:31:18.027 调整大小的图像在 0.001837 秒内
23:31:18.051 在 0.023700 秒内获得图像
23:31:18.053 在 0.001947 秒内调整大小的图像
23:31:18.088 在 0.035087 秒内获得图像
23:31:18.090 在 0.001687 秒内调整大小的图像
23:31:18.136 0.045304 秒获取图像


请注意,获取第一张图像需要 4.2 秒,而随后的所有图像仅用了百分之一秒。

有什么方法可以让我“启动泵”,可以说让这 4.2 秒的时间消失,并让数据库准备好像处理随后的所有图像一样运行。理想情况下,将 4 秒延迟隐藏在某个后台线程上会很棒,这样用户就不必在应用程序的其他点体验它,只需将最初的 4 秒移动到其他地方即可。

谢谢。

【问题讨论】:

  • 我知道它与您的问题无关。然而,为什么不将“图像”保存在应用程序目录中并将该本地路径保存在数据库中。我认为这也是一个好主意,因为如果没有更多图像,那么您可能会遇到内存警告问题。
  • 我不确定为什么会做出这样的设计选择。我不是该应用程序的原始开发人员。我只是在处理其中的一部分,因为它已移交给我们的团队。
  • 检查第一个需要很长时间的图像大小。如果该图像很大,可能是原因,它将数据转换为图像需要 4 秒的时间。
  • 您每次都在调用方法从数据库中获取数据并一次返回一张图像。可能您可以获得所有数据(DATA 数组),然后根据您的需要调整图像大小。而不是每次都调整大小,当你保存图像时,在那里调整大小并将调整大小的图像保存在数据库中。
  • 所有图片尺寸相同... 640x480。我考虑过合并请求,但正如您所见,一旦完成第一个请求,其余的都很快。所以有些事情会在第一个请求时将数据库粘住。

标签: ios objective-c sqlite uiimage fmdb


【解决方案1】:

FMDB(或更准确地说,SQLite)不需要任何“启动”。我刚刚运行了以下代码:

CFAbsoluteTime last = CFAbsoluteTimeGetCurrent();

for (NSInteger i = 0; i < 10; i++) {
    FMResultSet *rs = [database executeQuery:@"select image_data from images where guid = ?", @(i)];
    NSAssert(rs, @"select failed: %@", [database lastErrorMessage]);
    if ([rs next]) {
        CFAbsoluteTime current = CFAbsoluteTimeGetCurrent();
        NSData *data = [rs dataForColumnIndex:0];
        NSLog(@"%lu %0.3f", (unsigned long)[data length], current - last);
        last = current;
    }
    [rs close];
}

在 iPhone 6+ 上,它报告:

2016-03-24 21:53:36.107 MyApp[3710:1262147] 1000000 0.010 2016-03-24 21:53:36.112 我的应用程序 [3710:1262147] 1000000 0.004 2016-03-24 21:53:36.115 我的应用程序 [3710:1262147] 1000000 0.004 2016-03-24 21:53:36.123 MyApp[3710:1262147] 1000000 0.008 2016-03-24 21:53:36.131 我的应用程序 [3710:1262147] 1000000 0.008 2016-03-24 21:53:36.138 MyApp[3710:1262147] 1000000 0.007 2016-03-24 21:53:36.146 我的应用程序 [3710:1262147] 1000000 0.007 2016-03-24 21:53:36.153 MyApp[3710:1262147] 1000000 0.007 2016-03-24 21:53:36.161 我的应用程序 [3710:1262147] 1000000 0.007 2016-03-24 21:53:36.168 MyApp[3710:1262147] 1000000 0.007

您可能从上面的日志中推断出,这是从数据库中检索 10 个 1 mb BLOB 的基准测试。

所以,有几个可能的问题:

  • 仔细检查您在哪里开始计时。确保在启动计时器和开始检索图像之间没有其他代码。也许您的代码中还有其他东西会降低应用程序的速度。

    我可能会建议运行 Instruments 的“时间分析器”并准确确认导致 4 秒延迟的原因。或者,稍微复杂一点,有时可以通过在“系统跟踪”工具的“系统调用”中对“等待时间”进行排序来识别阻塞调用。但是,最重要的是,确认 SQLite 实际上是 4 秒延迟的来源。

    如果您对 Instruments 不满意,您通常可以用老式方法识别瓶颈,插入越来越多的时序语句,并将其缩小到导致 4 秒延迟的一两行。 (例如,是executeQuery,还是step,或者是图像调整大小而不是图像检索,或者数据库的第一次打开可能是从捆绑到文档进行一些昂贵的复制等)

  • 如果问题确实出在 SQLite 中,我会确保您在 guid 上有一个索引(最好是唯一键)。这不太可能导致这个问题(特别是如果您在打开下一行之前关闭数据库),但如果性能延迟确实在 SQLite 中,那可能会有所帮助。

    另外,我注意到您在 SQL 语句中使用了limit 1。您是否有多个具有相同guid 的条目(通常是唯一的)?也许时差与您为第一个键获得的匹配数有关。

  • 还要确保没有任何其他进程试图同时在同一个 SQLite 数据库上工作。我认为情况并非如此,正如您所说,您在进行基准测试之前允许应用程序稳定下来,但我只是为了完整起见而提到这一点。

  • 根据 Wolverine 的观察,最好将图像存储在文件系统中并且只将文件名存储在数据库中,这绝对是正确的(如果我没记错的话,我上次进行基准测试时,从SQLite 而不是从 SQLite 获取文件名,然后直接从文件系统中检索图像)。过去的经验法则是,如果您的图像是缩略图大小,则将它们存储在 SQLite 中是可以的,但如果它们是兆字节大小(而不是 10 KB),那么将它们存储在文件系统中就会开始产生实质性的性能改进。不过,我认为这并不能回答为什么您的第一次通话如此缓慢的问题,但是如果图像更大,则值得考虑。

    如果您在 Stack Overflow 上搜索 [sqlite] blob performance 或执行类似的 google 搜索,您会看到很多关于在 SQLite 中存储超大 BLOB 对象的缺点的讨论。

话虽如此,我确实有一个与性能相关的观察:我不会每次都打开和关闭数据库。应用程序启动时打开数据库一次并保持打开状态。这只节省了 10 秒的毫秒数,因此这不会解决您的 4 秒延迟问题,但会更高效一些。

但是,归根结底,我无法重现您报告的性能延迟。如果您可以创建一个小型独立MCVE,那么我们可以帮助您诊断它。但我怀疑问题出在您迄今为止与我们共享的代码 sn-p 之外的其他地方。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-01-18
    • 1970-01-01
    • 1970-01-01
    • 2013-05-17
    • 2014-10-16
    • 1970-01-01
    • 1970-01-01
    • 2021-10-13
    相关资源
    最近更新 更多