将大型 CSV 文件加载到核心数据中的最快方法是啥

Posted

技术标签:

【中文标题】将大型 CSV 文件加载到核心数据中的最快方法是啥【英文标题】:What is the fastest way to load a large CSV file into core data将大型 CSV 文件加载到核心数据中的最快方法是什么 【发布时间】:2012-05-04 06:40:02 【问题描述】:

结论 我认为问题已解决。 看起来问题与方法无关,但 XCode 在构建之间没有正确清理项目。 看起来在所有这些测试之后,正在使用的 sqlite 文件仍然是第一个没有被索引的文件...... 当心 XCode 4.3.2,除了清理不清理或将文件添加到项目中不会自动添加到捆绑资源中的问题外,我什么都没有... 感谢不同的答案..

更新 3 由于我邀请任何人尝试相同的步骤以查看他们是否获得相同的结果,让我详细说明我做了什么: 我从空白项目开始 我用一个实体、3 个属性(2 个字符串、1 个浮点数)定义了一个数据模型 第一个字符串被索引 在没有finishLaunchingWithOptions,我打电话:

[self performSelectorInBackground:@selector(populateDB) withObject:nil];

populateDb 的代码如下:

-(void)populateDB
NSLog(@"start");
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
NSManagedObjectContext *context;
if (coordinator != nil) 
    context = [[NSManagedObjectContext alloc] init];
    [context setPersistentStoreCoordinator:coordinator];


NSString *filePath = [[NSBundle mainBundle] pathForResource:@"input" ofType:@"txt"];  
if (filePath)   
    NSString * myText = [[NSString alloc]
                               initWithContentsOfFile:filePath
                               encoding:NSUTF8StringEncoding
                               error:nil];
    if (myText) 
        __block int count = 0;


        [myText enumerateLinesUsingBlock:^(NSString * line, BOOL * stop) 
            line=[line stringByReplacingOccurrencesOfString:@"\t" withString:@" "];
            NSArray *lineComponents=[line componentsSeparatedByString:@" "];
            if(lineComponents)
                if([lineComponents count]==3)
                    float f=[[lineComponents objectAtIndex:0] floatValue];
                    NSNumber *number=[NSNumber numberWithFloat:f];
                    NSString *string1=[lineComponents objectAtIndex:1];
                    NSString *string2=[lineComponents objectAtIndex:2];
                    NSManagedObject *object=[NSEntityDescription insertNewObjectForEntityForName:@"Bigram" inManagedObjectContext:context];
                    [object setValue:number forKey:@"number"];
                    [object setValue:string1 forKey:@"string1"];
                    [object setValue:string2 forKey:@"string2"];
                    NSError *error;
                    count++;
                    if(count>=1000)
                        if (![context save:&error]) 
                            NSLog(@"Whoops, couldn't save: %@", [error localizedDescription]);
                        
                        count=0;

                    
                
            



        ];
        NSLog(@"done importing");
        NSError *error;
        if (![context save:&error]) 
            NSLog(@"Whoops, couldn't save: %@", [error localizedDescription]);
        

      

NSLog(@"end");

其他一切都是默认核心数据代码,没有添加任何内容。 我在模拟器中运行它。 我去~/Library/Application Support/iPhone Simulator/5.1/Applications//Documents 有生成的sqlite文件 我把它复制到我的包中 我注释掉对 populateDb 的调用 我编辑 persistentStoreCoordinator 以在第一次运行时将 sqlite 文件从包复制到文档

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator 

@synchronized (self)

    if (__persistentStoreCoordinator != nil)
        return __persistentStoreCoordinator;

    NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:@"myProject" ofType:@"sqlite"];
    NSString *storePath = [[[self applicationDocumentsDirectory] path] stringByAppendingPathComponent: @"myProject.sqlite"];

    NSError *error;
    if (![[NSFileManager defaultManager] fileExistsAtPath:storePath]) 
    
        if ([[NSFileManager defaultManager] copyItemAtPath:defaultStorePath toPath:storePath error:&error])
            NSLog(@"Copied starting data to %@", storePath);
        else 
            NSLog(@"Error copying default DB to %@ (%@)", storePath, error);
    

    NSURL *storeURL = [NSURL fileURLWithPath:storePath];

    __persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];

    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                             [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
                             [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];

    if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) 
    

        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
        

    return __persistentStoreCoordinator;
    

我从模拟器中删除了应用程序,我检查了 ~/Library/Application Support/iPhone Simulator/5.1/Applications/ 现在是否已删除我重新构建并再次启动 正如预期的那样,sqlite 文件被复制到 ~/Library/Application Support/iPhone Simulator/5.1/Applications//Documents

