/* * Copyright 2016 requery.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.requery.android.database.benchmark; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteStatement; import android.provider.BaseColumns; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import android.util.Log; import org.junit.Test; import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; @RunWith(AndroidJUnit4.class) public class Benchmark { private static final String TAG = "SQLite"; private static final int COUNT = 10000; private PlatformSQLite platformSQLite; private RequerySQLite requerySQLite; @Test public void runBenchmark() { final int runs = 5; Statistics android = new Statistics(); Statistics requery = new Statistics(); for (int i = 0; i < runs; i++) { Context context = InstrumentationRegistry.getContext(); String dbName = "testAndroid.db"; context.deleteDatabase(dbName); platformSQLite = new PlatformSQLite(context, dbName); dbName = "testRequery.db"; context.deleteDatabase(dbName); requerySQLite = new RequerySQLite(context, dbName); testAndroidSQLiteRead(android); testRequerySQLiteRead(requery); if (platformSQLite != null) { platformSQLite.close(); } if (requerySQLite != null) { requerySQLite.close(); } } Log.i(TAG, "Android: " + android.toString()); Log.i(TAG, "requery: " + requery.toString()); } private void testAndroidSQLiteRead(Statistics statistics) { testAndroidSQLiteWrite(statistics); Trace trace = new Trace("Android Read"); Cursor cursor = null; try { SQLiteDatabase db = platformSQLite.getReadableDatabase(); String[] projection = new String[] { Record.COLUMN_ID, Record.COLUMN_CONTENT, Record.COLUMN_CREATED_TIME, }; cursor = db.query(Record.TABLE_NAME, projection, null, null, null, null, null); readCursor(cursor); } finally { if (cursor != null) { cursor.close(); } } statistics.read( trace.exit() ); } private void testAndroidSQLiteWrite(Statistics statistics) { Trace trace = new Trace("Android Write"); SQLiteDatabase db = platformSQLite.getReadableDatabase(); SQLiteStatement statement = db.compileStatement( String.format("insert into %s (%s, %s) values (?,?)", Record.TABLE_NAME, Record.COLUMN_CONTENT, Record.COLUMN_CREATED_TIME)); try { db.beginTransaction(); for (int i = 0; i < COUNT; i++) { Record record = Record.create(i); statement.bindString(1, record.getContent()); statement.bindDouble(2, record.getCreatedTime()); long id = statement.executeInsert(); record.setId(id); } db.setTransactionSuccessful(); } finally { db.endTransaction(); } statistics.write( trace.exit() ); } private void testRequerySQLiteWrite(Statistics statistics) { Trace trace = new Trace("requery Write"); io.requery.android.database.sqlite.SQLiteDatabase db = requerySQLite.getWritableDatabase(); io.requery.android.database.sqlite.SQLiteStatement statement = db.compileStatement( String.format("insert into %s (%s, %s) values (?,?)", Record.TABLE_NAME, Record.COLUMN_CONTENT, Record.COLUMN_CREATED_TIME)); try { db.beginTransaction(); for (int i = 0; i < COUNT; i++) { Record record = Record.create(i); statement.bindString(1, record.getContent()); statement.bindDouble(2, record.getCreatedTime()); long id = statement.executeInsert(); record.setId(id); } db.setTransactionSuccessful(); } finally { db.endTransaction(); } statistics.write( trace.exit() ); } private void testRequerySQLiteRead(Statistics statistics) { testRequerySQLiteWrite(statistics); Trace trace = new Trace("requery Read"); Cursor cursor = null; try { io.requery.android.database.sqlite.SQLiteDatabase db = requerySQLite.getWritableDatabase(); String[] projection = new String[] { Record.COLUMN_ID, Record.COLUMN_CONTENT, Record.COLUMN_CREATED_TIME, }; cursor = db.query(Record.TABLE_NAME, projection, null, null, null, null, null); readCursor(cursor); } finally { if (cursor != null) { cursor.close(); } } statistics.read( trace.exit() ); } private static void readCursor(Cursor cursor) { if(cursor != null) { int indexId = cursor.getColumnIndexOrThrow(Record.COLUMN_ID); int indexContent = cursor.getColumnIndexOrThrow(Record.COLUMN_CONTENT); int indexCreatedTime = cursor.getColumnIndexOrThrow(Record.COLUMN_CREATED_TIME); while (cursor.moveToNext()) { Record record = new Record(); record.setId(cursor.getLong(indexId)); record.setContent(cursor.getString(indexContent)); record.setCreatedTime(cursor.getLong(indexCreatedTime)); } } } private static class Record { private final static String CREATE_STATEMENT = "CREATE TABLE '" + Record.TABLE_NAME + "' ('" + BaseColumns._ID + "' INTEGER PRIMARY KEY AUTOINCREMENT, '" + Record.COLUMN_CONTENT + "' TEXT, '" + Record.COLUMN_CREATED_TIME + "' INTEGER);"; static final String TABLE_NAME = "record"; static final String COLUMN_ID = BaseColumns._ID; static final String COLUMN_CONTENT = "content"; static final String COLUMN_CREATED_TIME = "created"; static Record create(int index) { Record record = new Record(); record.setContent("position" + String.valueOf(index)); record.setCreatedTime(System.currentTimeMillis()); return record; } private long id; private String content; private long createdTime; public long getId() { return id; } public void setId(long id) { this.id = id; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public long getCreatedTime() { return createdTime; } public void setCreatedTime(long createdTime) { this.createdTime = createdTime; } } private static class PlatformSQLite extends SQLiteOpenHelper { public PlatformSQLite(Context context, String name) { super(context, name, null, 1); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(Record.CREATE_STATEMENT); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { throw new UnsupportedOperationException(); } } private static class RequerySQLite extends io.requery.android.database.sqlite.SQLiteOpenHelper { public RequerySQLite(Context context, String name) { super(context, name, null, 1); } @Override public void onCreate(io.requery.android.database.sqlite.SQLiteDatabase db) { db.execSQL(Record.CREATE_STATEMENT); } @Override public void onUpgrade(io.requery.android.database.sqlite.SQLiteDatabase db, int oldVersion, int newVersion) { throw new UnsupportedOperationException(); } } private static class Statistics { private final List<Long> reads = new ArrayList<>(); private final List<Long> writes = new ArrayList<>(); void read(long elapsedMS) { reads.add(elapsedMS); } void write(long elapsedMS) { writes.add(elapsedMS); } float readAverageMS() { return average(reads); } float writeAverageMS() { return average(writes); } float average(List<Long> times) { long total = 0; for (Long time : times) { total += time; } return total / (float) times.size(); } @Override public String toString() { return "Read AVG " + readAverageMS() + " Write AVG " + writeAverageMS() + "\n" + " Rows/sec " + COUNT / readAverageMS() * 1000f + " Inserts/sec " + COUNT / writeAverageMS() * 1000f; } } private static class Trace { private String source; private long start; public Trace(String source) { this.source = source; enter(); } public void enter() { //Log.i(TAG, "enter " + source); start = System.nanoTime(); } public long exit() { //Log.i(TAG, "exit " + source); long stop = System.nanoTime(); long elapsed = stop - start; long elapsedMS = TimeUnit.NANOSECONDS.toMillis(elapsed); Log.i(TAG, source + " " + elapsedMS + "ms"); return elapsedMS; } } }