2013-04-18 136 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

相關問題