Быстро ли работают Java-классы ByteBuffer / IntBuffer / ShortBuffer? - PullRequest
5 голосов
/ 28 октября 2010

Я работаю над приложением Android (очевидно, на Java) и недавно обновил свой код для чтения UDP. В обеих версиях я настроил несколько буферов и получил пакет UDP:

byte[] buf = new byte[10000];
short[] soundData = new short[1000];
DatagramPacket packet = new DatagramPacket (buf, buf.length);
socket.receive (packet);

В первоначальной версии я собирал данные обратно по одному байту за раз (на самом деле это 16 аудиоданных PCM):

for (int i = 0; i < count; i++)
    soundData[i] = (short) (((buf[k++]&0xff) << 8) + (buf[k++]&0xff));

В обновленной версии я использовал несколько классных инструментов Java, о которых я не знал, когда начинал:

bBuffer  = ByteBuffer.wrap (buf);
sBuffer  = bBuffer.asShortBuffer();
sBuffer.get (soundData, 0, count);

В обоих случаях «количество» заполняется правильно (я проверял). Тем не менее, появляются новые проблемы с моим потоковым аудио - возможно, оно обрабатывается недостаточно быстро - что не имеет никакого смысла для меня. Очевидно, что буферный код компилируется в более чем три оператора кода JVM, но когда я начинал, мне показалось разумным предположение, что вторая версия будет быстрее первой.

Патентно, я не настаиваю на том, чтобы в моем коде использовались буферы Java NIO, но, по крайней мере, на первый взгляд, это выглядит как "бетта" для этого.

Кто-нибудь получил какие-либо рекомендации для быстрого и простого считывателя Java UDP и есть ли общепринятый «лучший способ» ??

Спасибо, Р.

Ответы [ 3 ]

3 голосов
/ 11 мая 2012

Ваш код был бы более эффективным, если бы вместо чтения пакета в байтовый массив (копирование данных из собственного буфера в массив), а затем обернуть его в новый ByteBuffer (создать новый объект) и преобразовать в ShortBuffer(создание нового объекта) вы устанавливаете свои объекты только один раз и избегаете копирования.

Вы можете сделать это, используя DatagramChannel.socket () для создания вашего сокета, затем подключив его как обычно и используя socket.getChannel() чтобы получить объект DatagramChannel.Этот объект позволит вам читать пакеты напрямую в существующий ByteBuffer (который вы должны создать с помощью ByteBuffer.allocateDirect для максимальной эффективности).Затем вы можете использовать asShortBuffer () всего один раз, чтобы создать представление ваших данных в виде шортов, и читать из этого ShortBuffer после каждого повторного заполнения ByteBuffer.

Код выглядит следующим образом:

 DatagramSocket socket = DatagramChannel.socket();
 // code to connect socket
 DatagramChannel channel = socket.getChannel();
 ByteBuffer buffer = ByteBuffer.allocateDirect (10000);
 // you may want to invoke buffer.order(...) here to tell it what byte order to use
 ShortBuffer shortBuf = buffer.asShortBuffer();

 // in your receive loop:
 buffer.clear();
 channel.receive(buffer);
 shortBuf.position(0).limit(buffer.position()/2); // may ignore a byte if odd number received
 shortBuf.get(soundBuf,0,shortBuf.limit());

Вы должны обнаружить, что это намного более эффективно, чем ваш предыдущий код, потому что он позволяет избежать полной копии данных, а преобразование формата обрабатывается оптимизированным вручную кодом, а не созданной компилятором обработкой байтов, которая может быть неоптимальной.Это будет несколько более эффективно, если вы будете использовать собственный порядок байтов в платформе (я полагаю, что Android использует порядок байтов с прямым порядком байтов на всех платформах, для которых он доступен, и ваш приведенный выше код выглядит как big-endian, так что это может оказаться невозможным).для вас), в этом случае shortBuf.get () становится прямой копией памяти.

1 голос
/ 28 октября 2010

В целом, работа с примитивными типами напрямую будет более эффективной, чем работа с объектами, потому что вы избегаете некоторых накладных расходов при создании объектов, вызовах функций и т. Д.

Существуют и другие причины использования служебных объектов, кроме скорости: удобство, безопасность и т. Д.

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

Вы также можете использовать профилировщик Android, чтобы увидеть, где ваши проблемы на самом деле. См. TraceView .

0 голосов
/ 29 октября 2010

Я бы использовал DataInputStream для этой задачи, обернутый вокруг ByteArrayInputStream, обернутый вокруг байтового массива. Инкапсулирует сдвиг в readShort () без издержек ByteBuffer.

В качестве альтернативы вы можете читать напрямую в DirectByteBuffer через DatagramChannel, а не с помощью DatagramPacket & DatagramSocket. В настоящее время вы используете смесь java.net и java.nio.

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

...