为什么 Java HTTP 请求这么慢(与 Python 相比),我怎样才能使它们更快?

Why are Java HTTP requests so slow (in comparison to Python), and how can I make them faster?

Java 是一种美丽的语言,据说也非常高效。来自使用 Python 的背景,我想看看这两种语言之间的区别 - 从一开始我就对 Java 基于 OOP 的语法的明确性和清晰性印象深刻。但是,我也想测试一下这些语言之间的性能差异。

我首先尝试通过计算速度测试两种语言之间的性能差异。为此,我用每种语言编写了一些代码——该程序试图计算一个数学问题,并且会迭代很多次。我不会在此处添加此代码,但我会说结果 - Python 几乎比 Java 慢 2 倍(按时间测量)。有趣,但这是意料之中的。毕竟,我想尝试使用 Java 的全部原因是因为有很多人吹嘘计算速度。

之后,我进行了第二次测试——与网站建立 HTTP 连接,以下载网页。对于这个测试,我编写了另一个测试程序,它的功能与上一个测试相同,除了不是计算数学方程式,而是使用 HTTP 库下载网页。

我最终在 Python 中编写了以下脚本。它非常简单,它在下载网页时迭代 10 次,然后打印平均值。

from requests import get
from time import time

# Start the timer
start = time()

# Loop 10 times
for i in range(10):
    # Execute GET request
    get("https://httpbin.org/get")

# Stop the timer
stop = time()

# Calculate and print average
avg = (stop - start) / 10

print(avg)
# Prints 0.5385, on my system.

为了Java测试,我写了下面这段代码。它与之前的测试相同,但在 Java.

中实施
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

import java.io.IOException;
import java.util.Objects;

public class Test {

    public static String run(String url) throws IOException {
        // Code taken from OKHTTP docs
        // https://square.github.io/okhttp/
        // https://raw.githubusercontent.com/square/okhttp/master/samples/guide/src/main/java/okhttp3/guide/GetExample.java
        OkHttpClient client = new OkHttpClient();

        Request request = new Request.Builder()
            .url(url)
            .build();

        try (Response response = client.newCall(request).execute()) {
            return Objects.requireNonNull(response.body()).string();
        }
    }

    public static void main(String[] args) throws IOException {
        // Start the timer
        long startTime = System.nanoTime();

        // Loop 10 times
        for (int i = 0; i < 10; i++) {
            // Execute GET request
            run("https://httpbin.org/get");
        }

        // Stop the timer
        long endTime = System.nanoTime();

        // Calculate the average
        float average = (((float) (endTime - startTime)) / 1000000000) / 10;

        // Print results (1.05035 on my system)
        System.out.println(average);
    }
}

咦……真是出乎意料。唉,难道 Java 不应该比 Python 快吗?我很震惊地看到 Java 在这个测试中几乎比 Python 慢 2 倍,但我决心找到一个有利于 Java 的结论。为了满足这一点,我决定使用 Java 默认库而不是 OkHttp 库重新编写测试。我不会在这里显示代码,因为它很长,但我使用 HttpURLConnection 来帮助我。我的结果仍然相同,但比使用 OkHttp 库要快一些。

我的最终测试是与之前的测试相同,但是在 http:// 网站上(以防由于 SSL 连接而导致速度缓慢)。我的结果还是一样——Python 快了将近 2 倍。我唯一能想到的是为什么会发生这种情况,因为 requests Python 库将用 C 编码,但正如您从 their GitHub page 的“语言”部分中看到的那样,所有 requests 库都是用纯 Python.

编写的

我想了解为什么 Java 在 运行 HTTP 连接时这么慢,如果我的系统设置或 Java 测试代码有问题,我应该怎么办有写改善结果吗?此外,如果可能的话,如何发送 Java HTTP 请求,使其比 Python requests 对应的请求更快?

我真的对你得到的结果持怀疑态度,所以我尝试了与你完全相同的 Python 代码和 main Java 方法(使用 https) .
这是读取响应的整个 JSON 内容的 Java run 方法:

private static String run(String url) throws IOException {
    final URLConnection c = new URL(url).openConnection();
    c.connect();
    try (InputStream is = c.getInputStream()) {
        final byte[] buf = new byte[1024];
        final StringBuilder b = new StringBuilder();
        int read = 0;
        while ((read = is.read(buf)) != -1) {
            b.append(new String(buf, 0, read));
        }
        return b.toString();
    }
}

我系统上的结果:

  • Python 2.7.12: 0.5117351770401001
  • Python 3.5.2: 0.48344600200653076
  • Java 1.8: 0.19684727

10 次迭代可能不足以获得好的结果,但在这里,Java 至少快 2 倍。

TLDR:

请求的大部分生命周期都花在了实际的互联网流量上。即使 Java 比 Python 快,它也只能为每个请求节省几毫秒,因为 1 个请求记录的大部分时间是由于服务器 lag/latency。此外,重用 Python Session 和 Java OkHttpClient 对象以选择加入关键优化,从而减少大量计算时间。


我在 post 中犯了一些错误。第一个是我为每个请求生成一个新的 OkHttpClient 对象并直接使用 get 方法。正如 Jesse 在评论中指出的那样,通过使用这些,我错过了重要的优化,因此得到了不公平的结果。

为了解决这个问题,我使用了一个 Session 对象来保存我的请求历史记录,并同样保存了相同的 OkHttpClient 对象。

我在 Python 中实施的改进:

from requests import Session
from time import time

# Start the timer
start = time()

# Create a new Session     <-----
s = Session()

# Loop a few times
for i in range(50):
    # Execute GET request
    s.get("http://httpbin.org/get")

# Stop the timer
stop = time()

# Calculate and print average
avg = (stop - start) / 50

print(avg) # 180ms on my system

同样,我在 Java 中使用基本单例 class 和 OkHttp 库顶部的一些包装器 class 实现了相同的概念。我不会在此处 post 使用整个代码,因为我决定将其扩展到许多 classes,但基本思想很简单。进行这些更改并记录我新发现的统计数据后,我得到了以下图表:

如图所示,Python 实际上对执行的第一个请求有更快的初始化过程。但是,您还可以注意到,对于平均请求(平均 50 个连续和同步请求),Java 库(URLConnectionOkHttp)与 Python requests 图书馆.

总结:

通过重用 Python Session 和 Java OkHttpClient 对象(初始化对象一次并将其用于所有请求,而不是使每个请求一个新的),进行了大量优化,因此执行时间大大降低。然而,当谈到平均值时,Java 只比 Python 少了几毫秒,因为请求期间花费的大部分时间来自网络传输时间(在计算机之间发送数据所花费的时间通过互联网)。

如果有人想评论更多信息或在另一个答案中展示他们自己的发现,我会欣喜若狂地阅读更多相关信息。感谢那些对我的问题发表评论并帮助我找出优化过程的几个关键组成部分的人。 万岁Java:)