diff --git a/ORM-Benchmark/src/main/java/com/littleinc/orm_benchmark/Application.java b/ORM-Benchmark/src/main/java/com/littleinc/orm_benchmark/Application.java index 5c8ae05..4680253 100644 --- a/ORM-Benchmark/src/main/java/com/littleinc/orm_benchmark/Application.java +++ b/ORM-Benchmark/src/main/java/com/littleinc/orm_benchmark/Application.java @@ -3,6 +3,7 @@ import com.littleinc.orm_benchmark.greendao.GreenDaoExecutor; import com.littleinc.orm_benchmark.ormlite.ORMLiteExecutor; import com.littleinc.orm_benchmark.sqlite.SQLiteExecutor; +import com.littleinc.orm_benchmark.optimizedsqlite.SQLiteRawExecutor; public class Application extends android.app.Application { @@ -13,6 +14,7 @@ public void onCreate() { super.onCreate(); SQLiteExecutor.INSTANCE.init(this, USE_IN_MEMORY_DB); + SQLiteRawExecutor.INSTANCE.init(this, USE_IN_MEMORY_DB); ORMLiteExecutor.INSTANCE.init(this, USE_IN_MEMORY_DB); GreenDaoExecutor.INSTANCE.init(this, USE_IN_MEMORY_DB); } diff --git a/ORM-Benchmark/src/main/java/com/littleinc/orm_benchmark/MainActivity.java b/ORM-Benchmark/src/main/java/com/littleinc/orm_benchmark/MainActivity.java index 7a6a1bb..1aa7541 100644 --- a/ORM-Benchmark/src/main/java/com/littleinc/orm_benchmark/MainActivity.java +++ b/ORM-Benchmark/src/main/java/com/littleinc/orm_benchmark/MainActivity.java @@ -20,7 +20,6 @@ import android.support.v4.app.DialogFragment; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentTransaction; -import android.support.v4.app.ShareCompat.IntentBuilder; import android.text.Html; import android.util.Log; import android.util.SparseArray; @@ -31,6 +30,7 @@ import com.littleinc.orm_benchmark.greendao.GreenDaoExecutor; import com.littleinc.orm_benchmark.ormlite.ORMLiteExecutor; import com.littleinc.orm_benchmark.sqlite.SQLiteExecutor; +import com.littleinc.orm_benchmark.optimizedsqlite.SQLiteRawExecutor; import com.littleinc.orm_benchmark.util.Util; public class MainActivity extends FragmentActivity { @@ -44,7 +44,9 @@ public class MainActivity extends FragmentActivity { private Button mShowResultsBtn; private BenchmarkExecutable[] mOrms = new BenchmarkExecutable[] { - SQLiteExecutor.INSTANCE, ORMLiteExecutor.INSTANCE, + SQLiteExecutor.INSTANCE, + SQLiteRawExecutor.INSTANCE, + ORMLiteExecutor.INSTANCE, GreenDaoExecutor.INSTANCE }; private SparseArray>> mGlobalResults; @@ -71,8 +73,13 @@ public void runBenchmark(View v) { v.setEnabled(false); mShowResultsBtn.setEnabled(false); - new ProfilerTask(v).execute(CREATE_DB, WRITE_DATA, READ_DATA, - READ_INDEXED, READ_SEARCH, DROP_DB); + new ProfilerTask(v).execute( + CREATE_DB, + WRITE_DATA, + READ_DATA, + READ_INDEXED, + READ_SEARCH, + DROP_DB); } else { mResults = buildResults(); Log.d(MainActivity.class.getSimpleName(), "Results:\n" + mResults); diff --git a/ORM-Benchmark/src/main/java/com/littleinc/orm_benchmark/optimizedsqlite/DataBaseHelper.java b/ORM-Benchmark/src/main/java/com/littleinc/orm_benchmark/optimizedsqlite/DataBaseHelper.java new file mode 100644 index 0000000..7032a0d --- /dev/null +++ b/ORM-Benchmark/src/main/java/com/littleinc/orm_benchmark/optimizedsqlite/DataBaseHelper.java @@ -0,0 +1,25 @@ +package com.littleinc.orm_benchmark.optimizedsqlite; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; + +public class DataBaseHelper extends SQLiteOpenHelper { + + // DB CONFIG + private static int DB_VERSION = 1; + + private static String DB_NAME = "sqliteopt_db"; + + public DataBaseHelper(Context context, boolean isInMemory) { + super(context, (isInMemory ? null : DB_NAME), null, DB_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + } +} diff --git a/ORM-Benchmark/src/main/java/com/littleinc/orm_benchmark/optimizedsqlite/Message.java b/ORM-Benchmark/src/main/java/com/littleinc/orm_benchmark/optimizedsqlite/Message.java new file mode 100644 index 0000000..8d472ec --- /dev/null +++ b/ORM-Benchmark/src/main/java/com/littleinc/orm_benchmark/optimizedsqlite/Message.java @@ -0,0 +1,160 @@ +package com.littleinc.orm_benchmark.optimizedsqlite; + +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteStatement; +import android.provider.BaseColumns; + +import java.util.List; + +public class Message { + + public static final String TABLE_NAME = "message"; + + public static final String CONTENT = "content"; + + public static final String READERS = "readers"; + + public static final String SORTED_BY = "sorted_by"; + + public static final String CLIENT_ID = "client_id"; + + public static final String SENDER_ID = "sender_id"; + + public static final String CHANNEL_ID = "channel_id"; + + public static final String COMMAND_ID = "command_id"; + + public static final String CREATED_AT = "created_at"; + + private long mId; + + private long mClientId; + + private long mCommandId; + + private double mSortedBy; + + private int mCreatedAt; + + private String mContent; + + private long mSenderId; + + private long mChannelId; + + private List mReaders; + + public static final String[] PROJECTION = new String[] { CONTENT, + SORTED_BY, CLIENT_ID, SENDER_ID, CHANNEL_ID, COMMAND_ID, CREATED_AT }; + + public static void createTable(DataBaseHelper helper) { + SQLiteDatabase db = helper.getWritableDatabase(); + + db.execSQL(new StringBuilder("CREATE TABLE '").append(TABLE_NAME) + .append("' ('").append(BaseColumns._ID) + .append("' INTEGER PRIMARY KEY AUTOINCREMENT, '") + .append(CLIENT_ID).append("' INTEGER, '").append(SORTED_BY) + .append("' REAL, '").append(CREATED_AT).append("' INTEGER, '") + .append(CONTENT).append("' TEXT, '").append(SENDER_ID) + .append("' INTEGER NOT NULL, '").append(CHANNEL_ID) + .append("' INTEGER NOT NULL, '").append(COMMAND_ID) + .append("' INTEGER);").toString()); + + db.execSQL(new StringBuilder("CREATE INDEX IDX_MESSAGE_COMMAND_ID ON ") + .append(TABLE_NAME).append(" (").append(COMMAND_ID) + .append(");").toString()); + } + + public static void dropTable(DataBaseHelper helper) { + SQLiteDatabase db = helper.getWritableDatabase(); + + db.execSQL(new StringBuilder("DROP TABLE '").append(TABLE_NAME) + .append("';").toString()); + } + + public long getId() { + return mId; + } + + public void setId(long id) { + this.mId = id; + } + + public long getClientId() { + return mClientId; + } + + public void setClientId(long clientId) { + this.mClientId = clientId; + } + + public long getCommandId() { + return mCommandId; + } + + public void setCommandId(long commandId) { + this.mCommandId = commandId; + } + + public double getSortedBy() { + return mSortedBy; + } + + public void setSortedBy(double sortedBy) { + this.mSortedBy = sortedBy; + } + + public int getCreatedAt() { + return mCreatedAt; + } + + public void setCreatedAt(int createdAt) { + this.mCreatedAt = createdAt; + } + + public String getContent() { + return mContent; + } + + public void setContent(String content) { + this.mContent = content; + } + + public long getSenderId() { + return mSenderId; + } + + public void setSenderId(long senderId) { + this.mSenderId = senderId; + } + + public long getChannelId() { + return mChannelId; + } + + public void setChannelId(long channelId) { + this.mChannelId = channelId; + } + + public void setReaders(List readers) { + mReaders = readers; + } + + public List getReaders() { + return mReaders; + } + + public boolean hasReaders() { + return mReaders != null && !mReaders.isEmpty(); + } + + public void prepareForInsert(final SQLiteStatement insertMessage) { + insertMessage.bindString(1, mContent); + insertMessage.bindDouble(2, mSortedBy); + insertMessage.bindLong(3, mClientId); + insertMessage.bindLong(4, mSenderId); + insertMessage.bindLong(5, mChannelId); + insertMessage.bindLong(6, mCommandId); + insertMessage.bindLong(7, mCreatedAt); + } +} diff --git a/ORM-Benchmark/src/main/java/com/littleinc/orm_benchmark/optimizedsqlite/SQLiteRawExecutor.java b/ORM-Benchmark/src/main/java/com/littleinc/orm_benchmark/optimizedsqlite/SQLiteRawExecutor.java new file mode 100644 index 0000000..146573f --- /dev/null +++ b/ORM-Benchmark/src/main/java/com/littleinc/orm_benchmark/optimizedsqlite/SQLiteRawExecutor.java @@ -0,0 +1,278 @@ +package com.littleinc.orm_benchmark.optimizedsqlite; + +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteStatement; +import android.util.Log; + +import com.littleinc.orm_benchmark.BenchmarkExecutable; +import com.littleinc.orm_benchmark.util.Util; + +import java.sql.SQLException; +import java.util.LinkedList; +import java.util.List; + +import static com.littleinc.orm_benchmark.util.Util.getRandomString; + +/** + * This executor takes the basic idea of the standard Android sqlLite query helper, but adds a few optimizations. + * + * Select optimization + * A Cursor can only access columns by their respective position in the result + * the standard way of reading the fields value is cursor.getString(cursor.getColumnIndex("field_name")) + * this actually loops through all column names until it finds one that matches and then return that index. + * + * To decrease unnecessary lookup, we do this once before we read any of the rows, and remembers the position + * in local variables. + * + * Insert optimizations + * The database.Insert function does not cache the insert query. Instead it re-creates the full statement + * for each time its called including adding all values to the statement + * What we do here instead is to create the SQL-statement manually and let the database driver compile it + * for us. This creates a re-usable and very fast executable statement for us. + * + * For each row we just need to insert the values into the statement via the bind function and then + * execute it. + * + * Everything should of course still be encapsulated by a transaction, otherwise you will get a huge overhead + * per row. + * + */ +public enum SQLiteRawExecutor implements BenchmarkExecutable { + + INSTANCE; + + private DataBaseHelper mHelper; + + @Override + public int getProfilerId() { + return 4; + } + + @Override + public String getOrmName() { + return "RAW_OPTIMIZED"; + } + + @Override + public void init(Context context, boolean useInMemoryDb) { + mHelper = new DataBaseHelper(context, useInMemoryDb); + } + + @Override + public long createDbStructure() throws SQLException { + long start = System.nanoTime(); + User.createTable(mHelper); + Message.createTable(mHelper); + return System.nanoTime() - start; + } + + @Override + public long writeWholeData() throws SQLException { + List users = new LinkedList(); + for (int i = 0; i < NUM_USER_INSERTS; i++) { + User newUser = new User(); + newUser.setLastName(getRandomString(10)); + newUser.setFirstName(getRandomString(10)); + + users.add(newUser); + } + + List messages = new LinkedList(); + for (int i = 0; i < NUM_MESSAGE_INSERTS; i++) { + Message newMessage = new Message(); + newMessage.setCommandId(i); + newMessage.setSortedBy(System.nanoTime()); + newMessage.setContent(Util.getRandomString(100)); + newMessage.setClientId(System.currentTimeMillis()); + newMessage + .setSenderId(Math.round(Math.random() * NUM_USER_INSERTS)); + newMessage + .setChannelId(Math.round(Math.random() * NUM_USER_INSERTS)); + newMessage.setCreatedAt((int) (System.currentTimeMillis() / 1000L)); + + messages.add(newMessage); + } + + long start = System.nanoTime(); + SQLiteDatabase db = mHelper.getWritableDatabase(); + + SQLiteStatement insertUser = db.compileStatement( + String.format("Insert into %s (%s, %s) values (?,?)", + User.TABLE_NAME, + User.FIRST_NAME_COLUMN, + User.LAST_NAME_COLUMN)); + + SQLiteStatement insertMessage = db.compileStatement( + String.format("Insert into %s (%s, %s, %s, %s, %s, %s, %s) values (?,?,?,?,?,?,?)", + Message.TABLE_NAME, + Message.CONTENT, + Message.SORTED_BY, + Message.CLIENT_ID, + Message.SENDER_ID, + Message.CHANNEL_ID, + Message.COMMAND_ID, + Message.CREATED_AT )); + + try { + db.beginTransaction(); + + for (User user : users) { + user.prepareForInsert(insertUser); + insertUser.execute(); + } + Log.d(SQLiteRawExecutor.class.getSimpleName(), "Done, wrote " + + NUM_USER_INSERTS + " users"); + + for (Message message : messages) { + message.prepareForInsert(insertMessage); + insertMessage.execute(); + } + Log.d(SQLiteRawExecutor.class.getSimpleName(), "Done, wrote " + + NUM_MESSAGE_INSERTS + " messages"); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + return System.nanoTime() - start; + } + + @Override + public long readWholeData() throws SQLException { + long start = System.nanoTime(); + Cursor c = null; + try { + SQLiteDatabase db = mHelper.getReadableDatabase(); + List messages = new LinkedList(); + c = db.query(Message.TABLE_NAME, Message.PROJECTION, null, null, + null, null, null); + + if(c != null) { + + int channelIdIndex = c.getColumnIndex(Message.CHANNEL_ID); + int clientIdIndex = c.getColumnIndex(Message.CLIENT_ID); + int commandIdIndex = c.getColumnIndex(Message.COMMAND_ID); + int contentIndex = c.getColumnIndex(Message.CONTENT); + int createdAtIndex = c.getColumnIndex(Message.CREATED_AT); + int senderIdIndex = c.getColumnIndex(Message.SENDER_ID); + int sortedByIndex = c.getColumnIndex(Message.SORTED_BY); + + while (c.moveToNext()) { + Message newMessage = new Message(); + newMessage.setChannelId(c.getLong(channelIdIndex)); + newMessage.setClientId(c.getLong(clientIdIndex)); + newMessage.setCommandId(c.getLong(commandIdIndex)); + newMessage.setContent(c.getString(contentIndex)); + newMessage.setCreatedAt(c.getInt(createdAtIndex)); + newMessage.setSenderId(c.getLong(senderIdIndex)); + newMessage.setSortedBy(c.getDouble(sortedByIndex)); + + messages.add(newMessage); + } + } + Log.d(SQLiteRawExecutor.class.getSimpleName(), + "Read, " + messages.size() + " rows"); + } finally { + if (c != null) { + c.close(); + } + } + return System.nanoTime() - start; + } + + @Override + public long readIndexedField() throws SQLException { + long start = System.nanoTime(); + Cursor c = null; + try { + SQLiteDatabase db = mHelper.getReadableDatabase(); + String selection = Message.COMMAND_ID + "=?"; + String[] selectionArgs = new String[] { String + .valueOf(LOOK_BY_INDEXED_FIELD) }; + c = db.query(Message.TABLE_NAME, Message.PROJECTION, selection, + selectionArgs, null, null, null); + + if (c != null && c.moveToFirst()) { + Message newMessage = new Message(); + newMessage.setChannelId(c.getLong(c + .getColumnIndex(Message.CHANNEL_ID))); + newMessage.setClientId(c.getLong(c + .getColumnIndex(Message.CLIENT_ID))); + newMessage.setCommandId(c.getLong(c + .getColumnIndex(Message.COMMAND_ID))); + newMessage.setContent(c.getString(c + .getColumnIndex(Message.CONTENT))); + newMessage.setCreatedAt(c.getInt(c + .getColumnIndex(Message.CREATED_AT))); + newMessage.setSenderId(c.getLong(c + .getColumnIndex(Message.SENDER_ID))); + newMessage.setSortedBy(c.getDouble(c + .getColumnIndex(Message.SORTED_BY))); + + Log.d(SQLiteRawExecutor.class.getSimpleName(), + "Read, " + c.getCount() + " rows"); + } + } finally { + if (c != null) { + c.close(); + } + } + return System.nanoTime() - start; + } + + @Override + public long readSearch() throws SQLException { + long start = System.nanoTime(); + Cursor c = null; + try { + SQLiteDatabase db = mHelper.getReadableDatabase(); + String selection = Message.CONTENT + " LIKE ?"; + List messages = new LinkedList(); + String[] selectionArgs = new String[] { '%' + SEARCH_TERM + '%' }; + c = db.query(Message.TABLE_NAME, Message.PROJECTION, selection, + selectionArgs, null, null, null, + String.valueOf(SEARCH_LIMIT)); + + if(c != null) { + + int channelIdIndex = c.getColumnIndex(Message.CHANNEL_ID); + int clientIdIndex = c.getColumnIndex(Message.CLIENT_ID); + int commandIdIndex = c.getColumnIndex(Message.COMMAND_ID); + int contentIndex = c.getColumnIndex(Message.CONTENT); + int createdAtIndex = c.getColumnIndex(Message.CREATED_AT); + int senderIdIndex = c.getColumnIndex(Message.SENDER_ID); + int sortedByIndex = c.getColumnIndex(Message.SORTED_BY); + + while (c.moveToNext()) { + Message newMessage = new Message(); + newMessage.setChannelId(c.getLong(channelIdIndex)); + newMessage.setClientId(c.getLong(clientIdIndex)); + newMessage.setCommandId(c.getLong(commandIdIndex)); + newMessage.setContent(c.getString(contentIndex)); + newMessage.setCreatedAt(c.getInt(createdAtIndex)); + newMessage.setSenderId(c.getLong(senderIdIndex)); + newMessage.setSortedBy(c.getDouble(sortedByIndex)); + + messages.add(newMessage); + } + } + + Log.d(SQLiteRawExecutor.class.getSimpleName(), + "Read, " + messages.size() + " rows"); + } finally { + if (c != null) { + c.close(); + } + } + return System.nanoTime() - start; + } + + @Override + public long dropDb() throws SQLException { + long start = System.nanoTime(); + User.dropTable(mHelper); + Message.dropTable(mHelper); + return System.nanoTime() - start; + } +} diff --git a/ORM-Benchmark/src/main/java/com/littleinc/orm_benchmark/optimizedsqlite/User.java b/ORM-Benchmark/src/main/java/com/littleinc/orm_benchmark/optimizedsqlite/User.java new file mode 100644 index 0000000..43d3dcc --- /dev/null +++ b/ORM-Benchmark/src/main/java/com/littleinc/orm_benchmark/optimizedsqlite/User.java @@ -0,0 +1,66 @@ +package com.littleinc.orm_benchmark.optimizedsqlite; + +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteStatement; +import android.provider.BaseColumns; + +public class User { + + public static final String TABLE_NAME = "user"; + + public static final String LAST_NAME_COLUMN = "last_name"; + + public static final String FIRST_NAME_COLUMN = "first_name"; + + private long mId; + + private String mLastName; + + private String mFirstName; + + public static void createTable(DataBaseHelper helper) { + helper.getWritableDatabase().execSQL( + new StringBuilder("CREATE TABLE '").append(TABLE_NAME) + .append("' ('").append(BaseColumns._ID) + .append("' INTEGER PRIMARY KEY AUTOINCREMENT, '") + .append(LAST_NAME_COLUMN).append("' TEXT, '") + .append(FIRST_NAME_COLUMN).append("' TEXT);") + .toString()); + } + + public static void dropTable(DataBaseHelper helper) { + SQLiteDatabase db = helper.getWritableDatabase(); + + db.execSQL(new StringBuilder("DROP TABLE '").append(TABLE_NAME) + .append("';").toString()); + } + + public long getId() { + return mId; + } + + public void setId(long id) { + this.mId = id; + } + + public String getLastName() { + return mLastName; + } + + public void setLastName(String lastName) { + this.mLastName = lastName; + } + + public String getFirstName() { + return mFirstName; + } + + public void setFirstName(String firstName) { + this.mFirstName = firstName; + } + + public void prepareForInsert(final SQLiteStatement insertUser) { + insertUser.bindString(1, mLastName); + insertUser.bindString(2, mFirstName); + } +}