blob: 2cbc453358142f8f9773de56d66aecc8043691f4 [file] [log] [blame]
# vim: set ts=8 sts=4 et sw=4 tw=99:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import os, re
import tempfile
import subprocess
import sys, math
import datetime
import random
def realpath(k):
return os.path.realpath(os.path.normpath(k))
class UCTNode:
def __init__(self, loop):
self.children = None
self.loop = loop
self.visits = 1
self.score = 0
def addChild(self, child):
if self.children == None:
self.children = []
self.children.append(child)
def computeUCB(self, coeff):
return (self.score / self.visits) + math.sqrt(coeff / self.visits)
class UCT:
def __init__(self, benchmark, bestTime, enableLoops, loops, fd, playouts):
self.bm = benchmark
self.fd = fd
self.numPlayouts = playouts
self.maxNodes = self.numPlayouts * 20
self.loops = loops
self.enableLoops = enableLoops
self.maturityThreshold = 20
self.originalBest = bestTime
self.bestTime = bestTime
self.bias = 20
self.combos = []
self.zobrist = { }
random.seed()
def expandNode(self, node, pending):
for loop in pending:
node.addChild(UCTNode(loop))
self.numNodes += 1
if self.numNodes >= self.maxNodes:
return False
return True
def findBestChild(self, node):
coeff = self.bias * math.log(node.visits)
bestChild = None
bestUCB = -float('Infinity')
for child in node.children:
ucb = child.computeUCB(coeff)
if ucb >= bestUCB:
bestUCB = ucb
bestChild = child
return child
def playout(self, history):
queue = []
for i in range(0, len(self.loops)):
queue.append(random.randint(0, 1))
for node in history:
queue[node.loop] = not self.enableLoops
zash = 0
for i in range(0, len(queue)):
if queue[i]:
zash |= (1 << i)
if zash in self.zobrist:
return self.zobrist[zash]
self.bm.generateBanList(self.loops, queue)
result = self.bm.treeSearchRun(self.fd, ['-m', '-j'], 3)
self.zobrist[zash] = result
return result
def step(self, loopList):
node = self.root
pending = loopList[:]
history = [node]
while True:
# If this is a leaf node...
if node.children == None:
# And the leaf node is mature...
if node.visits >= self.maturityThreshold:
# If the node can be expanded, keep spinning.
if self.expandNode(node, pending) and node.children != None:
continue
# Otherwise, this is a leaf node. Run a playout.
score = self.playout(history)
break
# Find the best child.
node = self.findBestChild(node)
history.append(node)
pending.remove(node.loop)
# Normalize the score.
origScore = score
score = (self.originalBest - score) / self.originalBest
for node in history:
node.visits += 1
node.score += score
if int(origScore) < int(self.bestTime):
print('New best score: {0:f}ms'.format(origScore))
self.combos = [history]
self.bestTime = origScore
elif int(origScore) == int(self.bestTime):
self.combos.append(history)
def run(self):
loopList = [i for i in range(0, len(self.loops))]
self.numNodes = 1
self.root = UCTNode(-1)
self.expandNode(self.root, loopList)
for i in range(0, self.numPlayouts):
self.step(loopList)
# Build the expected combination vector.
combos = [ ]
for combo in self.combos:
vec = [ ]
for i in range(0, len(self.loops)):
vec.append(int(self.enableLoops))
for node in combo:
vec[node.loop] = int(not self.enableLoops)
combos.append(vec)
return [self.bestTime, combos]
class Benchmark:
def __init__(self, JS, fname):
self.fname = fname
self.JS = JS
self.stats = { }
self.runList = [ ]
def run(self, fd, eargs):
args = [self.JS]
args.extend(eargs)
args.append(fd.name)
return subprocess.check_output(args).decode()
# self.stats[name] = { }
# self.runList.append(name)
# for line in output.split('\n'):
# m = re.search('line (\d+): (\d+)', line)
# if m:
# self.stats[name][int(m.group(1))] = int(m.group(2))
# else:
# m = re.search('total: (\d+)', line)
# if m:
# self.stats[name]['total'] = m.group(1)
def winnerForLine(self, line):
best = self.runList[0]
bestTime = self.stats[best][line]
for run in self.runList[1:]:
x = self.stats[run][line]
if x < bestTime:
best = run
bestTime = x
return best
def chart(self):
sys.stdout.write('{0:7s}'.format(''))
sys.stdout.write('{0:15s}'.format('line'))
for run in self.runList:
sys.stdout.write('{0:15s}'.format(run))
sys.stdout.write('{0:15s}\n'.format('best'))
for c in self.counters:
sys.stdout.write('{0:10d}'.format(c))
for run in self.runList:
sys.stdout.write('{0:15d}'.format(self.stats[run][c]))
sys.stdout.write('{0:12s}'.format(''))
sys.stdout.write('{0:15s}'.format(self.winnerForLine(c)))
sys.stdout.write('\n')
def preprocess(self, lines, onBegin, onEnd):
stack = []
counters = []
rd = open(self.fname, 'rt')
for line in rd:
if re.search('\/\* BEGIN LOOP \*\/', line):
stack.append([len(lines), len(counters)])
counters.append([len(lines), 0])
onBegin(lines, len(lines))
elif re.search('\/\* END LOOP \*\/', line):
old = stack.pop()
onEnd(lines, old[0], len(lines))
counters[old[1]][1] = len(lines)
else:
lines.append(line)
return [lines, counters]
def treeSearchRun(self, fd, args, count = 5):
total = 0
for i in range(0, count):
output = self.run(fd, args)
total += int(output)
return total / count
def generateBanList(self, counters, queue):
if os.path.exists('/tmp/permabans'):
os.unlink('/tmp/permabans')
fd = open('/tmp/permabans', 'wt')
for i in range(0, len(counters)):
for j in range(counters[i][0], counters[i][1] + 1):
fd.write('{0:d} {1:d}\n'.format(j, int(queue[i])))
fd.close()
def internalExhaustiveSearch(self, params):
counters = params['counters']
# iterative algorithm to explore every combination
ncombos = 2 ** len(counters)
queue = []
for c in counters:
queue.append(0)
fd = params['fd']
bestTime = float('Infinity')
bestCombos = []
i = 0
while i < ncombos:
temp = i
for j in range(0, len(counters)):
queue[j] = temp & 1
temp = temp >> 1
self.generateBanList(counters, queue)
t = self.treeSearchRun(fd, ['-m', '-j'])
if (t < bestTime):
bestTime = t
bestCombos = [queue[:]]
print('New best time: {0:f}ms'.format(t))
elif int(t) == int(bestTime):
bestCombos.append(queue[:])
i = i + 1
return [bestTime, bestCombos]
def internalTreeSearch(self, params):
fd = params['fd']
methodTime = params['methodTime']
tracerTime = params['tracerTime']
combinedTime = params['combinedTime']
counters = params['counters']
# Build the initial loop data.
# If the method JIT already wins, disable tracing by default.
# Otherwise, enable tracing by default.
if methodTime < combinedTime:
enableLoops = True
else:
enableLoops = False
enableLoops = False
uct = UCT(self, combinedTime, enableLoops, counters[:], fd, 50000)
return uct.run()
def treeSearch(self):
fd, counters = self.ppForTreeSearch()
os.system("cat " + fd.name + " > /tmp/k.js")
if os.path.exists('/tmp/permabans'):
os.unlink('/tmp/permabans')
methodTime = self.treeSearchRun(fd, ['-m'])
tracerTime = self.treeSearchRun(fd, ['-j'])
combinedTime = self.treeSearchRun(fd, ['-m', '-j'])
#Get a rough estimate of how long this benchmark will take to fully compute.
upperBound = max(methodTime, tracerTime, combinedTime)
upperBound *= 2 ** len(counters)
upperBound *= 5 # Number of runs
treeSearch = False
if (upperBound < 1000):
print('Estimating {0:d}ms to test, so picking exhaustive '.format(int(upperBound)) +
'search.')
else:
upperBound = int(upperBound / 1000)
delta = datetime.timedelta(seconds = upperBound)
if upperBound < 180:
print('Estimating {0:d}s to test, so picking exhaustive '.format(int(upperBound)))
else:
print('Estimating {0:s} to test, so picking tree search '.format(str(delta)))
treeSearch = True
best = min(methodTime, tracerTime, combinedTime)
params = {
'fd': fd,
'counters': counters,
'methodTime': methodTime,
'tracerTime': tracerTime,
'combinedTime': combinedTime
}
print('Method JIT: {0:d}ms'.format(int(methodTime)))
print('Tracing JIT: {0:d}ms'.format(int(tracerTime)))
print('Combined: {0:d}ms'.format(int(combinedTime)))
if 1 and treeSearch:
results = self.internalTreeSearch(params)
else:
results = self.internalExhaustiveSearch(params)
bestTime = results[0]
bestCombos = results[1]
print('Search found winning time {0:d}ms!'.format(int(bestTime)))
print('Combos at this time: {0:d}'.format(len(bestCombos)))
#Find loops that traced every single time
for i in range(0, len(counters)):
start = counters[i][0]
end = counters[i][1]
n = len(bestCombos)
for j in bestCombos:
n -= j[i]
print('\tloop @ {0:d}-{1:d} traced {2:d}% of the time'.format(
start, end, int(n / len(bestCombos) * 100)))
def ppForTreeSearch(self):
def onBegin(lines, lineno):
lines.append('GLOBAL_THINGY = 1;\n')
def onEnd(lines, old, lineno):
lines.append('GLOBAL_THINGY = 1;\n')
lines = ['var JINT_START_TIME = Date.now();\n',
'var GLOBAL_THINGY = 0;\n']
lines, counters = self.preprocess(lines, onBegin, onEnd)
fd = tempfile.NamedTemporaryFile('wt')
for line in lines:
fd.write(line)
fd.write('print(Date.now() - JINT_START_TIME);\n')
fd.flush()
return [fd, counters]
def preprocessForLoopCounting(self):
def onBegin(lines, lineno):
lines.append('JINT_TRACKER.line_' + str(lineno) + '_start = Date.now();\n')
def onEnd(lines, old, lineno):
lines.append('JINT_TRACKER.line_' + str(old) + '_end = Date.now();\n')
lines.append('JINT_TRACKER.line_' + str(old) + '_total += ' + \
'JINT_TRACKER.line_' + str(old) + '_end - ' + \
'JINT_TRACKER.line_' + str(old) + '_start;\n')
lines, counters = self.preprocess(onBegin, onEnd)
fd = tempfile.NamedTemporaryFile('wt')
fd.write('var JINT_TRACKER = { };\n')
for c in counters:
fd.write('JINT_TRACKER.line_' + str(c) + '_start = 0;\n')
fd.write('JINT_TRACKER.line_' + str(c) + '_end = 0;\n')
fd.write('JINT_TRACKER.line_' + str(c) + '_total = 0;\n')
fd.write('JINT_TRACKER.begin = Date.now();\n')
for line in lines:
fd.write(line)
fd.write('JINT_TRACKER.total = Date.now() - JINT_TRACKER.begin;\n')
for c in self.counters:
fd.write('print("line ' + str(c) + ': " + JINT_TRACKER.line_' + str(c) +
'_total);')
fd.write('print("total: " + JINT_TRACKER.total);')
fd.flush()
return fd
if __name__ == '__main__':
script_path = os.path.abspath(__file__)
script_dir = os.path.dirname(script_path)
test_dir = os.path.join(script_dir, 'tests')
lib_dir = os.path.join(script_dir, 'lib')
# The [TESTS] optional arguments are paths of test files relative
# to the jit-test/tests directory.
from optparse import OptionParser
op = OptionParser(usage='%prog [options] JS_SHELL test')
(OPTIONS, args) = op.parse_args()
if len(args) < 2:
op.error('missing JS_SHELL and test argument')
# We need to make sure we are using backslashes on Windows.
JS = realpath(args[0])
test = realpath(args[1])
bm = Benchmark(JS, test)
bm.treeSearch()
# bm.preprocess()
# bm.run('mjit', ['-m'])
# bm.run('tjit', ['-j'])
# bm.run('m+tjit', ['-m', '-j'])
# bm.chart()