关于AndroBench在Android 10以后性能衰减的问题分析
背景
自Android 10发布以来,由于Google默认禁用了在Android P时引入的Compatibility WAL,加之AndroBench这一应用在代码层面存在于Android SQLite API不兼容的情况,导致Android 10以后的AndroBench SQLite部分跑分出现大幅下滑;
通过反编译AndroBench,我们得知,主要原因在于AndroBench没有使用Android framework API (enableWriteAheadLogging),而是使用raw语句来设置日志模式(PRAGMA journal_mode=wal),这导致Android framework无法获知日志模式发生改变,继而无法自动切换与之匹配的默认同步模式(PRAGMA synchronous)。
原生AOSP对于日志模式与同步模式的默认匹配如下:
WAL - NORMAL
非WAL - FULL
默认由于默认禁用了Compatibility WAL,因此Android framework默认认为所有SQLite数据库的日志模式为DELETE(可在config.xml中修改db_default_journal_mode的值来改变这一默认日志模式),因此对应的同步模式为FULL
正常情况下,如果APP调用enableWriteAheadLogging来显式切换日志模式为WAL,Android framework会自动切换默认的同步模式;
而AndroBench是通过rawQuery进行参数传导,这导致Android framework API无法监测并及时修改默认的同步模式,从而使AndroBench在Android 10以上的版本中测试SQLite时使用WAL的日志模式,却在没有显式修改同步模式的情况下,没有使用默认的NORMAL同步模式;
分析步骤
1. 反编译AndroBench:
AndroBench没有使用混淆、加固等手段,因此使用d2j-dex2jar即可反编译;
定位到Testing_SQLite_DBHelper.java后发现,其在onConfigure回调中做了如下操作:
public void onConfigure(final SQLiteDatabase sqLiteDatabase) {
final Cursor rawQuery = sqLiteDatabase.rawQuery("PRAGMA journal_mode=" + this.journalMode, (String[])null);
rawQuery.moveToNext();
System.out.println("journal_mode=" + rawQuery.getString(0));
rawQuery.close();
}
这种情况下,Android framework只会将字符串直接下发给sqlite,而不会去判断其参数的分类,从而导致此时同步模式依旧为非WAL的默认值——FULL;
Q:为什么Android P版本此处是NORMAL?
A:因为在Android P上,Google引入了compatibility wal模式的默认支持,因此除非应用显示禁用WAL模式:
如下是Android P上通过日志模式确认同步模式的代码,Android 10上是类似的,就不分别列举了:
private void setWalModeFromConfiguration() {
if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
// 此处会在AndroBench通过rawQuery修改日志模式之前执行,并且由于其没有通过API调用,因此此处始终为false;
final boolean walEnabled =
(mConfiguration.openFlags & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0;
// Use compatibility WAL unless an app explicitly set journal/synchronous mode
// or DISABLE_COMPATIBILITY_WAL flag is set
// 此处Android P上默认为true,Android 10上默认为false
final boolean useCompatibilityWal = mConfiguration.useCompatibilityWal();
if (walEnabled || useCompatibilityWal) {
//Android P走这里面
setJournalMode("WAL");
if (mConfiguration.syncMode != null) {
setSyncMode(mConfiguration.syncMode);
} else if (useCompatibilityWal && SQLiteCompatibilityWalFlags.areFlagsSet()) {
setSyncMode(SQLiteCompatibilityWalFlags.getWALSyncMode());
} else {
setSyncMode(SQLiteGlobal.getWALSyncMode());
}
} else {
//Android 10走这里面
setJournalMode(mConfiguration.journalMode == null
? SQLiteGlobal.getDefaultJournalMode() : mConfiguration.journalMode);
setSyncMode(mConfiguration.syncMode == null
? SQLiteGlobal.getDefaultSyncMode() : mConfiguration.syncMode);
}
}
}
2. 验证效果
如果上述分析正确,那么通过修改SQLiteGlobal.java中的getDefaultSyncMode()方法的参数,则可作用于AndroBench的同步模式;
以某SM7250平台手机(6GB + 128GB)为例,在user版本上进行验证:
(非正式版,此处仅用于展示改动前后的变化,具体数值在正式版上可能会有差异)
默认情况下:
Insert: 1822.90 1828.72 1896.34
Update: 2452.78 2499.33 2559.36
Delete: 2783.88 2760.58 2789.39
通过adb shell setprop debug.sqlite.syncmode NORMAL后:
Insert: 3100.91 3128.59 3045.44
Update: 3863.16 3956.29 3999.25
Delete: 4966.03 4933.55 4834.18
简单取平均值后对比发现,当WAL工作在NORMAL同步模式下时,AndroBench成绩较FULL同步模式分别提升:
FULL NORMAL DIFF
Insert: 1849.32 3091.65 167%
Update: 2503.82 3939.57 157%
Delete: 2777.95 4911.25 176%
3. 改进措施
a. 通过在父类SQLiteOpenHelper的onConfigure回调之后进行判断,如果Android framework API检测到WAL没有启用(!db.isWriteAheadLoggingEnabled()),但实际通过rawQuery获取到的journal_mode确是WAL,此时认为App通过了rawQuery修改日志模式,此时显式调用一次enableWriteAheadLogging()即可;
b. 通过对SQL语句进行拦截,对PRAGMA journal_mode与PRAGMA synchronous两个参数的设置进行判断、绑定,使其与Android framework API的表现一致;
目前a方案已经开发完成,但是考虑到其可能存在部分特殊App功能发生改变:
如果存在某一应用,同时使用了PRAGMA journal_mode=wal,与PRAGMA synchronous=FULL(或OFF),方案a会覆盖其同步模式为NORMAL,这改变了App本身的逻辑,不符合修改的基本要求;
因此建议使用b方案实现;