changeset 1152:6b290f26e637

7901332: Main JMH process runs out of memory due to accumulated Result-related garbage Summary: Tune up a few places showing up in heap dumps.
author shade
date Wed, 11 Mar 2015 01:25:03 +0300
parents bc8c05e9feb1
children 7f5b4d3188ec
files jmh-core/src/main/java/org/openjdk/jmh/results/IterationResult.java jmh-core/src/main/java/org/openjdk/jmh/results/Result.java jmh-core/src/main/java/org/openjdk/jmh/runner/link/BinaryLinkClient.java jmh-core/src/main/java/org/openjdk/jmh/runner/link/ClassConventions.java jmh-core/src/main/java/org/openjdk/jmh/util/SingletonStatistics.java jmh-core/src/test/java/org/openjdk/jmh/util/TestSingletonStatistics.java
diffstat 6 files changed, 283 insertions(+), 28 deletions(-) [+]
line wrap: on
line diff
--- a/jmh-core/src/main/java/org/openjdk/jmh/results/IterationResult.java	Wed Mar 04 12:50:30 2015 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/results/IterationResult.java	Wed Mar 11 01:25:03 2015 +0300
@@ -32,6 +32,7 @@
 import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
@@ -42,17 +43,19 @@
 public class IterationResult implements Serializable {
     private static final long serialVersionUID = 960397066774710819L;
 
+    private static final Multimap<String, Result> EMPTY_MAP = new TreeMultimap<String, Result>();
+    private static final List<Result> EMPTY_LIST = Collections.emptyList();
+
     private final BenchmarkParams benchmarkParams;
     private final IterationParams params;
-    private final List<Result> primaryResults;
-    private final Multimap<String, Result> secondaryResults;
-    private String scoreUnit;
+    private Collection<Result> primaryResults;
+    private Multimap<String, Result> secondaryResults;
 
     public IterationResult(BenchmarkParams benchmarkParams, IterationParams params) {
         this.benchmarkParams = benchmarkParams;
         this.params = params;
-        this.primaryResults = new ArrayList<Result>();
-        this.secondaryResults = new TreeMultimap<String, Result>();
+        this.primaryResults = EMPTY_LIST;
+        this.secondaryResults = EMPTY_MAP;
     }
 
     public void addResults(Collection<? extends Result> rs) {
@@ -63,17 +66,22 @@
 
     public void addResult(Result result) {
         if (result.getRole().isPrimary()) {
-            if (scoreUnit == null) {
-                scoreUnit = result.getScoreUnit();
+            if (primaryResults == EMPTY_LIST) {
+                primaryResults = Collections.singleton(result);
+            } else if (primaryResults.size() == 1) {
+                List<Result> newResults = new ArrayList<Result>(2);
+                newResults.addAll(primaryResults);
+                newResults.add(result);
+                primaryResults = newResults;
             } else {
-                if (!scoreUnit.equals(result.getScoreUnit())) {
-                    throw new IllegalStateException("Adding the result with another score unit!");
-                }
+                primaryResults.add(result);
             }
-            primaryResults.add(result);
         }
 
         if (result.getRole().isSecondary()) {
+            if (secondaryResults == EMPTY_MAP) {
+                secondaryResults = new TreeMultimap<String, Result>();
+            }
             secondaryResults.put(result.getLabel(), result);
         }
     }
@@ -103,7 +111,7 @@
 
     public Result getPrimaryResult() {
         @SuppressWarnings("unchecked")
-        Aggregator<Result> aggregator = primaryResults.get(0).getThreadAggregator();
+        Aggregator<Result> aggregator = primaryResults.iterator().next().getThreadAggregator();
         return aggregator.aggregate(primaryResults);
     }
 
@@ -116,7 +124,7 @@
     }
 
     public String getScoreUnit() {
-        return scoreUnit;
+        return getPrimaryResult().getScoreUnit();
     }
 
 }
--- a/jmh-core/src/main/java/org/openjdk/jmh/results/Result.java	Wed Mar 04 12:50:30 2015 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/results/Result.java	Wed Mar 11 01:25:03 2015 +0300
@@ -24,7 +24,8 @@
  */
 package org.openjdk.jmh.results;
 
-import org.openjdk.jmh.util.ListStatistics;
+import org.openjdk.jmh.util.Deduplicator;
+import org.openjdk.jmh.util.SingletonStatistics;
 import org.openjdk.jmh.util.Statistics;
 
 import java.io.PrintWriter;
@@ -36,6 +37,7 @@
  */
 public abstract class Result<T extends Result<T>> implements Serializable {
     private static final long serialVersionUID = -7332879501317733312L;
+    private static final Deduplicator<String> DEDUP = new Deduplicator<String>();
 
     protected final ResultRole role;
     protected final String label;
@@ -45,16 +47,14 @@
 
     public Result(ResultRole role, String label, Statistics s, String unit, AggregationPolicy policy) {
         this.role = role;
-        this.label = label;
-        this.unit = unit;
+        this.label = DEDUP.dedup(label);
+        this.unit = DEDUP.dedup(unit);
         this.statistics = s;
         this.policy = policy;
     }
 
     protected static Statistics of(double v) {
-        ListStatistics s = new ListStatistics();
-        s.addValue(v);
-        return s;
+        return new SingletonStatistics(v);
     }
 
     /**
--- a/jmh-core/src/main/java/org/openjdk/jmh/runner/link/BinaryLinkClient.java	Wed Mar 04 12:50:30 2015 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/runner/link/BinaryLinkClient.java	Wed Mar 11 01:25:03 2015 +0300
@@ -48,6 +48,7 @@
 
 public final class BinaryLinkClient {
 
+    private static final int RESET_EACH = Integer.getInteger("jmh.link.resetEach", 100);
     private static final int BUFFER_SIZE = Integer.getInteger("jmh.link.bufferSize", 64*1024);
 
     private final Object lock;
@@ -59,6 +60,7 @@
     private final ForwardingPrintStream streamOut;
     private final OutputFormat outputFormat;
     private volatile boolean failed;
+    private int resetToGo;
 
     public BinaryLinkClient(String hostName, int hostPort) throws IOException {
         this.lock = new Object();
@@ -91,13 +93,21 @@
         }
 
         // It is important to reset the OOS to avoid garbage buildup in internal identity
-        // tables, and as much as important to flush the stream to let the other party
-        // know we pushed something out.
+        // tables. However, we cannot do that after each frame since the huge referenced
+        // objects like benchmark and iteration parameters will be duplicated on the receiver
+        // side. This is why we reset only each RESET_EACH frames.
+        //
+        // It is as much as important to flush the stream to let the other party know we
+        // pushed something out.
 
         synchronized (lock) {
             try {
+                if (resetToGo-- < 0) {
+                    oos.reset();
+                    resetToGo = RESET_EACH;
+                }
+
                 oos.writeObject(frame);
-                oos.reset();
                 oos.flush();
             } catch (IOException e) {
                 failed = true;
--- a/jmh-core/src/main/java/org/openjdk/jmh/runner/link/ClassConventions.java	Wed Mar 04 12:50:30 2015 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/runner/link/ClassConventions.java	Wed Mar 11 01:25:03 2015 +0300
@@ -25,17 +25,26 @@
 package org.openjdk.jmh.runner.link;
 
 import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
 
 class ClassConventions {
 
+    private static final Map<Method, String> METHOD_NAMES = new HashMap<Method, String>();
+
     public static String getMethodName(Method m) {
-        StringBuilder builder = new StringBuilder();
-        builder.append(m.getName());
-        for (Class<?> paramType : m.getParameterTypes()) {
-            builder.append(paramType.getName());
-            builder.append(",");
+        String result = METHOD_NAMES.get(m);
+        if (result == null) {
+            StringBuilder builder = new StringBuilder();
+            builder.append(m.getName());
+            for (Class<?> paramType : m.getParameterTypes()) {
+                builder.append(paramType.getName());
+                builder.append(",");
+            }
+            result = builder.toString();
+            METHOD_NAMES.put(m, result);
         }
-        return builder.toString();
+        return result;
     }
 
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jmh-core/src/main/java/org/openjdk/jmh/util/SingletonStatistics.java	Wed Mar 11 01:25:03 2015 +0300
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package org.openjdk.jmh.util;
+
+/**
+ * Calculate statistics with just a single value.
+ */
+public class SingletonStatistics extends AbstractStatistics {
+
+    private static final long serialVersionUID = -90642978235578197L;
+
+    private final double value;
+
+    public SingletonStatistics(double value) {
+        this.value = value;
+    }
+
+    @Override
+    public double getMax() {
+        return value;
+    }
+
+    @Override
+    public double getMin() {
+        return value;
+    }
+
+    @Override
+    public long getN() {
+        return 1;
+    }
+
+    @Override
+    public double getSum() {
+        return value;
+    }
+
+    @Override
+    public double getPercentile(double rank) {
+        return value;
+    }
+
+    @Override
+    public double getVariance() {
+        return Double.NaN;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jmh-core/src/test/java/org/openjdk/jmh/util/TestSingletonStatistics.java	Wed Mar 11 01:25:03 2015 +0300
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package org.openjdk.jmh.util;
+
+import junit.framework.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Tests for Statistics
+ */
+public class TestSingletonStatistics {
+
+    private static final double VALUE = 42.73638635;
+
+    private static final ListStatistics listStats = new ListStatistics();
+    private static final SingletonStatistics singStats = new SingletonStatistics(VALUE);
+
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+        listStats.addValue(42.73638635);
+    }
+
+    /**
+     * Test of getN method, of class Statistics.
+     */
+    @Test
+    public strictfp void testGetN() {
+        assertEquals(listStats.getN(), singStats.getN());
+    }
+
+    /**
+     * Test of getSum method, of class Statistics.
+     */
+    @Test
+    public strictfp void testGetSum() {
+        assertEquals(listStats.getSum(), singStats.getSum(), 0.001);
+    }
+
+    /**
+     * Test of getMean method, of class Statistics.
+     */
+    @Test
+    public strictfp void testGetMean() {
+        assertEquals(listStats.getMean(), singStats.getMean(), 0.001);
+    }
+
+    /**
+     * Test of getMax method, of class Statistics.
+     */
+    @Test
+    public strictfp void testGetMax() {
+        assertEquals(listStats.getMax(), singStats.getMax(), 0.001);
+    }
+
+    /**
+     * Test of getMin method, of class Statistics.
+     */
+    @Test
+    public strictfp void testGetMin() {
+        assertEquals(listStats.getMin(), singStats.getMin(), 0.001);
+    }
+
+    /**
+     * Test of getVariance method, of class Statistics.
+     */
+    @Test
+    public strictfp void testGetVariance() {
+        assertEquals(listStats.getVariance(), singStats.getVariance(), 0.001);
+    }
+
+    /**
+     * Test of getStandardDeviation method, of class Statistics.
+     */
+    @Test
+    public strictfp void testGetStandardDeviation() {
+        assertEquals(listStats.getStandardDeviation(), singStats.getStandardDeviation(), 0.001);
+    }
+
+    /**
+     * Test of getConfidenceIntervalAt, of class Statistics
+     */
+    @Test
+    public strictfp void testGetConfidenceInterval() {
+        double[] listInterval = listStats.getConfidenceIntervalAt(0.999);
+        double[] singInterval = singStats.getConfidenceIntervalAt(0.999);
+        assertEquals(listInterval[0], singInterval[0], 0.001);
+        assertEquals(listInterval[1], singInterval[1], 0.001);
+    }
+
+    @Test
+    public strictfp void testPercentile_00() {
+        assertEquals(listStats.getPercentile(0), singStats.getPercentile(0), 0.002);
+    }
+
+    @Test
+    public strictfp void testPercentile_50() {
+        assertEquals(listStats.getPercentile(50), singStats.getPercentile(50), 0.002);
+    }
+
+    @Test
+    public strictfp void testPercentile_90() {
+        assertEquals(listStats.getPercentile(90), singStats.getPercentile(90), 0.002);
+    }
+
+    @Test
+    public strictfp void testPercentile_99() {
+        assertEquals(listStats.getPercentile(99), singStats.getPercentile(99), 0.002);
+    }
+
+    @Test
+    public strictfp void testPercentile_100() {
+        assertEquals(listStats.getPercentile(100), singStats.getPercentile(100), 0.002);
+    }
+
+    @Test
+    public strictfp void testSingle() {
+        SingletonStatistics s = new SingletonStatistics(42.0D);
+
+        Assert.assertEquals(1, s.getN());
+        Assert.assertEquals(42.0D, s.getSum());
+        Assert.assertEquals(42.0D, s.getMin());
+        Assert.assertEquals(42.0D, s.getMax());
+        Assert.assertEquals(42.0D, s.getMean());
+        Assert.assertEquals(Double.NaN, s.getMeanErrorAt(0.5));
+        Assert.assertEquals(Double.NaN, s.getVariance());
+        Assert.assertEquals(Double.NaN, s.getStandardDeviation());
+        Assert.assertEquals(Double.NaN, s.getConfidenceIntervalAt(0.50)[0]);
+        Assert.assertEquals(Double.NaN, s.getConfidenceIntervalAt(0.50)[1]);
+        Assert.assertEquals(42.0D, s.getPercentile(0));
+        Assert.assertEquals(42.0D, s.getPercentile(100));
+    }
+
+}