Можно ли получить больший импульс оптимизации для следующего пакетного подхода INSERT для SQLIte в Android? - PullRequest
0 голосов
/ 23 февраля 2019

Я занимаюсь разработкой кроссплатформенного средства импорта данных и в настоящее время пытаюсь оптимизировать общую производительность импорта как в среде выполнения 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, или это просто близко к пределам, так что без оптимизации дальше?Благодаря.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...