Как я могу ускорить производительность Java DatagramSocket? - PullRequest
3 голосов
/ 27 мая 2009

Я использую класс Java DatagramSocket для отправки грамм данных UDP на конечную точку. Датаграмма должна прибыть в конечную точку с интервалом 60 мс.

Я обнаружил, что DatagramSocket.Send часто может занимать> 1 мс (около 2) для упаковки и отправки пакетов размером не более 56 байт. Это приводит к тому, что мои пакеты доставляются с интервалами 62 мс, а не с интервалами 60 мс.

Это на машине с Windows Vista. Вот как я измеряю время:

              DatagramPacket d = new DatagramPacket(out, out.length, address, port);
              long nanoTime = System.nanoTime();
    socket.send(d);
    long diff = System.nanoTime() - nanoTime;
    System.out.println( out.length + " in " + diff + "ms." );

Есть ли у кого-нибудь советы или хитрости для ускорения этого процесса?

Ответы [ 9 ]

6 голосов
/ 27 мая 2009

Вы можете использовать класс Timer для планирования события.

    Timer timer = new Timer();
    TimerTask task = new TimerTask() {
        public void run() {
            //send packet here
        }};
    timer.scheduleAtFixedRate(task, 0, 60);

Это создаст повторяющееся событие каждые 60 мсек для выполнения команды «Выполнить». При прочих равных условиях пакет должен подключаться каждые 60 мс (хотя первый пакет будет задержан на некоторое количество, а сборка мусора / другие задачи / и т. Д. Может немного задержать это число).

4 голосов
/ 27 мая 2009

Вы видите время, затрачиваемое на копирование данных из пространства пользователя в пространство ядра. Отправка через уровни UDP, IP и Ethernet занимает еще больше времени, и датаграмма может пересекать физическую сеть к месту назначения в течение различного времени.

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

Вам необходимо вызывать send каждые 60 мсек, независимо от того, сколько времени занимает выполнение метода send (). Вы не можете ждать 60 мс между вызовами. Вам нужно измерить, сколько времени потребуется, чтобы выполнить тело вашего цикла (send () и все остальное) и вычесть это из 60 мс, чтобы получить время ожидания.

2 голосов
/ 28 мая 2009

Используйте таймер, как упоминал Джеймс Ван Хьюис. Таким образом, вы по крайней мере получите правильную среднюю частоту.

Цитата из Javadoc:

Если выполнение по какой-либо причине задерживается (например, сборка мусора или другие фоновые действия), два или более выполнения будут выполняться в быстрой последовательности, чтобы «догнать». В долгосрочной перспективе частота выполнения будет точно равна обратной величине указанного периода (при условии, что системные часы, лежащие в основе Object.wait (long), точны).

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

    datagram.setData(out);
    socket.send(datagram);

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

1 голос
/ 25 января 2011

У меня была такая же проблема, как и у вас. Я нашел решение. Короче говоря, вот оно:

Найдите класс Java ScheduledThreadPoolExecutor, он есть в Java 5 JDK / JRE. (я могу опубликовать только одну ссылку, как я только что узнал, иначе я бы указал на Oracle JavaDoc)

ScheduledThreadPoolExecutor schedThPoolExec = new ScheduledThreadPoolExecutor(1);   

/*
 * 
 * cue a byte buffer for sending in equal segments on the udp port with a inter-pkt-delay
 * 
 */
public void send(byte[] data, String destinationHost, int destinationPort, double interPacketDelayMs) {

        long interDelayNanos = (long) ( interPacketDelayMs * 1000000.0 );

        schedThPoolExec.scheduleAtFixedRate( new SendPacketsTimerTask(data, destinationHost, destinationPort), 0, interDelayNanos , TimeUnit.NANOSECONDS);

}

/*
 * 
 *
 * 
 */

class SendPacketsTimerTask implements Runnable {

    int offset = 0;
    byte[] buffer;
    String host;
    int port;

    public SendPacketsTimerTask(byte[] buffer, String destinationHost, int destinationPort) {

        this.buffer = buffer;
        host = destinationHost;
        port = destinationPort;
    }

    @Override
    public void run() {

        if(offset + PKT_SIZE < buffer.length) {

            //copy from cue to packet               
            byte[] tmp_pkt_buffer = new byte[PKT_SIZE];
            System.arraycopy(buffer, offset, tmp_pkt_buffer, 0, PKT_SIZE);

            try {
                //send packet       
                socket.send( new DatagramPacket(tmp_pkt_buffer, tmp_pkt_buffer.length, InetAddress.getByName(host), port) );
                //increment offset
                offset += tmp_pkt_buffer.length;



            } catch (UnknownHostException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } 

        }
    }
}

Наряду с классом TimerTask (как уже упоминалось выше) вы можете запланировать периодическое проведение мероприятия. Теперь вам просто нужно написать TimerTask, который будет отправлять ваши сообщения, или, как в моем случае, буфер данных.

Моя проблема была на самом деле в том, что я обрабатываю потоки мультимедиа в реальном времени и мне нужна пропускная способность 15 Мбит + (видеоданные). Это приводит к задержке между пакетами в 0,5 мс. Таким образом, гранулярность метода Thread.sleep (который принимает аргументы в наносекундах true, но, тем не менее, имеет гранулярность в миллисекундах - также true). Так что я застрял с 6 Мбит скорость передачи. Когда я проверил класс Timer, я подумал, что нашел свое решение. Окончательно обнаружив, что этот класс не справлялся и с моими низкими периодами исполнения. В поисках людей с похожими проблемами я нашел эту статью что было очень полезно. Вместо класса Timer вы можете использовать вышеупомянутый класс планировщика потоков, который, соответственно, привязан к собственному коду, используя полную производительность вашей системы, для периодического запуска метода send с максимально возможным разрешением.

Примечание: общее значение (также у моего работодателя) Java было бы «слишком медленным», «иметь неточные временные характеристики») для выполнения критически важных приложений, и высокая пропускная способность данных в сети, как правило, считается НЕПРАВИЛЬНОЙ. Это было правдой. Наконец, с Java 5 мы можем достичь всех возможных возможностей синхронизации и, следовательно, производительности приложений:)

1 голос
/ 27 мая 2009

Кроме очевидного и хитроумного ответа «подожди только 59 мс», на самом деле не так уж много можно сделать. Любая операция, которую вы выполняете, займет некоторое время, которое вряд ли будет последовательным. Таким образом, невозможно гарантировать, что ваши пакеты будут доставлены с интервалом точно в 60 мс.

Помните, что требуется время, чтобы обернуть ваше крошечное 56-байтовое сообщение в заголовки, необходимые для уровней UDP и IP, и еще больше времени, чтобы перенаправить его на сетевую карту и отправить в путь. Это добавляет еще 8 байтов для уровня UDP, 20 для уровня IP и еще больше для того, что нужно канальному уровню. Вы ничего не можете сделать, чтобы избежать этого.

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

0 голосов
/ 04 мая 2012

Вы измеряете nanoTime, поэтому он даст вам нано секунды вместо миллисекунд.

long diff = System.nanoTime () - nanoTime; System.out.println (out.length + "in" + diff + "ms.");

0 голосов
/ 27 мая 2009

Поскольку вы не используете Java в реальном времени, нет никакого способа убедиться, что вы всегда будете отправлять пакет каждые 60 мс. Я бы настроил поток таймера, который будет «уведомлять» о двух других ожидающих потоках, которые фактически отправляют пакет. Вы можете обойтись только одним потоком для отправки, но я вроде как с резервной копией на случай, если есть проблема.

0 голосов
/ 27 мая 2009

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

Есть ли причина, по которой пакеты должны приниматься с точностью до 60 мс? Если это так, существуют другие протоколы, которые могут помочь вам достичь этого.

0 голосов
/ 27 мая 2009

Как насчет отправки ваших пакетов с интервалом 58 миллисекунд?

Независимо от того, как вы оптимизируете (и на самом деле никаких возможностей для этого нет; использование NIO, ориентированной на канал, сделает ту же работу), для отправки данных потребуется некоторое время, и, скорее всего, изменчивость там. Если требуется точное время, требуется некоторая стратегия, которая признает время передачи.

Также обратите внимание на измерение: не измеряйте задержку до тех пор, пока не будет выполнено несколько тысяч итераций. Это дает оптимизатору возможность выполнять свою работу и дает более репрезентативные сроки.


Одно время разрешение по времени в Windows было плохим. Тем не менее, разрешение в 1 миллисекунду стало обычным явлением. Попробуйте следующий тест, чтобы увидеть, насколько точна ваша машина.

  public static void main(String... argv)
    throws InterruptedException
  {
    final int COUNT = 1000;
    long time = System.nanoTime();
    for (int i = 0; i < COUNT; ++i) {
      Thread.sleep(57);
    }
    time = System.nanoTime() - time;
    System.out.println("Average wait: " + (time / (COUNT * 1000000F)) + " ms");
  }

На моем компьютере с Windows XP среднее время ожидания составляет 57,7 мс.

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