关于AndroBench在Android 10以后性能衰减的问题分析

关于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方案实现;

友情链接: