Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: jruby/jruby
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 2cf954fbc0d0
Choose a base ref
...
head repository: jruby/jruby
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: e598dec140d6
Choose a head ref
  • 2 commits
  • 1 file changed
  • 1 contributor

Commits on Dec 2, 2015

  1. Copy the full SHA
    a0ac5fc View commit details
  2. More refactoring

    enebo committed Dec 2, 2015
    Copy the full SHA
    e598dec View commit details
Showing with 70 additions and 54 deletions.
  1. +70 −54 core/src/main/java/org/jruby/ir/interpreter/Profiler.java
124 changes: 70 additions & 54 deletions core/src/main/java/org/jruby/ir/interpreter/Profiler.java
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@

import org.jruby.RubyModule;
import org.jruby.compiler.Compilable;
import org.jruby.internal.runtime.methods.DynamicMethod;
import org.jruby.ir.Counter;
import org.jruby.ir.IRClosure;
import org.jruby.ir.IRMethod;
@@ -11,7 +12,6 @@
import org.jruby.ir.operands.Operand;
import org.jruby.ir.operands.WrappedIRClosure;
import org.jruby.runtime.CallSite;
import org.jruby.runtime.callsite.CacheEntry;
import org.jruby.runtime.callsite.CachingCallSite;

import java.util.*;
@@ -27,6 +27,7 @@
public class Profiler {
public static final int UNASSIGNED_VERSION = -1;
private static final int PROFILE_PERIOD = 20000;
private static final float INSIGNIFICANT_PERCENTAGE = 1.0f; // FIXME: arbitrarily chosen

// Structure on what a callsite is. It lives in an IC. It will be some form of call (CallBase).
// It might have been called count times.
@@ -118,42 +119,50 @@ private static boolean isStillBootstrapping() {
return periodsWithoutChanges < NUMBER_OF_NON_MODIFYING_EXECUTIONS;
}

private static void analyzeProfile() {
//System.out.println("MOD COUNT: " + codeModificationsCount + ", Periods wo change: " + periodsWithoutChanges);
// Don't bother doing any analysis until we see the system start to settle down from lots of modifications.
if (isStillBootstrapping()) return;

versionCount++;

// System.out.println("-------------------start analysis-----------------------");

final ArrayList<IRCallSite> callSites = new ArrayList<>();
/**
* Examine callProfiles looking for eligible monomorphic callsites. Return total number of calls
* executed from the profile.
*
* @param callSites out param of eligible callsites
* @return total number of calls executed by all entries in the callprofile
*/
private static long findInliningCandidates(List<IRCallSite> callSites) {
long total = 0; // Total number of calls found in this scope.

long total = 0; // Total number of times
// Register all monomorphic found callsites which are eligible for inlining.
for (Long id: callProfile.keySet()) {
CallSiteProfile csp = callProfile.get(id);
IRCallSite cs = csp.cs;
CallSiteProfile callSiteProfile = callProfile.get(id);
IRCallSite callSite = callSiteProfile.cs;
boolean monomorphic = callSiteProfile.retallyCallCount();

boolean monomorphic = csp.retallyCallCount();
// System.out.println("CS CALL COUNT: " + cs.count + ", MONO: " + monomorphic + ", NUMBER OF CS TYPES: " + csp.counters.size());
if (monomorphic && !callSite.call.inliningBlocked()) {
CallSite runtimeCallSite = callSite.call.getCallSite();
if (runtimeCallSite != null && runtimeCallSite instanceof CachingCallSite) {
DynamicMethod method = ((CachingCallSite) runtimeCallSite).getCache().method;

CallBase call = cs.call;
if (monomorphic && !call.inliningBlocked()) {
CallSite runtimeCS = call.getCallSite();
if (runtimeCS != null && runtimeCS instanceof CachingCallSite) {
CachingCallSite ccs = (CachingCallSite)runtimeCS;
CacheEntry ce = ccs.getCache();
if (!(method instanceof Compilable) || !((Compilable) method).getIRScope().isFullBuildComplete()) continue;

if (!(ce.method instanceof Compilable) || !((Compilable) ce.method).getIRScope().isFullBuildComplete()) continue;

callSites.add(cs);
cs.liveMethod = (Compilable) ce.method;
callSites.add(callSite);
callSite.liveMethod = (Compilable) method;
}
}

total += cs.count;
total += callSite.count;
}

return total;
}

private static void analyzeProfile() {
//System.out.println("MOD COUNT: " + codeModificationsCount + ", Periods wo change: " + periodsWithoutChanges);
// Don't bother doing any analysis until we see the system start to settle down from lots of modifications.
if (isStillBootstrapping()) return;

versionCount++;

final ArrayList<IRCallSite> callSites = new ArrayList<>();
long total = findInliningCandidates(callSites);

Collections.sort(callSites, new java.util.Comparator<IRCallSite> () {
@Override
public int compare(IRCallSite a, IRCallSite b) {
@@ -165,16 +174,14 @@ public int compare(IRCallSite a, IRCallSite b) {
// Find top N call sites
double freq = 0.0;
int i = 0;
boolean noInlining = true;
Set<InterpreterContext> inlinedScopes = new HashSet<>();
for (IRCallSite ircs: callSites) {
double contrib = (ircs.count*100.0)/total;
for (IRCallSite callSite: callSites) {
double percentOfTotalCalls = (callSite.count * 100.0) / total;

// 1% is arbitrary
if (contrib < 1.0) break;
if (percentOfTotalCalls < INSIGNIFICANT_PERCENTAGE) break;

i++;
freq += contrib;
freq += percentOfTotalCalls;

// This check is arbitrary
if (i == 100 || freq > 99.0) break;
@@ -183,20 +190,21 @@ public int compare(IRCallSite a, IRCallSite b) {
//" in scope " + ircs.ic.getScope() + " with count " + ircs.count + "; contrib " + contrib + "; freq: " + freq);

// Now inline here!
CallBase call = ircs.call;
CallBase call = callSite.call;

InterpreterContext hs = ircs.ic;
boolean isHotClosure = hs.getScope() instanceof IRClosure;
IRScope hc = isHotClosure ? hs.getScope() : null;
// This has couple of assumptions in it:
// 1. nothing hot could ever not exist in a non-fully built parent scope so FIC is available.
InterpreterContext ic = callSite.ic;
boolean isClosure = ic.getScope() instanceof IRClosure;

// This has several of assumptions in it:
// 1. nothing hot could ever not exist in a non-fully built parent scope so FIC is available. This assumption cannot be true
// 2. if we ever have three ICs (startup, full, profiled) [or more than three] then we can:
// a. use full and ignore profiled
// b. use profiled (or last profiled in case more multiple profiled versions)
hs = isHotClosure ? hs.getScope().getLexicalParent().getFullInterpreterContext() : hs;
ic = isClosure ? ic.getScope().getLexicalParent().getFullInterpreterContext() : ic;

Compilable tgtMethod = ircs.liveMethod;
Compilable tgtMethod = callSite.liveMethod;

// MOVE INTO shouldInline
Instr[] instrs = tgtMethod.getIRScope().getFullInterpreterContext().getInstructions();
// Dont inline large methods -- 500 is arbitrary
// Can be null if a previously inlined method hasn't been rebuilt
@@ -206,21 +214,13 @@ public int compare(IRCallSite a, IRCallSite b) {
continue;
}

RubyModule implClass = ircs.liveMethod.getImplementationClass();
int classToken = implClass.getGeneration();
boolean inlineCall = true;
if (isHotClosure) {
Operand clArg = call.getClosureArg(null);
inlineCall = (clArg instanceof WrappedIRClosure) && (((WrappedIRClosure)clArg).getClosure() == hc);
}

if (inlineCall && hs.getScope().isFullBuildComplete()) {
//noInlining = false;
if (shouldInline(call, ic, isClosure)) {
RubyModule implClass = callSite.liveMethod.getImplementationClass();
long start = new java.util.Date().getTime();
hs.getScope().inlineMethod(tgtMethod, implClass, classToken, null, call, !inlinedScopes.contains(hs));
inlinedScopes.add(hs);
ic.getScope().inlineMethod(tgtMethod, implClass, implClass.getGeneration(), null, call, !inlinedScopes.contains(ic));
inlinedScopes.add(ic);
long end = new java.util.Date().getTime();
System.out.println("Inlined " + tgtMethod + " in " + hs + " @ instr " + call + " in time (ms): " + (end-start) + " # instrs: " + instrs.length);
System.out.println("Inlined " + tgtMethod + " in " + ic + " @ instr " + call + " in time (ms): " + (end-start) + " # instrs: " + instrs.length);

inlineCount++;
} else {
@@ -248,6 +248,22 @@ public int compare(IRCallSite a, IRCallSite b) {
}
}

/**
* All methods will inline so long as they have been fully built. A hot closure will inline through the method
* which call it.
*/
private static boolean shouldInline(CallBase call, InterpreterContext ic, boolean isClosure) {
// FIXME: Closure getting lexical parent can end up with null. We should fix that in parent method to remove this null check.
boolean fullBuild = ic != null && ic.getScope().isFullBuildComplete();

if (isClosure) {
Operand closureArg = call.getClosureArg(null);
return fullBuild && closureArg instanceof WrappedIRClosure && ((WrappedIRClosure) closureArg).getClosure() == ic.getScope();
}

return fullBuild;
}

private static void outputProfileStats() {
ArrayList<IRScope> scopes = new ArrayList<IRScope>(scopeThreadPollCounts.keySet());
Collections.sort(scopes, new java.util.Comparator<IRScope> () {