Peter is a DZone MVB and is not an employee of DZone and has posted 162 posts at DZone. You can read more from them at their website. View Full User Profile

Low GC Coding: Efficient Listeners (Exercise)

04.18.2013
| 4882 views |
  • submit to reddit

Overview

There are many ways to implement a listener pattern depending on the assumptions you want to make. This is an exercise to get you thinking about how these can be implemented efficiently.

Simple set of listeners

Implement a thread safe set of listeners which supports add and remove.  Assume there is *many* more events than add/removes and you want to optimize the notification use case.
 public interface Observer {
    void update(Observable o, Object arg);
}
public class Observable {
    public void addObserver(Observer o) {
    }
   publicvoid removeObserver(Observer o) {
    }
   publicvoid notifyObservers(Object arg) {
        // must be thread safe
        // no locks, no garbage
    }
}
In case you think this is an easy task, note that the built-in Observable uses both a lock and creates lots of garbage.
As a part of this task, you should use a set to ignore duplicates, but keep the order Observers were added. i.e. call them in this order.

Bonus Exercise

a) Compare the performance with the built-in Observable for 0, 1 and 10 Observers. Can you explain the results you get?

b) change the order each time the notifyObservers is called (or almost every time) so that each Observable has an equal change of being first. (do this without locks or creating garbage)

Solution

Solution to follow after some time to solve the problem yourself.

Possible solution - CopyOnWriteArraySet

Running the following with -XX:-UseTLAB -XX:+DoEscapeAnalysis -XX:+AggressiveOpts
public class TestIterations {
    public static void main(String... ignored) {
        CopyOnWriteArraySet observers = new CopyOnWriteArraySet<>();
        for (int i = 0; i < 100; i++) {
            long before = memoryUsed();
            callObservers(observers);
            long size = memoryUsed() - before;
            System.out.printf("10000x Iterations used %,d bytes%n", size);
        }
    }
    private static void callObservers(CopyOnWriteArraySet observers) {
        for (int j = 0; j < 10000; j++) {
            for (Observer observer : observers) {
                observer.update(null, null);
            }
        }
    }
    static int counter = 0;
    static class MyObserver implements Observer {
        @Override
        public void update(Observable o, Object arg) {
            counter++;
        }
    }
    public static long memoryUsed() {
        return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
    }
}
prints
10000x Iterations used 240,000 bytes
10000x Iterations used 240,000 bytes
10000x Iterations used 240,000 bytes
10000x Iterations used 240,000 bytes
10000x Iterations used 240,000 bytes
10000x Iterations used 240,000 bytes
10000x Iterations used 240,000 bytes
10000x Iterations used 240,000 bytes
10000x Iterations used 240,000 bytes
It is possible to avoid any garbage.

 

Published at DZone with permission of Peter Lawrey, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

dieter von holten replied on Thu, 2013/04/18 - 11:00am

tja, your post seems to indicate that iterating over an empty set doesnt consume memory...


Peter Lawrey replied on Thu, 2013/04/18 - 1:02pm in response to: dieter von holten

Iterating over a set of any size creates an Iterator and uses 24 bytes on a 64-bit JVM.

Ma Gri replied on Fri, 2013/04/26 - 10:49am

not sure what you want to demonstrate by 10000 times iterating over an empty set.

Peter Lawrey replied on Fri, 2013/04/26 - 12:34pm in response to: Ma Gri

That it produces garbage which is not optimised away.  The default compile threshold is 10,000

dieter von holten replied on Sun, 2013/04/28 - 6:04am

 you might try to run it without EscapeAnalysis. I'd guess, that the Iterator is local and non-escaping, thus allocated on the stack. Therefore you cant see it in heap-space.

Anyway, wasnt the post supposed to tell us something about the Oberserver and it's use? No Observer in the game - only passively sitting ( 'resting' might be the more precise word )  in the class-file.

Peter Lawrey replied on Sun, 2013/04/28 - 11:52am in response to: dieter von holten

It is the escape analysis which places objects on the stack so turning it off would prevent that.  As the test show, this is not optimised away.

You can read from the Observerable code that it is very inefficient as it take a copy of the list of Observers on every notify. (even if that list is empty)

The Observer isn't supposed to be doing anything in this case as it would make it obvious that any garage being created is done the iteration.  I am not sure what else you can say about an interface.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.