【问题标题】:Java socket performance bottleneck: where?Java 套接字性能瓶颈:在哪里?
【发布时间】:2011-05-25 23:36:39
【问题描述】:

我最近开始开发一个密集使用网络的应用程序。 第一次尝试使用 RMI,出于几个原因,我们切换到纯套接字。然而,当通过网络测试套接字时,甚至在 localhost 上,我们的速度下降到 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()))

性能再次正常(与相同对象情况几乎相同的高吞吐量)

【问题讨论】:

  • 与此问题相同:stackoverflow.com/questions/2251051/… ...也没有答案。
  • 如果您的对象不相互引用,您可能需要在写入后调用 ObjectOutputStream.reset(),以防止流记住它写入的所有对象。虽然它通常会导致读取端的内存问题,而不是服务器端的性能问题。您是否尝试过分析您的应用程序以查看它花费最多时间的地方?

标签: java sockets


【解决方案1】:

找到了:

代替:

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

你应该使用:

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

out.flush();

发送消息时。

...有很大的不同...虽然我不知道具体原因。

【讨论】:

  • 这可能是问题的一部分,但只是其中的一部分。请看我的评论。
  • 我可以解释一下。如果您使用不使用缓冲区的流,则每个 write 都会导致一个系统调用。每个系统调用的开销可能是数千条额外的机器指令。
  • ObjectOutputStream 在内部运行自己的 1k 缓冲区。将 BufferedOutputStream 添加到堆栈中并没有太大的区别。
【解决方案2】:

我不会相信该基准测试的结果。它不能确保 JVM 已预热;即,在测量执行时间之前,类被加载、初始化并编译为本机代码。本地代码编译很有可能会在运行基准测试的过程中部分启动,这会增加一个或多个循环所花费的时间。

【讨论】:

    【解决方案3】:

    您遇到的问题不在于套接字的吞吐量低;这是缓慢的默认 Java 序列化。

    当您一遍又一遍地发送同一个对象时,它实际上只被序列化一次,然后每次都发送对它的引用;这就解释了为什么进展如此之快。

    当您每次创建一个新对象时,该新对象必须使用相对较慢的 Java 序列化机制进行序列化,这也可能比您需要的复杂得多。

    您可以做些什么来改进这一点,或者为您的类实现自定义序列化代码,或者制作您自己的类外部对象序列化协议(并使用 DataOutput 而不是 ObjectOutputStream)。

    这篇文章有很多很好的信息,即使它有些过时:http://java.sun.com/developer/technicalArticles/Programming/serialization/ 请参阅关于性能注意事项的最后一部分

    【讨论】:

    • 问题不在于“缓慢的默认 Java 序列化”,而是序列化对象和句柄之间的区别。只是重新发送同一个对象的测试实际上并没有进行任何有趣的操作,因此它的结果也没有意义。
    【解决方案4】:

    在处理小数据包时,您应该设置TCP_NODELAY 选项。否则,他们将在尝试优化网络通信时延迟(又名Nagle's algorithm)。

        socket.setTcpNoDelay(true);
    

    使用缓冲区有帮助的原因是延迟只针对个数据包。所以不是缓冲本身有帮助,而是数据包的大小。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-03-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-03
      • 2017-07-17
      • 2016-06-12
      相关资源
      最近更新 更多