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

mujs performance improvement or support quickjs #13131

Closed
ahaoboy opened this issue Dec 18, 2023 · 33 comments
Closed

mujs performance improvement or support quickjs #13131

ahaoboy opened this issue Dec 18, 2023 · 33 comments

Comments

@ahaoboy
Copy link

ahaoboy commented Dec 18, 2023

Expected behavior of the wanted feature

Mujs is a JavaScript engine designed for small devices, but devices typically used for video playing have sufficient performance, so it's possible to modify some of mujs's compilation parameters to enhance performance.

For example, the 'JS_ASTLIMIT' defaults to 100, some bundlers uses comma expression optimization for size reduction. This can result in code similar to the following directly throwing a 'too much recursion' error.

function f() {
  return 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100;
}

f()

Additionally, the 'REG_MAXCLASS' compilation option defaults to 16, which causes some third-party librarys' regular expressions to fail. 'JS_STACKSIZE' defaults to 256. When executing a bundled large JavaScript file, it often exceeds 1 MB, sometimes leading to direct stack overflow.

Alternative behavior of the wanted feature

Is it possible to support the QuickJS engine? QuickJS supports ES6 syntax and has significantly better performance compared to mujs. To ensure ES5 compatibility, a considerable amount of compatibility work is needed, resulting in increased file size.

@Andarwinux
Copy link
Contributor

Maybe consider some JS engines that support JIT. File size should no longer be a problem these days.

@ahaoboy
Copy link
Author

ahaoboy commented Dec 18, 2023

Maybe consider some JS engines that support JIT. File size should no longer be a problem these days.

Here is a comparison of several JavaScript engines. Currently, qjs is quite suitable in terms of both performance and size. As for JIT, there is currently an unofficial implementation available. However, qjs is already sufficiently fast, and JIT might not be necessary.

redis/redis#11128
https://github.com/bnoordhuis/quickjit

@avih
Copy link
Member

avih commented Dec 18, 2023

'JS_ASTLIMIT' defaults to 100
'REG_MAXCLASS' compilation option defaults to 16
'JS_STACKSIZE' defaults to 256

Increasing these will not increase performance. It will only allow bigger programs.

It's unclear if your bottle neck is program size or performance. According to the limits you want raised, it looks to be program size and not actually performance.

mpv has no control over how mujs was configured and built. If you build your own version of mujs then you can change these, or if you use a 3rd party build then you can request them. But mpv is not part of that.

The best thing would be to ask mujs to increase these limits. Limits have been raised in the past, and if there are good reasons (real world use cases), then it would probably be raised.

Here is a comparison of several JavaScript engines

This lists duktape, quickjs, and v8.

v8 was not and is not considered as it's very big.

mpv initially did support both mujs and duktape (quickjs didn't exist when JS was added to mpv), but eventually duktape support was removed because we only needed one engine, and mujs was good enough. Also, mujs is much simpler and cleaner code than duktape, and they're largely on par (duktape is a bit faster, and with a bit more features, but quite more complex).

quickjs is another matter. While embedding mujs or duktape is generally similar (to eachother and to lua), quickjs is a completely different beast.

Few initial attempts to integrate quickjs didn't go well, mainly because integrating it requires a lot more work than embedding lua or mujs or duktape, and there have not been efforts since. Contributions are welcome.

That being said, mujs did and does still hold up reasonably useful. Speed is generally not an issue for mpv scripts (and mujs is not much slower than lua, though luajit can be very fast in some cases), and size limits can be raised if really needed (at the mujs side)

So the bottom line is that the limits can be raised if you ask whoever builds it, or mujs themselves to raise the defaults.

No current plans to integrate v8 (too big), duktape (not enough value), or quickjs (hard, but if someone has the time and ability to work on it, why not).

@avih avih closed this as completed Dec 18, 2023
@ahaoboy
Copy link
Author

ahaoboy commented Dec 19, 2023

It's unclear if your bottle neck is program size or performance. According to the limits you want raised, it looks to be program size and not actually performance.

Currently, there are no bottlenecks in terms of size and performance. However, there are some compatibility issues on mujs that render it unusable. For developers, directly writing a 2-3 thousand line program in ES5 syntax is impractical. Generally, developers use bundling tools and third-party libraries. Mujs currently faces certain issues (ccxvii/mujs#180) that demand significant time and effort to polyfill some interfaces. On the other hand, QJS encounters fewer of these problems. Yet, integrating QJS into the MPV project indeed presents a substantial task. I will attempt to adjust the bundling tool's configuration to circumvent these issues.

@Kagami
Copy link
Contributor

Kagami commented Jan 12, 2024

duktape is a bit faster

According to Bellard's benchmarks, duktape is 2.5x faster than mujs.

Although QuickJS is 6x faster than MuJS and also supports more ES features, so you don't need to transpile modern features all way down to ES5. No way people would write code in ES5 nowadays, and transpiling/polyfills bring additional overhead, so MuJS takes additional hit in performance.

Another idea I have, is mpv cplugin + V8, similar to how coc.nvim in neovim operates. Not sure if that's easier/better than out-of-the-box QuickJS support but at least you won't have to worry about performance with a fast JS engine.

@ahaoboy
Copy link
Author

ahaoboy commented Jan 13, 2024

In the early experimental stage, compatibility and syntax conversion were truly a nightmare. In terms of performance, mujs is indeed the slowest, with the least supported syntax. However, it is also the smallest and easiest to integrate. Integrating qjs or other engines, on the other hand, requires a substantial amount of work, and it is unlikely to be completed in a short period.
The current performance bottlenecks are speculative. During the testing phase, I used over 100 components, and there was some initial delay in rendering, which seems to be related to the overlay creation. However, after the first successful rendering, the delay becomes acceptable for normal usage. The bottlenecks still need to be tested after the layout is completed. Layout management involves a considerable amount of computation, and around 50 elements are probably sufficient for uosc. Hopefully, mujs can meet the requirements.

@avih
Copy link
Member

avih commented Jan 13, 2024

According to Bellard's benchmarks, duktape is 2.5x faster than mujs.

In terms of performance, mujs is indeed the slowest

There were many performance improvements in mujs since that comparison was made some years ago, with several key bottlenecks improved greatly, mainly C-array for simple integer-index arrays instead of string-based (binary tree) index, much faster Array.join, GC which scales with the program size and has much lower overhead than before with big programs (as some of that benchmark tests are), and other imoprovements.

So it's maybe still a bit slower than duktape, but it's roughly in the same ballpark.

Although QuickJS is 6x faster than MuJS

Was back then. I'm guessing the gap is a bit smaller now.

mujs ... with the least supported syntax

It's ES5.1. If you think valid ES5.1 syntax is rejected by mujs, then it's a bug and you should report it. I'm not aware of such issues currently. That trailing comma in function args which was reported recently is not valid ES5.1.

the delay becomes acceptable for normal usage. The bottlenecks still need to be tested after the layout is completed. Layout management involves a considerable amount of computation, and around 50 elements are probably sufficient for uosc. Hopefully, mujs can meet the requirements.

Indeed, that's the bottom line, that it's likely sufficient for the vast majority of mpv scripts.

It could definitely have some bottlenecks with intense computations (as would quickjs, depending on the definition of "intense", where only JIT would bring it meaningfully closer to native performance), but it's largely quite good.

It's not intended compile huge programs into JS, WASM-like, but for normal, human written scripts, it's generally quite sufficient.

@avih
Copy link
Member

avih commented Jan 13, 2024

Also, if any of you need a Promise/A+ implementation which works great in mpv (and for typescript transpiling), try nopromise /shameless-plug.

@Kagami
Copy link
Contributor

Kagami commented Jan 13, 2024

There were many performance improvements in mujs since that comparison

Good to know.

It's ES5.1

I think he means that people nowadays write code with features like classes, arrow functions, iterators, async functions, etc. None of this is supported by MuJS, so needs to be transpiled, so performance may be even worse compared to interpreters which support later ES standards.

Promise/A+ implementation

Nice. Also the lack of convenient stdlib functions like
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replaceAll
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries
is a problem.

Seems like people use core-js to polyfill this stuff nowadays, but I looked at the implementation of one of this and it's so bloated... I'm afraid to feed poor little MuJS with that stuff 😄

(Of course if you need just few of those, it's easy to write by hand, I mean when you e.g. port some existing code which uses dozens of them.)

@avih
Copy link
Member

avih commented Jan 13, 2024

needs to be transpiled, so performance may be even worse compared to interpreters which support later ES standards.

Possibly. Feel free to provide some actual numbers.

I think he means that people nowadays write code with features like classes, arrow functions, iterators, async functions, etc

Well, it's ES5.1. Duktape is pretty much the same with few extensions from later versions, but it still only targets full compliance with 5.1. And yes, newer versions of the standard extended the language in many ways.

async/await can transpile to mujs just fine (in mpv, because it requires some async infrastructure, which mpv's js has in the form of compliant setTimeout et al), and very efficiently, if you have a compliant Promise implementation, like nopromise which was mentioned above.

I mean when you e.g. port some existing code which uses dozens of them.

Indeed. Porting big projects to mpv's mujs can be a pain, but then these projects probably target node.js or a browser, and mpv is just not that. So even if the language was big enough to support that (as a transpiler is likely to be able to handle), the context is still different than most packages target originally.

@avih
Copy link
Member

avih commented Jan 13, 2024

For reference, here's detailed instructions for async/await in mpv, written some time ago: #6079 (comment)

@Kagami
Copy link
Contributor

Kagami commented Jan 13, 2024

Duktape is pretty much the same

I meant QuickJS which supports ES2020, I think most people should be fine with that. (I understand that it's hard to integrate qjs to mpv.)

Porting big projects to mpv's mujs can be a pain

Not necessary big, could be just a bunch of little npm deps. (But not sure how well they would work, the lack of Node API inside MuJS runtime might be a massive pain point.)

Anyway, I agree, needs testing to see the real world bottlenecks of MuJS.

@avih
Copy link
Member

avih commented Jan 13, 2024

bunch of little npm deps. (But not sure how well they would work, the lack of Node API inside MuJS runtime might be a massive pain point.)

Exactly. mpv is not node.js, as also documented at the mpv JS docs. Some small (ES5.1) self-contained node modules might work as is in mpv, but for the most part, the node.js API and modules are just not there, because it's for mpv scripts, not system/server/general-app scripts...

@ahaoboy
Copy link
Author

ahaoboy commented Jan 17, 2024

I am trying to develop an mpv script toolkit using React and encountered a stack overflow issue with mujs. The specific reproduction code can be found in the demo: https://github.com/ahaoboy/mpv-easy-demo/blob/main/src/counter-ui.tsx. If the third-party library AssColor is used to calculate color values, it leads to mujs stack overflow.
To address this issue, I recompiled mujs on my local machine with adjusted compilation parameters and then used this modified mujs to compile mpv. This resolved the problem.
Due to mujs limitations, it is currently not possible to incorporate libraries that heavily rely on Proxy, as the stack size is set too small. This limitation also causes stack overflow when using dependencies like Redux.

#define JS_STACKSIZE 1024        /* value stack size */
#endif
#ifndef JS_ENVLIMIT
#define JS_ENVLIMIT 512                /* environment stack size */
#endif
#ifndef JS_TRYLIMIT
#define JS_TRYLIMIT 128                /* exception stack size */
#endif

@avih
Copy link
Member

avih commented Jan 17, 2024

Due to mujs limitations

You should report this to mujs, as these limits are only configurable at the build time of mujs, not at the build/run time of mpv. This is outside the control of mpv.

As I mentioned, default limits have been raised in the past, and may be raised again if there are real-world use cases which bump into these limits. So you should report it to mujs with explanations where and why it matters.

I know you reported some other issues at mujs, but you didn't yet request to raise these limits. So maybe now is the time.

Specifically, the mujs stack has a fixed size because it cannot be easly relocated (realloc-ed) due to some other implementation details which may keep reference to some specific stack values, which cannot be freed (e.g. by realloc). IIRC specifically short strings only.

However, with some effort (enhancing mujs), the stack can be made non-fixed, by keeping past-stacks (before allocating a bigger stack) in memory (not-freed) until the VM is finalized. So you can request it from mujs, or maybe have a go at it yourself and send them a PR.

@avih
Copy link
Member

avih commented Jan 17, 2024

@Kagami can you please enable issues at https://github.com/Kagami/mpv-promise (and let me know once you do)?

Thanks.

@Kagami
Copy link
Contributor

Kagami commented Jan 17, 2024

@avih done.

@avih
Copy link
Member

avih commented Jan 20, 2024

So it's maybe still a bit slower than duktape, but it's roughly in the same ballpark.

Although QuickJS is 6x faster than MuJS

Was back then. I'm guessing the gap is a bit smaller now.

For reference, @ccxvii (main author and maintainer of mujs) ran the same v8 benchmark with current quickjs, duktape, and mujs, and the results are better that I expected (for mujs).

TL;DR: mujs is now pretty much the same score as duktape (actually negligibly better), while the relation between quickjs and duktape remained the same, i.e. quickjs is now about 2.3x faster than both duktape and mujs.

Specific scores below:

quickjs:

$ quickjs-2024-01-13/qjs --std run.js 
Richards: 1097
DeltaBlue: 1054
Crypto: 1486
RayTrace: 1246
EarleyBoyer: 2184
RegExp: 356
Splay: 2446
NavierStokes: 2625
----
Score (version 7): 1345

duktape:

duktape-2.7.0/duk run-duk.js
Richards: 301
DeltaBlue: 360
Crypto: 462
RayTrace: 740
EarleyBoyer: 782
RegExp: 167
Splay: 1408
NavierStokes: 1675
----
Score (version 7): 572

mujs:

$ mujs run.js 
Richards: 381
DeltaBlue: 514
Crypto: 284
RayTrace: 752
EarleyBoyer: 722
RegExp: 292
Splay: 1701
NavierStokes: 777
----
Score (version 7): 573

So this probably makes the performance factor not really a factor anymore. While 2x (diff to quickjs) is not negligible, it also dwarfs compared to the difference to the next step in speed (JIT), which is about 30x faster than quickjs.

So this leaves mainly factors such as language support (ES5 or newer), and program size limits.

Since mujs doesn't plan to extend beyond ES5.1, and transpaliers to ES5.1 do exist, I don't consider the language too big a limit for now.

So let's focus on getting as much data as possible which reflects real world program size limits, at ccxvii/mujs#182 .

@avih
Copy link
Member

avih commented Jan 25, 2024

For reference, mujs increased the limits as follows:

JS_STACKSIZE from 256 to 4096 (affects call nesting depth and more).
JS_ENVLIMIT from 128 to 1024 (call nesting depth).
JS_ASTLIMIT from 100 to 400 (related to source file blocks nesting depth).

And also, the regexp limits were both raised greatly and improved to be global for the regexp rather than per character class.

@Kagami
Copy link
Contributor

Kagami commented Jan 25, 2024

Nice. My regexp example ''.match(/%t([aAbBcCdDeFgGhHIjmMnprRStTuUVwWxXyYzZ])/) works now (although I've already removed that code 😄). Now just need to wait for a new release and update in all modern distros :)

In general it seems like you just have to be a bit careful with programming for mujs runtime. I try to be careful even for V8 but it might be not so simple if you import third-party libraries from e.g. npm. Complicated frameworks, stacked on top of each other, like in case of @ahaoboy might create problems with simple JS interpreters...

@Kagami
Copy link
Contributor

Kagami commented Jan 25, 2024

I've just found critical issue in MuJS: ccxvii/mujs#130 (comment)
Basically String.split is unusable.

@ahaoboy
Copy link
Author

ahaoboy commented Jan 26, 2024

Nice. My regexp example ''.match(/%t([aAbBcCdDeFgGhHIjmMnprRStTuUVwWxXyYzZ])/) works now (although I've already removed that code 😄). Now just need to wait for a new release and update in all modern distros :)

In general it seems like you just have to be a bit careful with programming for mujs runtime. I try to be careful even for V8 but it might be not so simple if you import third-party libraries from e.g. npm. Complicated frameworks, stacked on top of each other, like in case of @ahaoboy might create problems with simple JS interpreters...

The syntax and api can be supplemented with polyfills such as Babel and Core-js (excluding Proxy). However, the performance limitations of mujs and the additional burden brought by polyfills may not be suitable for computationally intensive tasks. For example, in the case of uosc's progress bar, it moves every frame, meaning that JavaScript scripts need to complete layout calculations and rendering within a single frame. This requires a lot of optimization. I think I need to create a demo to compare the time consumption of mujs ES5 and qjs ES6 in rendering with React.

@ahaoboy
Copy link
Author

ahaoboy commented Feb 4, 2024

I have completed the testing. qjs: qjs-ssr-demo. mujs: mpv-easy-ssr

Rendering the same component with 155 dom elements, qjs uses both ES6 and ES5, while mujs uses ES5.

qjs es6: 14ms
qjs es5: 24ms
mujs: 91ms

The test results are consistent with the official tests qjs bench. qjs is four times faster than mujs, and the performance is also affected by the degradation to es5.

In mpv-easy , there are approximately over 30 interactive elements, each with three overlays including text, background and border, totaling over 100 nodes. Switching themes, which is a high time-consuming operation, takes over 100 ms. Although optimizations can be made at the JavaScript level through algorithms and caching, the performance ceiling is relatively low due to the degradation of ES5 and the inherent performance issues of mujs. It is hard to implement a progress bar that moves with each frame, similar to uosc. Currently, the performance issues with JavaScript may be specific to mpv-easy. At present, the only option is to attempt optimizations at the JavaScript level.

image
image

@avih
Copy link
Member

avih commented Feb 4, 2024

I have completed the testing. qjs: qjs-ssr-demo. mujs: mpv-easy-ssr

Rendering the same component with 155 dom elements, qjs uses both ES6 and ES5, while mujs uses ES5.

qjs es6: 14ms
qjs es5: 24ms
mujs: 91ms

If you tested the mujs performance in mpv, but the qjs performance outside of mpv, then the comparison is wrong, because in mpv there are additional overheads.

You should compare instead with the stand-alone mujs binary - which is NOT shipped with mpv. You'd need to build it yourself.

Additionally, you did not post how you measured these numbers (source code of the measurement function/script), and while I assume you measured it correctly, it cannot be verified by anyone.

The test results are consistent with the official tests qjs bench. qjs is four times faster than mujs,

As noted above - #13131 (comment) , these test results are out of date. The current results of the same benchmark with recent qjs and mujs is 2.3x, not 4x.

Of course, it may still be the case that in your specific case it's still 4x, but this would not correlate to the benchmark (which is possible, because the benchmark is different than your test case).

Either way, react programs are big and complex, and generally designed to run in browser/node/deno/bun which have jit, which is 30x faster than qjs. It's simply not designed to run in a pure interpreter, even if qjs can run your specific example in 12 ms when all the stars align (and probably much more when the stars don't align).

And, finally, I already noted why qjs is not used, and that has not changed.

@ahaoboy
Copy link
Author

ahaoboy commented Feb 5, 2024

If you tested the mujs performance in mpv, but the qjs performance outside of mpv, then the comparison is wrong, because in mpv there are additional overheads.

I didn't use a stand-alone mujs to run the test code because mujs didn't produce any output or errors. It might require additional polyfills, but I'm too lazy to investigate the specific reasons, as it can be quite cumbersome.

even if qjs can run your specific example in 12 ms

Perhaps qjs may not achieve this level of performance either. The first update of the overlay is very slow, especially when compute_bounds is enabled. Subsequent updates will be much faster. This behavior is consistent between JavaScript and Lua. On my computer, creating 100 overlays only takes 1ms, but the initial update takes 240ms. If compute_bounds is enabled during the first update, it takes 580ms. This suggests that using an overlay pool may be a good optimization strategy.

var size = 100
var ovlList = Array(size)
var t1 = +Date.now()

for (var i = 0; i < size; i++) {
  var ovl = mp.create_osd_overlay("ass-events")
  ovl.data = i.toString()
  ovl.hidden = false
  // ovl.compute_bounds = true
  ovl.compute_bounds = false
  ovlList[i] = ovl
}
var t2 = +Date.now()

for (var i = 0; i < size; i++) {
  var ovl = ovlList[i]
  ovl.update()
}
var t3 = +Date.now()

for (var i = 0; i < size; i++) {
  var ovl = ovlList[i]
  ovl.hidden = false
  ovl.data = i + '-' + i
  ovl.compute_bounds = true
  ovl.update()
}
var t4 = +Date.now()
for (var i = 0; i < size; i++) {
  var ovl = ovlList[i]
  ovl.hidden = false
  ovl.data = i + '-' + i + '-'
  ovl.compute_bounds = false
  ovl.update()
}
var t5 = +Date.now()
print("create: ", t2 - t1)
print("update1: ", t3 - t2)
print("update2: ", t4 - t3)
print("update compute_bounds: ", t5 - t4)
local st = os.clock()
for i = 0, 100 do
  local ovl = mp.create_osd_overlay("ass-events")
  ovl.data = "" .. i
  ovl.hidden = false
  ovl.compute_bounds = true
  ovl:update()
end
local ed = os.clock()
print("lua time: ", st, ed, ed - st)
local x = ed-st
-- local s = 0
-- for i=1,100000 do s = s + i end
print(string.format("elapsed time: %.2f\n",x))

@avih
Copy link
Member

avih commented Feb 5, 2024

I didn't use a stand-alone mujs to run the test code because mujs didn't produce any output or errors. It might require additional polyfills, but I'm too lazy to investigate the specific reasons, as it can be quite cumbersome.

Sure, it's hard work, but that doesn't change the fact that the results are completely meaningless.

One has the overheads of mpv (and additional overheads of actually rendering it and possibly measuring the bounds), and the the other doesn't.

Comparing them is meaningless in this case.

In mpv-easy , there are approximately over 30 interactive elements, each with three overlays including text, background and border, totaling over 100 nodes. Switching themes, which is a high time-consuming operation, takes over 100 ms.

On my computer, creating 100 overlays only takes 1ms

Are these two referring to the same thing? because one is about 100x faster than the other, and 1ms should be considered more than good enough by any UI responsiveness scale...

but the initial update takes 240ms

In most performance measurements, the first run is excluded because it's a known fact that the first run is typically a lot slower than later runs, due to caches etc. So typically any performance measurement includes a "warm up" period where the test is executed few times but the results are excluded from the reported numbers.

Of course, if the thing is only executed once in practice by the user, then the first run does matter, but I think this is not the case here.

In general, if you want to measure JS performance, then you should remove the parts which are not JS.

Specifically, you can calculate your layout in JS, but don't actually call the overlay API.

This will measure how quickly the JS can calculate whatever. The rest is not upto JS performance, but rather mpv overlay performance.

And yes, computing the bounds is very CPU intensive and slow. From the compute_bounds docs:

You should be aware that this mechanism is very inefficient ... This feature is experimental, and may change in some way again.

@ahaoboy
Copy link
Author

ahaoboy commented Feb 5, 2024

Are these two referring to the same thing?

These two refer to different APIs, with create_osd_overlay is fast but update is slow.
More accurately, the first update is slow, but subsequent update is acceptable .

mp.create_osd_overlay("ass-events")
ovl.update()

@avih
Copy link
Member

avih commented Feb 5, 2024

update is slow

This is not done in JS. It calls C code in mpv which does things with libass, and the more things there are to update, the slower it gets.

You should not measure this as part of the JS performance (comparison), and obviously the quickjs test didn't include this.

@ahaoboy
Copy link
Author

ahaoboy commented Feb 17, 2024

I am trying to use qjs in the form of a C plugin mpv-qjs. Here is the performance data for rendering mpv-easy.
Currently, mpv-qjs is a very rudimentary implementation, only the necessary interfaces and functions for rendering have been implemented. Memory management is also not fully implemented. At present, the performance of mujs is sufficient for most use cases. If others require better performance, using a C plugin maybe is a good idea.

js engine first render average js file size
qjs 358ms 3.58ms 1.4M
qjs+minify 334ms 3.42ms 300k
mujs+es5 402 ms 14.05ms 1.1M

@Kagami
Copy link
Contributor

Kagami commented Feb 17, 2024

@ahaoboy that's cool. Any particular reason you've come with quickjs instead of V8 or JavaScriptCore? I don't think the size of V8 is a problem for most users.

@ahaoboy
Copy link
Author

ahaoboy commented Feb 17, 2024

@ahaoboy that's cool. Any particular reason you've come with quickjs instead of V8 or JavaScriptCore? I don't think the size of V8 is a problem for most users.

The reason to use qjs is because I am more familiar with it, and its size is relatively small—libmpvqjs.dll is only 1.5m. Trust me, v8 is too large and complex for most mpv scripts. If there are scenarios where v8 must be used for performance requirements, it is best to implement the core logic in the form of a C plugin. This way, acceleration can be achieved using SIMD or GPU, and so on.
Because it's not feasible to replace the built-in mujs in mpv in the short term(perhaps never), mpv-qjs is just an experiment to accelerate the mpv-easy UI library. Perhaps you could also implement mpv-v8 to automatically execute JavaScript scripts under the script-v8 directory.

@ahaoboy
Copy link
Author

ahaoboy commented Feb 22, 2024

@Kagami I have implemented running JS scripts on V8 using Rust and Deno. Although it can render the UI for mpv-easy, it still lacks many features, making the interface appear imperfect. Additionally, there is no input interaction. You can experience it by downloading the mpv-easy-deno-windows.zip from the release page.

Currently missing features:
Memory management: MPV seems to use talloc, and I'm unsure how to use it in Rust.
Mouse and keyboard input: I haven't figured out how to obtain mouse and keyboard input in a C plugin yet. While the mouse position can be obtained through the mouse-pos property, capturing mouse and keyboard click events within the event loop remains unresolved. As a result, the demo can respond to mouse hover and movement events, but there is no reaction to click events.
mpv_command_node Function: When executing this function, it requires using mpv_node as a parameter and retrieving the function's return value. This might not be straightforward to implement in Rust, causing the disorder in the UI layout. Currently, in Rust, it's challenging to obtain the return value of the mpv_command_node function, resulting in create_osd_overlay not correctly returning the bounding box size.

Performance Comparison, due to the UI drawn using ASS, the refresh rate cannot exceed that of the currently playing video. Therefore, the performance of V8 here is actually excessive. However, using Deno means that we can leverage the Rust and Deno ecosystems, making development much simpler compared to using C.

js engine first render average js file size
qjs 358ms 3.58ms 1.4M
qjs+minify 334ms 3.42ms 300k
mujs+es5 402 ms 14.05ms 1.1M
deno 291 ms 0.23ms 1.4M
deno+minify 464 ms 0.23ms 306k

@Kagami
Copy link
Contributor

Kagami commented Feb 22, 2024

@ahaoboy that's very interesting, thank you!

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

No branches or pull requests

4 participants