diff --git a/benchmark/README.md b/benchmark/README.md new file mode 100644 index 00000000..c565779b --- /dev/null +++ b/benchmark/README.md @@ -0,0 +1,19 @@ +The benchmarks in here attempt to faithfully implement the exact same algorithm in a few different languages. We're using Lua, Python, and Ruby for comparison here because those are all in Wren's ballpark: dynamically-typed, object-oriented, bytecode-compiled. + +A bit about each benchmark: + +## binary_trees + +This benchmark stress object creation and garbage collection. It builds a few big, deeply nested binaries and then traverses them. + +## fib + +This is just a simple naïve Fibonacci number calculator. It was the first benchmark I wrote when Wren supported little more than function calls and arithmetic. It isn't particularly representative of real-world code, but it does stress function call and arithmetic. + +## for + +This microbenchmark just tests the performance of for loops. Not too useful, but i used it when implementing `for` in Wren to make sure it wasn't too far off the mark. + +## method_call + +This is the most useful benchmark: it tests dynamic dispatch and polymorphism. You'll note that the main iteration loop is unrolled in all of the implementations. This is to ensure that the loop overhead itself doesn't dwarf the method call time. diff --git a/benchmark/binary_trees.wren b/benchmark/binary_trees.wren index 19c4a2a2..0057562a 100644 --- a/benchmark/binary_trees.wren +++ b/benchmark/binary_trees.wren @@ -33,10 +33,8 @@ var longLivedTree = new Tree(0, maxDepth) // iterations = 2 ** maxDepth var iterations = 1 -var d = 0 -while (d < maxDepth) { +for (d in 0...maxDepth) { iterations = iterations * 2 - d = d + 1 } var depth = minDepth diff --git a/benchmark/fib.wren b/benchmark/fib.wren index 06823acc..378cf79b 100644 --- a/benchmark/fib.wren +++ b/benchmark/fib.wren @@ -7,9 +7,7 @@ var fib = fn(n) { } var start = OS.clock -var i = 0 -while (i < 5) { +for (i in 1..5) { IO.write(fib.call(28)) - i = i + 1 } IO.write("elapsed: " + (OS.clock - start).toString) diff --git a/benchmark/for.wren b/benchmark/for.wren index 8466350b..ea1153f2 100644 --- a/benchmark/for.wren +++ b/benchmark/for.wren @@ -1,12 +1,6 @@ var list = [] -{ - var i = 0 - while (i < 2000000) { - list.add(i) - i = i + 1 - } -} +for (i in 0...2000000) list.add(i) var start = OS.clock var sum = 0 diff --git a/benchmark/method_call.lua b/benchmark/method_call.lua index da7d80d8..45ce5357 100644 --- a/benchmark/method_call.lua +++ b/benchmark/method_call.lua @@ -54,12 +54,21 @@ end function main () local start = os.clock() - local N = 1000000 + local N = 100000 local val = 1 local toggle = Toggle:new(val) for i=1,N do val = toggle:activate():value() + val = toggle:activate():value() + val = toggle:activate():value() + val = toggle:activate():value() + val = toggle:activate():value() + val = toggle:activate():value() + val = toggle:activate():value() + val = toggle:activate():value() + val = toggle:activate():value() + val = toggle:activate():value() end print(val and "true" or "false") @@ -67,6 +76,15 @@ function main () local ntoggle = NthToggle:new(val, 3) for i=1,N do val = ntoggle:activate():value() + val = ntoggle:activate():value() + val = ntoggle:activate():value() + val = ntoggle:activate():value() + val = ntoggle:activate():value() + val = ntoggle:activate():value() + val = ntoggle:activate():value() + val = ntoggle:activate():value() + val = ntoggle:activate():value() + val = ntoggle:activate():value() end print(val and "true" or "false") io.write(string.format("elapsed: %.8f\n", os.clock() - start)) diff --git a/benchmark/method_call.py b/benchmark/method_call.py index 7e9cdbd9..ac3906e8 100644 --- a/benchmark/method_call.py +++ b/benchmark/method_call.py @@ -29,12 +29,21 @@ class NthToggle(Toggle): def main(): start = time.clock() - NUM = 1000000 + NUM = 100000 val = 1 toggle = Toggle(val) for i in xrange(0,NUM): val = toggle.activate().value() + val = toggle.activate().value() + val = toggle.activate().value() + val = toggle.activate().value() + val = toggle.activate().value() + val = toggle.activate().value() + val = toggle.activate().value() + val = toggle.activate().value() + val = toggle.activate().value() + val = toggle.activate().value() if val: print "true" else: @@ -44,6 +53,15 @@ def main(): ntoggle = NthToggle(val, 3) for i in xrange(0,NUM): val = ntoggle.activate().value() + val = ntoggle.activate().value() + val = ntoggle.activate().value() + val = ntoggle.activate().value() + val = ntoggle.activate().value() + val = ntoggle.activate().value() + val = ntoggle.activate().value() + val = ntoggle.activate().value() + val = ntoggle.activate().value() + val = ntoggle.activate().value() if val: print "true" else: diff --git a/benchmark/method_call.rb b/benchmark/method_call.rb index 9349411f..40611589 100644 --- a/benchmark/method_call.rb +++ b/benchmark/method_call.rb @@ -39,12 +39,21 @@ end def main() start = Time.now - n = 1000000 + n = 100000 val = 1 toggle = Toggle.new(val) n.times do val = toggle.activate().value() + val = toggle.activate().value() + val = toggle.activate().value() + val = toggle.activate().value() + val = toggle.activate().value() + val = toggle.activate().value() + val = toggle.activate().value() + val = toggle.activate().value() + val = toggle.activate().value() + val = toggle.activate().value() end if val then puts "true" else puts "false" end @@ -52,6 +61,15 @@ def main() ntoggle = NthToggle.new(val, 3) n.times do val = ntoggle.activate().value() + val = ntoggle.activate().value() + val = ntoggle.activate().value() + val = ntoggle.activate().value() + val = ntoggle.activate().value() + val = ntoggle.activate().value() + val = ntoggle.activate().value() + val = ntoggle.activate().value() + val = ntoggle.activate().value() + val = ntoggle.activate().value() end if val then puts "true" else puts "false" end diff --git a/benchmark/method_call.wren b/benchmark/method_call.wren index 69a8b23f..6338818d 100644 --- a/benchmark/method_call.wren +++ b/benchmark/method_call.wren @@ -29,14 +29,21 @@ class NthToggle is Toggle { } var start = OS.clock -var n = 1000000 -var i = 0 +var n = 100000 var val = true var toggle = new Toggle(val) -while (i < n) { +for (i in 0...n) { + val = toggle.activate.value + val = toggle.activate.value + val = toggle.activate.value + val = toggle.activate.value + val = toggle.activate.value + val = toggle.activate.value + val = toggle.activate.value + val = toggle.activate.value + val = toggle.activate.value val = toggle.activate.value - i = i + 1 } IO.write(toggle.value) @@ -44,10 +51,17 @@ IO.write(toggle.value) val = true var ntoggle = new NthToggle(val, 3) -i = 0 -while (i < n) { +for (i in 0...n) { + val = ntoggle.activate.value + val = ntoggle.activate.value + val = ntoggle.activate.value + val = ntoggle.activate.value + val = ntoggle.activate.value + val = ntoggle.activate.value + val = ntoggle.activate.value + val = ntoggle.activate.value + val = ntoggle.activate.value val = ntoggle.activate.value - i = i + 1 } IO.write(ntoggle.value) diff --git a/corelib.wren b/corelib.wren index 17e83eb7..0f396eea 100644 --- a/corelib.wren +++ b/corelib.wren @@ -21,3 +21,28 @@ class List { return result } } + +class Range { + new(min, max) { + _min = min + _max = max + } + + min { return _min } + max { return _max } + + iterate(previous) { + if (previous == null) return _min + if (previous == _max) return false + return previous + 1 + } + + iteratorValue(iterator) { + return iterator + } +} + +class Num { + .. other { return new Range(this, other) } + ... other { return new Range(this, other - 1) } +} diff --git a/src/wren_compiler.c b/src/wren_compiler.c index 923369e3..6a09afc7 100644 --- a/src/wren_compiler.c +++ b/src/wren_compiler.c @@ -56,6 +56,8 @@ typedef enum TOKEN_RIGHT_BRACE, TOKEN_COLON, TOKEN_DOT, + TOKEN_DOTDOT, + TOKEN_DOTDOTDOT, TOKEN_COMMA, TOKEN_STAR, TOKEN_SLASH, @@ -573,7 +575,24 @@ static void readRawToken(Parser* parser) case '}': makeToken(parser, TOKEN_RIGHT_BRACE); return; case ';': makeToken(parser, TOKEN_LINE); return; case ':': makeToken(parser, TOKEN_COLON); return; - case '.': makeToken(parser, TOKEN_DOT); return; + case '.': + if (peekChar(parser) == '.') + { + nextChar(parser); + if (peekChar(parser) == '.') + { + nextChar(parser); + makeToken(parser, TOKEN_DOTDOTDOT); + return; + } + + makeToken(parser, TOKEN_DOTDOT); + return; + } + + makeToken(parser, TOKEN_DOT); + return; + case ',': makeToken(parser, TOKEN_COMMA); return; case '*': makeToken(parser, TOKEN_STAR); return; case '%': makeToken(parser, TOKEN_PERCENT); return; @@ -697,6 +716,8 @@ static void nextToken(Parser* parser) case TOKEN_LEFT_BRACKET: case TOKEN_LEFT_BRACE: case TOKEN_DOT: + case TOKEN_DOTDOT: + case TOKEN_DOTDOTDOT: case TOKEN_COMMA: case TOKEN_STAR: case TOKEN_SLASH: @@ -1066,6 +1087,7 @@ typedef enum PREC_IS, // is PREC_EQUALITY, // == != PREC_COMPARISON, // < > <= >= + PREC_RANGE, // .. ... PREC_BITWISE, // | & PREC_TERM, // + - PREC_FACTOR, // * / % @@ -1664,6 +1686,8 @@ GrammarRule rules[] = /* TOKEN_RIGHT_BRACE */ UNUSED, /* TOKEN_COLON */ UNUSED, /* TOKEN_DOT */ INFIX(PREC_CALL, call), + /* TOKEN_DOTDOT */ INFIX_OPERATOR(PREC_RANGE, ".. "), + /* TOKEN_DOTDOTDOT */ INFIX_OPERATOR(PREC_RANGE, "... "), /* TOKEN_COMMA */ UNUSED, /* TOKEN_STAR */ INFIX_OPERATOR(PREC_FACTOR, "* "), /* TOKEN_SLASH */ INFIX_OPERATOR(PREC_FACTOR, "/ "), diff --git a/src/wren_core.c b/src/wren_core.c index b32c7fae..a8860efa 100644 --- a/src/wren_core.c +++ b/src/wren_core.c @@ -70,6 +70,31 @@ const char* coreLibSource = " result = result + \"]\"\n" " return result\n" " }\n" +"}\n" +"\n" +"class Range {\n" +" new(min, max) {\n" +" _min = min\n" +" _max = max\n" +" }\n" +"\n" +" min { return _min }\n" +" max { return _max }\n" +"\n" +" iterate(previous) {\n" +" if (previous == null) return _min\n" +" if (previous == _max) return false\n" +" return previous + 1\n" +" }\n" +"\n" +" iteratorValue(iterator) {\n" +" return iterator\n" +" }\n" +"}\n" +"\n" +"class Num {\n" +" .. other { return new Range(this, other) }\n" +" ... other { return new Range(this, other - 1) }\n" "}\n"; DEF_NATIVE(bool_not) @@ -584,26 +609,6 @@ void wrenInitializeCore(WrenVM* vm) vm->nullClass = defineClass(vm, "Null"); NATIVE(vm->nullClass, "toString", null_toString); - vm->numClass = defineClass(vm, "Num"); - NATIVE(vm->numClass, "abs", num_abs); - NATIVE(vm->numClass, "toString", num_toString) - NATIVE(vm->numClass, "-", num_negate); - NATIVE(vm->numClass, "- ", num_minus); - NATIVE(vm->numClass, "+ ", num_plus); - NATIVE(vm->numClass, "* ", num_multiply); - NATIVE(vm->numClass, "/ ", num_divide); - NATIVE(vm->numClass, "% ", num_mod); - NATIVE(vm->numClass, "< ", num_lt); - NATIVE(vm->numClass, "> ", num_gt); - NATIVE(vm->numClass, "<= ", num_lte); - NATIVE(vm->numClass, ">= ", num_gte); - NATIVE(vm->numClass, "~", num_bitwiseNot); - - // These are defined just so that 0 and -0 are equal, which is specified by - // IEEE 754 even though they have different bit representations. - NATIVE(vm->numClass, "== ", num_eqeq); - NATIVE(vm->numClass, "!= ", num_bangeq); - vm->stringClass = defineClass(vm, "String"); NATIVE(vm->stringClass, "contains ", string_contains); NATIVE(vm->stringClass, "count", string_count); @@ -629,6 +634,26 @@ void wrenInitializeCore(WrenVM* vm) NATIVE(vm->listClass, "[ ]", list_subscript); NATIVE(vm->listClass, "[ ]=", list_subscriptSetter); + vm->numClass = AS_CLASS(findGlobal(vm, "Num")); + NATIVE(vm->numClass, "abs", num_abs); + NATIVE(vm->numClass, "toString", num_toString) + NATIVE(vm->numClass, "-", num_negate); + NATIVE(vm->numClass, "- ", num_minus); + NATIVE(vm->numClass, "+ ", num_plus); + NATIVE(vm->numClass, "* ", num_multiply); + NATIVE(vm->numClass, "/ ", num_divide); + NATIVE(vm->numClass, "% ", num_mod); + NATIVE(vm->numClass, "< ", num_lt); + NATIVE(vm->numClass, "> ", num_gt); + NATIVE(vm->numClass, "<= ", num_lte); + NATIVE(vm->numClass, ">= ", num_gte); + NATIVE(vm->numClass, "~", num_bitwiseNot); + + // These are defined just so that 0 and -0 are equal, which is specified by + // IEEE 754 even though they have different bit representations. + NATIVE(vm->numClass, "== ", num_eqeq); + NATIVE(vm->numClass, "!= ", num_bangeq); + ObjClass* ioClass = AS_CLASS(findGlobal(vm, "IO")); NATIVE(ioClass->metaclass, "write__native__ ", io_writeString); } diff --git a/test/number/range.wren b/test/number/range.wren new file mode 100644 index 00000000..c7b98c72 --- /dev/null +++ b/test/number/range.wren @@ -0,0 +1,14 @@ +var inclusive = 2..5 +IO.write(inclusive is Range) // expect: true +IO.write(inclusive.min) // expect: 2 +IO.write(inclusive.max) // expect: 5 + +var exclusive = 2...5 +IO.write(exclusive is Range) // expect: true +IO.write(exclusive.min) // expect: 2 +IO.write(exclusive.max) // expect: 4 + +// TODO: Non-number RHS. +// TODO: Non-integer RHS. +// TODO: Range iteration. +// TODO: Empty or negative ranges.