1

I am trying to measure the performance of client side code. Meaning how much time end to end client side code takes and few other classes inside the client code. So I did my benchmarking around that.

Below is the simple program I am using currently. But there is a problem with that.

public class PerformanceTest {

    public static void main(String[] args) {

        ExecutorService executor = Executors.newFixedThreadPool(5);

        try {
            for (int i = 0; i < 10 * 5; i++) {
                executor.submit(new ThreadTask(i));
            }

            executor.shutdown();
            executor.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
        } catch (InterruptedException e) {

        }
    }
}

Below is the class that implements Runnable interface

class ThreadTask implements Runnable {
    private int id;
    public static ConcurrentHashMap<Long, AtomicLong> millisecondsMap = new ConcurrentHashMap<Long, AtomicLong>();


    public ThreadTask(int id) {
        this.id = id;
    }

    @Override
    public void run() {


        long start = System.nanoTime();

        attributes = beClient.getAttributes(columnsList);

        long end = System.nanoTime() - start;

        final AtomicLong before = millisecondsMap.putIfAbsent(end / 1000000L, new AtomicLong(1L));
        if (before != null) {
            before.incrementAndGet(); //this is a blocking call in IBM JVM
        }
    }
}

Problem Statement:-

I am running IBM JVM in my machine. As I am working in a company where they use IBM JVM instead of SUN JVM so I cannot change the JVM part.

So in IBM JVM incrementAndGet() method looks like, it was very strange to me first of all

/**
 * Atomically increments by one the current value.
 *
 * @return the updated value
 */
public final synchronized long incrementAndGet() {                          //IBM-perf_AtomicLong
   ++value;                                                                 //IBM-perf_AtomicLong
   return value;                                                            //IBM-perf_AtomicLong
}

So it's a blocking call as it is synchronized, meaning each thread will be waiting for each other. And now I am looking for Lock free solution for this to measure the performance of each method.

I know, there will be tiny latency here. But whatever methods performance I want to measure inside the client side code, I usually put the below line just above that method

long start = System.nanoTime();

And these two lines after the same method but with different ConcurrentHashMap

long end = System.nanoTime() - start;

final AtomicLong before = millisecondsMap.putIfAbsent(end / 1000000L, new AtomicLong(1L));
        if (before != null) {
            before.incrementAndGet();// this is a blocking call in IBM JVM
        }

So if I have same above code around 5-8 different methods in a different classes on the client side code. Then end to end performance measurement will be wrong as each thread will be waiting there to increment the value. So that is the reason I am looking for Lock free solution for this.

Is there any simple way to do this? Can anyone provide any example for this?

Thanks in advance.

Updated Code:-

public static ConcurrentHashMap<Long, Long> millisecondsMap = new ConcurrentHashMap<Long, Long>();

@Override
public void run() {


    long start = System.nanoTime();

    beAttributes = client.getAttributes(columnsList);

    long end = System.nanoTime() - start;

    long key = end / 1000000L;
    boolean done = false;

    while(!done) {
        long oldValue = millisecondsMap.get(key);
        done = millisecondsMap.replace(key, oldValue, oldValue + 1);
    }

}

Does this code is thread safe code as well? As it will be accessed by multiple threads as well.

arsenal
  • 23,366
  • 85
  • 225
  • 331
  • Is the IBM jdk source available to inspect somewhere? I have an idea. – Michael Deardeuff Apr 14 '13 at 03:01
  • I don't have the source code with me I guess. Only class file in eclipse getting show. And that class file is in `rt.jar`. – arsenal Apr 14 '13 at 03:33
  • @TechGeeky I'm curious: why did you abandon the `FutureTask` strategy? Too messy? – acdcjunior Apr 14 '13 at 06:03
  • @acdcjunior, That was some good stuff to learn from you on that. But I was thinking there might be some other easy way to achieve the same thing as compared to FutureTask one. If I haven't posted this question, I haven't got to know that we can achieve the same problem in other ways as well which will also do the same thing. But again, I will running some benchmark on both of the options and will check how each one is behaving – arsenal Apr 14 '13 at 06:13

1 Answers1

1

Rather than use an AtomicLong, use the ConcurrentHashMap's replace(key, old value, new value) method to increment the value.

