2

I need to execute up to 10 HTTP Post operations as quickly as possible. All posts are to the same destination, and to simplify things you can assume they are all doing the same operation, for now. Every millisecond counts. Executing a single HTTP post operation typically takes 50ms when executed as part of the main code, without any threads:

long start = System.currentTimeMillis( );
response = httpClient.execute(httppost, context);
long end = System.currentTimeMillis( );
long diff = end - start;
System.out.println(Thread.currentThread().getName() + ": Difference is: " + diff);

But to do a few at a time, I create a Thread per HTTP post operation, and pass each thread the same HttpClient object, complete with PoolingHttpClientConnectionManager:

protected HttpClient createHttpClient() {
    PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
    cm.setMaxTotal(20);
    cm.setDefaultMaxPerRoute(24);
    RequestConfig.Builder requestBuilder = RequestConfig.custom();
    requestBuilder = requestBuilder.setConnectTimeout(5000);
    requestBuilder = requestBuilder.setSocketTimeout(5000);

    HttpClientBuilder builder = HttpClientBuilder.create();
    builder.setDefaultRequestConfig(requestBuilder.build());
    builder.setConnectionManager(cm);

    return builder.build();
}

CloseableHttpClient httpClient = (CloseableHttpClient) createHttpClient();
HttpParams httpParams = new DefaultHttpClient().getParams();

Runnable threadJob = new MyRunnable(httpClient, post);

Thread one = new Thread(threadJob);
Thread two = new Thread(threadJob);
Thread three = new Thread(threadJob);
Thread four = new Thread(threadJob);
Thread five = new Thread(threadJob);
Thread six = new Thread(threadJob);
Thread seven = new Thread(threadJob);
Thread eight = new Thread(threadJob);
Thread nine = new Thread(threadJob);
Thread ten = new Thread(threadJob);
one.start();
two.start();
three.start();
four.start();
five.start();
six.start();
seven.start();
eight.start();
nine.start();
ten.start();

Within the MyRunnable class, the same 50ms operation seems to take a good bit longer. Note I am not including the overhead of creating the thread etc in my metrics – I expect that to add to the overall time, but what’s perplexing me is why the same httpclient.execute() operation takes longer inside a thread, than it would be outside it. Inside MyRunnable's run() method:

long start = System.currentTimeMillis( );
response = httpClient.execute(httppost, context);
long end = System.currentTimeMillis( );
long diff = end - start;
System.out.println(Thread.currentThread().getName() + ": Difference is: " + diff);

My machine has 2 cores but 4 logical processors.
If I execute 4 threads, the times logged are typically like this:

58ms 155ms 160ms 179ms

It is consistently like this for ‘n’ threads – the first thread to complete registers a time slightly longer than ‘par’ time of 50ms, then the remainder all take around 3 times as long. Another typical result set for 10 threads:

52ms 156ms 156ms 160ms 156ms 161ms 175ms 158ms 176ms 178ms

Questions:

  1. Is my ‘before and after’ logging method a reliable method of how long it takes?

  2. Is this just the way it is when executing an http post operation inside a thread, or is there anything I can do to bring the average time down? (short of buying a more powerful machine).

S Clark
  • 33
  • 4

2 Answers2

1

Within the MyRunnable class, the same 50ms operation seems to take a good bit longer. Note I am not including the overhead of creating the thread etc in my metrics – I expect that to add to the overall time, but what’s perplexing me is why the same httpclient.execute() operation takes longer inside a thread, than it would be outside it.

You need to understand first on how multithreading works. It is not that when you create & start a Thread simply it runs in parallel, rather it waits for it's turn to execute the run() method (when number_of_threads > processors).

In other words, when there is a single thread running (or number_of_threads < processors), you don't see any overhead as the processor(s) will be free and simply executes your request(s). Now, coming to the other scenario, where you run threads more than the processors available, obviously, threads will take some time to get their turn which is called context switching overhead, so you will notice some difference in the response times obviously.

You might be interested in knowing how threads take priority to get their turn to execute the run() method, you can look here on this. In simple words, this is actually handled by the Thread Scheduler inside the JVM.

Also, I suggest you read here and understand the basics of how multithreading works.

Is my ‘before and after’ logging method a reliable method of how long it takes?

Your System.currentTimeMillis() is fine or you can also use Instant API from Java8.

Is this just the way it is when executing an http post operation inside a thread, or is there anything I can do to bring the average time down? (short of buying a more powerful machine)?

I suggest not to create & maintain Threads manually by yourself, rather use ExecutorService API like ExecutorService.newFixedThreadPool(10). This might not reduce the average time rather it will help you to manage the threads properly as a pool.

Vasu
  • 21,832
  • 11
  • 51
  • 67
  • Yeah I am aware of how it works, which is why I included an example of 4 threads, which is the same as the number of logical processors. Even if I just do 2 threads, the first thread to complete takes considerably less time than the second thread. – S Clark Apr 09 '17 at 09:21
  • How do you know that the processors are actually free? Are you monitoring? – Vasu Apr 09 '17 at 09:25
  • No I don't know that or how to check that? Actually one of my doubts was whether it's the number of cores or the number or logical processors which matters. I was assuming the latter. – S Clark Apr 09 '17 at 09:45
  • You can check here on this: http://stackoverflow.com/questions/4687757/tools-to-monitor-java-thread-execution – Vasu Apr 09 '17 at 10:02
0

You should also try to measure user and system time as well. See here for a way to do that. Right now you are measuring wall clock time, which might not show you the right time.

It seems to me like what might be happening is that you start all of your threads at once, and they all very quickly block on on httpClient.execute. Then they all finish at roughly the same time, and each of them measures how much the whole system waited for the operation to happen, which is 170ish milliseconds.

There might be other functionality in the connection manager to limit the number of active requests, connections globally or per route.

Horia Coman
  • 8,681
  • 2
  • 23
  • 25
  • Thanks, will take a look at the suggestions on that link. I am quite prepared that the time it's taking is to be expected when doing threads, but I want to be sure I am taking every step to reduce the average time as much as possible. What perplexes me is even 2 threads exhibit this behaviour, e.g. typical result is first thread 63 ms, second one 169 ms. Perhaps measuring user/system time will explain more what's going on.... – S Clark Apr 09 '17 at 09:34
  • On further analysis, you're right that the threads all (except the first) finish at the same time. The conn manager allows me to set the max number of connections, or connections per route... but adjusting these numbers down just makes the overall time increase. I can't see what else can be tweaked on the conn manager to help with this scenario where all threads want to do httpClient.execute at the same time -- I had naively assumed that, as it's a pooled connection manager, there would be no such contention between threads. But it seems the contention is at the level of the java method. – S Clark Apr 09 '17 at 21:34