Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[OPTIMISATION] Python method implemented in JS: improving them ? #2306

Open
Tracked by #2250
denis-migdal opened this issue Oct 31, 2023 · 5 comments
Open
Tracked by #2250

[OPTIMISATION] Python method implemented in JS: improving them ? #2306

denis-migdal opened this issue Oct 31, 2023 · 5 comments

Comments

@denis-migdal
Copy link
Contributor

denis-migdal commented Oct 31, 2023

Hi,

I noticed that some Python method were implemented in JS like that:

list.__init__ = function(self, arg){
    var $ = $B.args('__init__', 1, {self: null}, ['self'], arguments, {},
            'args', null),
        self = $.self,
        args = $.args
    if(args.length > 1){
        throw _b_.TypeError.$factory('expected at most 1 argument, got ' +
            args.length)
    }
    var arg = args[0]
    var len_func = $B.$call($B.$getattr(self, "__len__")),
        pop_func = $B.$getattr(self, "pop", _b_.None)
    if(pop_func !== _b_.None){
        pop_func = $B.$call(pop_func)
        while(len_func()){pop_func()}
    }
    if(arg === undefined){
        return _b_.None
    }
    var arg = $B.$iter(arg),
        next_func = $B.$call($B.$getattr(arg, "__next__")),
        pos = len_func()
    while(1){
        try{
            var res = next_func()
            self[pos++] = res
        }catch(err){
            if(err.__class__ === _b_.StopIteration){
                break
            }
            else{throw err}
        }
    }
    return _b_.None
}

I'm seeing different issues with that:

  1. some methods has an internal $foo() and an external foo(), but other hasn't.
  2. the argument parsing is tedious (and doesn't use my improved argument parsing).
  3. $B.$call($B.$getattr()) is costly and do some copies / checks we might not need internally.
  4. using iterator seems a little slow.

Hence, I suggest the following:

1./2. Add a addMethod() function

function __init__({self, args}) { // internal function.
   // do stuff
}

addMethod( list, __init__, "self, **args");
// would do:
list.__init__ = function foo(...args) {
     const locals = $B.args0(foo, args); // checks arguments
     return __init__(locals);
}
list.__init__.$info = {....} // set the method info like other Python functions are.

This will slow down a little Brython initialization, but I don't think it'll be significant compare to the python source parsing.
However, this would :

  • help development (quicker dev, less bugs), as we just have to put the python parameter declaration as a string.
  • allow to use my improved argument parsing internally.
  • ensure we always have an internal function we can use internally to speed up things.
  • enable to automatically add leave_frame / enter_frame (?) and other stuffs like that ?

3. Why using $B.$call($B.$getattr()) and not call the internal directly ?

4. Internally speed up iterations by several means :

  • a getAll() internal function that'd allow to get all element from the iterable, if it is an array, would directly return the array.
  • when possible, use a Symbol() as a sentinel (is it faster than exception ???) OR having an internal hasNext or isEmpty function ? :
let value;
while(1) {
     value = next(generator, STOP_SYMBOL);
     if( value === STOP_SYMBOL)
           break;
      // use value...
}
  • check if the iterable is an array, and do a different loop if it is the case ?
if( Array.isArray(arg) ) {
      let result = new Array(arg.length);
      for(let i = 0; i < arg.length; ++i)
           result[i] = 2*arg[i];
} else {
     while(1) {} // etc.
}

@PierreQuentel What do you think ?

Cordially,

PierreQuentel added a commit that referenced this issue Nov 1, 2023
…d for lists, tuples or dicts. Rewrite list.__init__(), as reported in issue #2306.
@PierreQuentel
Copy link
Contributor

I agree with your comments on list.__init__(), it was written very long ago and not updated to take advantage of more "modern Brython" features such as $B.make_js_iterator(), an equivalent of your suggestion n° 4.

Here is the new version

list.__init__ = function(){
    var $ = $B.args('__init__', 1, {self: null}, ['self'], arguments, {},
            'args', 'kw'),
        self = $.self,
        args = $.args,
        kw = $.kw
    if(args.length > 1){
        throw _b_.TypeError.$factory('expected at most 1 argument, got ' +
            args.length)
    }
    if(_b_.dict.__len__(kw) > 0){
        throw _b_.TypeError.$factory('list() takes no keyword arguments')
    }
    while(self.length > 0){
        self.pop()
    }
    var arg = args[0]
    if(arg === undefined){
        return _b_.None
    }
    var pos = 0
    for(var item of $B.make_js_iterator(arg)){
        self[pos++] = item
    }
    return _b_.None
}

I honestly don't remember why __pop__() was used to clear the list before re-initializing it with the new arguments...

@denis-migdal
Copy link
Contributor Author

Thanks for your answer, it helps me understand Brython internals.

What do you think of an addMethod() function to help the writing of Python method in JS, e.g. for argument parsing ?

@PierreQuentel
Copy link
Contributor

What do you think of an addMethod() function to help the writing of Python method in JS, e.g. for argument parsing ?

I agree. Could you implement it for py_list.js first ?

@denis-migdal
Copy link
Contributor Author

If addMethod() is too heavy of a change, we can also think of a generateArgs() function:

const __args__init__ = generateArgs("__init__", "self, *args, **kw"); // compute it only once.

list.__init__ = function(){
        const {self, args, kw} = $B.args0(__args__init__, arguments); // use new optimized function for parsing.
        //....
 }

would replace

list.__init__ = function(){
    var $ = $B.args('__init__', 1, {self: null}, ['self'], arguments, {},
            'args', 'kw'),
        self = $.self,
        args = $.args,
        kw = $.kw
        //...
 }

@PierreQuentel What do you think ? Should I work on it also ?

@denis-migdal
Copy link
Contributor Author

denis-migdal commented Nov 1, 2023

What do you think of an addMethod() function to help the writing of Python method in JS, e.g. for argument parsing ?

I agree. Could you implement it for py_list.js first ?

Oh I didn't saw your message.
Ok, I'm adding this to my todolist.

I'll first make a generateArgs to better test it, then a addMethod().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants