-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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
Comments
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 |
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.
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). |
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. |
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. |
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. |
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 So it's maybe still a bit slower than duktape, but it's roughly in the same ballpark.
Was back then. I'm guessing the gap is a bit smaller now.
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.
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. |
Also, if any of you need a Promise/A+ implementation which works great in mpv (and for typescript transpiling), try nopromise /shameless-plug. |
Good to know.
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.
Nice. Also the lack of convenient stdlib functions like 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.) |
Possibly. Feel free to provide some actual numbers.
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
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. |
For reference, here's detailed instructions for async/await in mpv, written some time ago: #6079 (comment) |
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.)
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. |
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... |
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. #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 |
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 ( 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. |
@Kagami can you please enable issues at https://github.com/Kagami/mpv-promise (and let me know once you do)? Thanks. |
@avih done. |
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:
duktape:
mujs:
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 . |
For reference, mujs increased the limits as follows: JS_STACKSIZE from 256 to 4096 (affects call nesting depth and more). And also, the regexp limits were both raised greatly and improved to be global for the regexp rather than per character class. |
Nice. My regexp example 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... |
I've just found critical issue in MuJS: ccxvii/mujs#130 (comment) |
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. |
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.
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. |
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 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.
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. |
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.
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))
|
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.
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...
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:
|
These two refer to different APIs, with create_osd_overlay is fast but update is slow. mp.create_osd_overlay("ass-events") ovl.update() |
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. |
I am trying to use qjs in the form of a C plugin mpv-qjs. Here is the performance data for rendering mpv-easy.
|
@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. |
@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: 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.
|
@ahaoboy that's very interesting, thank you! |
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.
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.
The text was updated successfully, but these errors were encountered: