Подтвердить что ты не робот

Эффективно отправлять большие int [] поверх сокетов в Java

Я работаю над Java-приложением, где мне нужно как можно быстрее отправить массив из 500 000 целых чисел с одного телефона Android на другой телефон Android через сокет. Главным узким местом, по-видимому, является преобразование целых чисел, поэтому сокет может их принимать, независимо от того, использую ли я ObjectOutputStreams, ByteBuffers или низкоуровневое преобразование масок и сдвиг. Каков самый быстрый способ отправить int [] через сокет из одного приложения Java в другое?

Вот код для всего, что я пробовал до сих пор, с бенчмарками на LG Optimus V, на которых я тестирую (процессор ARM с частотой 600 МГц, Android 2.2).

Маска и сдвиг низкого уровня: 0,2 секунды

public static byte[] intToByte(int[] input)
{
    byte[] output = new byte[input.length*4];

    for(int i = 0; i < input.length; i++) {
        output[i*4] = (byte)(input[i] & 0xFF);
        output[i*4 + 1] = (byte)((input[i] & 0xFF00) >>> 8);
        output[i*4 + 2] = (byte)((input[i] & 0xFF0000) >>> 16);
        output[i*4 + 3] = (byte)((input[i] & 0xFF000000) >>> 24);
    }

    return output;
}

Использование ByteBuffer и IntBuffer: 0,75 секунды

public static byte[] intToByte(int[] input)
{
    ByteBuffer byteBuffer = ByteBuffer.allocate(input.length * 4);        
    IntBuffer intBuffer = byteBuffer.asIntBuffer();
    intBuffer.put(input);

    byte[] array = byteBuffer.array();

    return array;
}

ObjectOutputStream: 3,1 секунды (я испробовал варианты этого с использованием DataOutPutStream и writeInt() вместо writeObject(), но это не сильно повлияло)

public static void sendSerialDataTCP(String address, int[] array) throws IOException
{
    Socket senderSocket = new Socket(address, 4446);

    OutputStream os = senderSocket.getOutputStream();
    BufferedOutputStream  bos = new BufferedOutputStream (os);
    ObjectOutputStream oos = new ObjectOutputStream(bos);
    oos.writeObject(array);

    oos.flush();
    bos.flush();
    os.flush();
    oos.close();
    os.close();
    bos.close();

    senderSocket.close();
}

Наконец, код, который я использовал для отправки байта []: занимает дополнительные 0.2 секунды над функциями intToByte()

public static void sendDataTCP(String address, byte[] data) throws IOException
{
    Socket senderSocket = new Socket(address, 4446);

    OutputStream os = senderSocket.getOutputStream();
    os.write(data, 0, data.length);
    os.flush();

    senderSocket.close();
}

Я пишу код с обеих сторон сокета, поэтому я могу попробовать любой тип endianness, сжатие, сериализацию и т.д. Должен быть способ сделать это преобразование более эффективно на Java. Пожалуйста, помогите!

4b9b3361

Ответ 1

Как я уже отмечал в комментарии, я думаю, что вы сталкиваетесь с ограничениями своего процессора. Поскольку это может быть полезно другим, я сломаю его. Здесь ваш цикл для преобразования целых чисел в байты:

    for(int i = 0; i < input.length; i++) {
        output[i*4] = (byte)(input[i] & 0xFF);
        output[i*4 + 1] = (byte)((input[i] & 0xFF00) >>> 8);
        output[i*4 + 2] = (byte)((input[i] & 0xFF0000) >>> 16);
        output[i*4 + 3] = (byte)((input[i] & 0xFF000000) >>> 24);
    }

Этот цикл выполняет 500 000 раз. Процессор 600 МГц может обрабатывать около 600 000 000 операций в секунду. Поэтому каждая итерация цикла будет потреблять примерно 1/1200 секунды для каждой операции.

Опять же, используя очень грубые цифры (я не знаю набор команд ARM, поэтому может быть больше или меньше за действие), здесь количество операций:

  • Test/branch: 5 (восстановить счетчик, получить длину массива, сравнить, разветкить, счетчик приращений)
  • Маска и сдвиг: 10 x 4 (восстановить счетчик, получить базу входных массивов, добавить, восстановить маску и, сдвинуть, умножить счетчик, добавить смещение, добавить в базу вывода, сохранить)

ОК, поэтому в грубых числах этот цикл занимает в лучшем случае 55/1200 секунды, или 0,04 секунды. Однако вы не имеете дело с лучшим сценарием. Во-первых, с большим массивом вы не будете использовать кеш процессора, поэтому вы будете вводить состояния ожидания в каждый массив и загружать хранилище.

Кроме того, основные операции, которые я описал, могут или не могут быть переведены непосредственно в машинный код. Если нет (и я подозреваю, что нет), цикл будет стоить дороже, чем я описал.

Наконец, если вам действительно не повезло, JVM не JIT-ed ваш код, поэтому для некоторой части (или всего) цикла он интерпретирует байт-код, а не выполняет собственные инструкции. Я не знаю достаточно о Дальвике, чтобы прокомментировать это.

Ответ 2

Java была IMO, которая никогда не предназначалась для эффективного реинтерпретации области памяти от int[] до byte[], как вы могли бы сделать на C. У нее даже нет такой модели адресной памяти.

Вам либо нужно отправиться на родной, чтобы отправить данные, либо попытаться найти некоторые микро-оптимизации. Но я сомневаюсь, что вы выиграете много.

например. это может быть немного быстрее, чем ваша версия (если она вообще работает)

public static byte[] intToByte(int[] input)
{
    byte[] output = new byte[input.length*4];

    for(int i = 0; i < input.length; i++) {
        int position = i << 2;
        output[position | 0] = (byte)((input[i] >>  0) & 0xFF);
        output[position | 1] = (byte)((input[i] >>  8) & 0xFF);
        output[position | 2] = (byte)((input[i] >> 16) & 0xFF);
        output[position | 3] = (byte)((input[i] >> 24) & 0xFF);
    }
    return output;
}

Ответ 3

Если вы не отрицательно относитесь к использованию библиотеки, вы можете проверить "Буферы протоколов" в Google. Он построен для гораздо более сложной сериализации объектов, но я бы поспорил, что они много работали над тем, как быстро сериализовать массив целых чисел в Java.

EDIT: я посмотрел исходный код Protobuf, и он использует что-то похожее на вашу маску низкого уровня и сдвиг.

Ответ 4

Я бы сделал это следующим образом:

Socket senderSocket = new Socket(address, 4446);

OutputStream os = senderSocket.getOutputStream();
BufferedOutputStream bos = new BufferedOutputStream(os);
DataOutputStream dos = new DataOutputStream(bos);

dos.writeInt(array.length);
for(int i : array) dos.writeInt(i);
dos.close();

С другой стороны, прочитайте это как:

Socket recieverSocket = ...;
InputStream is = recieverSocket.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
DataInputStream dis = new DataInputStream(bis);

int length = dis.readInt();
int[] array = new int[length];

for(int i = 0; i < length; i++) array[i] = dis.readInt();
dis.close();