changeset 5346:4c3d953f8131

added mechanism (enabled by -G:PICache and -G:PiFilter) for saving/loading method profiling info to/from disk
author Doug Simon <doug.simon@oracle.com>
date Thu, 03 May 2012 13:39:45 +0200
parents 00803ae428d2
children 4471a30a9728
files graal/com.oracle.graal.compiler/src/com/oracle/graal/compiler/GraalOptions.java graal/com.oracle.graal.hotspot/src/com/oracle/graal/hotspot/ri/HotSpotMethodResolvedImpl.java graal/com.oracle.graal.hotspot/src/com/oracle/graal/hotspot/ri/HotSpotProfilingInfo.java graal/com.oracle.graal.hotspot/src/com/oracle/graal/hotspot/ri/HotSpotXirGenerator.java graal/com.oracle.graal.java/src/com/oracle/graal/java/GraphBuilderPhase.java graal/com.oracle.max.cri/src/com/oracle/max/cri/ci/CiUtil.java graal/com.oracle.max.cri/src/com/oracle/max/cri/ri/RiProfilingInfo.java graal/com.oracle.max.cri/src/com/oracle/max/cri/ri/RiTypeProfile.java graal/com.oracle.max.criutils/src/com/oracle/max/criutils/BaseProfilingInfo.java graal/com.oracle.max.criutils/src/com/oracle/max/criutils/JniMangle.java graal/com.oracle.max.criutils/src/com/oracle/max/criutils/SnapshotProfilingInfo.java
diffstat 11 files changed, 448 insertions(+), 57 deletions(-) [+]
line wrap: on
line diff
--- a/graal/com.oracle.graal.compiler/src/com/oracle/graal/compiler/GraalOptions.java	Wed May 02 18:23:12 2012 +0200
+++ b/graal/com.oracle.graal.compiler/src/com/oracle/graal/compiler/GraalOptions.java	Thu May 03 13:39:45 2012 +0200
@@ -22,6 +22,8 @@
  */
 package com.oracle.graal.compiler;
 
+import com.oracle.max.criutils.TTY.Filter;
+
 /**
  * This class encapsulates options that control the behavior of the Graal compiler.
  * The help message for each option is specified by a {@linkplain #helpMap help map}.
@@ -245,6 +247,16 @@
 
     public static int InstanceOfMaxHints = 1;
 
+    /**
+     * The profiling info cache directory.
+     */
+    public static String PICache = null;
+
+    /**
+     * Filters the methods for which profiling info is loaded from/saved to the {@link #PICache}.
+     */
+    public static String PIFilter = null;
+
     static {
         // turn detailed assertions on when the general assertions are on (misusing the assert keyword for this)
         assert (DetailedAsserts = true) == true;
--- a/graal/com.oracle.graal.hotspot/src/com/oracle/graal/hotspot/ri/HotSpotMethodResolvedImpl.java	Wed May 02 18:23:12 2012 +0200
+++ b/graal/com.oracle.graal.hotspot/src/com/oracle/graal/hotspot/ri/HotSpotMethodResolvedImpl.java	Thu May 03 13:39:45 2012 +0200
@@ -22,6 +22,7 @@
  */
 package com.oracle.graal.hotspot.ri;
 
+import java.io.*;
 import java.lang.annotation.*;
 import java.lang.reflect.*;
 import java.util.*;
@@ -226,18 +227,72 @@
         return compilationComplexity;
     }
 
