-
Notifications
You must be signed in to change notification settings - Fork 511
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] Implements str as string with prototype replacement. #2269
Comments
Okay, in the same fashion, we can also do it for arrays.
The issue relies in the JS <=> Brython interactions.
(*) the alternative would requires the array to be a wrapper in the first place, with synchronization issues, that'd add a little cost on the Brython side. I think we could argue that perfs on Brython side are more important than perfs on JS side. I think this would boost execution speed on array operations in Brython, while handling good JS <=> Brython conversions, without sacrificing perfs in the Brython side. |
For tupple, we could have a class inheriting Array (enables using |
These ideas are worth considering, but the main question remains, will they improve the overall performance ? We need testing before answering this question. If you add one, or two, or three tests inside I did a quick try with this code in $B.__string__ = {
lower: function(){
var $ = $B.args('lower', 0, {}, [], arguments, {}, null, null)
return this.toLowerCase()
}
}
$B.$getattr_pep657 = function(obj, attr, position){
if(typeof obj == 'string' || obj instanceof String){
if($B.__string__.hasOwnProperty(attr)){
return $B.__string__[attr].bind(obj)
}
}
try{
return $B.$getattr(obj, attr)
}catch(err){
$B.set_exception_offsets(err, $B.decode_position(position))
throw err
}
} I measured the time taken by these tests from time import perf_counter as timer
t0 = timer()
for i in range(1_000_000):
'ABcD'.lower()
print('__string__', timer() - t0)
t0 = timer()
x = 0
for i in range(1_000_000):
x.bit_count()
print('int.bit_count', timer() - t0) The result is a 13% improvement for Side note : I don't coerce |
This comment was marked as outdated.
This comment was marked as outdated.
Hi ;) I might be forgetting some details, but I think I found a way to avoid doing tests for each supported type of object (0(n)) for a small overcost ((O(1)). I don't know if this small overcost is greater than doing a test. Depending on the context I think this small overcost would be either :
I think I can also prevent some of the function calls. The rational is to put our own getattr/setattr brython function into supported object prototype. // INITIALIZATION
function addBrythonSupportFor(target, bry_obj) {
target.prototype.$BRY_getattr = function(name){ // equiv to $B.getattr()
return bry_obj[name];
}
target.prototype.$BRY_setattr = function(name, value) {} // etc....
}
addBrythonSupportFor( String, {
replace: function foo() {}
} );
addBrythonSupportFor( Object, {
} );
//... etc.
//NOTE: we may also use Symbol, it'll be cleaner, and maybe faster (but I have doubt).
// const $BRY_getattr = Symbol();
// target.prototype[$BRY_getattr] = function(name){ // equiv to $B.getattr()
// return bry_obj[name];
// }
// define $callM to call methods // equiv to $B.getattr_pepXXXX() + $B.call + ()
$B.$callM(target, method, ...args) {
try {
let m = target.$BRY_getattr(method);
// [OPTI???] target.__proto__.$BRY_getattr(method)
// [SYMBOL?] target[$BRY_getattr] // target.__proto__[$BRY_getattr](method)
// do brython stuff
let ret = m.call(target, ...args)
// do brython stuff
return ret
} catch(e) {
// do things
}
}
// OR
$B.$callM(target, method) { // equiv to $B.getattr_pepXXXX() + $B.call
try {
let m = target.$BRY_getattr(method);
// do brython stuff
let fct = m.bind(target)
// do brython stuff
return fct
} catch(e) {
// do things
}
}
// call of a method :
$B.$callM( obj, "replace", ...args )
// OR
$B.$callM( obj, "replace")( ...args ) This is the "simple version". In fact, $B.$callM( [0,1,2], [3,4,5], obj, "replace", ...args);
// or
$B.$callM(obj, [0,1,2], "replace", [3,4,5], ...args);
// or
$B.$callM([0,1,2], obj, [3,4,5], "replace", ...args); |
Wouldn't this system also solves the issue for arrays JS <=> Brython conversions and synchronizations as the content would always be the same being in JS or Brython, only the API to access and use the stored object changes ? This would avoid wrappings, and copies. Maybe it could even help for JS and Brython functions and classes ? EDIT: this would also enables Brython users to redefine the API of some JS classes that are not currently redefined. So even if it would maybe cost a little more, the cost may be acceptable regarding to the advantages ? |
The behavior of function foo() { console.log(this) }
foo() //Window
foo.call(null) //Window
let x = {}
x.foo = foo
x.foo() // Object { foo: foo() }
z = x.foo
z() // Window
(function(){return x.foo})() //function foo() The behavior of class Z:
def foo(self):
print(self)
foo = Z.foo
foo()
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# TypeError: Z.foo() missing 1 required positional argument: 'self'
z = Z()
z.foo() # <__main__.Z object at 0x7f978caa8400>
x = z.foo
x() # <__main__.Z object at 0x7f978caa8400>
a = A()
a.foo = z.foo
a.foo() # <__main__.Z object at 0x7f978caa8400> I now understand a little better why there is a In the solution I proposed, it'll force you to $B.callM( "eee", "replace", ...);
$B.callM = function(obj, method, ...args) {
getattr( obj, method ).call(obj, ...args) // "this" in python method isn't modified as binded at creation, "this" in JS method is modified.
} With "getattr" not doing any bind, and This could be a way to not have strange surprises with JS "this" and Brython "self" bindings while avoiding doing binds at each function/method access ? |
Did some tests. The consequences of that is that if we define // INITIALIZATION
const $BRY_GETATTR = Symbol();
function addBrythonSupportFor(target, bry_obj) {
target.prototype[$BRY_GETATTR] = function(name){
return bry_obj[name];
}
}
addBrythonSupportFor( String, {
replace: function foo() {}
} );
addBrythonSupportFor( Object, {} );
// in $B.getattr_pep or $B.callM :
let m = target[$BRY_GETATTR]; Note: this also enable to :
const $BRY_GETATTR = Symbol();
const $BRY_STRICT_MODE = true;
function addBrythonSupportFor(target, bry_obj, enforce) {
target.prototype[$BRY_GETATTR] = ( enforce === undefined ? enforce : $BRY_STRICT_MODE )
? function(name){
if( bry_obj.hasOwnProperty[name] ) // maybe there are ways to optimise it.
return bry_obj[name]
throw new Error('Property ... doesn't exists (you are in strict mode');
}
: function(name){
if( bry_obj.hasOwnProperty[name] ) // maybe there are ways to optimise it.
return bry_obj[name]
if( ! this.hasOwnProperty[name] )
throw new Error('Property ... doesn't exists')
return this[name];
}
} If the property is a JS property, it costs an additional check in non-strict mode.
If |
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
I'm hiding previous comments to keep the conversation clean. It seems that
TL:DR; So in conclusion, |
Just though of it, but we also could force users to explicitly indicate when they want to use the JS API ? Then I'd assume a strict mode would be unnecessary as JSAPI calls would be explicit ? // not optimized, not fully written, it is just for the example
function addBrythonSupportFor(target, bry_obj, enforce) {
for(let elem in target.prototype)
bry_obj['$JSAPI_' + elem] = target.property[elem];
target.prototype[$BRY_GETATTR] = ...
}
addBrythonSupportFor( String, {
replace: function foo() {}
} );
//usage :
"eee".replace("e", "f"); // function foo
"eee".$JSAPI_replace("e", "f"); // original JS replace function |
mmm... now that's funny. The advantage compared to the standard way of assigning values, is that we can configure whether :
|
@PierreQuentel What should be the next step on this issue ? Knowing that I think prototype substitution has several advantages (cf this message and the following) :
I think there are ways to redefine little by little Brython functions in order to add this new feature little by little for an overcost, until the time we are ready to make the full switch :
This would make JS <=> Brython conversions more extensibles (enable Brython users to declare a conversion for one JS class), with delegation of responsibilities, and should induce an increase in performances (remove a function call and conditions at the cost of a method call).
Once done, we should be able to control whether we wants to use a wrapper or the original object with substitute prototype, simply by changing the return value of the
In the eventuality a small over cost is induced I'd argue that extensibility and clean code are more important than raw execution speed, and that should reduce also some memory usage. I'd argue that an eventual over cost should be so small (function call vs method call) that it shouldn't matter, other parts of code are doing way more costly operations and can be optimized. I'd also argue that normally we should get performances increase due to the removal of some checks/conditions. |
I think we really NEED to do that for lists/tuples as seen in #2298. Brython <=> JS conversions are unsafe as they are asymmetrical, as well as a bit complex and chaotic. Tuple would use
|
Hi,
Currently, Brython converts all
string
into his own structure thanks to$B.String(jsobj)
(cf code).I suggest to not make conversions between
str
andstring
, but instead doing some kind of "hot prototype replacement" when a method is called.Currently, Brython functions call are converted into JS :
becomes :
I suggest, in the function
$B.$getattr_pep657
function, to do the following :With
$B.__string__
being a global variable :The cost would be two additionals conditions that you are very likely already doing (so 0 cost in fact).
The gain, would be avoiding
str
<=>string
conversions while making the code/process cleaner/easier.@PierreQuentel What do you think ?
Cordially,
The text was updated successfully, but these errors were encountered: