Performance and Resource impacts of using Persistent vs. Local

34 views
Skip to first unread message

Joel Scarfone

unread,
Sep 10, 2019, 3:10:12 PM9/10/19
to v8-users
What is the difference between using an `EscapableHandleScope` to propagate a `Value` and just creating a `Persistent` from the local I intent to return from a function? Is one more performant than the other, or is it just usability?

Joel

Ben Noordhuis

unread,
Sep 10, 2019, 4:03:15 PM9/10/19
to v8-users
On Tue, Sep 10, 2019 at 9:10 PM Joel Scarfone <joelrs...@gmail.com> wrote:
>
> What is the difference between using an `EscapableHandleScope` to propagate a `Value` and just creating a `Persistent` from the local I intent to return from a function? Is one more performant than the other, or is it just usability?
>
> Joel

EscapableHandleScope is faster. Persistent handles create more work
for the garbage collector.

(That's the pithy version of a longer response I wrote and then
scrapped because of too much details.)

Joel Scarfone

unread,
Sep 10, 2019, 5:21:19 PM9/10/19
to v8-users
Dang, I was kind-of hoping for the long version :). I would assume it wouldn't actually be any more memory intensive, but rather then it comes down to speed? So I did a quick test. If you are interested in the full source, I can provide it, however for simplicity I will just provide snippets. I made two functions:

Local<String> NewLocalString(Isolate* isolate) {
  EscapableHandleScope handle_scope(isolate);
  Local<String> specifier = String::NewFromUtf8(isolate, "foobar");
  return handle_scope.Escape(specifier);
}

Persistent<String> *NewPersistentString(Isolate *isolate) {
  HandleScope handle_scope(isolate);
  Local<String> specifier = String::NewFromUtf8(isolate, "foobar");
  return new Persistent<String>(isolate, specifier);
}

Both just create a string, but one uses a Local and the other uses a Persistent. Then I simulate using them both a million times:

{
  HandleScope handle_scope(isolate);
  for(int i = 0; i < 1000 * 1000; i ++ ) {
    Local<String> str = NewLocalString(isolate);
  }
}

{
  for(int i = 0; i < 1000 * 1000; i ++ ) {
    Persistent<String> *str = NewPersistentString(isolate);
    str->Reset();
    delete str;
  }
}

Wrapping each in a std::chrono::high_resolution_clock::now(); produced interesting results. Here are my first 5 runs of the compiled binary that tries to benchmark both each run:

./speed_check
Elapsed Local: 100991684ns
Elapsed Persistent: 102445417ns

./speed_check
Elapsed Local: 95274546ns
Elapsed Persistent: 103189948ns

./speed_check
Elapsed Local: 116059276ns
Elapsed Persistent: 103059742ns

./speed_check
Elapsed Local: 118289635ns
Elapsed Persistent: 102510472ns

./speed_check
Elapsed Local: 116038521ns
Elapsed Persistent: 103183176ns 

This is just one crude isolated test case on one hardware profile, but the fluctuating results are interesting.
Reply all
Reply to author
Forward
0 new messages