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

Viewed 1052

Java is a beautiful language, and is also supposedly very efficient. Coming from a background of having used Python, I wanted to see the difference between the 2 languages- and from the start I was very impressed by the explicitness and clarity of Java's OOP based syntax. However, I also wanted to test out the performance differences between the languages.

I started off by trying to test the performance difference between the 2 languages by computation speed. For this, I wrote some code in each language- the program attempted to calculate a mathematical problem, and would iterate many times. I won't be adding this code here, but I will say the results- Python was almost 2 times slower (measured by time) than Java. Interesting, but it was expected. After all, the whole reason I wanted to try using Java is due to the amount of people bragging about the computation speed.

Following that, I proceeded to my second test- making HTTP connections to websites, in order to download web pages. For this test, I wrote another test program which would do the same as the last test, except instead of computing a math equation, it would download a web page using an HTTP library.

I ended up writing the following script in Python. It is very straightforward, it iterates 10 times over downloading a web page, then prints the average.

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.

For the Java test, I wrote the following piece of code. It is the same test as before, but implemented in 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);
    }
}

Huh... that's unexpected. Alas, isn't Java supposed to be faster than Python? I'm shocked to see that Java is almost 2 times slower than Python in this test, but I'm determined to find a conclusion that favors Java. To satisfy this, I decided to re-write the test using the Java default libraries instead of the OkHttp library. I won't be showing the code here since it's pretty long, but I used HttpURLConnection to assist me. My result was still the same, but slightly quicker than with the OkHttp library.

My final test was to do the same as the previous tests, but on http:// websites (in case the slowness occurs due to the SSL connection). My result was still the same- Python was faster by almost 2 times. The only thing I could think of to why this is happening is because the requests Python library would be coded in C, but as you can see from the "Languages" section of their GitHub page, all of the requests library was programmed in pure Python.

I would like to understand why Java is so slow at running HTTP connections, and if I did something wrong with my system setup or Java test code, what should I have written to improve the results? Also, if it's possible, how can a Java HTTP request be sent so that it is faster than its Python requests counterpart?

2 Answers

I was really skeptical about the results you got, so I gave it a try with the exact same Python code and main Java method (that uses https) as yours.
Here is the Java run method that reads the entire JSON content of the response:

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();
    }
}

Results on my system:

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

10 iterations is probably not enough to get a good result, but here, Java is at least 2 times faster.

TLDR:

The majority of a request's lifespan is spent out in the actual internet traffic. Even though Java is faster than Python, it can only shave off a few milliseconds per request, as most of the time that is recorded for 1 request is due to server lag/latency. Also, reuse the Python Session and Java OkHttpClient objects in order to opt-in to critical optimizations that knock off drastic computation time.


There are a few mistakes that I made in my post. The first of which is that I generate a new OkHttpClient object for each request and used the get method directly. As Jesse pointed out in a comment, by using these, I miss out on heavy optimizations, and therefore get an unfair result.

To fix this, I used a Session object to persist my request history, and saved the same OkHttpClient object likewise.

My improvement implemented in 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

Likewise, I implemented the same concept in Java using a basic Singleton class and a few wrapper classes on top of the OkHttp library. I won't be posting the entire code here as I decided to expand it across many classes, but the underlying idea is simple. After making these changes and logging my newfound statistics, I ended up with the following chart:

statistics

As shown, Python does in fact have a quicker initialization process for the first request executed. However, you can also notice that for the Average Request (average of 50 consecutive and synchronous requests) the Java libraries (URLConnection & OkHttp) shave off a few milliseconds in comparison to the Python requests library.

Summary:

By reusing the Python Session and Java OkHttpClient objects (initialize the object once and use it for all requests, instead of making a new one for each request), heavy optimizations are done, and therefore the execution times are drastically lowered. However, when it comes to averages, Java only beats Python by a few measly milliseconds, as most of the time spent during a request is from network transmission time (the time it takes to send data between computers over the Internet).

If anyone would like to comment more information or show their own findings in another answer, I would be ecstatic to read more about it. Thank you to those who commented on my question and helped me figure out a few key components to the optimization process. Long live Java :)

Related