但文件的大小明显小于捆绑包中的大小! 另外,使用这样的谓词进行简单查询 predicate = [NSPredicate predicateWithFormat:@"string1 == %@", string1];清楚地表明 string1 不再被索引 之后,我创建了一个新版本的数据模型,并进行了无意义的更新,只是为了强制进行轻量级迁移 如果在模拟器上运行,迁移需要几秒钟,数据库大小会翻倍,现在返回相同的查询只需不到一秒钟而不是几分钟。 这将解决我的问题,强制迁移,但相同的迁移在 iPad 上需要 3 分钟并发生在前台。 所以这就是我现在所处的位置,对我来说最好的解决方案仍然是防止索引被删除,在启动时任何其他导入解决方案都需要太多时间。 如果您需要更多说明,请告诉我...

更新 2 所以到目前为止我得到的最好的结果是使用具有类似数据模型的快速工具生成的 sqlite 文件为核心数据数据库播种,但在生成 sqlite 文件时没有设置索引。然后,我将这个 sqlite 文件导入核心数据应用程序并设置索引,并允许进行轻量级迁移。对于新 iPad 上的 200 万条记录,此迁移剧照需要 3 分钟。最终的应用程序应该有这个数量的 5 倍的记录,所以我们仍然在寻找一个很长的处理时间。 如果我走那条路,新的问题将是:可以在后台执行轻量级迁移吗?

更新 我的问题不是如何创建一个工具来填充 Core Data 数据库,然后将 sqlite 文件导入我的应用程序。 我知道如何做到这一点,我已经做过无数次了。 但直到现在,我没有意识到这种方法可能会产生一些副作用:在我的情况下,以这种方式导入 sqlite 文件时,结果数据库中的索引属性显然会“未索引”。 如果您能够验证在此类传输后任何索引数据仍然被索引,我很想知道您如何进行,或者有效地播种此类数据库的最佳策略是什么。 原创

我有一个包含 4 列、字符串和浮点数的大型 CSV 文件(数百万行)。 这是针对 ios 应用程序的。 我需要在第一次加载应用程序时将其加载到核心数据中。 在数据可用之前,该应用程序几乎无法运行,因此加载时间很重要,因为第一次使用的用户显然不希望应用程序在能够运行之前加载 20 分钟。 现在,我当前的代码在新 iPad 上需要 20 分钟来处理一个 200 万行的 csv 文件。 我正在使用后台上下文来不锁定 UI,并且每 1,000 条记录保存一次上下文 我的第一个想法是在模拟器上生成数据库,然后在首次启动时将其复制/粘贴到文档文件夹中,因为这是播种大型数据库的常见非官方方式。不幸的是,索引似乎无法在这样的传输中幸存下来,尽管数据库在几秒钟后就可以使用,但性能很糟糕,因为我的索引丢失了。我已经发布了一个关于索引的问题,但似乎没有一个好的答案。 所以我要找的是:

一种提高核心数据中数百万条记录加载性能的方法 如果数据库在首次启动时预加载并移动,这是一种保留索引的方法 处理这种情况的最佳实践。我不记得使用过任何需要我在第一次使用前等待 x 分钟的应用程序(但也许是 The Daily,那是一次糟糕的体验)。 任何让用户在不知不觉中等待的创造性方法:在浏览教程时进行后台导入等... 不使用核心数据? ...

【问题讨论】:

那么您是如何最终“清理”项目以使其正常工作的? 清理不起作用,但重新启动笔记本电脑、手动清理对文件的所有引用等似乎已经“解决”了问题。奇怪......虽然我也不得不删除轻量级迁移线以强制不迁移(因为这需要很多分钟)。总的来说,这不是我希望的干净的实现,但这很有效......直到第 2 版需要升级数据模型,然后我就有麻烦了 【参考方案1】:

使用用 Cocoa 编写的离线应用程序(例如,命令行实用程序)预生成数据库,该应用程序在 OS X 上运行,并使用与 iOS 相同的 Core Data 框架。您无需担心“索引是否存在”或任何其他问题 - 输出是 Core Data 生成的 .sqlite 数据库文件,可供 iOS 应用直接且立即使用。

只要您可以离线生成数据库,这是迄今为止最好的解决方案。我自己已经成功地使用这种技术为 iOS 部署预先生成了数据库。查看我以前的问题/答案以了解更多详细信息。

【讨论】:

