From 0a32108926b38b9a29939e8b9fb489259c76c41a Mon Sep 17 00:00:00 2001 From: denmigda Date: Mon, 30 Oct 2023 13:41:34 +0100 Subject: [PATCH 1/3] Use JS/Python prototype instead of conditions to choose the correct JS <=> Brython conversion method --- www/speed_results.html | 137 +++++++++++++++++++++ www/speed_results.json | 182 ++++++++++++++++++++++++++++ www/speed_results.txt | 30 +++++ www/src/brython_builtins.js | 26 ++++ www/src/js_objects.js | 231 ++++++++++++++++-------------------- www/src/py_dict.js | 24 ++++ www/src/py_dom.js | 16 +++ www/src/py_list.js | 21 ++++ www/src/py_string.js | 6 + www/test.html | 25 ++++ 10 files changed, 566 insertions(+), 132 deletions(-) create mode 100644 www/speed_results.html create mode 100644 www/speed_results.json create mode 100644 www/speed_results.txt create mode 100644 www/test.html diff --git a/www/speed_results.html b/www/speed_results.html new file mode 100644 index 000000000..372fdd513 --- /dev/null +++ b/www/speed_results.html @@ -0,0 +1,137 @@ + + + + +Brython speed compared to CPython + + + + +

Brython 3.12.0 performance compared to CPython 3.10.12

+User agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/118.0 +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TestBrython
(100 = CPython)
Code
simple assignment81
for i in range(1000000):
+    a = 1
augmented assignment59
a = 0
+for i in range(1000000):
+    a  = 1
augmented assignment and list append677
t = []
+i = 0
+while i < 100000:
+    t.append(i)
+    i  = 1
simple assignment to float82
for i in range(1000000):
+    a = 1.0
big integers179
n = 60
+for i in range(10000):
+    2 ** n
hash string608
for i in range(1000000):
+    hash('abcdef')
hash float454
for i in range(1000000):
+    hash(86.55)
build dictionary368
for i in range(1000000):
+    a = {0: 0, 'a': 'a'}
build dictionary 2156
d = {}
+for i in range(100000):
+    d[i] = i
set dictionary item210
a = {0: 0}
+for i in range(1000000):
+    a[0] = i
build set932
for i in range(1000000):
+    a = {0, 2.7, "x"}
build list78
for i in range(1000000):
+    a = [1, 2, 3]
set list item96
a = [0]
+for i in range(1000000):
+    a[0] = i
list slice195
a = [1, 2, 3]
+for i in range(100000):
+    a[:]
integer addition88
a, b, c = 1, 2, 3
+for i in range(1000000):
+    a   b   c
integer float71
a, b = 1, 2.0
+for i in range(1000000):
+    a   b
float addition92
a, b = 1.0, 2.0
+for i in range(1000000):
+    a   b
string addition58
a, b, c = 'a', 'b', 'c'
+for i in range(1000000):
+    a   b   c
cast int to string212
for i in range(100000):
+    str(i)
create function without arguments875
for i in range(1000000):
+    def f():
+        pass
create function, single positional argument936
for i in range(1000000):
+    def f(x):
+        pass
create function, complex arguments910
for i in range(1000000):
+    def f(x, y=1, *args, **kw):
+        pass
function call730
def f(x):
+    return x
+for i in range(1000000):
+    f(i)
function call, complex arguments767
def f(x, y=0, *args, **kw):
+    return x
+for i in range(100000):
+    f(i, 5, 6, a=8)
create simple class114
for i in range(10000):
+    class A:
+        pass
create class with init136
for i in range(10000):
+    class A:
+        def __init__(self, x):
+            self.x = x
create instance of simple class556
class A:
+    pass
+
+for i in range(1000000):
+    A()
create instance of class with init1133
class A:
+    def __init__(self):
+        pass
+
+for i in range(100000):
+    A()
call instance method911
class A:
+    
+    def f(self):
+        pass
+
+a = A()
+for i in range(100000):
+    a.f()
set instance attribute261
class A:
+    pass
+
+a = A()
+for i in range(100000):
+    a.x = i
+ + \ No newline at end of file diff --git a/www/speed_results.json b/www/speed_results.json new file mode 100644 index 000000000..e98f22add --- /dev/null +++ b/www/speed_results.json @@ -0,0 +1,182 @@ +[ + { + "test": "assignment.py", + "description": "simple assignment", + "src": "for i in range(1000000):\n a = 1", + "ratio": 81 + }, + { + "test": "augm_assign.py", + "description": "augmented assignment", + "src": "a = 0\nfor i in range(1000000):\n a = 1", + "ratio": 59 + }, + { + "test": "augm_assign_and_append.py", + "description": "augmented assignment and list append", + "src": "t = []\ni = 0\nwhile i < 100000:\n t.append(i)\n i = 1", + "ratio": 677 + }, + { + "test": "assignment_float.py", + "description": "simple assignment to float", + "src": "for i in range(1000000):\n a = 1.0", + "ratio": 82 + }, + { + "test": "big_integers.py", + "description": "big integers", + "src": "n = 60\nfor i in range(10000):\n 2 ** n", + "ratio": 179 + }, + { + "test": "hash_string.py", + "description": "hash string", + "src": "for i in range(1000000):\n hash('abcdef')", + "ratio": 608 + }, + { + "test": "hash_float.py", + "description": "hash float", + "src": "for i in range(1000000):\n hash(86.55)", + "ratio": 454 + }, + { + "test": "build_dict.py", + "description": "build dictionary", + "src": "for i in range(1000000):\n a = {0: 0, 'a': 'a'}", + "ratio": 368 + }, + { + "test": "add_dict.py", + "description": "build dictionary 2", + "src": "d = {}\nfor i in range(100000):\n d[i] = i", + "ratio": 156 + }, + { + "test": "set_dict_item.py", + "description": "set dictionary item", + "src": "a = {0: 0}\nfor i in range(1000000):\n a[0] = i", + "ratio": 210 + }, + { + "test": "build_set.py", + "description": "build set", + "src": "for i in range(1000000):\n a = {0, 2.7, \"x\"}", + "ratio": 932 + }, + { + "test": "build_list.py", + "description": "build list", + "src": "for i in range(1000000):\n a = [1, 2, 3]", + "ratio": 78 + }, + { + "test": "set_list_item.py", + "description": "set list item", + "src": "a = [0]\nfor i in range(1000000):\n a[0] = i", + "ratio": 96 + }, + { + "test": "list_slice.py", + "description": "list slice", + "src": "a = [1, 2, 3]\nfor i in range(100000):\n a[:]", + "ratio": 195 + }, + { + "test": "add_integers.py", + "description": "integer addition", + "src": "a, b, c = 1, 2, 3\nfor i in range(1000000):\n a b c", + "ratio": 88 + }, + { + "test": "add_int_float.py", + "description": "integer float", + "src": "a, b = 1, 2.0\nfor i in range(1000000):\n a b", + "ratio": 71 + }, + { + "test": "add_floats.py", + "description": "float addition", + "src": "a, b = 1.0, 2.0\nfor i in range(1000000):\n a b", + "ratio": 92 + }, + { + "test": "add_strings.py", + "description": "string addition", + "src": "a, b, c = 'a', 'b', 'c'\nfor i in range(1000000):\n a b c", + "ratio": 58 + }, + { + "test": "str_of_int.py", + "description": "cast int to string", + "src": "for i in range(100000):\n str(i)", + "ratio": 212 + }, + { + "test": "create_function_no_arg.py", + "description": "create function without arguments", + "src": "for i in range(1000000):\n def f():\n pass", + "ratio": 875 + }, + { + "test": "create_function_single_pos_arg.py", + "description": "create function, single positional argument", + "src": "for i in range(1000000):\n def f(x):\n pass", + "ratio": 936 + }, + { + "test": "create_function_complex_args.py", + "description": "create function, complex arguments", + "src": "for i in range(1000000):\n def f(x, y=1, *args, **kw):\n pass", + "ratio": 910 + }, + { + "test": "function_call.py", + "description": "function call", + "src": "def f(x):\n return x\nfor i in range(1000000):\n f(i)", + "ratio": 730 + }, + { + "test": "function_call_complex.py", + "description": "function call, complex arguments", + "src": "def f(x, y=0, *args, **kw):\n return x\nfor i in range(100000):\n f(i, 5, 6, a=8)", + "ratio": 767 + }, + { + "test": "create_class_simple.py", + "description": "create simple class", + "src": "for i in range(10000):\n class A:\n pass", + "ratio": 114 + }, + { + "test": "create_class_with_init.py", + "description": "create class with init", + "src": "for i in range(10000):\n class A:\n def __init__(self, x):\n self.x = x", + "ratio": 136 + }, + { + "test": "create_instance_simple_class.py", + "description": "create instance of simple class", + "src": "class A:\n pass\n\nfor i in range(1000000):\n A()", + "ratio": 556 + }, + { + "test": "create_instance_with_init.py", + "description": "create instance of class with init", + "src": "class A:\n def __init__(self):\n pass\n\nfor i in range(100000):\n A()", + "ratio": 1133 + }, + { + "test": "call_instance_method.py", + "description": "call instance method", + "src": "class A:\n \n def f(self):\n pass\n\na = A()\nfor i in range(100000):\n a.f()", + "ratio": 911 + }, + { + "test": "set_instance_attribute.py", + "description": "set instance attribute", + "src": "class A:\n pass\n\na = A()\nfor i in range(100000):\n a.x = i", + "ratio": 261 + } +] \ No newline at end of file diff --git a/www/speed_results.txt b/www/speed_results.txt new file mode 100644 index 000000000..9e80fb945 --- /dev/null +++ b/www/speed_results.txt @@ -0,0 +1,30 @@ +simple assignment;81 +augmented assignment;59 +augmented assignment and list append;677 +simple assignment to float;82 +big integers;179 +hash string;608 +hash float;454 +build dictionary;368 +build dictionary 2;156 +set dictionary item;210 +build set;932 +build list;78 +set list item;96 +list slice;195 +integer addition;88 +integer float;71 +float addition;92 +string addition;58 +cast int to string;212 +create function without arguments;875 +create function, single positional argument;936 +create function, complex arguments;910 +function call;730 +function call, complex arguments;767 +create simple class;114 +create class with init;136 +create instance of simple class;556 +create instance of class with init;1133 +call instance method;911 +set instance attribute;261 diff --git a/www/src/brython_builtins.js b/www/src/brython_builtins.js index 888588d3e..9d8c0f9cb 100644 --- a/www/src/brython_builtins.js +++ b/www/src/brython_builtins.js @@ -122,6 +122,32 @@ $B.builtin_funcs = {} // Builtin classes $B.builtin_classes = [] +// enable to list the wrappers + +$B.wrappers_JS2Py = new Map(); +$B.wrappers_Py2JS = new Map(); + +$B.SYMBOL_JS2PY_WRAPPER = Symbol(); +$B.SYMBOL_PY2JS_WRAPPER = Symbol(); + +$B.SYMBOL_JSOBJ = Symbol(); +$B.SYMBOL_PYOBJ = Symbol(); + +$B.addJS2PyWrapper = function(jsclass, fct) { + if( Object.hasOwnProperty(jsclass.prototype, $B.SYMBOL_JS2PY_WRAPPER) ) { + console.log(jsclass); + throw new Error("A JS2PY Wrapper already has been defined for", jsclass.constructor.name); + } + jsclass.prototype[$B.SYMBOL_JS2PY_WRAPPER] = fct; + $B.wrappers_JS2Py.set(jsclass, fct); +} +$B.addPy2JSWrapper = function (pyclass, fct) { + if( pyclass[$B.SYMBOL_PY2JS_WRAPPER] !== undefined ) + throw new Error("A PY2JS Wrapper already has been defined for", pyclass.__name__); + pyclass[$B.SYMBOL_PY2JS_WRAPPER] = fct; + $B.wrappers_Py2JS.set(pyclass, fct); +} + $B.__getattr__ = function(attr){return this[attr]} $B.__setattr__ = function(attr, value){ // limited to some attributes diff --git a/www/src/js_objects.js b/www/src/js_objects.js index 186b21193..9e021c21a 100644 --- a/www/src/js_objects.js +++ b/www/src/js_objects.js @@ -149,50 +149,41 @@ JSConstructor.$factory = function(obj){ } } -const JSOBJ = Symbol() -const PYOBJ = Symbol() -const PYOBJFCT = Symbol() -const PYOBJFCTS = Symbol() +const JSOBJ = $B.SYMBOL_JSOBJ; +const PYOBJ = $B.SYMBOL_PYOBJ; -var jsobj2pyobj = $B.jsobj2pyobj = function(jsobj, _this){ - // If _this is passed and jsobj is a function, the function is called - // with built-in value `this` set to _this - switch(jsobj) { - case true: - case false: - return jsobj - } +const JSOBJ_FCT = $B.SYMBOL_PY2JS_WRAPPER; +const PYOBJ_FCT = $B.SYMBOL_JS2PY_WRAPPER; - if(jsobj === undefined){ - return $B.Undefined - } - if(jsobj === null){ - return null - } +const PYOBJFCT = Symbol() +const PYOBJFCTS = Symbol() - if(Array.isArray(jsobj)){ - // set it as non-enumerable, prevents issues when looping on it in JS. - Object.defineProperty(jsobj, "$is_js_array", {value: true}); - return jsobj // $B.$list(jsobj.map(jsobj2pyobj)) - } - if(typeof jsobj === 'number'){ - if(jsobj % 1 === 0){ //TODO: dangerous, it can also be a float with no decimals. +//TODO: optimize unwrap... +$B.addJS2PyWrapper(Boolean, function(jsobj){ + return jsobj; +}); +$B.addJS2PyWrapper(Number, function(jsobj){ + if(jsobj % 1 === 0){ //TODO: DANGEROUS! It can also be a float with no decimals. return _b_.int.$factory(jsobj) } // for now, lets assume a float return _b_.float.$factory(jsobj) - } - if(typeof jsobj == "string"){ - return $B.String(jsobj) - } - - let pyobj = jsobj[PYOBJ] - if(pyobj !== undefined) { - return pyobj; - } - - if(typeof jsobj === "function"){ - +}); + + + +$B.addPy2JSWrapper(_b_.float, function(pyobj) { + + // floats are implemented as + // {__class__: _b_.float, value: } + return pyobj.value // dangerous => can be later converted as int when browser fetch it back. +}); + +$B.addJS2PyWrapper(String, function(jsobj){ //TODO: move 2 py_str ? + return $B.String(jsobj); +}); +$B.addJS2PyWrapper(Function, function(jsobj, _this) { + // transform Python arguments to equivalent JS arguments _this = _this === undefined ? null : _this @@ -239,107 +230,19 @@ var jsobj2pyobj = $B.jsobj2pyobj = function(jsobj, _this){ __qualname__: jsobj.name } return res - } - - if(jsobj.$kw){ - return jsobj - } +}); - if($B.$isNode(jsobj)){ - const res = $B.DOMNode.$factory(jsobj) - jsobj[PYOBJ] = res - res[JSOBJ] = jsobj - return res - } - - const _res = $B.JSObj.$factory(jsobj) - jsobj[PYOBJ] = _res - _res[JSOBJ] = jsobj - - return _res; -} - -var pyobj2jsobj = $B.pyobj2jsobj = function(pyobj){ - // conversion of a Python object into a Javascript object - if(pyobj === true || pyobj === false){ - return pyobj - } - if(pyobj === $B.Undefined){ - return undefined - } - if(pyobj === null) { - return null - } - - let _jsobj = pyobj[JSOBJ] - if(_jsobj !== undefined){ - return _jsobj - } - var klass = $B.get_class(pyobj) - if(klass === undefined){ - // not a Python object, consider arg as Javascript object instead - return pyobj - } - - if(klass === JSConstructor){ - // Instances of JSConstructor are transformed into the +$B.addPy2JSWrapper(JSConstructor, function(pyobj) { + // Instances of JSConstructor are transformed into the // underlying Javascript object if(pyobj.js_func !== undefined){ return pyobj.js_func } return pyobj.js - } - if(klass === $B.DOMNode || - klass.__mro__.indexOf($B.DOMNode) > -1){ +}); - // instances of DOMNode or its subclasses are transformed into the - // underlying DOM element - return pyobj - - } - if([_b_.list, _b_.tuple].indexOf(klass) > -1){ - - // Python list : transform its elements - return pyobj.map(pyobj2jsobj) - - } - if(klass === _b_.dict || _b_.issubclass(klass, _b_.dict)){ - - // Python dictionaries are transformed into a Javascript object - // whose attributes are the dictionary keys - // Non-string keys are converted to strings by str(key). This will - // affect Python dicts such as {"1": 'a', 1: "b"}, the result will - // be the Javascript object {1: "b"} - var jsobj = {} - for(var entry of _b_.dict.$iter_items_with_hash(pyobj)){ - var key = entry.key - if(typeof key !== "string"){ - key = _b_.str.$factory(key) - } - if(typeof entry.value === 'function'){ - // set "this" to jsobj - entry.value.bind(jsobj) - } - jsobj[key] = pyobj2jsobj(entry.value) - } - return jsobj - - } - if(klass === _b_.str){ - - // Python strings are converted to the underlying value - return pyobj.valueOf() - - } - if(klass === _b_.float){ - - // floats are implemented as - // {__class__: _b_.float, value: } - return pyobj.value - - } - if(klass === $B.function || klass === $B.method){ +function convertMethodsOrFunctions(pyobj, _this) { if(pyobj.prototype && pyobj.prototype.constructor === pyobj && ! pyobj.$is_func){ @@ -361,7 +264,7 @@ var pyobj2jsobj = $B.pyobj2jsobj = function(pyobj){ return jsobj } // Transform into a Javascript function - var jsobj = function(){ + const jsobj = function(){ try{ // transform JS arguments to Python arguments var args = new Array(arguments.length) @@ -385,8 +288,72 @@ var pyobj2jsobj = $B.pyobj2jsobj = function(pyobj){ jsobj[PYOBJ] = pyobj return jsobj +} + +$B.addPy2JSWrapper($B.function, convertMethodsOrFunctions); +$B.addPy2JSWrapper($B.method , convertMethodsOrFunctions); + +$B.addJS2PyWrapper(Object, function(jsobj, _this) { //TODO: exclude isNode... + + if(jsobj.$kw){ // we really shouldn't be doing that... + return jsobj + } + + const _res = $B.JSObj.$factory(jsobj) + jsobj[PYOBJ] = _res + _res[JSOBJ] = jsobj + + return _res; +}); + +var jsobj2pyobj = $B.jsobj2pyobj = function(jsobj, _this){ + // If _this is passed and jsobj is a function, the function is called + // with built-in value `this` set to _this + + // handle undefined and null first => cause issues... + if(jsobj === undefined) + return $B.Undefined; + if(jsobj === null) + return null; + + const pyobj = jsobj[PYOBJ]; + if(pyobj !== undefined) + return pyobj; + + return jsobj[PYOBJ_FCT](jsobj, _this); +} + +var pyobj2jsobj = $B.pyobj2jsobj = function(pyobj){ + // conversion of a Python object into a Javascript object + + // handle undefined and null first => cause issues... + if(pyobj === $B.Undefined) + return undefined + + if( ! (pyobj instanceof Object) ) // not a python type (not even an object)... + return pyobj; + + const klass = $B.get_class(pyobj) + if(klass === undefined){ + // not a Python object, consider arg as Javascript object instead + return pyobj } - return pyobj + + let jsobj = pyobj[JSOBJ] + if(jsobj !== undefined) + return jsobj + + const mro = klass.__mro__; + let jsobj_fct = klass[JSOBJ_FCT]; + let offset = 0; + while ( jsobj_fct === undefined && offset < mro.length ) { + jsobj_fct = mro[offset++][JSOBJ_FCT]; + } + + if(jsobj_fct !== undefined) + return jsobj_fct(pyobj); + + return pyobj // no convertion known... } $B.JSConstructor = JSConstructor diff --git a/www/src/py_dict.js b/www/src/py_dict.js index da65bd8b1..9ba2b9921 100644 --- a/www/src/py_dict.js +++ b/www/src/py_dict.js @@ -153,6 +153,30 @@ var dict = { $match_mapping_pattern: true // for pattern matching (PEP 634) } + +$B.addPy2JSWrapper(dict, function(pyobj) { + + // Python dictionaries are transformed into a Javascript object + // whose attributes are the dictionary keys + // Non-string keys are converted to strings by str(key). This will + // affect Python dicts such as {"1": 'a', 1: "b"}, the result will + // be the Javascript object {1: "b"} + let jsobj = {} + for(var entry of _b_.dict.$iter_items_with_hash(pyobj)){ + var key = entry.key + if(typeof key !== "string"){ + key = _b_.str.$factory(key) + } + if(typeof entry.value === 'function'){ + // set "this" to jsobj + entry.value.bind(jsobj) + } + jsobj[key] = $B.pyobj2jsobj(entry.value) + } + return jsobj +}); + + dict.$to_obj = function(d){ // Function applied to dictionary that only has string keys, // return a Javascript objects with the keys mapped to the value, diff --git a/www/src/py_dom.js b/www/src/py_dom.js index bff197ec8..b95ce272f 100644 --- a/www/src/py_dom.js +++ b/www/src/py_dom.js @@ -505,6 +505,22 @@ var DOMNode = $B.make_class('DOMNode', } ) + +$B.addJS2PyWrapper(Node, function(jsobj, _this) { + + const res = $B.DOMNode.$factory(jsobj) + jsobj[PYOBJ] = res + res[JSOBJ] = jsobj + return res +}); + +$B.addPy2JSWrapper(DOMNode, function(pyobj) { + + // instances of DOMNode or its subclasses are transformed into the + // underlying DOM element + return pyobj +}); + DOMNode.__add__ = function(self, other){ // adding another element to self returns an instance of TagSum var res = TagSum.$factory() diff --git a/www/src/py_list.js b/www/src/py_list.js index d7503a5b8..ea7c6660f 100644 --- a/www/src/py_list.js +++ b/www/src/py_list.js @@ -28,6 +28,20 @@ var list = { __dir__: object.__dir__ } + + +$B.addJS2PyWrapper(Array, function(jsobj) { + // set it as non-enumerable, prevents issues when looping on it in JS. + Object.defineProperty(jsobj, "$is_js_array", {value: true}); + return jsobj // $B.$list(jsobj.map(jsobj2pyobj)) +}); + +$B.addPy2JSWrapper(list, function(pyobj) { + + // Python list : transform its elements + return pyobj.map($B.pyobj2jsobj); +}); + list.__add__ = function(self, other){ if($B.get_class(self) !== $B.get_class(other)){ var this_name = $B.class_name(self) // can be tuple @@ -929,6 +943,12 @@ var tuple = { $match_sequence_pattern: true, // for Pattern Matching (PEP 634) } +$B.addPy2JSWrapper(tuple, function(pyobj) { + + // Python list : transform its elements + return pyobj.map($B.pyobj2jsobj); +}); + var tuple_iterator = $B.make_iterator_class("tuple_iterator") tuple.__iter__ = function(self){ return tuple_iterator.$factory(self) @@ -1070,4 +1090,5 @@ _b_.tuple = tuple _b_.object.__bases__ = tuple.$factory() _b_.type.__bases__ = $B.fast_tuple([_b_.object]) + })(__BRYTHON__) diff --git a/www/src/py_string.js b/www/src/py_string.js index 4c79f2de7..a4d75b959 100644 --- a/www/src/py_string.js +++ b/www/src/py_string.js @@ -118,6 +118,12 @@ var str = { $native: true } + +$B.addPy2JSWrapper(str, function(pyobj) { + // Python strings are converted to the underlying value + return pyobj.valueOf() +}); + str.$to_string = to_string function normalize_start_end($){ diff --git a/www/test.html b/www/test.html new file mode 100644 index 000000000..6dfa8517d --- /dev/null +++ b/www/test.html @@ -0,0 +1,25 @@ + + + + + + + +

+
+ \ No newline at end of file From 6c850b817aa2255e43f9a44a08d165c728ea97a7 Mon Sep 17 00:00:00 2001 From: denmigda Date: Thu, 16 Nov 2023 13:51:53 +0100 Subject: [PATCH 2/3] Fix issues after merge --- www/speed_results.html | 137 --------------------------- www/speed_results.json | 182 ------------------------------------ www/speed_results.txt | 30 ------ www/src/brython_builtins.js | 14 +-- www/src/js_objects.js | 36 ++++--- www/test.html | 25 ----- 6 files changed, 29 insertions(+), 395 deletions(-) delete mode 100644 www/speed_results.html delete mode 100644 www/speed_results.json delete mode 100644 www/speed_results.txt delete mode 100644 www/test.html diff --git a/www/speed_results.html b/www/speed_results.html deleted file mode 100644 index 372fdd513..000000000 --- a/www/speed_results.html +++ /dev/null @@ -1,137 +0,0 @@ - - - - -Brython speed compared to CPython - - - - -

Brython 3.12.0 performance compared to CPython 3.10.12

-User agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/118.0 -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TestBrython
(100 = CPython)
Code
simple assignment81
for i in range(1000000):
-    a = 1
augmented assignment59
a = 0
-for i in range(1000000):
-    a  = 1
augmented assignment and list append677
t = []
-i = 0
-while i < 100000:
-    t.append(i)
-    i  = 1
simple assignment to float82
for i in range(1000000):
-    a = 1.0
big integers179
n = 60
-for i in range(10000):
-    2 ** n
hash string608
for i in range(1000000):
-    hash('abcdef')
hash float454
for i in range(1000000):
-    hash(86.55)
build dictionary368
for i in range(1000000):
-    a = {0: 0, 'a': 'a'}
build dictionary 2156
d = {}
-for i in range(100000):
-    d[i] = i
set dictionary item210
a = {0: 0}
-for i in range(1000000):
-    a[0] = i
build set932
for i in range(1000000):
-    a = {0, 2.7, "x"}
build list78
for i in range(1000000):
-    a = [1, 2, 3]
set list item96
a = [0]
-for i in range(1000000):
-    a[0] = i
list slice195
a = [1, 2, 3]
-for i in range(100000):
-    a[:]
integer addition88
a, b, c = 1, 2, 3
-for i in range(1000000):
-    a   b   c
integer float71
a, b = 1, 2.0
-for i in range(1000000):
-    a   b
float addition92
a, b = 1.0, 2.0
-for i in range(1000000):
-    a   b
string addition58
a, b, c = 'a', 'b', 'c'
-for i in range(1000000):
-    a   b   c
cast int to string212
for i in range(100000):
-    str(i)
create function without arguments875
for i in range(1000000):
-    def f():
-        pass
create function, single positional argument936
for i in range(1000000):
-    def f(x):
-        pass
create function, complex arguments910
for i in range(1000000):
-    def f(x, y=1, *args, **kw):
-        pass
function call730
def f(x):
-    return x
-for i in range(1000000):
-    f(i)
function call, complex arguments767
def f(x, y=0, *args, **kw):
-    return x
-for i in range(100000):
-    f(i, 5, 6, a=8)
create simple class114
for i in range(10000):
-    class A:
-        pass
create class with init136
for i in range(10000):
-    class A:
-        def __init__(self, x):
-            self.x = x
create instance of simple class556
class A:
-    pass
-
-for i in range(1000000):
-    A()
create instance of class with init1133
class A:
-    def __init__(self):
-        pass
-
-for i in range(100000):
-    A()
call instance method911
class A:
-    
-    def f(self):
-        pass
-
-a = A()
-for i in range(100000):
-    a.f()
set instance attribute261
class A:
-    pass
-
-a = A()
-for i in range(100000):
-    a.x = i
- - \ No newline at end of file diff --git a/www/speed_results.json b/www/speed_results.json deleted file mode 100644 index e98f22add..000000000 --- a/www/speed_results.json +++ /dev/null @@ -1,182 +0,0 @@ -[ - { - "test": "assignment.py", - "description": "simple assignment", - "src": "for i in range(1000000):\n a = 1", - "ratio": 81 - }, - { - "test": "augm_assign.py", - "description": "augmented assignment", - "src": "a = 0\nfor i in range(1000000):\n a = 1", - "ratio": 59 - }, - { - "test": "augm_assign_and_append.py", - "description": "augmented assignment and list append", - "src": "t = []\ni = 0\nwhile i < 100000:\n t.append(i)\n i = 1", - "ratio": 677 - }, - { - "test": "assignment_float.py", - "description": "simple assignment to float", - "src": "for i in range(1000000):\n a = 1.0", - "ratio": 82 - }, - { - "test": "big_integers.py", - "description": "big integers", - "src": "n = 60\nfor i in range(10000):\n 2 ** n", - "ratio": 179 - }, - { - "test": "hash_string.py", - "description": "hash string", - "src": "for i in range(1000000):\n hash('abcdef')", - "ratio": 608 - }, - { - "test": "hash_float.py", - "description": "hash float", - "src": "for i in range(1000000):\n hash(86.55)", - "ratio": 454 - }, - { - "test": "build_dict.py", - "description": "build dictionary", - "src": "for i in range(1000000):\n a = {0: 0, 'a': 'a'}", - "ratio": 368 - }, - { - "test": "add_dict.py", - "description": "build dictionary 2", - "src": "d = {}\nfor i in range(100000):\n d[i] = i", - "ratio": 156 - }, - { - "test": "set_dict_item.py", - "description": "set dictionary item", - "src": "a = {0: 0}\nfor i in range(1000000):\n a[0] = i", - "ratio": 210 - }, - { - "test": "build_set.py", - "description": "build set", - "src": "for i in range(1000000):\n a = {0, 2.7, \"x\"}", - "ratio": 932 - }, - { - "test": "build_list.py", - "description": "build list", - "src": "for i in range(1000000):\n a = [1, 2, 3]", - "ratio": 78 - }, - { - "test": "set_list_item.py", - "description": "set list item", - "src": "a = [0]\nfor i in range(1000000):\n a[0] = i", - "ratio": 96 - }, - { - "test": "list_slice.py", - "description": "list slice", - "src": "a = [1, 2, 3]\nfor i in range(100000):\n a[:]", - "ratio": 195 - }, - { - "test": "add_integers.py", - "description": "integer addition", - "src": "a, b, c = 1, 2, 3\nfor i in range(1000000):\n a b c", - "ratio": 88 - }, - { - "test": "add_int_float.py", - "description": "integer float", - "src": "a, b = 1, 2.0\nfor i in range(1000000):\n a b", - "ratio": 71 - }, - { - "test": "add_floats.py", - "description": "float addition", - "src": "a, b = 1.0, 2.0\nfor i in range(1000000):\n a b", - "ratio": 92 - }, - { - "test": "add_strings.py", - "description": "string addition", - "src": "a, b, c = 'a', 'b', 'c'\nfor i in range(1000000):\n a b c", - "ratio": 58 - }, - { - "test": "str_of_int.py", - "description": "cast int to string", - "src": "for i in range(100000):\n str(i)", - "ratio": 212 - }, - { - "test": "create_function_no_arg.py", - "description": "create function without arguments", - "src": "for i in range(1000000):\n def f():\n pass", - "ratio": 875 - }, - { - "test": "create_function_single_pos_arg.py", - "description": "create function, single positional argument", - "src": "for i in range(1000000):\n def f(x):\n pass", - "ratio": 936 - }, - { - "test": "create_function_complex_args.py", - "description": "create function, complex arguments", - "src": "for i in range(1000000):\n def f(x, y=1, *args, **kw):\n pass", - "ratio": 910 - }, - { - "test": "function_call.py", - "description": "function call", - "src": "def f(x):\n return x\nfor i in range(1000000):\n f(i)", - "ratio": 730 - }, - { - "test": "function_call_complex.py", - "description": "function call, complex arguments", - "src": "def f(x, y=0, *args, **kw):\n return x\nfor i in range(100000):\n f(i, 5, 6, a=8)", - "ratio": 767 - }, - { - "test": "create_class_simple.py", - "description": "create simple class", - "src": "for i in range(10000):\n class A:\n pass", - "ratio": 114 - }, - { - "test": "create_class_with_init.py", - "description": "create class with init", - "src": "for i in range(10000):\n class A:\n def __init__(self, x):\n self.x = x", - "ratio": 136 - }, - { - "test": "create_instance_simple_class.py", - "description": "create instance of simple class", - "src": "class A:\n pass\n\nfor i in range(1000000):\n A()", - "ratio": 556 - }, - { - "test": "create_instance_with_init.py", - "description": "create instance of class with init", - "src": "class A:\n def __init__(self):\n pass\n\nfor i in range(100000):\n A()", - "ratio": 1133 - }, - { - "test": "call_instance_method.py", - "description": "call instance method", - "src": "class A:\n \n def f(self):\n pass\n\na = A()\nfor i in range(100000):\n a.f()", - "ratio": 911 - }, - { - "test": "set_instance_attribute.py", - "description": "set instance attribute", - "src": "class A:\n pass\n\na = A()\nfor i in range(100000):\n a.x = i", - "ratio": 261 - } -] \ No newline at end of file diff --git a/www/speed_results.txt b/www/speed_results.txt deleted file mode 100644 index 9e80fb945..000000000 --- a/www/speed_results.txt +++ /dev/null @@ -1,30 +0,0 @@ -simple assignment;81 -augmented assignment;59 -augmented assignment and list append;677 -simple assignment to float;82 -big integers;179 -hash string;608 -hash float;454 -build dictionary;368 -build dictionary 2;156 -set dictionary item;210 -build set;932 -build list;78 -set list item;96 -list slice;195 -integer addition;88 -integer float;71 -float addition;92 -string addition;58 -cast int to string;212 -create function without arguments;875 -create function, single positional argument;936 -create function, complex arguments;910 -function call;730 -function call, complex arguments;767 -create simple class;114 -create class with init;136 -create instance of simple class;556 -create instance of class with init;1133 -call instance method;911 -set instance attribute;261 diff --git a/www/src/brython_builtins.js b/www/src/brython_builtins.js index c1e00d12f..c3d4233de 100644 --- a/www/src/brython_builtins.js +++ b/www/src/brython_builtins.js @@ -132,24 +132,26 @@ $B.builtin_classes = [] $B.wrappers_JS2Py = new Map(); $B.wrappers_Py2JS = new Map(); -$B.SYMBOL_JS2PY_WRAPPER = Symbol(); -$B.SYMBOL_PY2JS_WRAPPER = Symbol(); +$B.SYMBOL_JS2PY_WRAPPER = Symbol("JS2PY"); +$B.SYMBOL_PY2JS_WRAPPER = Symbol("PY2JS"); -$B.SYMBOL_JSOBJ = Symbol(); -$B.SYMBOL_PYOBJ = Symbol(); +$B.SYMBOL_JSOBJ = Symbol("JSOBJ"); +$B.SYMBOL_PYOBJ = Symbol("PYOBJ"); -$B.addJS2PyWrapper = function(jsclass, fct) { +$B.addJS2PyWrapper = function(jsclass, fct, desc = "") { if( Object.hasOwnProperty(jsclass.prototype, $B.SYMBOL_JS2PY_WRAPPER) ) { console.log(jsclass); throw new Error("A JS2PY Wrapper already has been defined for", jsclass.constructor.name); } jsclass.prototype[$B.SYMBOL_JS2PY_WRAPPER] = fct; + fct.desc = desc; $B.wrappers_JS2Py.set(jsclass, fct); } -$B.addPy2JSWrapper = function (pyclass, fct) { +$B.addPy2JSWrapper = function (pyclass, fct, desc = "") { if( pyclass[$B.SYMBOL_PY2JS_WRAPPER] !== undefined ) throw new Error("A PY2JS Wrapper already has been defined for", pyclass.__name__); pyclass[$B.SYMBOL_PY2JS_WRAPPER] = fct; + fct.desc = desc; $B.wrappers_Py2JS.set(pyclass, fct); } diff --git a/www/src/js_objects.js b/www/src/js_objects.js index 12dcad1c2..bc3312a39 100644 --- a/www/src/js_objects.js +++ b/www/src/js_objects.js @@ -113,7 +113,7 @@ const PYOBJFCTS = Symbol() //TODO: optimize unwrap... $B.addJS2PyWrapper(Boolean, function(jsobj){ return jsobj; -}); +}, 'Boolean => Boolean'); $B.addJS2PyWrapper(Number, function(jsobj){ if(jsobj % 1 === 0){ //TODO: DANGEROUS! It can also be a float with no decimals. return _b_.int.$factory(jsobj) @@ -121,8 +121,7 @@ $B.addJS2PyWrapper(Number, function(jsobj){ // for now, lets assume a float return _b_.float.$factory(jsobj) -}); - +}, 'Number => PyInt or PyFloat'); $B.addPy2JSWrapper(_b_.float, function(pyobj) { @@ -130,18 +129,16 @@ $B.addPy2JSWrapper(_b_.float, function(pyobj) { // floats are implemented as // {__class__: _b_.float, value: } return pyobj.value // dangerous => can be later converted as int when browser fetch it back. -}); +}, 'Number <= PyFloat'); -$B.addJS2PyWrapper(String, function(jsobj){ //TODO: move 2 py_str ? +$B.addJS2PyWrapper(String, function(jsobj){ return $B.String(jsobj); -}); +}, "String => PyString"); - /* -if(jsobj instanceof Promise){ +$B.addJS2PyWrapper(Promise, function(jsobj) { // cf. issue #2321 return jsobj.then(x => jsobj2pyobj(x)).catch($B.handle_error) - } -*/ +}, "Promise => Promise Wrapper"); $B.addJS2PyWrapper(Function, function(jsobj, _this) { @@ -171,8 +168,10 @@ $B.addJS2PyWrapper(Function, function(jsobj, _this) { args[i] = pyobj2jsobj(arguments[i]) } try{ - return jsobj2pyobj(jsobj.apply(_this, args)) + const ret = jsobj.apply(_this, args); + return jsobj2pyobj(ret); }catch(err){ + console.log(err); throw $B.exception(err) } } @@ -191,7 +190,7 @@ $B.addJS2PyWrapper(Function, function(jsobj, _this) { __qualname__: jsobj.name } return res -}); +}, "Function => Wrapper"); function convertMethodsOrFunctions(pyobj, _this) { @@ -243,20 +242,24 @@ function convertMethodsOrFunctions(pyobj, _this) { } $B.addPy2JSWrapper($B.function, convertMethodsOrFunctions); -$B.addPy2JSWrapper($B.method , convertMethodsOrFunctions); +$B.addPy2JSWrapper($B.method , convertMethodsOrFunctions, "? <= method or function"); $B.addJS2PyWrapper(Object, function(jsobj, _this) { //TODO: exclude isNode... if(jsobj.$kw){ // we really shouldn't be doing that... return jsobj } + + if( typeof jsobj === "bigint") { // bigint is in fact converted to an object... + return _b_.int.$int_or_long(jsobj); + } const _res = $B.JSObj.$factory(jsobj) jsobj[PYOBJ] = _res _res[JSOBJ] = jsobj return _res; -}); +}, "Object => PyObj + BigInt => Number"); var jsobj2pyobj = $B.jsobj2pyobj = function(jsobj, _this){ // If _this is passed and jsobj is a function, the function is called @@ -272,7 +275,9 @@ var jsobj2pyobj = $B.jsobj2pyobj = function(jsobj, _this){ if(pyobj !== undefined) return pyobj; - return jsobj[PYOBJ_FCT](jsobj, _this); + const fct = jsobj[PYOBJ_FCT]; + + return fct(jsobj, _this); } var pyobj2jsobj = $B.pyobj2jsobj = function(pyobj){ @@ -284,6 +289,7 @@ var pyobj2jsobj = $B.pyobj2jsobj = function(pyobj){ if( ! (pyobj instanceof Object) ) // not a python type (not even an object)... return pyobj; + const klass = $B.get_class(pyobj) if(klass === undefined){ diff --git a/www/test.html b/www/test.html deleted file mode 100644 index 6dfa8517d..000000000 --- a/www/test.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - -

-
- \ No newline at end of file From 17024bfb70edb787a4aaac01f441d8641c49c6da Mon Sep 17 00:00:00 2001 From: denmigda Date: Thu, 16 Nov 2023 14:04:01 +0100 Subject: [PATCH 3/3] Move functions back to js_object.js --- www/src/js_objects.js | 48 ++++++++++++++++++++++++++++++++++++++++ www/src/py_dict.js | 23 ------------------- www/src/py_exceptions.js | 3 +++ www/src/py_list.js | 19 ---------------- www/src/py_string.js | 6 ----- 5 files changed, 51 insertions(+), 48 deletions(-) diff --git a/www/src/js_objects.js b/www/src/js_objects.js index bc3312a39..4c5a64329 100644 --- a/www/src/js_objects.js +++ b/www/src/js_objects.js @@ -110,6 +110,54 @@ const PYOBJ_FCT = $B.SYMBOL_JS2PY_WRAPPER; const PYOBJFCT = Symbol() const PYOBJFCTS = Symbol() + +$B.addJS2PyWrapper(Array, function(jsobj) { + // set it as non-enumerable, prevents issues when looping on it in JS. + Object.defineProperty(jsobj, "$is_js_array", {value: true}); + return jsobj // $B.$list(jsobj.map(jsobj2pyobj)) +}, "Array => JSArray"); + +$B.addPy2JSWrapper(_b_.list, function(pyobj) { + + // Python list : transform its elements + return pyobj.map($B.pyobj2jsobj); +}, "list => Array (clone)"); + + +$B.addPy2JSWrapper(_b_.str, function(pyobj) { + // Python strings are converted to the underlying value + return pyobj.valueOf() +}, "string <= str"); + + +$B.addPy2JSWrapper(_b_.dict, function(pyobj) { + + // Python dictionaries are transformed into a Javascript object + // whose attributes are the dictionary keys + // Non-string keys are converted to strings by str(key). This will + // affect Python dicts such as {"1": 'a', 1: "b"}, the result will + // be the Javascript object {1: "b"} + let jsobj = {} + for(var entry of _b_.dict.$iter_items_with_hash(pyobj)){ + var key = entry.key + if(typeof key !== "string"){ + key = _b_.str.$factory(key) + } + if(typeof entry.value === 'function'){ + // set "this" to jsobj + entry.value.bind(jsobj) + } + jsobj[key] = $B.pyobj2jsobj(entry.value) + } + return jsobj +}, "JSObj <= dict"); + +$B.addPy2JSWrapper(_b_.tuple, function(pyobj) { + + // Python list : transform its elements + return pyobj.map($B.pyobj2jsobj); +}, "tuple => Array (clone)"); + //TODO: optimize unwrap... $B.addJS2PyWrapper(Boolean, function(jsobj){ return jsobj; diff --git a/www/src/py_dict.js b/www/src/py_dict.js index 28d1d69a8..c4d8577e2 100644 --- a/www/src/py_dict.js +++ b/www/src/py_dict.js @@ -155,29 +155,6 @@ var dict = { } -$B.addPy2JSWrapper(dict, function(pyobj) { - - // Python dictionaries are transformed into a Javascript object - // whose attributes are the dictionary keys - // Non-string keys are converted to strings by str(key). This will - // affect Python dicts such as {"1": 'a', 1: "b"}, the result will - // be the Javascript object {1: "b"} - let jsobj = {} - for(var entry of _b_.dict.$iter_items_with_hash(pyobj)){ - var key = entry.key - if(typeof key !== "string"){ - key = _b_.str.$factory(key) - } - if(typeof entry.value === 'function'){ - // set "this" to jsobj - entry.value.bind(jsobj) - } - jsobj[key] = $B.pyobj2jsobj(entry.value) - } - return jsobj -}); - - dict.$to_obj = function(d){ // Function applied to dictionary that only has string keys, // return a Javascript objects with the keys mapped to the value, diff --git a/www/src/py_exceptions.js b/www/src/py_exceptions.js index c613ea764..0ef67d759 100644 --- a/www/src/py_exceptions.js +++ b/www/src/py_exceptions.js @@ -1145,6 +1145,9 @@ $B.show_error = function(err){ } $B.handle_error = function(err){ + + console.log(err); + // Print the error traceback on the standard error stream if(err.$handled){ return diff --git a/www/src/py_list.js b/www/src/py_list.js index 7b02c3a4b..ef4a092bc 100644 --- a/www/src/py_list.js +++ b/www/src/py_list.js @@ -30,19 +30,6 @@ var list = { } - -$B.addJS2PyWrapper(Array, function(jsobj) { - // set it as non-enumerable, prevents issues when looping on it in JS. - Object.defineProperty(jsobj, "$is_js_array", {value: true}); - return jsobj // $B.$list(jsobj.map(jsobj2pyobj)) -}); - -$B.addPy2JSWrapper(list, function(pyobj) { - - // Python list : transform its elements - return pyobj.map($B.pyobj2jsobj); -}); - list.__add__ = function(self, other){ if($B.get_class(self) !== $B.get_class(other)){ var this_name = $B.class_name(self) // can be tuple @@ -917,12 +904,6 @@ var tuple = { $match_sequence_pattern: true, // for Pattern Matching (PEP 634) } -$B.addPy2JSWrapper(tuple, function(pyobj) { - - // Python list : transform its elements - return pyobj.map($B.pyobj2jsobj); -}); - var tuple_iterator = $B.make_iterator_class("tuple_iterator") tuple.__iter__ = function(self){ return tuple_iterator.$factory(self) diff --git a/www/src/py_string.js b/www/src/py_string.js index 17d610e32..5f2c8eac0 100644 --- a/www/src/py_string.js +++ b/www/src/py_string.js @@ -119,12 +119,6 @@ var str = { $native: true } - -$B.addPy2JSWrapper(str, function(pyobj) { - // Python strings are converted to the underlying value - return pyobj.valueOf() -}); - str.$to_string = to_string function normalize_start_end($){