Я занимаюсь разработкой кроссплатформенного средства импорта данных и в настоящее время пытаюсь оптимизировать общую производительность импорта как в среде выполнения JRE, так и в среде Android.Используя JDBC, мне удалось оптимизировать импортер по тестовым данным (~ 500K строк, если я не ошибаюсь) для времени выполнения JRE от 12 с до 3,5 с 4, просто используя JDBC addBatch
и executeBatch
со всем выполненнымв одной транзакции.Теперь я пытаюсь перенести некоторые компоненты в среду выполнения Android, и у меня гораздо более низкая производительность.Я прочитал много связанных вопросов по SO и статьям в Интернете, но я не совсем уверен, правильно ли то, что я делаю для Android.Следующий псевдо-демонстрационный подход получил некоторое повышение производительности с 119-120 до 59-61 с на моем мобильном устройстве (кэширование скомпилированных операторов, пакетная обработка и генерация массовых INSERT
операторов).Итак, вот упрощенная версия прототипа (возможны ошибки при преобразовании из прототипа в псевдо-демонстрационный код):
final class Test {
private Test() {
}
// SQLite meta stuff
private static final int MAX_SQLITE_VARS_PER_STATEMENT = 999;
// Application core
private static final class Foo {
}
private static final class Bar {
}
// SQL stuff
private static final int FOO_TUPLE_SIZE = 8;
private static final int BAR_TUPLE_SIZE = 4;
private static final Joiner commaJoiner = Joiner.on(',');
private static SQLiteStatement compileStatement(final Class<?> clazz, final int bulkCount, final SQLiteDatabase database) {
final String statementPattern;
final String variableTuple;
final int variableCount;
if ( clazz == Foo.class ) {
statementPattern = "INSERT INTO foos(c1,c2,c3,c4,c5,c6,c7,c8)VALUES %s;";
variableTuple = "(?,?,?,?,?,?,?,?)";
variableCount = FOO_TUPLE_SIZE;
} else if ( clazz == Bar.class ) {
statementPattern = "INSERT INTO bars(c1,c2,c3,c4)VALUES %s;";
variableTuple = "(?,?,?,?)";
variableCount = BAR_TUPLE_SIZE;
} else {
throw new AssertionError(clazz);
}
if ( bulkCount * variableCount > MAX_SQLITE_VARS_PER_STATEMENT ) {
throw new IllegalArgumentException("Too many variables");
}
final String joinedTuples = commaJoiner.join(Iterables.limit(Iterables.cycle(variableTuple), bulkCount));
final String statement = String.format(statementPattern, joinedTuples);
return database.compileStatement(statement);
}
private static SQLiteStatement bindFoosStatement(final Collection<Foo> foos) {
final SQLiteStatement statement = statementCache.getUnchecked(new BulkKey(Foo.class, foos.size()));
int i = 0;
for ( final Foo foo : foos ) {
// bind all variables:
// * bind variable 1 (and then 9, 17, 25...)
// * bind variable 2 (and then 10, 18, 26...)
// ...
// * bind variable 8 (and then 16, 24, 32...)
i += FOO_TUPLE_SIZE;
}
return statement;
}
private static SQLiteStatement bindBarsStatement(final Collection<Bar> bars) {
final SQLiteStatement statement = statementCache.getUnchecked(new BulkKey(Bar.class, bars.size()));
int i = 0;
for ( final Bar bar : bars ) {
// bind all variables:
// * bind variable 1 (and then 5, 9, 13...)
// * bind variable 2 (and then 6, 10, 14...)
// ...
// * bind variable 4 (and then 8, 12, 16...)
i += BAR_TUPLE_SIZE;
}
return statement;
}
// Statements caching
@EqualsAndHashCode
private static final class BulkKey {
private final Class<?> clazz;
private final int count;
private BulkKey(final Class<?> clazz, final int count) {
this.clazz = clazz;
this.count = count;
}
}
private static final LoadingCache<BulkKey, SQLiteStatement> statementCache = CacheBuilder.newBuilder()
.removalListener((RemovalListener<BulkKey, SQLiteStatement>) notification -> notification.getValue().close())
.build(new CacheLoader<BulkKey, SQLiteStatement>() {
@Override
public SQLiteStatement load(final BulkKey bulkKey) {
return compileStatement(bulkKey.clazz, bulkKey.count, sqliteDatabase);
}
});
// Application
private static final SQLiteDatabase sqliteDatabase = null; // dummy
private static final List<Foo> foos = ImmutableList.copyOf(Iterables.limit(Iterables.cycle(new Foo()), 10000));
private static final List<Bar> bars = ImmutableList.copyOf(Iterables.limit(Iterables.cycle(new Bar()), 4500));
public static void main(final String... args) {
// The following code is a very simplified version of the prototype
// The prototype is written in reactive manner and the code below just mimics the batching
// Initialize the import operation
final List<List<Foo>> type1Batches = Lists.partition(foos, MAX_SQLITE_VARS_PER_STATEMENT / FOO_TUPLE_SIZE); // = 124
sqliteDatabase.beginTransaction();
for ( final List<Foo> batch : type1Batches ) {
final SQLiteStatement statement = bindFoosStatement(batch);
statement.executeInsert();
statement.clearBindings();
}
final List<List<Bar>> type2Batches = Lists.partition(bars, MAX_SQLITE_VARS_PER_STATEMENT / BAR_TUPLE_SIZE); // = 249
for ( final List<Bar> batch : type2Batches ) {
final SQLiteStatement statement = bindBarsStatement(batch);
statement.executeInsert();
statement.clearBindings();
}
// Finalize the import operation
sqliteDatabase.setTransactionSuccessful();
sqliteDatabase.endTransaction();
statementCache.invalidateAll();
}
}
Приведенный выше подход пытается использовать конвертирование пакетов в объем INSERT
операторов в зависимости от размера пакета и количества переменных в указанном объекте кортежа класса, не превышающем ограничение SQLite в 999 переменных на оператор.
Мне интересно, чего мне не хватает, чтобы я мог повысить производительностьBoost, или это просто близко к пределам, так что без оптимизации дальше?Благодаря.