fiber stored as register variable

and the benchmark script refactored, and now it will generate an
html report
This commit is contained in:
Thakee Nathees 2022-04-14 07:09:34 +05:30
parent 9d6d37fa09
commit b285336895
10 changed files with 305 additions and 181 deletions

4
.gitignore vendored
View File

@ -6,6 +6,10 @@
.DS_Store
build/
tests/benchmarks/report.html
docs/build/
docs/static/wasm/
# Prerequisites
*.d

3
docs/.gitignore vendored
View File

@ -1,3 +0,0 @@
build/
static/wasm/

View File

@ -677,14 +677,14 @@ static void reportError(PKVM* vm) {
* RUNTIME *
*****************************************************************************/
static PkResult runFiber(PKVM* vm, Fiber* fiber) {
static PkResult runFiber(PKVM* vm, Fiber* fiber_) {
// Set the fiber as the vm's current fiber (another root object) to prevent
// it from garbage collection and get the reference from native functions.
vm->fiber = fiber;
vm->fiber = fiber_;
ASSERT(fiber->state == FIBER_NEW || fiber->state == FIBER_YIELDED, OOPS);
fiber->state = FIBER_RUNNING;
ASSERT(fiber_->state == FIBER_NEW || fiber_->state == FIBER_YIELDED, OOPS);
fiber_->state = FIBER_RUNNING;
// The instruction pointer.
register const uint8_t* ip;
@ -692,21 +692,22 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber) {
register Var* rbp; //< Stack base pointer register.
register CallFrame* frame; //< Current call frame.
register Module* module; //< Currently executing module.
register Fiber* fiber = fiber_;
#if DEBUG
#define PUSH(value) \
do { \
ASSERT(vm->fiber->sp < (vm->fiber->stack + (vm->fiber->stack_size - 1)), \
OOPS); \
(*vm->fiber->sp++ = (value)); \
#define PUSH(value) \
do { \
ASSERT(fiber->sp < (fiber->stack + (fiber->stack_size - 1)), \
OOPS); \
(*fiber->sp++ = (value)); \
} while (false)
#else
#define PUSH(value) (*vm->fiber->sp++ = (value))
#define PUSH(value) (*fiber->sp++ = (value))
#endif
#define POP() (*(--vm->fiber->sp))
#define DROP() (--vm->fiber->sp)
#define PEEK(off) (*(vm->fiber->sp + (off)))
#define POP() (*(--fiber->sp))
#define DROP() (--fiber->sp)
#define PEEK(off) (*(fiber->sp + (off)))
#define READ_BYTE() (*ip++)
#define READ_SHORT() (ip+=2, (uint16_t)((ip[-2] << 8) | ip[-1]))
@ -714,11 +715,12 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber) {
// done with the fiber or aborting it for runtime errors.
#define FIBER_SWITCH_BACK() \
do { \
Fiber* caller = vm->fiber->caller; \
Fiber* caller = fiber->caller; \
ASSERT(caller == NULL || caller->state == FIBER_RUNNING, OOPS); \
vm->fiber->state = FIBER_DONE; \
vm->fiber->caller = NULL; \
vm->fiber = caller; \
fiber->state = FIBER_DONE; \
fiber->caller = NULL; \
fiber = caller; \
vm->fiber = fiber; \
} while (false)
// Check if any runtime error exists and if so returns RESULT_RUNTIME_ERROR.
@ -744,12 +746,12 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber) {
// Load the last call frame to vm's execution variables to resume/run the
// function.
#define LOAD_FRAME() \
do { \
frame = &vm->fiber->frames[vm->fiber->frame_count-1]; \
ip = frame->ip; \
rbp = frame->rbp; \
module = frame->closure->fn->owner; \
#define LOAD_FRAME() \
do { \
frame = &fiber->frames[fiber->frame_count-1]; \
ip = frame->ip; \
rbp = frame->rbp; \
module = frame->closure->fn->owner; \
} while (false)
// Update the frame's execution variables before pushing another call frame.
@ -807,9 +809,9 @@ L_vm_main_loop:
OPCODE(SWAP):
{
Var tmp = *(vm->fiber->sp - 1);
*(vm->fiber->sp - 1) = *(vm->fiber->sp - 2);
*(vm->fiber->sp - 2) = tmp;
Var tmp = *(fiber->sp - 1);
*(fiber->sp - 1) = *(fiber->sp - 2);
*(fiber->sp - 2) = tmp;
DISPATCH();
}
@ -979,8 +981,7 @@ L_vm_main_loop:
if (is_immediate) {
// rbp[0] is the return value, rbp + 1 is the first local and so on.
closure->upvalues[i] = captureUpvalue(vm, vm->fiber,
(rbp + 1 + index));
closure->upvalues[i] = captureUpvalue(vm, fiber, (rbp + 1 + index));
} else {
// The upvalue is already captured by the current function, reuse it.
closure->upvalues[i] = frame->closure->upvalues[index];
@ -993,7 +994,7 @@ L_vm_main_loop:
OPCODE(CLOSE_UPVALUE):
{
closeUpvalues(vm->fiber, vm->fiber->sp - 1);
closeUpvalues(fiber, fiber->sp - 1);
DROP();
DISPATCH();
}
@ -1026,7 +1027,7 @@ L_vm_main_loop:
// body of the module, so the main function will return what's at the
// rbp without modifying it. So at the end of the main function the
// stack top would be the module itself.
Var* module_ret = vm->fiber->sp - 1;
Var* module_ret = fiber->sp - 1;
UPDATE_FRAME(); //< Update the current frame's ip.
pushCallFrame(vm, imported->body, module_ret);
@ -1040,11 +1041,7 @@ L_vm_main_loop:
OPCODE(TAIL_CALL):
{
const uint8_t argc = READ_BYTE();
// The call might change the vm->fiber so we need the reference to the
// fiber that actually called the function.
Fiber* call_fiber = vm->fiber;
Var* callable = call_fiber->sp - argc - 1;
Var* callable = fiber->sp - argc - 1;
const Closure* closure = NULL;
@ -1076,8 +1073,8 @@ L_vm_main_loop:
}
// Next call frame starts here. (including return value).
call_fiber->ret = callable;
*(call_fiber->ret) = VAR_NULL; //< Set the return value to null.
fiber->ret = callable;
*(fiber->ret) = VAR_NULL; //< Set the return value to null.
if (closure->fn->is_native) {
@ -1095,13 +1092,18 @@ L_vm_main_loop:
// would be null if we're not running the function with a fiber.
if (vm->fiber == NULL) return PK_RESULT_SUCCESS;
// Load the top frame to vm's execution variables.
if (vm->fiber != call_fiber) LOAD_FRAME();
// Pop function arguments except for the return value.
// Don't use 'vm->fiber' because calling fiber_new() and yield()
// would change the fiber.
call_fiber->sp = call_fiber->ret + 1;
// Note that calling fiber_new() and yield() would change the
// vm->fiber so we're using fiber.
fiber->sp = fiber->ret + 1;
// If the fiber has changed, Load the top frame to vm's execution
// variables.
if (vm->fiber != fiber) {
fiber = vm->fiber;
LOAD_FRAME();
}
CHECK_ERROR();
} else {
@ -1145,8 +1147,8 @@ L_vm_main_loop:
// TODO: move this to a function in pk_core.c.
OPCODE(ITER):
{
Var* value = (vm->fiber->sp - 1);
Var* iterator = (vm->fiber->sp - 2);
Var* value = (fiber->sp - 1);
Var* iterator = (fiber->sp - 2);
Var seq = PEEK(-3);
uint16_t jump_offset = READ_SHORT();
@ -1295,32 +1297,32 @@ L_vm_main_loop:
{
// Close all the locals of the current frame.
closeUpvalues(vm->fiber, rbp + 1);
closeUpvalues(fiber, rbp + 1);
// Set the return value.
Var ret_value = POP();
// Pop the last frame, and if no more call frames, we're done with the
// current fiber.
if (--vm->fiber->frame_count == 0) {
if (--fiber->frame_count == 0) {
// TODO: if we're evaluating an expression we need to set it's
// value on the stack.
//vm->fiber->sp = vm->fiber->stack; ??
//fiber->sp = fiber->stack; ??
FIBER_SWITCH_BACK();
if (vm->fiber == NULL) {
if (fiber == NULL) {
return PK_RESULT_SUCCESS;
} else {
*vm->fiber->ret = ret_value;
*fiber->ret = ret_value;
}
} else {
*rbp = ret_value;
// Pop the params (locals should have popped at this point) and update
// stack pointer.
vm->fiber->sp = rbp + 1; // +1: rbp is returned value.
fiber->sp = rbp + 1; // +1: rbp is returned value.
}
LOAD_FRAME();

View File

@ -1,22 +1,17 @@
#!python
## Copyright (c) 2020-2021 Thakee Nathees
## Copyright (c) 2021-2022 Pocketlang Contributors
## Distributed Under The MIT License
import subprocess, re, os, sys, platform
from os.path import join, abspath, dirname, relpath
import re, os, sys
import subprocess, platform
from shutil import which
from os.path import join, abspath, dirname, relpath, exists
## The absolute path of this file, when run as a script.
## This file is not intended to be included in other files at the moment.
THIS_PATH = abspath(dirname(__file__))
## Map from systems to the relative pocket binary path.
SYSTEM_TO_BINARY_PATH = {
"Windows": "..\\..\\build\\release\\bin\\pocket.exe",
"Linux" : "../../build/release/pocket",
"Darwin" : "../../build/release/pocket",
}
## A list of benchmark directories, relative to THIS_PATH
BENCHMARKS = (
"factors",
@ -26,140 +21,246 @@ BENCHMARKS = (
"primes",
)
## Map from file extension to it's interpreter, Will be updated.
INTERPRETERS = {}
## Map the files extension with it's interpreter. (executable, extension).
INTERPRETERS = {
'pocketlang' : ('pocket', '.pk'),
'python' : ('python3', '.py'),
'wren' : ('wren', '.wren'),
'ruby' : ('ruby', '.rb'),
'lua' : ('lua', '.lua'),
## Javascript on Node is using V8 that compiles the function before calling it
## which makes it way faster than every other language in this list, we're
## only comparing byte-code interpreted VM languages. Node is the odd one out.
#'javascript' : ('node', '.js'),
}
## Map from systems to the relative pocket binary path.
SYSTEM_TO_POCKET_PATH = {
"current" : {
"Windows": "..\\..\\build\\release\\bin\\pocket.exe",
"Linux" : "../../build/release/pocket",
"Darwin" : "../../build/release/pocket",
},
## This maps the older version of pocket in the system path, to compare
## pocketlang with it's older version.
"older" : {
"Windows": "..\\..\\build\\release\\bin\\pocket_older.exe",
"Linux" : "../../build/release/pocket_older",
"Darwin" : "../../build/release/pocket_older",
}
}
## The html template to display the report.
HTML_TEMPLATE = "template.html"
def main():
results = dict()
update_interpreters()
run_all_benchmarsk()
## ----------------------------------------------------------------------------
## RUN ALL BENCHMARKS
## ----------------------------------------------------------------------------
def run_all_benchmarsk():
for benchmark in BENCHMARKS:
print_title(benchmark.title())
dir = join(THIS_PATH, benchmark)
for file in _source_files(os.listdir(dir)):
file = abspath(join(dir, file))
ext = get_ext(file) ## File extension.
lang, interp, val = INTERPRETERS[ext]
if not interp: continue
for lang in INTERPRETERS:
interp, ext = INTERPRETERS[lang]
source = abspath(join(THIS_PATH, benchmark, benchmark + ext))
if not exists(source): continue
print(" %-10s : "%lang, end=''); sys.stdout.flush()
result = _run_command([interp, file])
time = re.findall(r'elapsed:\s*([0-9\.]+)\s*s',
result.stdout.decode('utf8'),
re.MULTILINE)
result = subprocess.run([interp, source],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
time = get_time(result.stdout.decode('utf8'))
print('%.6fs' % float(time))
if len(time) != 1:
print() # Skip the line.
error_exit(r'elapsed:\s*([0-9\.]+)\s*s --> no mach found.')
print('%.6fs'%float(time[0]))
pass
if benchmark not in results:
results[benchmark] = []
results[benchmark].append([lang, time])
def _run_command(command):
return subprocess.run(command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
## Returns a list of valid source files to run benchmarks.
def _source_files(files):
global INTERPRETERS
ret = []
for file in files:
ext = get_ext(file)
if ext not in INTERPRETERS: continue
ret.append(file)
ret.sort(key=lambda f : INTERPRETERS[get_ext(f)][2])
return ret
## ----------------------------------------------------------------------------
## UPDATE INTERPRETERS
## ----------------------------------------------------------------------------
display_results(results)
## Set the pocketlang path for the current system to the compiled output.
def update_interpreters():
pocket = _get_pocket_binary()
python = 'python' if platform.system() == 'Windows' else 'python3'
print_title("CHECKING FOR INTERPRETERS")
global INTERPRETERS
order = 0
INTERPRETERS['.pk'] = _find_interp('pocketlang', pocket, order); order+=1
INTERPRETERS['.wren'] = _find_interp('wren', 'wren', order); order+=1
INTERPRETERS['.py'] = _find_interp('python', python, order); order+=1
INTERPRETERS['.rb'] = _find_interp('ruby', 'ruby', order); order+=1
INTERPRETERS['.lua'] = _find_interp('lua', 'lua', order); order+=1
INTERPRETERS['.js'] = _find_interp('javascript', 'node', order); order+=1
## This will return the path of the pocket binary (on different platforms).
## The debug version of it for enabling the assertions.
def _get_pocket_binary():
system = platform.system()
if system not in SYSTEM_TO_BINARY_PATH:
error_exit("Unsupported platform %s" % system)
if system not in SYSTEM_TO_POCKET_PATH['current']:
print("Unsupported platform %s" % system)
sys.exit(1)
pocket = abspath(join(THIS_PATH, SYSTEM_TO_BINARY_PATH[system]))
if not os.path.exists(pocket):
error_exit("Pocket interpreter not found at: '%s'" % pocket)
global INTERPRETERS
pocket = abspath(join(THIS_PATH, SYSTEM_TO_POCKET_PATH['current'][system]))
pocket_older = abspath(join(THIS_PATH, SYSTEM_TO_POCKET_PATH['older'][system]))
if not exists(pocket):
print(f"{colmsg('Error', COL_RED)}: " +
"Pocket interpreter not found at: '%s'" % pocket)
sys.exit(1)
INTERPRETERS['pocketlang'] = (pocket, '.pk')
return pocket
## Add if older version of pocketlang if exists.
if exists(pocket_older):
INTERPRETERS['pk-older'] = (pocket_older, '.pk')
## Find and return the interpreter from the path.
## as (lang, interp, val) tuple, where the val is the additional.
## data related to the interpreter.
def _find_interp(lang, interpreter, val):
print('%-27s' % (' Searching for %s ' % lang), end='')
sys.stdout.flush()
if which(interpreter):
print_success('-- found')
return (lang, interpreter, val)
print_warning('-- not found (skipped)')
return (lang, None, val)
if 'python' in INTERPRETERS and system == "Windows":
INTERPRETERS['python'] = ('python', '.py')
missing = []
for lang in INTERPRETERS:
interpreter = INTERPRETERS[lang][0]
print('%-27s' % (' Searching for %s ' % lang), end='')
if which(interpreter):
print(f"-- {colmsg('found', COL_GREEN)}")
else:
print(f"-- {colmsg('missing', COL_YELLOW)}")
missing.append(lang)
for miss in missing:
INTERPRETERS.pop(miss)
## Return the extension from the file name.
def get_ext(file_name):
period = file_name.rfind('.'); assert period > 0
return file_name[period:]
## Match 'elapsed: <time>s' from the output of the process and return it.
def get_time(result_string):
time = re.findall(r'elapsed:\s*([0-9\.]+)\s*s', result_string, re.MULTILINE)
if len(time) != 1:
print(f'\n\'elapsed: <time>s\' {colmsg("No match found", COL_RED)}.')
sys.exit(1)
return float(time[0])
## Generate a report at 'report.html'.
def display_results(results):
for benchmark in results:
results[benchmark].sort(key=lambda x : x[1])
max_time = 0
for lang, time in results[benchmark]:
max_time = max(max_time, time)
## Add the width for the performance bar.
for entry in results[benchmark]:
time = entry[1]
entry.append(time/max_time * 100)
disp = ""
for benchmark in results:
disp += f'<h1 class="benchmark-title">{benchmark.title()}</h1>\n'
disp += '<table>\n'
disp += '<tbody>\n'
for lang, time, width in results[benchmark]:
class_ = "performance-bar"
if lang == 'pocketlang':
class_ += " pocket-bar"
lang = 'pocket' ## Shorten the name.
disp += '<tr>\n'
disp += f' <th>{lang}</th>\n'
disp += f' <td><div class="{class_}" style="width:{width}%">'
disp += f'{"%.2f"%time}s</div></td>\n'
disp += '</tr>\n'
disp += '</tbody>\n'
disp += '</table>\n'
disp += '\n'
global HTML
html = HTML.replace('{{ REPORT }}', disp)
report_path = abspath(join(THIS_PATH, 'report.html'))
with open(report_path, 'w') as out:
out.write(html)
print()
print(colmsg('Report Generated:', COL_GREEN), report_path)
## ----------------------------------------------------------------------------
## HTML REPORT
## ----------------------------------------------------------------------------
HTML = '''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
padding: 0;
margin: 0;
box-sizing: border-box;
font-family: sans-serif;
}
#main {
max-width: 600px;
margin: 50px auto;
}
.benchmark-title {
text-align: center;
margin: 40px auto 20px auto;
}
table {
width: 100%;
}
th {
font-weight: normal;
text-align: right;
width: 100px;
}
.performance-bar {
background-color: #1471c8;
margin: 1px 10px;
color:white;
padding: 2px;
}
.pocket-bar {
background-color: #02509B;
}
</style>
</head>
<body>
<div id="main">
{{ REPORT }}
</div>
</body>
</html>
'''
## ----------------------------------------------------------------------------
## PRINT FUNCTIONS
## ----------------------------------------------------------------------------
def _print_sep():
print("-------------------------------------")
def print_title(title):
print("-----------------------------------")
_print_sep()
print(" %s " % title)
print("-----------------------------------")
_print_sep()
## ANSI color codes to print messages.
COLORS = {
'GREEN' : '\u001b[32m',
'YELLOW' : '\033[93m',
'RED' : '\u001b[31m',
'UNDERLINE' : '\033[4m' ,
'END' : '\033[0m' ,
}
## Simple color logger --------------------------------------------------------
## https://stackoverflow.com/a/70599663/10846399
## Prints a warning message to stdout.
def print_warning(msg):
os.system('') ## This will enable ANSI codes in windows terminal.
for line in msg.splitlines():
print(COLORS['YELLOW'] + line + COLORS['END'])
def RGB(red=None, green=None, blue=None,bg=False):
if(bg==False and red!=None and green!=None and blue!=None):
return f'\u001b[38;2;{red};{green};{blue}m'
elif(bg==True and red!=None and green!=None and blue!=None):
return f'\u001b[48;2;{red};{green};{blue}m'
elif(red==None and green==None and blue==None):
return '\u001b[0m'
COL_NONE = RGB()
COL_RED = RGB(220, 100, 100)
COL_GREEN = RGB(15, 160, 100)
COL_BLUE = RGB(60, 140, 230)
COL_YELLOW = RGB(220, 180, 20)
COL_WHITE = RGB(255, 255, 255)
## print success message to stdout.
def print_success(msg):
os.system('') ## This will enable ANSI codes in windows terminal.
for line in msg.splitlines():
print(COLORS['GREEN'] + line + COLORS['END'])
def colmsg(msg, color):
return f"{color}{msg}{COL_NONE}"
## prints an error message to stderr and exit
## immediately.
def error_exit(msg):
os.system('') ## This will enable ANSI codes in windows terminal.
print(COLORS['RED'] + 'Error: ' + msg + COLORS['END'], end='')
sys.exit(1)
if __name__ == '__main__':
if __name__ == "__main__":
os.system('')
main()

View File

@ -8,15 +8,13 @@ local function reverse(arr)
end
end
local start = os.clock()
local N = 20000000
local list = {}
for i=1, N do
list[i] = i
end
local start = os.clock()
reverse(list)
local seconds = os.clock() - start
print('elapsed: ' .. seconds .. 's')

View File

@ -12,9 +12,10 @@ def reverse_list(list)
return list
end
start = clock()
N = 20000000
l = (0..N).as_list
start = clock()
reverse_list(l)
print('elapsed: ', clock() - start, 's')

View File

@ -8,8 +8,9 @@ def reverse_list(list):
list[i], list[last_index] = list[last_index], list[i]
return list
start = clock()
N = 20000000
l = list(range(N))
start = clock()
reverse_list(l)
print('elapsed: ', clock() - start, 's')

View File

@ -6,10 +6,9 @@ def reverse_list(list)
end
end
start = Time.now
N = 20000000
list = (0...N).to_a
reverse_list(list)
start = Time.now
reverse_list(list)
puts "elapsed: " + (Time.now - start).to_s + ' s'

View File

@ -0,0 +1,21 @@
var reverse_list = Fn.new { |list|
var count = (list.count / 2).floor
for (i in 0...count) {
var last_index = list.count - i - 1
var last = list[last_index]
list[last_index] = list[i]
list[i] = last
}
return list
}
var N = 20000000
var list = []
for (i in 0...N) {
list.add(i)
}
var start = System.clock
reverse_list.call(list)
System.print("elapsed: %(System.clock - start) s")

View File

@ -4,7 +4,7 @@
from lang import clock
N = 50000
N = 20000
def woo(n, acc)
if n == 0 then return acc end