什么意思,我不需要担心我的索引;正如我在我的问题中所说,我做了这个确切的方法,输出是一个 sqlite 数据库文件(200Mb),当在我的应用程序中使用完全相同的模型时,文件下降到 110Mb 并且显然性能表明我的索引不是在职的。所以我确实担心我的索引,这就是重点! @nafziger,您的意思是您的核心数据模型中有索引,并且您确定这些索引在您重用该 sqlite 文件后仍然可以正常工作吗?如果是这样,您采用什么方法来确保您的索引仍然有效? @JP Hribovsek SQLite 生成的普通旧 SQLite 数据库文件(Core Data 通常不能直接使用)和 Core Data 生成的 SQLite 数据库文件是有区别的。我在 iOS 应用程序和 OS X Cocoa 命令行实用程序中都使用了我建议的系统,使用完全相同的 Core Data 数据模型,没有任何问题。我还使用 SQLite 预先生成了一个 SQLite DB,用于使用 SQLite 的 iOS 应用程序,没有问题——但那是在 Core Data 出现在 iOS 之前,这大大简化了这个问题。 我可能没有正确描述这一点,但让我澄清一下我做了什么:我将 CSV 加载器用于模拟器上的核心数据代码。在同一个应用程序(相同的核心数据模型)中,我从模拟器数据中删除了 sqlite 文件,将其移动到包中,并编辑了代码以不再进行 CSV 导入,而只是重用包中的 sqlite 文件。我并不是说它在外观上不起作用,它确实“起作用”,因为我可以查询并获得我的结果。但是原本是 200Mb 的 sqlite,在移动到 bundle 时仍然是那个大小,变成了一个 120Mb 的文件,性能显示我的索引不见了 @JP Hribovsek 你为什么使用模拟器?创建一个真正的 OS X 应用程序来预生成数据库。【参考方案2】:

我刚开始使用 SQLite,我需要将数据库集成到我的一个应用程序中,该应用程序将在 SQLite 数据库中包含大量索引数据。我希望我可以做一些方法,我可以将我的信息批量插入 SQLite 文件并将该文件添加到我的项目中。在发现并阅读了您的问题、提供的答案和众多 cmets 之后,我决定查看 SQLite 源代码,看看我是否可以对这个问题做出正面或反面。

我最初的想法是,SQLite 的 iOS 实现实际上是在丢弃您的索引。原因是您最初在 x86/x64 系统上创建数据库索引。 iOS 是 ARM 处理器,数字的处理方式不同。如果您希望您的索引快速,您应该以这样一种方式生成它们,以便它们针对将在其中搜索它们的处理器进行优化。

由于 SQLite 适用于多个平台,因此可以删除在另一个架构中创建的任何索引并重新构建它们。然而,由于没有人愿意等待索引在第一次被访问时重建,SQLite 开发人员很可能决定直接删除索引。

深入研究 SQLite 代码后,我得出的结论是,最有可能发生这种情况。如果不是因为处理器架构的原因,我确实找到了代码(参见analyze.csqliteint.h 中的其他元信息),如果索引是在意外上下文中生成的,它们会被删除。我的预感是驱动这个过程的上下文是如何为现有密钥构建底层 b-tree 数据结构。如果当前 SQLite 实例无法使用该密钥,则将其删除。

值得一提的是,iOS Simulator 就是一个模拟器。它不是硬件的模拟器。因此,您的应用运行在伪 iOS 设备上,运行在 x86/x64 处理器上。

当您的应用和 SQLite DB 加载到您的 iOS 设备时,会加载一个 ARM 编译的变体,该变体还链接到 iOS 中的 ARM 编译库。我找不到与 SQLite 相关的 ARM 特定代码,所以我想 Apple 必须将其修改为适合他们的套装。这也可能是问题的一部分。这可能不是根 SQLite 代码的问题,它可能是 Apple/ARM 编译变体的问题。

我能想出的唯一合理的解决方案是,您可以创建一个在您的 iOS 机器上运行的生成器应用程序。运行应用程序,构建密钥,然后从设备中提取 SQLite 文件。我想这样的文件可以在所有设备上运行,因为 iOS 使用的所有 ARM 处理器都是 32 位的。

同样,这个答案有点有根据的猜测。我将把你的问题重新标记为 SQLite。希望大师可以发现这一点并能够权衡这个问题。为了自己的利益,我真的很想知道真相。

【讨论】:

以上是关于将大型 CSV 文件加载到核心数据中的最快方法是啥的主要内容,如果未能解决你的问题,请参考以下文章

将 20 GB csv 文件加载到 R 中的最佳方法是啥?

使用不同格式(csv、json、avro)将数据加载到 pd.DataFrame 的最快方法

按列合并多个 csv 文件的最快方法是啥?

将大型 csv 加载到数据框中,同时保持列结构

将 2x4 64b 结构的第一行加载到 AVX2 的 256b 寄存器中的最快方法是啥?

从多个文件中读取大数据并在python中聚合数据的最快方法是啥?