Author: sebb Date: Tue Dec 15 00:53:43 2009 New Revision: 890589 URL: http://svn.apache.org/viewvc?rev=890589&view=rev Log: Bug 48259 - Improve StatCalculator performance by using HashMap
Modified: jakarta/jmeter/trunk/src/jorphan/org/apache/jorphan/math/StatCalculator.java jakarta/jmeter/trunk/xdocs/changes.xml Modified: jakarta/jmeter/trunk/src/jorphan/org/apache/jorphan/math/StatCalculator.java URL: http://svn.apache.org/viewvc/jakarta/jmeter/trunk/src/jorphan/org/apache/jorphan/math/StatCalculator.java?rev=890589&r1=890588&r2=890589&view=diff ============================================================================== --- jakarta/jmeter/trunk/src/jorphan/org/apache/jorphan/math/StatCalculator.java (original) +++ jakarta/jmeter/trunk/src/jorphan/org/apache/jorphan/math/StatCalculator.java Tue Dec 15 00:53:43 2009 @@ -18,11 +18,10 @@ package org.apache.jorphan.math; -import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; -import java.util.Iterator; -import java.util.List; +import java.util.TreeSet; + +import org.apache.commons.lang.mutable.MutableLong; /** * This class serves as a way to calculate the median, max, min etc. of a list of values. @@ -31,8 +30,10 @@ */ public abstract class StatCalculator<T extends Number & Comparable<? super T>> { - private final List<T> values = new ArrayList<T>(); + // key is the type to collect (usually long), value = count of entries + private final HashMap<T, MutableLong> valuesMap = new HashMap<T, MutableLong>(); + // Running values, updated for each sample private double sum = 0; private double sumOfSquares = 0; @@ -43,13 +44,19 @@ private int count = 0; + private T min; + + private T max; + + private transient TreeSet<T> sortedKeys; // cached sorted set + private long bytes = 0; private final T ZERO; - private final T MAX_VALUE; + private final T MAX_VALUE; // e.g. Long.MAX_VALUE - private final T MIN_VALUE; + private final T MIN_VALUE; // e.g. Long.MIN_VALUE /** * This constructor is used to set up particular values for the generic class instance. @@ -58,20 +65,27 @@ * @param min - value to return for minimum if there are no values * @param max - value to return for maximum if there are no values */ - public StatCalculator(T zero, T min, T max) { + public StatCalculator(final T zero, final T min, final T max) { super(); ZERO = zero; MAX_VALUE = max; MIN_VALUE = min; + this.min = MAX_VALUE; + this.max = MIN_VALUE; + sortedKeys = null; } public void clear() { - values.clear(); + valuesMap.clear(); + sortedKeys = null; sum = 0; sumOfSquares = 0; mean = 0; deviation = 0; count = 0; + bytes = 0; + max = MIN_VALUE; + min = MAX_VALUE; } @@ -80,17 +94,13 @@ } public void addAll(StatCalculator<T> calc) { - Iterator<T> iter = calc.values.iterator(); - while (iter.hasNext()) { - addValue(iter.next()); + for (T val : calc.valuesMap.keySet()) { + addValue(val); } } public T getMedian() { - if (count > 0) { - return values.get((int) (values.size() * .5)); - } - return ZERO; + return getPercentPoint(0.5); } public long getTotalBytes() { @@ -107,10 +117,7 @@ * @return number of values less than the percentage */ public T getPercentPoint(float percent) { - if (count > 0) { - return values.get((int) (values.size() * percent)); - } - return ZERO; + return getPercentPoint((double) percent); } /** @@ -120,42 +127,45 @@ * are below, the remaining 10% are above. * * @param percent - * @return number of values less than the percentage + * @return the value which %percent% of the values are less than */ public T getPercentPoint(double percent) { - if (count > 0) { - return values.get((int) (values.size() * percent)); + if (count <= 0) { + return ZERO; + } + if (percent >= 1.0) { + return getMax(); } - return ZERO; + + // use Math.round () instead of simple (long) to provide correct value rounding + long target = Math.round (count * percent); + if (sortedKeys == null){ + sortedKeys = new TreeSet<T> (valuesMap.keySet()); + } + for (T val : sortedKeys) { + target -= valuesMap.get(val).longValue(); + if (target <= 0){ + return val; + } + } + return ZERO; // TODO should this be getMin()? } /** * Returns the distribution of the values in the list. * - * TODO round values to reduce the number of distinct entries. - * * @return map containing either Integer or Long keys; entries are a Number array containing the key and the [Integer] count. * TODO - why is the key value also stored in the entry array? */ public synchronized HashMap<Number, Number[]> getDistribution() { - HashMap<Number, Number[]> items = new HashMap<Number, Number[]>(); - Iterator<T> itr = this.values.iterator(); + HashMap<Number, Number[]> items = new HashMap <Number, Number[]> (); Number[] dis; - while (itr.hasNext()) { - Number nx = itr.next(); - if (!(nx instanceof Integer || nx instanceof Long)){ - nx=new Long(nx.longValue()); // convert to Long unless Integer or Long - } - if (items.containsKey(nx)) { - dis = items.get(nx); - dis[1] = new Integer(dis[1].intValue() + 1); - items.put(nx, dis); - } else { - dis = new Number[2]; - dis[0] = nx; - dis[1] = new Integer(1); - items.put(nx, dis); - } + + for (T nx : valuesMap.keySet()) { + dis = new Number[2]; + dis[0] = nx; + dis[1] = valuesMap.get(nx); + items.put(nx, dis); } return items; } @@ -169,17 +179,11 @@ } public T getMin() { - if (count > 0) { - return values.get(0); - } - return MIN_VALUE; + return min; } public T getMax() { - if (count > 0) { - return values.get(count - 1); - } - return MAX_VALUE; + return max; } public int getCount() { @@ -187,23 +191,29 @@ } public void addValue(T val) { - addSortedValue(val); + sortedKeys = null; + updateValueCount(val); count++; double currentVal = val.doubleValue(); sum += currentVal; sumOfSquares += currentVal * currentVal; mean = sum / count; deviation = Math.sqrt((sumOfSquares / count) - (mean * mean)); + if (val.compareTo(max) > 0){ + max=val; + } + if (val.compareTo(min) < 0){ + min=val; + } } - private void addSortedValue(T val) { - int index = Collections.binarySearch(values, val); - if (index >= 0 && index < values.size()) { - values.add(index, val); - } else if (index == values.size() || values.size() == 0) { - values.add(val); + private void updateValueCount(T val) { + MutableLong count = valuesMap.get(val); + if (count != null) { + count.increment(); } else { - values.add((index * (-1)) - 1, val); + // insert new value + valuesMap.put(val, new MutableLong(1L)); } } } Modified: jakarta/jmeter/trunk/xdocs/changes.xml URL: http://svn.apache.org/viewvc/jakarta/jmeter/trunk/xdocs/changes.xml?rev=890589&r1=890588&r2=890589&view=diff ============================================================================== --- jakarta/jmeter/trunk/xdocs/changes.xml (original) +++ jakarta/jmeter/trunk/xdocs/changes.xml Tue Dec 15 00:53:43 2009 @@ -154,6 +154,7 @@ <li>Bug 47952 - Added JSR223 Listener</li> <li>Bug 47474 - View Results Tree support for plugin renderers</li> <li>Allow Idle Time to be saved to sample log files</li> +<li>Bug 48259 - Improve StatCalculator performance by using HashMap</li> </ul> <h3>Timers, Assertions, Config, Pre- & Post-Processors</h3> --------------------------------------------------------------------- To unsubscribe, e-mail: jmeter-dev-unsubscr...@jakarta.apache.org For additional commands, e-mail: jmeter-dev-h...@jakarta.apache.org