+    private static final MethodFilter profilingInfoFilter = GraalOptions.PIFilter == null ? null : new MethodFilter(GraalOptions.PIFilter);
+
+    /**
+     * Determines if the profiling info cache should be used for this method.
+     */
+    private boolean useProfilingInfoCache() {
+        return GraalOptions.PICache != null && (profilingInfoFilter == null || profilingInfoFilter.matches(this));
+    }
+
+    private RiProfilingInfo loadProfilingInfo() {
+        if (!useProfilingInfoCache()) {
+            return null;
+        }
+        synchronized (this) {
+            File file = new File(GraalOptions.PICache, JniMangle.mangleMethod(holder, name, signature(), false));
+            if (file.exists()) {
+                try {
+                    SnapshotProfilingInfo snapshot = SnapshotProfilingInfo.load(file, compiler.getRuntime());
+                    if (snapshot.codeSize() != codeSize) {
+                        // The class file was probably changed - ignore the saved profile
+                        return null;
+                    }
+                    return snapshot;
+                } catch (Exception e) {
+                    // ignore
+                }
+            }
+            return null;
+        }
+    }
+
+    private void saveProfilingInfo(RiProfilingInfo info) {
+        if (useProfilingInfoCache()) {
+            synchronized (this) {
+                String base = JniMangle.mangleMethod(holder, name, signature(), false);
+                File file = new File(GraalOptions.PICache, base);
+                File txtFile = new File(GraalOptions.PICache, base + ".txt");
+                SnapshotProfilingInfo snapshot = info instanceof SnapshotProfilingInfo ? (SnapshotProfilingInfo) info : new SnapshotProfilingInfo(info);
+                try {
+                    snapshot.save(file, txtFile);
+                } catch (IOException e) {
+                    // ignore
+                }
+            }
+        }
+    }
+
     @Override
     public RiProfilingInfo profilingInfo() {
+        RiProfilingInfo info = loadProfilingInfo();
+        if (info != null) {
+            return info;
+        }
+
         if (GraalOptions.UseProfilingInformation && methodData == null) {
             methodData = compiler.getCompilerToVM().RiMethod_methodData(this);
         }
 
-        if (methodData == null) {
+        if (methodData == null || (!methodData.hasNormalData() && !methodData.hasExtraData())) {
             // Be optimistic and return false for exceptionSeen. A methodDataOop is allocated in case of a deoptimization.
-            return BaseProfilingInfo.get(RiExceptionSeen.FALSE);
+            info = BaseProfilingInfo.get(RiExceptionSeen.FALSE);
         } else {
-            return new HotSpotProfilingInfo(compiler, methodData);
+            info = new HotSpotProfilingInfo(compiler, methodData, codeSize);
+            saveProfilingInfo(info);
         }
+        return info;
     }
 
     @Override
--- a/graal/com.oracle.graal.hotspot/src/com/oracle/graal/hotspot/ri/HotSpotProfilingInfo.java	Wed May 02 18:23:12 2012 +0200
+++ b/graal/com.oracle.graal.hotspot/src/com/oracle/graal/hotspot/ri/HotSpotProfilingInfo.java	Thu May 03 13:39:45 2012 +0200
@@ -22,6 +22,7 @@
  */
 package com.oracle.graal.hotspot.ri;
 
+import com.oracle.max.cri.ci.*;
 import com.oracle.max.cri.ri.*;
 import com.oracle.graal.debug.*;
 import com.oracle.graal.hotspot.*;
@@ -38,15 +39,22 @@
     private int hintBCI;
     private HotSpotMethodDataAccessor dataAccessor;
     private HotSpotMethodData methodData;
+    private final int codeSize;
 
-    public HotSpotProfilingInfo(Compiler compiler, HotSpotMethodData methodData) {
+    public HotSpotProfilingInfo(Compiler compiler, HotSpotMethodData methodData, int codeSize) {
         super(compiler);
         this.methodData = methodData;
+        this.codeSize = codeSize;
         hintPosition = 0;
         hintBCI = -1;
     }
 
     @Override
+    public int codeSize() {
+        return codeSize;
+    }
+
+    @Override
     public RiTypeProfile getTypeProfile(int bci) {
         findBCI(bci, false);
         return dataAccessor.getTypeProfile(methodData, position);
@@ -140,4 +148,9 @@
         this.dataAccessor = dataAccessor;
         this.position = position;
     }
+
+    @Override
+    public String toString() {
+        return "HotSpotProfilingInfo<" + CiUtil.profileToString(this, null, "; ") + ">";
+    }
 }
--- a/graal/com.oracle.graal.hotspot/src/com/oracle/graal/hotspot/ri/HotSpotXirGenerator.java	Wed May 02 18:23:12 2012 +0200
+++ b/graal/com.oracle.graal.hotspot/src/com/oracle/graal/hotspot/ri/HotSpotXirGenerator.java	Thu May 03 13:39:45 2012 +0200
@@ -527,7 +527,15 @@
             if (hintCount == 0) {
                 assert !exact;
                 if (counters != null) {
-                    incCounter(asm, counter, counters, is(NULL_TYPE, flags) ? CheckcastCounter.noHints_unknown : is(INTERFACE_TYPE, flags) ? CheckcastCounter.noHints_iface : CheckcastCounter.noHints_class);
+                    CheckcastCounter cc;
+                    if (is(NULL_TYPE, flags)) {
+                        cc = CheckcastCounter.noHints_unknown;
+                    } else if (is(INTERFACE_TYPE, flags)) {
+                        cc = CheckcastCounter.noHints_iface;
+                    } else {
+                        cc = CheckcastCounter.noHints_class;
+                    }
+                    incCounter(asm, counter, counters, cc);
                 }
 
                 checkSubtype(asm, objHub, objHub, hub);
--- a/graal/com.oracle.graal.java/src/com/oracle/graal/java/GraphBuilderPhase.java	Wed May 02 18:23:12 2012 +0200
+++ b/graal/com.oracle.graal.java/src/com/oracle/graal/java/GraphBuilderPhase.java	Thu May 03 13:39:45 2012 +0200
@@ -148,7 +148,7 @@
 
         if (GraalOptions.PrintProfilingInformation) {
             TTY.println("Profiling info for " + method);
-            TTY.println(CiUtil.indent(CiUtil.profileAsString(method), "  "));
+            TTY.println(CiUtil.indent(CiUtil.profileToString(profilingInfo, method, CiUtil.NEW_LINE), "  "));
         }
 
         // compute the block map, setup exception handlers and get the entrypoint(s)
--- a/graal/com.oracle.max.cri/src/com/oracle/max/cri/ci/CiUtil.java	Wed May 02 18:23:12 2012 +0200
+++ b/graal/com.oracle.max.cri/src/com/oracle/max/cri/ci/CiUtil.java	Thu May 03 13:39:45 2012 +0200
@@ -739,62 +739,70 @@
     }
 
     /**
-     * Formats the profiling information associated with a given method to a string.
+     * Formats some profiling information associated as a string.
+     *
+     * @param info the profiling info to format
+     * @param method an optional method that augments the profile string returned
+     * @param sep the separator to use for each separate profile record
      */
-    public static String profileAsString(RiResolvedMethod method) {
+    public static String profileToString(RiProfilingInfo info, RiResolvedMethod method, String sep) {
         StringBuilder buf = new StringBuilder(100);
-        buf.append(String.format("canBeStaticallyBound: %b%n", method.canBeStaticallyBound())).
-            append(String.format("invocationCount: %d%n", method.invocationCount()));
-        RiProfilingInfo profilingInfo = method.profilingInfo();
-        if (profilingInfo != null) {
-            for (int i = 0; i < method.codeSize(); i++) {
-                if (profilingInfo.getExecutionCount(i) != -1) {
-                    buf.append(String.format("executionCount@%d: %d%n", i, profilingInfo.getExecutionCount(i)));
-                }
-
-                if (profilingInfo.getBranchTakenProbability(i) != -1) {
-                    buf.append(String.format("branchProbability@%d: %.3f%n", i, profilingInfo.getBranchTakenProbability(i)));
-                }
+        if (method != null) {
+            buf.append(String.format("canBeStaticallyBound: %b%s", method.canBeStaticallyBound(), sep)).
+            append(String.format("invocationCount: %d%s", method.invocationCount(), sep));
+        }
+        for (int i = 0; i < info.codeSize(); i++) {
+            if (info.getExecutionCount(i) != -1) {
+                buf.append(String.format("executionCount@%d: %d%s", i, info.getExecutionCount(i), sep));
+            }
 
-                double[] switchProbabilities = profilingInfo.getSwitchProbabilities(i);
-                if (switchProbabilities != null) {
-                    buf.append(String.format("switchProbabilities@%d:", i));
-                    for (int j = 0; j < switchProbabilities.length; j++) {
-                        buf.append(String.format(" %.3f", switchProbabilities[j]));
-                    }
-                    buf.append(NEW_LINE);
-                }
-
-                if (profilingInfo.getExceptionSeen(i) != RiExceptionSeen.FALSE) {
-                    buf.append(String.format("exceptionSeen@%d: %s%n", i, profilingInfo.getExceptionSeen(i).name()));
-                }
-
-                RiTypeProfile typeProfile = profilingInfo.getTypeProfile(i);
-                if (typeProfile != null) {
-                    ProfiledType[] ptypes = typeProfile.getTypes();
-                    if (ptypes != null) {
-                        buf.append(String.format("types@%d:%n", i));
-                        for (int j = 0; j < ptypes.length; j++) {
-                            ProfiledType ptype = ptypes[j];
-                            buf.append(String.format("  %.3f %s%n", ptype.probability, ptype.type));
-                        }
-                        buf.append(String.format("  %.3f <not recorded>%n", typeProfile.getNotRecordedProbability()));
-                    }
-                }
+            if (info.getBranchTakenProbability(i) != -1) {
+                buf.append(String.format("branchProbability@%d: %.3f%s", i, info.getBranchTakenProbability(i), sep));
             }
 
-            boolean firstDeoptReason = true;
-            for (RiDeoptReason reason: RiDeoptReason.values()) {
-                int count = profilingInfo.getDeoptimizationCount(reason);
-                if (count > 0) {
-                    if (firstDeoptReason) {
-                        buf.append("deoptimization history").append(NEW_LINE);
-                        firstDeoptReason = false;
+            double[] switchProbabilities = info.getSwitchProbabilities(i);
+            if (switchProbabilities != null) {
+                buf.append(String.format("switchProbabilities@%d:", i));
+                for (int j = 0; j < switchProbabilities.length; j++) {
+                    buf.append(String.format(" %.3f", switchProbabilities[j]));
+                }
+                buf.append(sep);
+            }
+
+            if (info.getExceptionSeen(i) != RiExceptionSeen.FALSE) {
+                buf.append(String.format("exceptionSeen@%d: %s%s", i, info.getExceptionSeen(i).name(), sep));
+            }
+
+            RiTypeProfile typeProfile = info.getTypeProfile(i);
+            if (typeProfile != null) {
+                ProfiledType[] ptypes = typeProfile.getTypes();
+                if (ptypes != null) {
+                    buf.append(String.format("types@%d:", i));
+                    for (int j = 0; j < ptypes.length; j++) {
+                        ProfiledType ptype = ptypes[j];
+                        buf.append(String.format(" %.3f (%s)%s", ptype.probability, ptype.type, sep));
                     }
-                    buf.append(String.format("  %s: %d%n", reason.name(), count));
+                    buf.append(String.format(" %.3f <not recorded>%s", typeProfile.getNotRecordedProbability(), sep));
                 }
             }
         }
-        return buf.toString();
+
+        boolean firstDeoptReason = true;
+        for (RiDeoptReason reason: RiDeoptReason.values()) {
+            int count = info.getDeoptimizationCount(reason);
+            if (count > 0) {
+                if (firstDeoptReason) {
+                    buf.append("deoptimization history").append(sep);
+                    firstDeoptReason = false;
+                }
+                buf.append(String.format(" %s: %d%s", reason.name(), count, sep));
+            }
+        }
+        if (buf.length() == 0) {
+            return "";
+        }
+        String s = buf.toString();
+        assert s.endsWith(sep);
+        return s.substring(0, s.length() - sep.length());
     }
 }
--- a/graal/com.oracle.max.cri/src/com/oracle/max/cri/ri/RiProfilingInfo.java	Wed May 02 18:23:12 2012 +0200
+++ b/graal/com.oracle.max.cri/src/com/oracle/max/cri/ri/RiProfilingInfo.java	Thu May 03 13:39:45 2012 +0200
@@ -30,6 +30,12 @@
  * as the profiling information may be changed by other Java threads at any time.
  */
 public interface RiProfilingInfo {
+
+    /**
+     * Gets the length of the code associated with this profile.
+     */
+    int codeSize();
+
     /**
      * Returns an estimate of how often the branch at the given byte code was taken.
      * @return The estimated probability, with 0.0 meaning never and 1.0 meaning always, or -1 if this information is not available.
@@ -52,8 +58,8 @@
 
     /**
      * Returns information if the given BCI did ever throw an exception.
-     * @return @link{RiExceptionSeen.TRUE} if the instruction has thrown an exception at least once,
-     * @link{RiExceptionSeen.FALSE} if it never threw an exception, and @link{RiExceptionSeen.UNKNOWN}
+     * @return {@link RiExceptionSeen#TRUE} if the instruction has thrown an exception at least once,
+     * {@link RiExceptionSeen#FALSE} if it never threw an exception, and {@link RiExceptionSeen#NOT_SUPPORTED}
      * if this information was not recorded.
      */
     RiExceptionSeen getExceptionSeen(int bci);
--- a/graal/com.oracle.max.cri/src/com/oracle/max/cri/ri/RiTypeProfile.java	Wed May 02 18:23:12 2012 +0200
+++ b/graal/com.oracle.max.cri/src/com/oracle/max/cri/ri/RiTypeProfile.java	Thu May 03 13:39:45 2012 +0200
@@ -37,7 +37,8 @@
      * A profiled type that has a probability. Profiled types are naturally sorted in
      * descending order of their probabilities.
      */
-    public static class ProfiledType implements Comparable<ProfiledType> {
+    public static class ProfiledType implements Comparable<ProfiledType>, Serializable {
+        private static final long serialVersionUID = 7838575753661305744L;
         public final RiResolvedType type;
         public final double probability;
 
--- a/graal/com.oracle.max.criutils/src/com/oracle/max/criutils/BaseProfilingInfo.java	Wed May 02 18:23:12 2012 +0200
+++ b/graal/com.oracle.max.criutils/src/com/oracle/max/criutils/BaseProfilingInfo.java	Thu May 03 13:39:45 2012 +0200
@@ -22,6 +22,7 @@
  */
 package com.oracle.max.criutils;
 
+import com.oracle.max.cri.ci.*;
 import com.oracle.max.cri.ri.*;
 
 
@@ -43,6 +44,11 @@
     }
 
     @Override
+    public int codeSize() {
+        return 0;
+    }
+
+    @Override
     public RiTypeProfile getTypeProfile(int bci) {
         return null;
     }
@@ -75,4 +81,9 @@
     public int getDeoptimizationCount(RiDeoptReason reason) {
         return 0;
     }
+
+    @Override
+    public String toString() {
+        return "BaseProfilingInfo<" + CiUtil.profileToString(this, null, "; ") + ">";
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.max.criutils/src/com/oracle/max/criutils/JniMangle.java	Thu May 03 13:39:45 2012 +0200
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2007, 2011, 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.
+ *
+ * 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 com.oracle.max.criutils;
+
+import com.oracle.max.cri.ci.*;
+import com.oracle.max.cri.ri.*;
+
+/**
+ * A utility for mangling Java method name and signatures into C function names.
+ *
+ * @see "http://java.sun.com/j2se/1.5.0/docs/guide/jni/spec/design.html#wp615"
+ */
+public final class JniMangle {
+
+    private JniMangle() {
+    }
+
+    /**
+     * Mangles a given string such that it can be represented as (part of) a valid C function name.
+     */
+    private static String mangle(String name) {
+        final StringBuilder mangledName = new StringBuilder(100);
+        final int length = name.length();
+        for (int i = 0; i < length; i++) {
+            final char ch = name.charAt(i);
+            if (isAlphaNumeric(ch)) {
+                mangledName.append(ch);
+            } else if (ch == '_') {
+                mangledName.append("_1");
+            } else if (ch == '.') {
+                mangledName.append("_");
+            } else if (ch == ';') {
+                mangledName.append("_2");
+            } else if (ch == '[') {
+                mangledName.append("_3");
+            } else {
+                mangledName.append(String.format("%04x", (int) ch));
+            }
+        }
+        return mangledName.toString();
+    }
+
+    /**
+     * The delimiter in the string returned by {@link #mangleMethod(TypeDescriptor, String, SignatureDescriptor, boolean)} separating
+     * the short mangled form from the suffix to be added to obtain the long mangled form.
+     */
+    public static final char LONG_NAME_DELIMITER = ' ';
+
+    /**
+     * Mangles a Java method to the symbol(s) to be used when binding it to a native function.
+     * If {@code signature} is {@code null}, then a non-qualified symbol is returned.
+     * Otherwise, a qualified symbol is returned. A qualified symbol has its non-qualified
+     * prefix separated from its qualifying suffix by {@link #LONG_NAME_DELIMITER} if
+     * {@code splitSuffix} is {@code true}.
+     *
+     * @param declaringClass a fully qualified class descriptor
+     * @param name a Java method name (not checked here for validity)
+     * @param signature if non-null, a method signature to include in the mangled name
+     * @param splitSuffix determines if {@link #LONG_NAME_DELIMITER} should be used as described above
+     * @return the symbol for the C function as described above
+     */
+    public static String mangleMethod(RiResolvedType declaringClass, String name, RiSignature signature, boolean splitSuffix) {
+        final StringBuilder result = new StringBuilder(100);
+        final String declaringClassName = CiUtil.toJavaName(declaringClass);
+        result.append("Java_").append(mangle(declaringClassName)).append('_').append(mangle(name));
+        if (signature != null) {
+            if (splitSuffix) {
+                result.append(LONG_NAME_DELIMITER);
+            }
+            result.append("__");
+            final String sig = signature.asString();
+            final String parametersSignature = sig.substring(1, sig.lastIndexOf(')')).replace('/', '.').replace('$', '.');
+            result.append(mangle(parametersSignature));
+        }
+        return result.toString();
+    }
+
+    private static boolean isAlphaNumeric(char ch) {
+        return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9');
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.max.criutils/src/com/oracle/max/criutils/SnapshotProfilingInfo.java	Thu May 03 13:39:45 2012 +0200
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2012, 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.
+ *
+ * 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 com.oracle.max.criutils;
+
+import java.io.*;
+
+import com.oracle.max.cri.ci.*;
+import com.oracle.max.cri.ri.*;
+
+/**
+ * A profiling info snapshot that can be {@linkplain #save(File, File) saved} to
+ * and {@linkplain #load(File, RiRuntime) loaded} from a file.
+ */
+public class SnapshotProfilingInfo implements RiProfilingInfo, Serializable {
+
+    private static final long serialVersionUID = -5837615128782960391L;
+    private final double[] branchTaken;
+    private final double[][] switches;
+    private final RiTypeProfile[] typeProfiles;
+    private final RiExceptionSeen[] exceptions;
+    private final int[] executions;
+    private final int[] deopts;
+
+    public SnapshotProfilingInfo(RiProfilingInfo other) {
+        int codeSize = other.codeSize();
+        branchTaken = new double[codeSize];
+        switches = new double[codeSize][];
+        typeProfiles = new RiTypeProfile[codeSize];
+        exceptions = new RiExceptionSeen[codeSize];
+        executions = new int[codeSize];
+        deopts = new int[RiDeoptReason.values().length];
+
+        for (int bci = 0; bci < codeSize; bci++) {
+            executions[bci] = other.getExecutionCount(bci);
+            exceptions[bci] = other.getExceptionSeen(bci);
+            branchTaken[bci] = other.getBranchTakenProbability(bci);
+            switches[bci] = other.getSwitchProbabilities(bci);
+            typeProfiles[bci] = other.getTypeProfile(bci);
+        }
+        for (RiDeoptReason reason: RiDeoptReason.values()) {
+            deopts[reason.ordinal()] = other.getDeoptimizationCount(reason);
+        }
+    }
+
+    @Override
+    public int codeSize() {
+        return branchTaken.length;
+    }
+
+    public double getBranchTakenProbability(int bci) {
+        return bci < branchTaken.length ? branchTaken[bci] : -1D;
+    }
+    public double[] getSwitchProbabilities(int bci) {
+        return bci < switches.length ? switches[bci] : null;
+    }
+    public RiTypeProfile getTypeProfile(int bci) {
+        return bci < typeProfiles.length ? typeProfiles[bci] : null;
+    }
+    public RiExceptionSeen getExceptionSeen(int bci) {
+        return bci < exceptions.length ? exceptions[bci] : RiExceptionSeen.NOT_SUPPORTED;
+    }
+    public int getExecutionCount(int bci) {
+        return bci < executions.length ? executions[bci] : -1;
+    }
+    public int getDeoptimizationCount(RiDeoptReason reason) {
+        return deopts[reason.ordinal()];
+    }
+
+    @Override
+    public String toString() {
+        return CiUtil.profileToString(this, null, "; ");
+    }
+
+    /**
+     * Deserializes a profile snapshot from a file.
+     *
+     * @param file a file created by {@link #save(File, File)}
+     * @param runtime the runtime used to resolve {@link RiResolvedType}s during deserialization
+     * @return
+     * @throws ClassNotFoundException
+     * @throws IOException
+     */
+    public static SnapshotProfilingInfo load(File file, RiRuntime runtime) throws ClassNotFoundException, IOException {
+        SnapshotProfilingInfo.SnapshotObjectInputStream ois = new SnapshotObjectInputStream(new BufferedInputStream(new FileInputStream(file), (int) file.length()), runtime);
+        try {
+            return (SnapshotProfilingInfo) ois.readObject();
+        } finally {
+            ois.close();
+        }
+    }
+
+    /**
+     * Serializes this snapshot to a file.
+     *
+     * @param file the file to which this snapshot is serialized
+     * @param txtFile
+     * @throws IOException
+     */
+    public void save(File file, File txtFile) throws IOException {
+        SnapshotProfilingInfo.SnapshotObjectOutputStream oos = new SnapshotObjectOutputStream(new FileOutputStream(file));
+        try {
+            oos.writeObject(this);
+        } finally {
+            oos.close();
+        }
+        if (txtFile != null) {
+            PrintStream out = new PrintStream(txtFile);
+            try {
+                out.println(CiUtil.profileToString(this, null, CiUtil.NEW_LINE));
+            } finally {
+                out.close();
+            }
+        }
+    }
+
+    static class RiResolvedTypePlaceholder implements Serializable {
+        private static final long serialVersionUID = -5149982457010023916L;
+        final Class javaMirror;
+        public RiResolvedTypePlaceholder(Class javaMirror) {
+            this.javaMirror = javaMirror;
+        }
+    }
+
+    static class SnapshotObjectOutputStream extends ObjectOutputStream {
+        public SnapshotObjectOutputStream(OutputStream out) throws IOException {
+            super(out);
+            enableReplaceObject(true);
+        }
+
+        @Override
+        protected Object replaceObject(Object obj) throws IOException {
+            if (obj instanceof RiResolvedType) {
+                return new RiResolvedTypePlaceholder(((RiResolvedType) obj).toJava());
+            }
+            return obj;
+        }
+    }
+
+    static class SnapshotObjectInputStream extends ObjectInputStream {
+        private final RiRuntime runtime;
+        public SnapshotObjectInputStream(InputStream in, RiRuntime runtime) throws IOException {
+            super(in);
+            enableResolveObject(true);
+            this.runtime = runtime;
+        }
+
+        @Override
+        protected Object resolveObject(Object obj) throws IOException {
+            if (obj instanceof SnapshotProfilingInfo.RiResolvedTypePlaceholder) {
+                RiResolvedType type = runtime.getType(((SnapshotProfilingInfo.RiResolvedTypePlaceholder) obj).javaMirror);
+                return type;
+            }
+            return obj;
+        }
+    }
+}