You are not logged in.
Pages: 1
For mORMot 2, I always wanted to keep SpiderMonkey in parallel with QuickJS. It is a much faster engine for sure, thanks to its optimized JIT.
And we have SyNode running and tested since years thanks to mpv and the UnityBase team!
That's why I created a "script" sub-folder, and a mormot.script.core.pas unit, which would be shared by both engine.
But SpiderMonkey seems to be not distributed as a stand-alone engine any more.
@mpv Do you think you would be able to continue maintaining SyNode?
As alternative to SM...
- Other small engines (like Duktape) are slower than QuickJS.
- ChakraCore is good but abandoned by MicroSoft IIRC.
- https://developer.apple.com/documentati … scriptcore may be viable - seems close to SM/V8
- https://github.com/BeRo1985/besen is written by Bero in ObjectPascal and has a small JIT (for most simple opcodes only - so I guess it is far behind V8 and SM JIT)
- V8 of course but not multi-thread friendly IIRC.
- ????
Besen seems to be a very good candidate.
But it is much less used than the others, so less proven in the field, and is at ES5 level not ES6. If I remember correctly, your tests of Besen was that it was slow and not so good at memory use.
Bero is a nice and very clever guy, he may be willing to help if we use its Besen engine.
Offline
Thank, @ab for this great addition.
I created a small pull request to add "mormot.lib.static" to the uses. Required by Before/AfertLibraryCall.
https://github.com/synopse/mORMot2/pull/23
Sharing some code. I hope this is the correct way to start using it.
var
ctx : JSContext;
rt: JSRuntime;
global : JSValueRaw;
begin
rt := JS_NewRuntime;
try
ctx := rt.New;
try
global := JS_GetGlobalObject(ctx);
JS_SetPropertyStr(ctx,global,'log',JS_NewCFunction(ctx, @log, 'log', 1));
ctx.Free(global);
Memo1.Lines.Add(ctx.EvalGlobal(SynEdit1.Lines.Text, 'code'));
finally
ctx.Done;
end;
finally
rt.Done;
end;
end;
Offline
Please don't try to use this unit - it is not yet workable in the git repository (it is on my side for a limited amount).
You don't even have the static binaries!
An eventual commit will enable it in the next days.
It is a very good sign that several users are very eager about using QuickJS!!!
But please be patient!
Offline
My use case is an enterprise apps where all server side businesses logic is in JS (low level abstractions, like DB access, HTTP server, thread pools etc is a mORMot classes). A typical product can be more when 30Mb of JavaScripts (with comments).
JS shine for businesses logic, because it allow quickly implement a new feature or adopt to a changes (what are often in a real life). Integration with other services is also very easy, thanks JSON is native for JS and REST wins in most areas.
The most important requirements for JS engine is my scenarios are (in order of priority):
0) vendor/maintainer support and API stability
1) multi-threading: most businesses app logic is about circles, if's, string concatenation etc. what is synchronous by nature, so async single-thread engines like nodejs simply blocks
2) debugging: for big project debugger is absolutely necessary. Better with VSCode / WebStorm integration
3) nodejs compatible core modules ( require, console, Buffer, fs, http, crypto etc.): almost anything is already implemented and published in NPM, for example we uses `lodash`, `jsPDF`, `XMLDom`, `mustache`, `dbf` and hundreds of other modules
4) ES6: most juniors even don't know how to write ES5 code, all samples is in ES6, most of npm modules are also ES6 (can be transpiled to ES5 but it adds complexity )
5) speed and memory consummation
So choices are
- V8, no because do not support multi-threading
- besen, I think, unfortunately, no, because not popular and ES5 only
- javascriptcore is too proprietary IMHO and C++ centric (debugger is a part of Safari and C++ API only, so we again need to implement a C++ -> C wrapper as with SM and this is pain)
- SpiderMonkey - I will continue to support it in SyNode in future and off cause will port SyNode to mORMot2 (slowly). The pros - all requirements is OK except support. But hope such projects as GNOME and MongoDB won't let SM go to waste.
- ChakraCore is actively developing (it's a part of Outlook, DocumentDB and UWP so will be supported even without EDGE) and meets all our requirements. A good candidate to replace a SM
- QuickJS - a small and simple, many good features like debugger and CommonJS support already implemented by community. I afraid mostly about performance. On synthetic tests it's slow, may be in real life tasks not. When it became a part of mORMot I can try to port a core modules from SyNode to QuickJS and test it on the real life tasks
Offline
Thanks @mpv for the feedback.
It is what I thought...
So we will investigate ChakraCore if you think it is worth it.
I am working on making the script support abstract enough to switch from one engine to another.
So I just started porting SyNode.pas reusable logic as abstract classes in https://github.com/synopse/mORMot2/blob … t.core.pas
It is a staring point. Not usable at all! It just compiles. I would like to have 100% features of SyNode available.
My idea is that we will have QuickJS implemented in mormot.script.quickjs.pas and SpiderMonky in mormot.script.spidermonkey.pas. And perhaps a new unit mormot.script.chakracore.pas in the close future, starting from existing Delphi wrappers.
My goal is to be able to switch from one engine to the other on need, to leverage the debugger logic, and the upcoming extended custom variant support with late-binding calls and interface-based implementation of JS classes in native code with all script engines available.
First question: what is the purpose of the MainEngine in SyNode? I guess there is no main thread in daemons, so is it used somewhere? What is the purpose of the "grandParent" feature in SyNode?
Offline
What is the purpose of the "grandParent" feature in SyNode?
This is a SpiderMonkey specific optimization.
We need one 'fake' parentContext to pass it into JS_NewContext(maxbytes, maxNurseryBytes, parentContext) - this allows SpiderMonkey storing there
some shared information and reduce overall program memory consummation up to 20% (depends on threadPoolSize)
Don't know exactly what is stored there, but on practice it's works without a speed penalty.
Last edited by mpv (2021-03-23 19:15:45)
Offline
what is the purpose of the MainEngine in SyNode? I guess there is no main thread in daemons, so is it used somewhere?
Actually I have a 'main thread' in UnityBase - in this thread I do some preparations for multi-thread program.
JS engine is used there, for example, to parse and merge JSONs from different locations in file system and pass a merged JSON as a string into working threads.
Also Unitybase can be executed in "server mode" (ub -cfg path/to/config.json starts a multithrad HTTP server) and in "command line mode" - in this case MainEngine is used for command line script evaluation ( ub -p "1+1" or ub someFile.js )
Last edited by mpv (2021-03-23 19:16:51)
Offline
Several things I planned to do, but haven't done yet:
1) hot reload of JS - it's implemented in SyNode but this is EVIL and I do not use it anymore (ContentVersion and company) - I propose to remove it from new classes. In opposite EngineExpireTimeOut is a good feature - we sets it to 8 hours on production, so even if script author do some bad things with memory (put something to global and forgot to remove etc.) engine is periodically recreated and memory freed up
2) Atoms
Both QuickJS and SpiderMonkey have this conception (not in SyNode yet but planned). Can be used to pre-allocate a JS strings on startup (for example by property names we know) and use these atimis instead of JS_NewString every time. See how GNOME uses it
3) [discussion] conception of ThreadSafeEngine, ThreadSafeConnection and so on including SynLog.
What is we use a fixed length array's instead of dynamic lists for it?
We know a size of server thread pool and before starts any thread can set these arrays length = threadPoolSize. And instead of ThreadSafeSomething(threadID) use a Something(threadIndex).
This is faster and do not require loops and critical sections.Can be implemented in IFDFES sections to allow apps without fixed size thread pool continue to work
Last edited by mpv (2021-03-23 20:23:49)
Offline
Just a thought about threads.
What if we don't actually assign a runtime/context to a given thread, but we maintain a list/pool of runtime/context, then assign them when we need for a thread?
I guess there is no threadvar used in SM nor QuickJS. We just have to not use the same runtime/context from several threads at once. But we can switch a runtime/context to one thread to another, if only one thread is using it at the same time.
Instead of ThreadSafeEngine we could just have EngineAcquire ... try ... Finally EngineRelease blocks in the user code - similar to BeginRequest/EndRequest.
It would help reducing the resource consumption, too, and then we could put a hard limit to the number of runtime/context in the pool.
About the fixes size of the thread pool: it is possible if you really know the number of threads. But it is not always the case, and if the design is always working, then it would be better.
The problem with current implementation is that if the code is creating a lot of threads, then the context/runtime will only be releases after the timeout, unless there is an explicit engine release when the thread ends.
It would need carefull thread destryoing interception, whereas the first idea of a shared pool of engines don't need it.
Offline
Note about Chakracore.
It seems that it is not thread-bound. It is called "rental-threading".
Check https://blogs.windows.com/msedgedev/201 … indows-10/
Offline
4) [discussion] FPU mask (before/afterLibraryCall in mORMot2).
What if we set a FPU masks to values expected by C by default. I think almost nobody handle a EDivisionByZero etc. in the real life programs. Or I miss something important?
Offline
About Chakra, https://github.com/microsoft/ChakraCore … verview.md is a good read.
Offline
About using a context/runtime pool and not a per-thread list, my idea was not so good because having a ThreadData is sometimes mandatory.
About having a thread list with a fixed size, I don't see why it should change anything in terms of performance or stability.
The only benefit may be to circumvent orphan context/runtime threads.
What I will do is adding a TThreadSafeManager.MaxEngines limit, with 512 identified threads by default.
Using atoms instead of strings is an interresting idea.
I will see how to do it. Perhaps by maintaining our own hash table in each context.
But AFAICT QuickJS has already a good hash table if you use RawUtf8. It may be beneficial only for SpiderMonkey, and ChakraCore which both use UTF-16...
Offline
I agree what the best solution is a per-thread pool, as it currently implemented for DB connections and JS engines in mORMot. Not only because many libs uses ThreadData (bwt OledDB), but also it's KISS.
My idea is to have a fixed size DB/JSEngine thread pools for apps, where HTTPserver.ThreadPoolSIze is also fixed to X (in http 1.0 mode) and all tasks are initiated from inside of HTTP server threads.
In this case instead of
function TSQLDBConnectionPropertiesThreadSafe.ThreadSafeConnection: TSQLDBConnection;
begin
fConnectionPool.Safe.Lock;
try
i := CurrentThreadConnectionIndex;
if i>=0 then begin
result := fConnectionPool.List[i];
exit;
end;
result := NewConnection;
finally
fConnectionPool.Safe.UnLock;
end;
end;
we can SetLength(poolArr, X); and simplify a thread-related DB connection retrieve to
function TSQLDBConnectionPropertiesThreadSafe.getConnectionForThread(indexOfThreadInMainAppThreadPool: int): TSQLDBConnection;
begin
if poolArr[i] = nil then
poolArr[i] = NewConnection;
Result := poolArr[i]
end;
I understand what this not work for current thread-per-connection WebSocket / HTTP 1.1 mORMot server implementation, but from my experience on Hi load we must have a fixed thread pool (http 1 mode).
So my propose is to add such feature in some $ifdef if it makes sense in terms of performance..
Last edited by mpv (2021-03-24 14:20:03)
Offline
About atoms - idea is not to move a hash table into pascal, but to avoid it at all
TMyJSEngine = class(TThreadSafeEngine)
private
atmCount, atmAtime: JSAtom
...
constructor Create();
inherited Create()
atmCount := JS_CreateAtom('count');
atmAtime := JS_CreateAtom('atime');
...
and use atmCount instead of JS_NewString('count') to set 'count' JS property if it need to be done often.
This is how it used by GNOME.
P.S.
For example here - https://github.com/synopse/mORMot/blob/ … s.pas#L157 instead of
obj.ptr.DefineProperty(cx, 'atime'...
can be
obj.ptr.DefinePropertyById(cx, atmAtime...
Last edited by mpv (2021-03-24 14:38:06)
Offline
About thread pool, my guess is that a fixed array won't be noticeable faster against the brute force search in TThreadSafeManager.ThreadEngineIndex().
I prefer to maintain the table with a maximum high limit.
About the atoms, I am not sure it would be noticeable too.
What I would like is to store the atoms with the RTTI, so that we can create objects or set properties from RTTI with no lookup.
Offline
Side note: I just was able to statically link QuickJS with Delphi Win32!
I couldn't use the Embarcadero C++ compiler to build QuickJS (not supported).
There was a lot of low-level tweaks to let the mingw binaries link with Delphi - but seems stable to pass the basic tests.
I hope (and guess) Delphi Win64 will be easier to link.
Offline
Has anything changed in this final version regarding the registration of functions?
The same example I posted above is not working now.
//registration
global := JS_GetGlobalObject(ctx);
JS_SetPropertyStr(ctx,global,'log',JS_NewCFunction(ctx, @log, 'log', 1));
...
function Log(ctx: JSContext; this_val: JSValueConst; argc: Integer; argv: PJSValueConstArr): JSValue; cdecl;
begin
//when this function is called argc is 20965536 and argv is empty
end;
Offline
Edit: I am not able to reproduce your problem. The regression tests have no issue with C functions.
Thanks ab, it's working now.
It was necessary to adapt the type of parameters.
From
function Log(ctx: JSContext; this_val: JSValueConst; argc: Integer; argv: PJSValueConstArr): JSValue; cdecl;
to
function Log(ctx: JSContext; this_val: JSValueRaw; argc: Integer; argv: PJSValues): JSValueRaw; cdecl;
Offline
Hi @ab,
Is this implementation Thread safe, right?
Both the runtime and context will be created, and used inside the thread.
Any concerns I should consider when running inside threads?
procedure TMyThread.execute;
var
ctx : JSContext;
rt: JSRuntime;
begin
rt := JS_NewRuntime;
ctx := rt.New;
...
ctx.Done;
rt.Done;
end;
Offline
Yes, each JSRuntime is thread-safe.
3.4.1 Runtime and contexts
JSRuntime represents a Javascript runtime corresponding to an object heap. Several runtimes can exist at the same time but they cannot exchange objects. Inside a given runtime, no multi-threading is supported.
JSContext represents a Javascript context (or Realm). Each JSContext has its own global objects and system objects. There can be several JSContexts per JSRuntime and they can share objects, similar to frames of the same origin sharing Javascript objects in a web browser.
Offline
Thank you for the informations.
I'll see more about this in QuickJs documentation.
As I understand it would not be necessary to create a runtime per thread.
I'm going to test if there are any performance and resource utilization benefits.
Offline
When application are compiled for win32, and evaluating some javascript with "var d = new Date(..)", causes an exception.
ELibStatic {Message:"Panic in quickjs2.c:15702: assert(list_empty(&rt->gc_obj_list))"} [Main] at 436b27
The exception occurs at the exact moment that "new Date()" is used, not in Runtime.done...
Lazarus + FPC Win32
When cross compiled to Win64 run without errors.
Offline
Sample project to reproduce:
https://github.com/devaex/QuickJsTest
With packages
Note: I don't use win32, I'm just reporting the problem I found by "accident".
Offline
I assumed it was something unrelated to mormot.
Thanks for reply.
Offline
Maybe related to
https://github.com/bellard/quickjs/issues/89
Offline
Pages: 1