Узкое место производительности сокетов Java: где? - PullRequest
8 голосов
/ 15 декабря 2010

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

После небольшого тестирования мы получили следующее (для localhost):

  • Отправка всегда одного и того же объекта: 31628 запросов / сек.
  • Отправка всегда нового объекта: 25 запросов / сек
  • Только скорость создания объекта: 3-4 миллиона в секунду (так что это не узкое место)

Вот код клиента: (серверная часть просто отвечает "ACK")

public static void main(String[] args) throws IOException, ClassNotFoundException
{
    Socket kkSocket = null;
    ObjectOutputStream out = null;
    ObjectInputStream in = null;


    kkSocket = new Socket("barium", 4444);
    out = new ObjectOutputStream(kkSocket.getOutputStream());
    in = new ObjectInputStream(kkSocket.getInputStream());


    long throughput;
    long millis;

    TcpRequest hello = null;


    throughput = 0;
    millis = System.currentTimeMillis();
    while (System.currentTimeMillis() < millis + 1000)
    {
        hello = new TcpRequest();
        hello.service = "hello";
        hello.payload = Math.random();
        throughput++;
    }

    System.out.println("-------------------------------------------------------");
    System.out.println("|        Objects created: " + (throughput)  + " requests/sec.");
    System.out.println("-------------------------------------------------------");


    throughput = 0;
    millis = System.currentTimeMillis();
    while (System.currentTimeMillis() < millis + 1000)
    {
        out.writeObject(hello);
        Object res = in.readObject();
        throughput++;
    }
    System.out.println("-------------------------------------------------------");
    System.out.println("|        Same object throughput: " + (throughput)  + " requests/sec.");
    System.out.println("-------------------------------------------------------");


    throughput = 0;
    millis = System.currentTimeMillis();
    while (System.currentTimeMillis() < millis + 1000)
    {
        hello = new TcpRequest();
        out.writeObject(hello);
        Object res = in.readObject();
        throughput++;
    }
    System.out.println("-------------------------------------------------------");
    System.out.println("|        New objetcs throughput: " + (throughput)  + " requests/sec.");
    System.out.println("-------------------------------------------------------");


    out.close();
    in.close();

    kkSocket.close();
}

Класс TcpRequest - это просто фиктивный класс, не имеющий ничего особенного.

Итак, если создание объекта происходит быстро, если отправка его по сети происходит быстро ... с какой стати отправка нового объекта по сети так медленно?!?!

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

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

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

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

РЕШИТЬ:

Заменив:

out = new ObjectOutputStream(kkSocket.getOutputStream());

С:

out = new ObjectOutputStream(new BufferedOutputStream(kkSocket.getOutputStream()))

Производительность снова нормальная (почти такая же высокая пропускная способность, как и в случае с тем же объектом)

Ответы [ 4 ]

8 голосов
/ 15 декабря 2010

Нашли:

Вместо:

out = new ObjectOutputStream(kkSocket.getOutputStream());

Вы должны использовать:

out = new ObjectOutputStream(new BufferedOutputStream(kkSocket.getOutputStream()));

И

out.flush();

при отправке сообщения.

... имеет огромное значение ... хотя я не знаю точно, почему.

1 голос
/ 15 декабря 2010

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

0 голосов
/ 06 декабря 2018

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

    socket.setTcpNoDelay(true);

Причина, по которой использование буфера помогает, состоит в том, что задержка достигает small *Только 1008 * пакетов.Так что помогает не сама по себе буферизация, а размер пакета.

0 голосов
/ 15 декабря 2010

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

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

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

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

Эта статья содержит много полезной информации, даже если она несколько устарела: http://java.sun.com/developer/technicalArticles/Programming/serialization/ См. Последнюю часть о соображениях производительности

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