Open denis-migdal opened 10 months ago
Tsk, previous idea (I hid it) won't work as functions can be substituted/redefined in Python...
Such a shame, this could have lead to precomputation of part of the result which would make argument parsing almost without any cost in some cases...
Python really have rules that prevents lot of optimizations...
Okay, still, I got a great idea of optimization for calls using named arguments.
Such calls is written as :
$B.$call(....)(a,b, {$kw: [{}, ...]})
Meaning that, when we do not have a **kwarg
parameter, we already have an object containing some of the function parameter.
So, inside the parser, instead of having to create a new result = {}
then later filling it with the value of args[i].$kw[0]
, we can just directly use args[i].$kw[0]
as result
. And just have to count the number of element prefilled in our result
.
This can possibly generate a big speed up.
Of course, we could even rewrite function calls as :
$B.$call(....)(a,b,
2, // pre-compute number of named arguments
{a: 32, b:34}, // named arguments
null // **kargs arguments, can be [...] if there are some
)
Sure, it'll create an object even when we do not use named arguments, BUT, we'd be able to recycle it if function doesn't accept **kwargs
, or if the precomputed number of named arguments is 0 (i.e. is empty).
Could be negative if there are no named arguments, but there is **kargs
arguments, which would allow the checks with a single comparison instead of 2.
Another optimization,
Object.create(null)
seems 1.18x faster than {}
for creation, access, and in
. Almost identical for modification.{}
is used in 368 places in Brython code.
EDIT: not a good idea, modification if forbidden in strict mode.
Creation with __proto__: null
is very slow.
In the commit above and the previous ones I have reduced the size of generated Javascript code for functions by delegating the creation of f.$infos
to functions $B.make_function_infos()
and $B.make_code_attr()
. I have also removed f.$defaults
in $B.make_function_defaults()
Cool.
I have currently some work to do + some personal matter so I'm not quite available theses days.
But once I'll have a little time I'll add the little opti I though for named arguments ;).
When publishing 3.12.1 and running the speed test script, I observed that function creation was slower than before, which is logical since the function argument parser is built on function creation, and it takes a little time.
In commit 4f5bc28278495476ada1bfb2223ce6e14f5cc03f I have slightly modified the feature : the parser is only built the first time the function is called. With this change, function creation is now faster, and execution is the same.
When publishing 3.12.1 and running the speed test script, I observed that function creation was slower than before, which is logical since the function argument parser is built on function creation, and it takes a little time.
There are several things :
Function
. We could pre-create some parsers that are used very often. But I don't think this will be a huge gain.Please note that Function
is very slow as we have to parse JS code in order to create the function. This cost is "hidden" when you directly write the function in the code as it is parsed before you start executing.
In commit 4f5bc28 I have slightly modified the feature : the parser is only built the first time the function is called. With this change, function creation is now faster, and execution is the same.
Hum, you add a condition (${name2}.$args_parser ?? $B.make_args_parser(${name2})
) on each function calls ?
Conditions are very expensive for such code.
Maybe you can do it like that to remove the condition:
function make_args_parser_then_parse(fct, args) {
fct.args_parser = $B.make_args_parser(fct); // replace the initial fct.args_parser by the newly created parser.
return fct.args_parser(fct, args); // you can do it in one line if you want.
}
// create a new function:
function foo() {} // do stuff
foo.args_parser = make_args_parser_then_parse; // called upon first parsing, create the parser then parse.
// consecutive parsing will call directly the parser.
In the same way, maybe there is a way to build the function $infos
only the first time it is needed (and build the parser at the same time ?) :
// create a new function:
function foo() {} // do stuff
Object.defineProperty("$infos", {
// some config.
get: function() { const $infos = createInfos(fct); Object.defineProperty("$infos", $infos); return $infos; }
});
Something like that. Then createInfos
could also call $B.make_args_parser
to prevent doing some operations twice.
Then, we could replace make_args_parser_then_parse
by make_$infos_and_parser_then_parse
.
Hum Object.defineProperty()
seems quite slow, a little too much. Then the best solution would be using make_args_parser_then_parse
.
Do we need $infos
outside of the args_parser()
/ set kw defaults / etc. ?
I'll also have to do some benchmarks to see where this extra-cost comes from.
If it is due to Function
, I do not think we really need to bother a lot. It's just that previously the cost was hidden has the JS code parsing time wasn't considered. If it is something else, I'll have to take a look to the optimizations possible.
Made a PR ( #2336 ) for the little opti I though about (x1.11 in parsing of named argument when no **kwargs
parameter).
Current tasklist: https://github.com/brython-dev/brython/issues/2283#issuecomment-1779264084
See [2275] for other optimizations about function calls.
========================================================
I started working on the optimisation of
$B.args0()
.I will edit this first message in the future to give a link to a github dedicated to this project + to give a summary of the results and the current progression of the project.
I will use the messages below to discuss about this issue.
Some ideas here.