TranscryptOrg / Transcrypt

Python 3.9 to JavaScript compiler - Lean, fast, open!
https://www.transcrypt.org
Apache License 2.0
2.82k stars 215 forks source link

method call performance #817

Closed jggatc closed 2 years ago

jggatc commented 2 years ago

Appears method calls are significantly slower than function calls, perhaps relates to JIT or JS function inlining. Tried running method_call repeatedly to warmup JS engine and to place cls.update in a var cls_update to avoid attr lookup overhead with only small improvement.

test_call.py:

from time import time

class Cls:
    def __init__(self):
        self.count = 0
    def update(self):
        self.count += 1

cls = Cls()

def update(cls):
    cls.count += 1

def function_call():
    for i in range(1000000):
        update(cls)

def method_call():
    for i in range(1000000):
        cls.update()

t = time()
function_call()
tf = time() - t
print('function call: {}s'.format(round(tf,3)))

t = time()
method_call()
tf = time() - t
print('method call: {}s'.format(round(tf,3)))

transcrypt -n test_call.py console: function call: 0.003s method call: 0.242s

Environment: Linux Python 3.9.6 Transcrypt 3.9.0 Chrome/Firefox

jggatc commented 2 years ago

Suppose the issue is in the get function in transcrypt runtime. Tried some code refactoring experiments to optimize method calls, uncertain the impact on compatibility but obtained nearly a 5-fold increase in performance with the the following change in the get function:

original:

return function () {
    var args = [] .slice.apply (arguments);
    return func.apply (null, [aThis.__proxy__ ? aThis.__proxy__ : aThis] .concat (args));
};

revised:

return function () {
    var args = [aThis.__proxy__ ? aThis.__proxy__ : aThis];
    args.push.apply (args, arguments);
    return func.apply (null, args);
};

transcrypt -n test_call.py console: function call: 0.003s method call: 0.052s

jggatc commented 2 years ago

Refactoring of __get__ function in transcrypt runtime gained significant performance improvement.

Revision of __get__ function:

method handling with fcall pragma

value: function () {
    var args = [aThis];
    args.push.apply (args, arguments);
    return func.apply (null, args);
},

method handling

return function () {
    var args = [aThis.__proxy__ ? aThis.__proxy__ : aThis];
    args.push.apply (args, arguments);
    return func.apply (null, args);
};

Testing:

test_method_call.py

from time import time

class Cls:
    def __init__(self):
        self.count = 0
    def update_count(self):
        self.count += 1

cls = Cls()

def method_call():
    for i in range(1000000):
        cls.update_count()

t = time()
method_call()
tf = time() - t
print('method call: {}s'.format(round(tf,3)))

transcrypt -n test_method_call.py

Performance of revised vs original __get__:

original __get__ (chrome) method call: 0.252s

revised __get__ (chrome) method call: 0.050s

original __get__ (firefox) method call: 0.458s

revised __get__ (firefox) method call: 0.117s

Performance of revised vs original __get__ using __pragma__ ('fcall'):

original __get__ (chrome) fcall method call: 0.219s method call after 3*1M loops: 0.205s

revised __get__ (chrome) fcall method call: 0.014s method call after 3*1M loops: 0.006s

original __get__ (firefox) fcall method call: 0.419s method call after 3*1M loops: 0.413s

revised __get__ (firefox) fcall method call: 0.078s method call after 3*1M loops: 0.075s

Revised __get__ performance was increased 5x for chrome and 4x for firefox. Using fcall pragma, with original __get__ there is approx. 10-20% performance improvement, whereas with revised __get__ there is sum performance improvement of 18x for chrome and 6x for firefox. Chrome achieved 42x performance with revised __get__ after warmup of fcall bound function that is close to performance of function calls.

Similar revision can be applied to class method and static method handling. Have done limited testing of __get__ revision, not certain of compatibility impact.

Note: further testing show significant improvement for only simple methods.