/*
This file is part of jpcsp.
Jpcsp is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Jpcsp 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 for more details.
You should have received a copy of the GNU General Public License
along with Jpcsp. If not, see <http://www.gnu.org/licenses/>.
*/
package jpcsp.Allegrex.compiler;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import jpcsp.Allegrex.compiler.nativeCode.NativeCodeManager;
import jpcsp.Allegrex.compiler.nativeCode.NativeCodeSequence;
import jpcsp.settings.AbstractBoolSettingsListener;
import jpcsp.settings.Settings;
import org.apache.log4j.Logger;
/**
* @author gid15
*
*/
public class Profiler {
public static Logger log = Logger.getLogger("profiler");
private static boolean profilerEnabled = false;
private static final HashMap<Integer, Long> callCounts = new HashMap<Integer, Long>();
private static final HashMap<Integer, Long> instructionCounts = new HashMap<Integer, Long>();
private static final HashMap<Integer, Long> backBranchCounts = new HashMap<Integer, Long>();
private static final Long zero = new Long(0);
private static final int detailedCodeBlockLogThreshold = 50;
private static final int codeLogMaxLength = 700;
private static final int backBranchMaxLength = 100;
private static final int backBranchContextBefore = 5;
private static final int backBranchContextAfter = 3;
private static ProfilerEnabledSettingsListerner profilerEnabledSettingsListerner;
private static int compilationCount;
private static long compilationTimeMicros;
private static long longestCompilationTimeMicros;
private static class ProfilerEnabledSettingsListerner extends AbstractBoolSettingsListener {
@Override
protected void settingsValueChanged(boolean value) {
setProfilerEnabled(value);
}
}
public static void initialise() {
if (profilerEnabledSettingsListerner == null) {
profilerEnabledSettingsListerner = new ProfilerEnabledSettingsListerner();
Settings.getInstance().registerSettingsListener("Profiler", "emu.profiler", profilerEnabledSettingsListerner);
}
reset();
}
private static void setProfilerEnabled(boolean enabled) {
profilerEnabled = enabled;
}
public static boolean isProfilerEnabled() {
return profilerEnabled;
}
public static void reset() {
if (!profilerEnabled) {
return;
}
callCounts.clear();
instructionCounts.clear();
backBranchCounts.clear();
compilationCount = 0;
compilationTimeMicros = 0;
longestCompilationTimeMicros = 0;
}
public static void exit() {
if (!profilerEnabled) {
return;
}
List<Integer> sortedBackBranches = new ArrayList<Integer>(backBranchCounts.keySet());
Collections.sort(sortedBackBranches, new BackBranchComparator());
List<CodeBlock> sortedCodeBlocks = new ArrayList<CodeBlock>(RuntimeContext.getCodeBlocks().values());
Collections.sort(sortedCodeBlocks, new CodeBlockComparator());
long allCycles = 0;
for (Long instructionCount : instructionCounts.values()) {
allCycles += instructionCount;
}
int count = 0;
double avg = compilationCount == 0 ? 0.0 : compilationTimeMicros / (double) compilationCount / 1000;
log.info(String.format("Compilation time %dms, %d calls, average %.1fms, longest %dms", compilationTimeMicros / 1000, compilationCount, avg, longestCompilationTimeMicros / 1000));
log.info(String.format("CodeBlocks profiling information (%,d total cycles):", allCycles));
for (CodeBlock codeBlock : sortedCodeBlocks) {
long callCount = getCallCount(codeBlock);
long instructionCount = getInstructionCount(codeBlock);
if (callCount == 0 && instructionCount == 0) {
// This and the following CodeBlocks have not been called since
// the Profiler has been reset. Skip them.
break;
}
logCodeBlock(codeBlock, allCycles, instructionCount, callCount, count, sortedBackBranches);
count++;
}
}
private static void logCodeBlock(CodeBlock codeBlock, long allCycles, long instructionCount, long callCount, int count, List<Integer> sortedBackBranches) {
String name = codeBlock.getClassName();
NativeCodeManager nativeCodeManager = Compiler.getInstance().getNativeCodeManager();
NativeCodeSequence nativeCodeSequence = nativeCodeManager.getCompiledNativeCodeBlock(codeBlock.getStartAddress());
if (nativeCodeSequence != null) {
name = String.format("%s (%s)", name, nativeCodeSequence.getName());
}
int lowestAddress = codeBlock.getLowestAddress();
int highestAddress = codeBlock.getHighestAddress();
int length = (highestAddress - lowestAddress) / 4 + 1;
double percentage = 0;
if (allCycles != 0) {
percentage = (instructionCount / (double) allCycles) * 100;
}
log.info(String.format("%s %,d instructions (%2.3f%%), %,d calls (%08X - %08X, length %d)", name, instructionCount, percentage, callCount, lowestAddress, highestAddress, length));
if (count < detailedCodeBlockLogThreshold && codeBlock.getLength() <= codeLogMaxLength) {
logCode(codeBlock);
}
for (int address : sortedBackBranches) {
if (address < lowestAddress || address > highestAddress) {
continue;
}
CodeInstruction codeInstruction = codeBlock.getCodeInstruction(address);
if (codeInstruction == null) {
continue;
}
int branchingToAddress = codeInstruction.getBranchingTo();
// Add 2 for branch instruction itself and delay slot
int backBranchLength = (address - branchingToAddress) / 4 + 2;
log.info(String.format(" Back Branch %08X %,d times (length %d)", address, backBranchCounts.get(address), backBranchLength));
if (count < detailedCodeBlockLogThreshold && backBranchLength <= backBranchMaxLength) {
logCode(codeBlock, branchingToAddress, backBranchLength, backBranchContextBefore, backBranchContextAfter, address);
}
}
}
private static void logCode(CodeBlock codeBlock) {
logCode(codeBlock, codeBlock.getLowestAddress(), codeBlock.getLength(), 0, 0, -1);
}
private static void logCode(SequenceCodeInstruction sequenceCodeInstruction, int highlightAddress) {
for (CodeInstruction codeInstruction : sequenceCodeInstruction.getCodeSequence().getInstructions()) {
logCode(codeInstruction, highlightAddress);
}
}
private static void logCode(CodeInstruction codeInstruction, int highlightAddress) {
if (codeInstruction == null) {
return;
}
int address = codeInstruction.getAddress();
if (codeInstruction instanceof SequenceCodeInstruction) {
logCode((SequenceCodeInstruction) codeInstruction, highlightAddress);
} else {
int opcode = codeInstruction.getOpcode();
String prefix = " ";
if (address == highlightAddress) {
prefix = "-->";
}
log.info(String.format("%s %08X:[%08X]: %s", prefix, address, opcode, codeInstruction.disasm(address, opcode)));
}
}
private static void logCode(CodeBlock codeBlock, int startAddress, int length, int contextBefore, int contextAfter, int highlightAddress) {
for (int i = -contextBefore; i < length + contextAfter; i++) {
int address = startAddress + (i * 4);
CodeInstruction codeInstruction = codeBlock.getCodeInstruction(address);
if (highlightAddress >= 0 && address == startAddress) {
// Also highlight startAddress
logCode(codeInstruction, startAddress);
} else {
logCode(codeInstruction, highlightAddress);
}
}
}
private static long getCallCount(CodeBlock codeBlock) {
Long callCount = callCounts.get(codeBlock.getStartAddress());
if (callCount == null) {
return 0;
}
return callCount;
}
private static long getInstructionCount(CodeBlock codeBlock) {
Long instructionCount = instructionCounts.get(codeBlock.getStartAddress());
if (instructionCount == null) {
return 0;
}
return instructionCount;
}
public static void addCall(int address) {
Long callCount = callCounts.get(address);
if (callCount == null) {
callCount = zero;
}
callCounts.put(address, callCount + 1);
}
public static void addInstructionCount(int count, int address) {
Long instructionCount = instructionCounts.get(address);
if (instructionCount == null) {
instructionCount = zero;
}
instructionCounts.put(address, instructionCount + count);
}
public static void addBackBranch(int address) {
Long backBranchCount = backBranchCounts.get(address);
if (backBranchCount == null) {
backBranchCount = zero;
}
backBranchCounts.put(address, backBranchCount + 1);
}
private static class BackBranchComparator implements Comparator<Integer> {
@Override
public int compare(Integer address1, Integer address2) {
long count1 = backBranchCounts.get(address1);
long count2 = backBranchCounts.get(address2);
return (count2 > count1 ? 1 : -1);
}
}
private static class CodeBlockComparator implements Comparator<CodeBlock> {
@Override
public int compare(CodeBlock codeBlock1, CodeBlock codeBlock2) {
long instructionCallCount1 = getInstructionCount(codeBlock1);
long instructionCallCount2 = getInstructionCount(codeBlock2);
if (instructionCallCount1 != instructionCallCount2) {
return (instructionCallCount2 > instructionCallCount1 ? 1 : -1);
}
long callCount1 = getCallCount(codeBlock1);
long callCount2 = getCallCount(codeBlock2);
if (callCount1 != callCount2) {
return (callCount2 > callCount1 ? 1 : -1);
}
return codeBlock2.getStartAddress() - codeBlock1.getStartAddress();
}
}
public static void addCompilation(long compilationTimeMicros) {
compilationCount++;
Profiler.compilationTimeMicros += compilationTimeMicros;
if (compilationTimeMicros > longestCompilationTimeMicros) {
longestCompilationTimeMicros = compilationTimeMicros;
}
}
}