Home  

Making Simulations Run Faster

 

 

Occasionally I run long simulations of big networks (up to 1000 nodes), which send a lot of  messages. These long simulations could take hours and even days to finish (sometimes they were practically unfinishable).
Thus, I have used JProfiler to identify the functions that run the majority of the time and the objects that are mostly allocated. After finding various un-optimized code pieces and optimizing them, my simulations run an order of magnitude faster - instead of days it takes hours (I have to mention that I needed to optimize only my code - the original JiST/SWANS is already optimized at a very good level).

I will share now a number of optimizations I made, since I believe them to be a common case for many Java programs

  1. High overhead of unnecessary logging and assertion code:
    The most time-consuming function in my code was StringBuffer.append() (called upon string concatenation) and the mostly allocated object was String.

    1. My code is abundant with debugging places: every sent/received message was passed to a special printDebug(String) function, serialized into a string, concatenated with sender id , destination id, etc... and printed to System.out. I had a DEBUG switch to prevent the actual I/O operation, which was turned off during the actual runs, BUT the switch was inside the printDebug() function, protecting only the System.out.println() instruction, and the message serialization took place anyway.

    2. I use A LOT of asserts in my code. The assert function receives some boolean condition and a string to be printed if the condition is false. Of course, during the actual run asserts did not fail, BUT the string parameter was sent to it anyway. A lot of times the string was actually a serialized message or a concatenation of various parameters. So all this string serialization/concatenation was in vain and consumed a lot of process resources. Instead, one can now use the new java assert booleanExpression : errorMessage; utility. It does the errorMessage evaluation only if the boolean expression is false.

    3. In AODV code SWANS uses printlnDebug() function. There is a RouteAodv.DEBUG_MODE flag inside this function, but a lot of times concatenated strings are passed to it, which is the same case as a. and b. So the solution is to put if (RouteAodv.DEBUG_MODE) outside the places that call printlnDebug(), to prevent the call itself.
       

  2. Be careful when using Java Collections  - avoid memory leakage:
    Java (actually any language with garbage collector) CAN have a floating garbage, which is not collected by the garbage collector. I mean the "logical garbage" - if you don't use certain object but accidentally keep some reference to it.  This mainly happens when putting some items in HashMap/Hashtable/Set/Vector/Array or what ever collection. You put an object there, but forget to remove it after the usage. The garbage gets accumulated until the memory explodes (it could also cause to swapping, which slows the program significantly).
    This phenomenon can be seen in various ways:
    a) In JProfiler virtual memory size constantly increases.
    b) In your program you can occasionally print Runtime.getRuntime().totalMemory() and see the memory growing.
    c) In JProfiler one of the mostly allocated objects was HashMap.Entry.

    In my case case, all those waists were due to the fact that every message that was sent, was put in some HashMap for a statistical analysis at the end, but it caused my program's memory to explode.
    Another example is an implementation of a flooding protocol - a node sends a message which is retransmitted by any other node that receives it for the first time. So every node has to remember the id of every message it received in order not to retransmit it again. A not careful implementation will store an Integer object for every message received by every node. Imagine 1000 nodes network, every node starting 1000 floodings. It will result in 1000*1000*1000 Integer objects, which is 1GB of memory.
    The solution could be to store only some last K message identifiers from every sender. Or simply occasionally garbage collect ids of message that were sent far in the past and thus there is no chance to receive the same messages again.
    So be careful when using Collections!

 

I hope you will find those tips useful. They are not any special "wisdom", just an experience I had.

 

Gabriel Kliot

11/07/2008