2013-04-18 58 views
3

我试图在Android和iOS上为项目执行SQLite性能之间的基准测试,并且与Android相比,iOS平台上的性能似乎非常糟糕。Android和iOS上的SQLite之间的性能差异

我想实现的是测量插入SQLite数据库中的行数(5000)和平台之间比较的时间。对于Android,我得到500ms左右的结果来执行所有5000次插入,但对于iOS,同样的操作需要20秒以上。怎么会这样?

这是我的iOS代码(插入部分)片段,dataArray中的是5000个随机100字符NSString的数组:

int numEntries = 5000; 
self.dataArray = [[NSMutableArray alloc] initWithCapacity:numEntries];//Array for random data to write to database 

//generate random data (100 char strings) 
for (int i=0; i<numEntries; i++) { 
    [self.dataArray addObject:[self genRandStringLength:100]]; 
} 

// Get the documents directory 
NSArray *dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 

NSString *docsDir = [dirPaths objectAtIndex:0]; 

// Build the path to the database file 
NSString *databasePath = [[NSString alloc] initWithString:[docsDir stringByAppendingPathComponent: @"benchmark.db"]]; 

NSString *resultHolder = @""; 

//Try to open DB, if file not present, create it 
if (sqlite3_open([databasePath UTF8String], &db) == SQLITE_OK){ 

    sql = @"CREATE TABLE IF NOT EXISTS BENCHMARK(ID INTEGER PRIMARY KEY AUTOINCREMENT, TESTCOLUMN TEXT)"; 

    //Create table 
    if (sqlite3_exec(db, [sql UTF8String], NULL, NULL, NULL) == SQLITE_OK){ 
     NSLog(@"DB created"); 
    }else{ 
     NSLog(@"Failed to create DB"); 
    } 

     //START: INSERT BENCHMARK 
     NSDate *startTime = [[NSDate alloc] init];//Get timestamp for insert-timer 

     //Insert values in DB, one by one 
     for (int i = 0; i<numEntries; i++) { 
      sql = [NSString stringWithFormat:@"INSERT INTO BENCHMARK (TESTCOLUMN) VALUES('%@')",[self.dataArray objectAtIndex:i]]; 
      if (sqlite3_exec(db, [sql UTF8String], NULL, NULL, NULL) == SQLITE_OK){ 
       //Insert successful 
      } 
     } 

     //Append time consumption to display string 
     resultHolder = [resultHolder stringByAppendingString:[NSString stringWithFormat:@"5000 insert ops took %f sec\n", [startTime timeIntervalSinceNow]]]; 

     //END: INSERT BENCHMARK 

Android的代码片段:

  // SETUP 
      long startTime, finishTime; 

     // Get database object 
      BenchmarkOpenHelper databaseHelper = new BenchmarkOpenHelper(getApplicationContext()); 
      SQLiteDatabase database = databaseHelper.getWritableDatabase(); 

      // Generate array containing random data 
      int rows = 5000; 
      String[] rowData = new String[rows]; 
      int dataLength = 100; 

      for (int i=0; i<rows; i++) { 
       rowData[i] = generateRandomString(dataLength); 
      } 

      // FIRST TEST: Insertion 
      startTime = System.currentTimeMillis(); 

      for(int i=0; i<rows; i++) { 
       database.rawQuery("INSERT INTO BENCHMARK (TESTCOLUMN) VALUES(?)", new String[] {rowData[i]}); 
      } 

      finishTime = System.currentTimeMillis(); 
      result += "Insertion test took: " + String.valueOf(finishTime-startTime) + "ms \n"; 
      // END FIRST TEST 

回答

2

您需要使用交易 - 开始执行BEGIN并以COMMIT完成。

这应该会大大提高INSERT的表现。

http://www.titaniumdevelopment.com.au/blog/2012/01/27/10x-faster-inserts-in-sqlite-using-begin-commit-in-appcelerator-titanium-mobile/

一旦这样做了,我期望5000点的插入是相当快两个平台上。

这里是另一个StackOverflow的答案,其中列出一吨的不同的东西,可以提高SQLite的性能,包括使用绑定变量,并启用各种PRAGMA模式,这权衡鲁棒性的速度:Improve INSERT-per-second performance of SQLite?

+1

我没有Android设备来测试,所以我不知道。但是,我在OS X和Linux上使用SQLite的经验是,OS X实际上承认了“刷新到磁盘”的请求,这会导致I/O阻塞,而Linux只是一种手动方式,并且不会阻塞。这可能可以解释它。 – StilesCrisis

+0

我觉得C(iOS)的做法比Java(Android)方式快一点。 –

+0

谢谢。我通过turing的同步获得了一些速度! – Andain

4

在iOS上,除了BEGIN/COMMIT变化是StilesCrisis讨论的,如果你想进一步优化您的iOS表现它提供了最显着的性能差异,考虑准备SQL一次,然后反复调用sqlite3_bind_textsqlite3_stepsqlite3_reset。在这种情况下,它似乎使它大约快两倍。

所以,这里是我现有的iOS逻辑的演绎与sqlite3_exec(使用stringWithFormat%@每次都手动构建SQL):

- (void)insertWithExec 
{ 
    NSDate *startDate = [NSDate date]; 

    NSString *sql; 

    if (sqlite3_exec(database, "BEGIN", NULL, NULL, NULL) != SQLITE_OK) 
     NSLog(@"%s: begin failed: %s", __FUNCTION__, sqlite3_errmsg(database)); 

    for (NSString *value in dataArray) 
    { 
     sql = [NSString stringWithFormat:@"INSERT INTO BENCHMARK (TESTCOLUMN) VALUES('%@')", value]; 
     if (sqlite3_exec(database, [sql UTF8String], NULL, NULL, NULL) != SQLITE_OK) 
      NSLog(@"%s: exec failed: %s", __FUNCTION__, sqlite3_errmsg(database)); 
    } 

    if (sqlite3_exec(database, "COMMIT", NULL, NULL, NULL) != SQLITE_OK) 
     NSLog(@"%s: commit failed: %s", __FUNCTION__, sqlite3_errmsg(database)); 

    NSTimeInterval elapsed = [[NSDate date] timeIntervalSinceDate:startDate]; 

    // log `elapsed` here 
} 

这里就是我准备的SQL代码的优化再现只有一次,但随后用sqlite3_bind_text绑定我们的数据相同的?占位符的SQL所使用的Android代码:

- (void)insertWithBind 
{ 
    NSDate *startDate = [NSDate date]; 

    if (sqlite3_exec(database, "BEGIN", NULL, NULL, NULL) != SQLITE_OK) 
     NSLog(@"%s: begin failed: %s", __FUNCTION__, sqlite3_errmsg(database)); 

    sqlite3_stmt *statement; 

    NSString *sql = @"INSERT INTO BENCHMARK (TESTCOLUMN) VALUES(?)"; 

    if (sqlite3_prepare_v2(database, [sql UTF8String], -1, &statement, NULL) != SQLITE_OK) 
     NSLog(@"%s: prepare failed: %s", __FUNCTION__, sqlite3_errmsg(database)); 

    for (NSString *value in dataArray) 
    { 
     if (sqlite3_bind_text(statement, 1, [value UTF8String], -1, NULL) != SQLITE_OK) 
      NSLog(@"%s: bind failed: %s", __FUNCTION__, sqlite3_errmsg(database)); 

     if (sqlite3_step(statement) != SQLITE_DONE) 
      NSLog(@"%s: step failed: %s", __FUNCTION__, sqlite3_errmsg(database)); 

     if (sqlite3_reset(statement) != SQLITE_OK) 
      NSLog(@"%s: reset failed: %s", __FUNCTION__, sqlite3_errmsg(database)); 
    } 

    sqlite3_finalize(statement); 

    if (sqlite3_exec(database, "COMMIT", NULL, NULL, NULL) != SQLITE_OK) 
     NSLog(@"%s: commit failed: %s", __FUNCTION__, sqlite3_errmsg(database)); 

    NSTimeInterval elapsed = [[NSDate date] timeIntervalSinceDate:startDate]; 

    // log `elapsed` here 
} 

在我的iPhone 5,用您的sqlite3_exec逻辑(我的insertWithExec方法)插入5,000条记录需要280-290ms,并且需要110-127ms才能插入与sqlite3_bind_text,sqlite3_stepsqlite3_reset(我的insertWithBind方法)相同的5000条记录。我的编号与您的编号无法比较(不同的设备,插入不同的对象,我在后台队列中编写过,等等),但值得注意的是,准备一次SQL语句花费的时间不到一半,然后只重复绑定,步骤和重置呼叫。我注意到你正在使用?占位符,所以我认为它在幕后也在做sqlite3_bind_text(尽管我不知道它是否准备一次,绑定/步进/每次重置或每次重新准备;可能是后者)。


顺便说一句,作为一个一般的经验法则,你应该总是使用?占位符,就像你在Android的那样,而不是stringWithFormat手动构建SQL,因为它可以节省你从需要手工添加逃逸撇号在您的数据,保护您免受SQL注入攻击等。

+0

@ user2295573尽管我很欣赏你接受我的答案,并且很高兴你发现它有帮助,但我认为99%的性能问题可以用BEGIN/COMMIT解决,所以如果你接受StilesCrisis的答案,我不会冒犯你。 – Rob

相关问题