Diferencias de rendimiento entre SQLite en Android e iOS
Estoy tratando de realizar un punto de referencia entre el rendimiento de SQLite en Android y iOS para un proyecto, y parecen obtener un rendimiento muy malo en la plataforma iOS, en comparación con Android.
Lo que estoy tratando de lograr es medir el tiempo para insertar un número de filas (5000) en el SQLite DB y comparar entre plataformas. Para Android obtengo resultados alrededor de 500ms para realizar las 5000 inserciones, pero para iOS la misma operación toma más de 20s. ¿Cómo puede ser esto?
- Sqlite no devuelve ninguna tabla, pero la tabla está presente cuando se obtiene en el dispositivo OnePlusTwo
- Esquema de la base de datos de Android Sqlite
- Cambiar la profundidad de bits de Bitmap
- La mejor manera de trabajar con las fechas en Android SQLite
- GetWritableDatabase llamado recursivamente
Este es un fragmento de mi código de iOS (la parte de la inserción), dataArray es una matriz con 5000 NSStrings al azar 100 caracteres:
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
Snippet de código de 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
- Android OrmLite base de datos pre-poblar
- La aplicación se bloquea al actualizar la base de datos sqlite por primera vez
- ¿Es posible comparar dos cursores?
- Cómo evitar errores de bloqueo de SQLiteException
- Cómo lograr una relación de "herencia" en la base de datos SQLite en Android
- Los datos guardados se perdieron al reiniciar la aplicación (android)
- SQLiteOpenHelper no puede llamar a onCreate?
- Cómo almacenar (imagen de mapa de bits) y recuperar la imagen de la base de datos sqlite en android?
Debe utilizar una transacción: inicie ejecutando BEGIN
y finalizando con COMMIT
.
Esto mejorará en gran medida el rendimiento de INSERT
.
Una vez hecho esto esperaría que 5000 insertos fueran bastante rápidos en ambas plataformas.
Aquí hay otra respuesta de StackOverflow que enumera una tonelada de cosas diferentes que pueden mejorar el rendimiento de SQLite, incluyendo el uso de variables de enlace y la habilitación de varios modos PRAGMA que intercambian la robustez por la velocidad: ¿ Mejora el rendimiento INSERT-per-second de SQLite?
En iOS, además del cambio BEGIN
/ COMMIT
que discutió StilesCrisis, que ofrece la diferencia de rendimiento más dramática, si desea optimizar aún más su rendimiento de iOS, considere la posibilidad de preparar SQL una vez y luego repetidamente llamar a sqlite3_bind_text
, sqlite3_step
y sqlite3_reset
. En este caso, parecía hacerse aproximadamente el doble de rápido.
Por lo tanto, aquí está mi versión de su lógica iOS existente con sqlite3_exec
(que utiliza stringWithFormat
y %@
para construir manualmente el SQL cada vez):
- (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 }
He aquí una interpretación optimizada del código en el que preparo el SQL solo una vez, pero luego utilizamos sqlite3_bind_text
para enlazar nuestros datos al mismo ?
marcador de posición en el SQL que utilizó su código de 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 }
En mi iPhone 5, tomó 280-290ms para insertar 5.000 registros usando su lógica sqlite3_exec
(mi método insertWithExec
) y tomó 110-127ms para insertar los mismos 5.000 registros con sqlite3_bind_text
, sqlite3_step
y sqlite3_reset
(mi método insertWithBind
). Mis números no son comparables a los suyos (dispositivo diferente, insertar diferentes objetos dataValues
, lo hice en una cola de fondo, etc.), pero es notable que tomó menos de la mitad del tiempo al preparar la sentencia SQL una vez, y luego sólo repitiendo las llamadas bind, step y reset.
Mirando el código de Android, me doy cuenta de que está usando el ?
placeholder, así que supongo que está haciendo sqlite3_bind_text
detrás de las escenas, también (aunque no sé si se está preparando una vez y vinculante / pisar / restablecer cada vez, o volver a preparar cada vez, probablemente el último).
Como un aparte, como regla general, siempre debe utilizar el ?
, como lo hiciste en Android, en lugar de construir el SQL manualmente con stringWithFormat
, ya que evita que necesites apóstrofos de escape manuales en tus datos, te protege contra ataques de inyección de SQL, etc.
- Al codificar para Android, ¿hay una manera de obtener el uso de la CPU para cada núcleo del dispositivo de forma individual?
- Enviar correo electrónico con datos adjuntos desde almacenamiento interno