【问题标题】:How to investigate Java socket program performance issue如何调查 Java 套接字程序性能问题
【发布时间】:2017-12-20 17:29:48
【问题描述】:

我有两个相同的 Java 程序集 [Server.java 和 Client.java] 和 [ServerTest.java 和 ClientTest.java] 的变体。它们都做同样的事情,客户端连接到服务器并将成对的整数发送到服务器以进行相乘,并将结果返回给客户端,然后在客户端打印。每次执行 100 次。

但是,在测试版本中,我为整数对的每次传递及其乘法(执行 100 次乘法)创建并关闭一个新套接字。在普通版本中,我打开一个持久套接字并执行与客户端的所有交互,然后关闭。

直观地说,我认为创建一个持久套接字的方法比每次创建、接受和关闭一个套接字要快一些——实际上,创建、接受和关闭一个新套接字的方法明显更快。平均而言,持久套接字方法大约需要 8 秒,而每次创建一个新套接字的方法大约需要 0.4 秒。

我检查了两者的系统调用活动,发现两者之间没有什么不同。然后我在另一台计算机(macOS Sierra)上测试了相同的程序,两者之间的差异可以忽略不计。因此,问题似乎不在于应用程序代码,而在于它与操作系统的交互方式(我运行的是 Ubuntu LTS 16.04)。

有谁知道为什么这里的性能会有如此大的差异,或者如何进一步调查这个问题?在执行程序时,我还检查了系统范围的指标(内存使用率和 CPU 使用率),似乎有足够的内存并且 CPU 有足够的空闲时间。


请参阅下面的代码 sn-p,了解两种方法的不同之处:

每次创建新套接字的方法:

// this is called one hundred times
public void listen() {
    try {
        while (true) {
            // Listens for a connection to be made to this socket.                                        
            Socket socket = my_serverSocket.accept();

            DataInputStream in = new DataInputStream(socket
                    .getInputStream());

            // Read in the numbers
            int numberOne = in.readInt();
            int numberTwo = in.readInt();

            int result = numberOne * numberTwo;
            DataOutputStream out = new DataOutputStream(socket.getOutputStream());
            out.writeInt(result);
            // tidy up
            socket.close();
        }
    } catch (IOException ioe) {
        ioe.printStackTrace();
    } catch (SecurityException se) {
        se.printStackTrace();
    }
}

持久套接字方法:

public void listen() {
    try {
        while (true) {
            // Listens for a connection to be made to this socket.
            Socket socket = my_serverSocket.accept();
            for (int i = 0; i < 100; i++) {
                DataInputStream in = new DataInputStream(socket
                        .getInputStream());

                // Read in the numbers
                int numberOne = in.readInt();
                int numberTwo = in.readInt();

                int result = numberOne * numberTwo;
                DataOutputStream out = new DataOutputStream(socket.getOutputStream());
                out.writeInt(result);
            }

            // tidy up
            socket.close();
        }
    } catch (IOException ioe) {
        ioe.printStackTrace();
    } catch (SecurityException se) {
        se.printStackTrace();
    }
}

【问题讨论】:

    标签: java performance sockets


    【解决方案1】:

    您没有向我们展示发送整数进行乘法运算的代码。您是否碰巧有一个循环,在每次迭代中您发送一对并接收结果?如果是这样,请确保关闭 Nagle 算法。

    Nagle 的算法试图克服“小数据包问题”,即当应用程序重复以小块形式发出数据时。这会导致巨大的开销,因为数据包标头通常比数据本身大得多。该算法本质上结合了许多小的传出消息并一次发送它们。如果没有收集到足够的数据,那么算法可能仍会发送消息,但前提是超时已过。

    在您的情况下,您将小块数据写入客户端和服务器端的套接字中。数据没有立即传输。而是套接字等待更多数据到来(没有),所以每次超时都必须过去。

    【讨论】:

    • 在服务器端和客户端都禁用 Nagle 算法后,持久套接字方法的性能下降到 0.01 秒(比 new-socket-every-time 方法的 0.4 秒快)。仅在服务器端或仅在客户端启用它可以将运行时间减少到大约 4 秒,并在两者上启用将其减少到 0.01。您能否详细说明正在发生的事情/Nagle 的算法是什么?
    【解决方案2】:

    实际上,这 2 段代码之间的唯一区别不是它们如何处理传入连接(是否有一个持久套接字),不同之处在于,在你称之为“持久”的那个中,100 对数字是相乘,而在另一个中,只有一对数字相乘然后返回。这可以解释时间上的差异。

    【讨论】:

    • 在这两个程序中都执行了一百次操作。对于在listen中没有for循环的方法,Listen()被调用了一百次。
    • 明白。我要做的第一件事是删除 for 循环之外的 'in' 和 'out' 的初始化。虽然没有回答你的问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-05-23
    • 1970-01-01
    • 2011-08-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多