【问题标题】:Why are Java HTTP requests so slow (in comparison to Python), and how can I make them faster?为什么 Java HTTP 请求这么慢(与 Python 相比),我怎样才能使它们更快?
【发布时间】:2021-07-28 22:04:21
【问题描述】:

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 对应的请求更快?

【问题讨论】:

  • 很难说。连接重用?缓存?一般来说,我预计等待远程服务器所花费的时间会支配其他一切。
  • 您应该使用分析器来衡量运行时执行情况——这将更深入地了解时间花费在哪里......
  • 网络传输时间至少比执行时间大 2 个数量级。因此,它极不可能是语言问题。
  • @Xiddoc 仅传输一个字节仍然比您在问题中显示的所有代码慢得多。我认为问题出在计时器上。仅仅因为它被称为nanoTime 并不意味着你真的得到了纳秒级的分辨率。
  • 保留相同的 OkHttpClient 实例并将其重用于所有请求。为每个调用创建一个新客户端非常糟糕,您会失去连接池和许多其他优化。

标签: java http networking python-requests okhttp


【解决方案1】:

我真的对你得到的结果持怀疑态度,所以我尝试使用与你完全相同的 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 倍。

【讨论】:

  • 哇!这在我的计算机上运行得比我的其他测试快得多。你能帮我理解为什么这比我运行的 Java 库测试快得多吗,它主要基于 this tutorial (我没有包括所有数据/参数/标头部分,因为它们不是必需的基本的 HTTP 请求)?它们都使用提供的内置 Java 方法,我似乎无法找到您的代码所做的一些主要优化与教程/测试有很大不同。
  • 我试图在我的回答中使代码尽可能小而高效。然后,我按照您评论中提到的教程进行操作,但仍然有类似的结果。你能提供你所做的确切代码吗?我可能会发现一些明显的东西。
【解决方案2】:

TLDR:

请求生命周期的大部分时间都花在了实际的互联网流量上。尽管 Java 比 Python 快,但它只能将每个请求缩短几毫秒,因为记录 1 个请求的大部分时间是由于服务器延迟/延迟造成的。此外,重用 Python Session 和 Java OkHttpClient 对象,以便选择加入可以缩短计算时间的关键优化。


我在帖子中犯了一些错误。第一个是我为每个请求生成一个新的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 中使用一个基本的 Singleton 类和一些基于 OkHttp 库的包装类在 Java 中实现了相同的概念。我不会在这里发布整个代码,因为我决定将它扩展到许多类,但基本思想很简单。在进行这些更改并记录我新发现的统计数据后,我得到了以下图表:

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

总结:

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-04-22
    • 2020-06-28
    • 1970-01-01
    • 2021-08-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多