ConcurrentHashMap<Long, Long> millisecondsMap = new ConcurrentHashMap<>();
long key = end / 1000000L;
boolean done = false;
while(!done) {
    Long oldValue = millisecondsMap.putIfAbsent(key, 1L);
    if(oldValue != null) {
        done = millisecondsMap.replace(key, oldValue, oldValue + 1);
    } else {
        done = true;
    }
}
arsenal
  • 23,366
  • 85
  • 225
  • 331
Zim-Zam O'Pootertoot
  • 17,888
  • 4
  • 41
  • 69
  • Thanks Zim-Zam for the suggestion. Can you provide me an example for that? By that, it will make sense to me more. Thanks for the help. – arsenal Apr 14 '13 at 03:29
  • See my edit. `replace` will return false if `oldValue` changed between the assignment and the `replace` call and will leave the map's value unchanged, and will return true if `oldValue` matches the map's current value associated with the key and replaces the value with `oldValue + 1`. – Zim-Zam O'Pootertoot Apr 14 '13 at 04:47
  • Thanks Zim-Zam, I just updated my question with sample code. I just wanted to make sure, I got everything right or not. Can you please take a look and let me know if it looks good to you? And one more thing, does this code is thread safe code? As it will be accessed by multiple threads. – arsenal Apr 14 '13 at 05:56
  • By the way: this is the same strategy (a [compare-and-swap](http://en.wikipedia.org/wiki/Compare-and-swap) algorithm) that Sun's `AtomicLong` do on `incrementAndGet()`. – acdcjunior Apr 14 '13 at 06:02
  • Your code looks good to me. And yes, this is thread-safe - like acdcjunior commented, this is a compare-and-swap algorithm that is pretty commonly found in a lot of lock-free implementations. See for example the C# [Interlocked](http://msdn.microsoft.com/en-us/library/system.threading.interlocked.aspx) class's CompareExchange methods that do the same thing. – Zim-Zam O'Pootertoot Apr 14 '13 at 13:58
  • Another example that I've used recently is the [DynamoDB Put method](http://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/). DynamoDB is essentially a distributed ConcurrentHashMap, and so to perform a thread-safe update you'd use its compare-and-swap method (i.e. putItem(new PutItemRequest().withItem(newValue).withExpected(oldValue)); this returns true on success, and returns false (and doesn't change the hashmap) if the expected value does not match the current value. – Zim-Zam O'Pootertoot Apr 14 '13 at 16:42
  • Thanks Zim Zam for confirming it. But as soon as I ran the program, I got `NPE` whenever it tries to retrieve the `key` from the map. Meaning at this line- `long oldValue = millisecondsMap.get(key);` Any idea why it is throwing that? – arsenal Apr 14 '13 at 20:08
  • You'll need to initialize the value if it's absent. See my edit. putIfAbsent will initialize the value to 1 if there isn't a value associated with they key (returning null), otherwise it will return the current value if one exists. – Zim-Zam O'Pootertoot Apr 14 '13 at 20:17
  • Just to be clear, the `ConcurrentHashMap` (and `AtomicLong`) are going to lock internally. `ConcurrentHashMap` is actually a very slow implementation and it is completely rewritten in Java 8. I have found that my application much better off whenever I can avoid usage of `ConcurrentHashMap`. With that said, for one-off usage, it won't hurt anything and it is still thread safe. – pickypg Apr 14 '13 at 20:20
  • Yeah that makes sense. Still there is just one compilation error on this line `millisecondsMap.putIfAbsent(key, 1);`. `1` should be `1L` I guess. – arsenal Apr 14 '13 at 20:20
  • Yup, my mistake. Also oldvalue should be a Long instead of a long unlike how I'd originally written it. – Zim-Zam O'Pootertoot Apr 14 '13 at 20:21
  • As you mentioned `ConcurrentHashMap` will be locked internally. I totally agree with you on this. Do you know any other better way to achieve the same scenario with little bit performance improvement as compared to `ConcurrentHashMap` one. – arsenal Apr 14 '13 at 20:25
  • The only other solution I can think of is to completely rewrite your solution using something like the [Akka library](http://akka.io/) – Zim-Zam O'Pootertoot Apr 14 '13 at 20:34