Эффективно отправлять большие 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. Пожалуйста, помогите!
Ответы
Ответ 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();