2013-04-18 2 views
3

Я пытаюсь выполнить контрольный показатель производительности SQLite в Android и iOS для проекта и, по-видимому, очень плохо работает на платформе iOS, по сравнению с Android.Различия в производительности между SQLite на Android и iOS

То, что я пытаюсь достичь, - это измерить время, чтобы вставить несколько строк (5000) в SQLite DB и сравнить между платформами. Для Android я получаю результаты около 500 мс для выполнения всех 5000 вставок, но для iOS одна и та же операция занимает более 20 секунд. Как это может быть?

Это фрагмент моего кода IOS (вставка часть), DataArray представляет собой массив с 5000 случайных 100 гольцов NSStrings:

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-устройства для тестирования, поэтому я не уверен. Тем не менее, мой опыт работы с SQLite на OS X и Linux состоял в том, что OS X фактически выполняет запрос на «сброс на диск», что вызывает блокировку ввода-вывода, а Linux просто вроде ручных волн и не блокирует. Это может объяснить это. – StilesCrisis

+0

Я как-то чувствую, что способ C (iOS) это немного быстрее, чем Java (Android). –

+0

Спасибо. Я получил некоторую скорость за счет синхронизации! – Andain

4

КСН, в дополнение к BEGIN//////////////В этом случае он, казалось, делал это примерно в два раза быстрее.

Итак, вот мое исполнение существующей логики 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, потребовалось 280-290 мс, чтобы вставить 5000 записей, используя логику sqlite3_exec (мой метод insertWithExec), и потребовалось 110-127 мс для ввода тех же 5000 записей с sqlite3_bind_text, sqlite3_step и sqlite3_reset (мой метод insertWithBind). Мои номера не сопоставимы с вашими (разные устройства, вставляя разные объекты dataValues, я делал это в фоновом режиме и т. Д.), Но примечательно, что для подготовки SQL-инструкции один раз потребовалось меньше половины времени, а затем только повторяя вызовы bind, step и reset.

Ознакомившись с кодом Android, я заметил, что вы используете заполнитель ?, поэтому я предполагаю, что он тоже делает sqlite3_bind_text (хотя я не знаю, готов ли он его один раз и привязка/каждый раз переустанавливается или перерабатывается каждый раз, возможно, последний).


Как и в сторону, как общее правило, вы всегда должны использовать ? заполнитель, как вы это делали в Android, а не строить SQL вручную с stringWithFormat, как это избавляет вас от необходимости ручного побега апострофы в ваших данных, защищает вас от атак SQL-инъекций и т. д.

+0

@ user2295573 Хотя я ценю ваше согласие с моим ответом и рад, что вы сочли это полезным, я думаю, что 99% проблемы с производительностью разрешено с помощью BEGIN/COMMIT, поэтому я бы не стал обижаться, если бы вы приняли ответ StilesCrisis. – Rob

Смежные вопросы