Используйте Java небезопасно, чтобы указать массив символов в ячейке памяти - PullRequest
0 голосов
/ 13 октября 2018

Некоторый анализ Java-приложения показал, что он тратит много времени на декодирование байтовых массивов UTF-8 в объекты String.Поток байтов UTF-8 поступает из базы данных LMDB, а значения в базе данных являются сообщениями Protobuf, поэтому он так сильно декодирует UTF-8.Это вызвано еще одной проблемой: строки занимают большой кусок памяти из-за декодирования из карты памяти в объект String в JVM.

Я хочу реорганизовать это приложение, чтобы оно не выделялосьновая строка каждый раз, когда она читает сообщение из базы данных.Я хочу, чтобы базовый массив char в объекте String просто указывал на область памяти.

package testreflect;

import java.lang.reflect.Field;

import sun.misc.Unsafe;

public class App {
    public static void main(String[] args) throws Exception {
        Field field = Unsafe.class.getDeclaredField("theUnsafe");
        field.setAccessible(true);
        Unsafe UNSAFE = (Unsafe) field.get(null);

        char[] sourceChars = new char[] { 'b', 'a', 'r', 0x2018 };

        // Encoding to a byte array; asBytes would be an LMDB entry
        byte[] asBytes = new byte[sourceChars.length * 2];
        UNSAFE.copyMemory(sourceChars, 
                UNSAFE.arrayBaseOffset(sourceChars.getClass()), 
                asBytes, 
                UNSAFE.arrayBaseOffset(asBytes.getClass()), 
                sourceChars.length*(long)UNSAFE.arrayIndexScale(sourceChars.getClass()));

        // Copying the byte array to the char array works, but is there a way to
        // have the char array simply point to the byte array without copying?
        char[] test = new char[sourceChars.length];
        UNSAFE.copyMemory(asBytes, 
                UNSAFE.arrayBaseOffset(asBytes.getClass()), 
                test, 
                UNSAFE.arrayBaseOffset(test.getClass()), 
                asBytes.length*(long)UNSAFE.arrayIndexScale(asBytes.getClass()));

        // Allocate a String object, but set its underlying 
        // byte array manually to avoid the extra memory copy   
        long stringOffset = UNSAFE.objectFieldOffset(String.class.getDeclaredField("value"));
        String stringTest = (String) UNSAFE.allocateInstance(String.class);
        UNSAFE.putObject(stringTest, stringOffset, test);
        System.out.println(stringTest);
    }
}

До сих пор я выяснил, как скопировать байтовый массив в массив char и установить базовый массивв объекте String с использованием пакета Unsafe.Это должно уменьшить количество процессорного времени, которое приложение тратит на декодирование байтов UTF-8.

Однако это не решает проблему с памятью.Есть ли способ, чтобы массив символов указывал на область памяти и вообще избегал выделения памяти?Отказ от копирования в целом сократит количество ненужных выделений, которые JVM делает для этих строк, оставляя ОС больше места для кэширования записей из базы данных LMDB.

1 Ответ

0 голосов
/ 13 октября 2018

Я думаю, что вы используете неправильный подход.

До сих пор я выяснил, как скопировать байтовый массив в массив char и установить базовый массив в объект String, используяНебезопасный пакет.Это должно уменьшить количество процессорного времени, которое приложение тратит на декодирование байтов UTF-8.

Erm ... №.

Использование копии памяти для копирования из byte[] вchar[] не собирается работать.Каждый char в пункте назначения char[] будет фактически содержать 2 байта от оригинала.Если вы затем попытаетесь обернуть char[] в String, вы получите странный вид mojibake .

Какое реальное преобразование UTF-8 в String делает этопреобразовать от 1 до 4 байтов (кодовых единиц), представляющих кодовую точку UTF-8, в 1 или 2 16-битных кодовых единицы, представляющих одну и ту же кодовую точку в UTF-16.Это невозможно сделать с помощью простой копии в памяти.

Если вы с ней не знакомы, стоит прочитать статью Википедии о UTF-8 , чтобы вы поняли как кодируется текст.


Решение зависит от того, что вы намереваетесь делать с текстовыми данными.

  • Если данные действительно должны бытьв виде String (или StringBuilder или char[]) объектов, тогда у вас действительно нет другого выбора, кроме как выполнить полное преобразование.Попробуйте что-нибудь еще, и вы можете все испортить;например искаженный текст и потенциальные сбои JVM.

  • Если вы хотите что-то, похожее на строку, вы могли бы реализовать собственный подкласс CharSequence, который оборачивает байты в сообщенияхи декодирует UTF-8 на лету.Но выполнение этого эффективно создает проблему, особенно при реализации метода charAt в качестве метода O(1).

  • Если вы просто хотите сохранить и / или сравнить (весь)тексты, это может быть сделано путем представления их в виде или в byte[] объектов.Эти операции могут выполняться непосредственно с данными в кодировке UTF-8.

  • Если входной текст действительно может быть отправлен в кодировке символов с фиксированным 8-битным размером символов (например, ASCII, Latin-1 и т. Д.) Или как UTF-16, что упрощает вещи.

...