On Fri, Aug 1, 2025 at 3:11 PM Leszek Swirski <[email protected]> wrote:
> Interesting, I like this idea in broad strokes. Unbounded growth is of > course a problem as you say, especially for things less obvious than loops > (like the compilers running optimizations to a fixed point). I wonder if we > could use trap handlers to grow the backing store, maybe copying it and > leaving the old store around only as a root set for the GC (which it could > clean up after returning to the event loop). Probably the > PersistentHandlesScope backing could use a similar trick, swapping itself > in for the address and limit of the per-thread handle backing; > ReopenAndCanonicalizeHandlesInNewScope just reads some values off existing > handles, and creates new handles for them in the current block (whatever it > is), so there's not really any difficult magic there beyond normal handle > allocation. I don't think trap handlers are viable - too hard to fix up state at arbitrary places in Clang-generated code. And anyway trap handlers only save virtual address space, and my proposition is that this is not a particularly scarce resource in 64 bit Chrome. > All this said, we are also looking into conservative stack scanning ( > https://crbug.com/41480448) which, if done process-wide, could eliminate > the need for HandleScopes entirely (you might have seen references to > DirectHandle around). So, it could be that any effort put into HandleScope > performance improvements is made obsolete by that project; right now > there's still some questions about the performance and false-positive rate, > but it's still an ongoing investigation and I'm hopeful. > If something like this proposal makes conservative GC less attractive by reducing the performance benefit of direct handles I would personally see that as a plus. A separate, but perhaps related proposal: If HandleScopeImplementer::GetSpareOrNewBlock() did a little alignment work, then the isolate could be stored at the start of the handle block and this would mean the isolate could be quickly accessed with a rounding operation + a deref from any Handle, even a Handle containing a Smi. This would eliminate most calls of Isolate::Current(), but I'm not sure what the performance of thread-locals is these days. Could be that it's not a win. This would also work with the linear handle backings, but it would not work with DirectHandle. > - Leszek > > On Friday, August 1, 2025 at 2:23:40 PM UTC+2 Erik Corry wrote: > > I'm not sure I'll have time to work on faster handles, but essentially the > idea is to use a large linear area as handle backing. On 64 bit machines, > virtual space is not very limited, so this makes more sense now than in the > early days of V8. By using the right mmap flags you can make it auto-grow > like a machine stack, so it only uses the high water memory. There would > be a guard page that segfaults for unbounded growth (see below). > > This would simplify the code generated for both handle creation and > HandleScope destruction by quite a bit. As a thought experiment I have > some handle-like code at https://godbolt.org/z/nWK7346TP Three lines are > commented out, so the initial state of the Godbolt example is what would > happen if we had linear handle backing. If you remove the comment > punctuation on those three lines you get something more like what we have > now, where a check of the limit is inlined, and there's an out-of-line call > to extend the backing with a new block. The generated code becomes much > worse. (The example uses Local<long>, obviously in reality the handles > would be Local<somepointertype>.) > > The reason the linear code is so much nicer is that the compiler has full > insight into what is going on. It can see that handle slots don't alias if > they come from different HandleScope::CreateHandle calls. Creating a Handle > becomes not much more than a pointer bump, and closing a scope is not much > more than resetting a pointer. > > There are some obvious issues with the idea and I have some ideas to fix > them: > > * I would make it so that the embedder can attach and detach handle scope > backings. This way you can only have one linear handle scope backing per > thread or perhaps CPU. Obviously this only makes sense back at the event > loop where the area is empty anyway. We can default to creating a linear > area in the Locker if there is not already one. The Unlocker (which I think > is not very heavily used) might need some modification too. > * Memory used is the high water mark of handle creation, which could be > wasteful. There are probably places (again, the event loop) where we can > release some of the higher pages with an madvise call. > * Some patterns use arbitrary numbers of handles: > * One of these is having a loop that doesn't have a handle scope at the > top of it. In this case if HandleScopes were much cheaper it would make > sense to put a handle scope at the top of the loop. Hitting the guard page > should show us where this needs fixing. > * Another is collections that use handles as elements. There are not > too many cases of this in the code base. They should be replaced with a > single handle that holds a reference to a FixedArray. If this is too slow > (on-heap allocation, write barriers, etc.) then a new collection should be > made where only the first N elements are put on-stack and use handle > backing. Above a certain size they turn into a reference to an on-heap > FixedArray. This is a bit like > https://chromium.googlesource.com/chromium/blink/+/refs/heads/main/Source/wtf/Vector.h > which has a templated inline capacity and uses allocation for bigger > arrays. You get the speed of on-stack operations for small vectors, and the > flexibility of arbitrarily sized on-heap vectors when needed. The off-heap > handles need to be reserved at collection creation time so we can push onto > the collection inside inner HandleScopes. > * LocalHeap needs its own linear area. This is not a big issue for us as > we mainly run V8 in single-threaded mode. For Chrome it means a linear area > per thread. > * PersistentHandlesScope would have to be done in a different way. > Currently several of the uses of PersistentHandlesScope are immediately > followed by ReopenAndCanonicalizeHandlesInNewScope. It seems like this > function could move the backings to a new area that was not in the linear > handle backing. This one is probably the hardest to solve unless the > number of handles in a PersistentHandlesScope can be bounded. > > Those are my ideas. Like I said, I don't have much time for this at the > moment, so it's on a back-burner. Happy to take a meeting to flesh out the > idea though. And perhaps there's a huge issue I have overlooked. > > On Fri, Aug 1, 2025 at 11:15 AM Leszek Swirski <[email protected]> > wrote: > > Hi Erik, > > Both of those docs are a decent enough reflection of what we ended up > doing. The purpose of PersistentHandles is to transfer ownership of a block > of handles between threads -- you can think of it as a more efficient, > single-owner block of global handles, which are expected to persist to the > main thread (hence the name) after a background thread compilation > completes. The PersistentHandlesScope is effectively a convenience scope to > allow us to create Handles inside that scope using normal factory and > Handle allocation methods, without needing to manually pass in which handle > backing they need to go into. > > I'd be interested to hear more about your plans to make HandleScopes more > efficient though, they're still a performance pain point. > > - Leszek > > On Wednesday, July 30, 2025 at 12:45:10 PM UTC+2 Erik Corry wrote: > > > https://docs.google.com/document/d/1qVFbzenbzOIGsVC3PSP6fe_RQD0R4YwwQKcZYLyVtQ4/edit > > This document from 2020 has no comments and no LGTMs, but it was > referenced from commits. Were the proposals accepted in this approximate > form, or is it a proposal that didn't get implemented in this form? > > A different document, "Handles and Local Heaps" > https://docs.google.com/document/d/17yKs-6apE2rGEag7tDsoyeRxg99c1dXyXQ2MfHe65tY/edit?tab=t.0 > is from the same period and has more engagement. > > I'm looking into making handles and scopes more efficient (by having > linear backing areas with no limit checks), and the PersistentHandlesScope > complicates this quite a lot, so I'm trying to understand the issues it > solves. > > -- > Erik > > -- > -- > v8-dev mailing list > [email protected] > http://groups.google.com/group/v8-dev > --- > You received this message because you are subscribed to the Google Groups > "v8-dev" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to [email protected]. > To view this discussion visit > https://groups.google.com/d/msgid/v8-dev/05027c6a-59ae-4755-8272-3517e1ba195dn%40googlegroups.com > <https://groups.google.com/d/msgid/v8-dev/05027c6a-59ae-4755-8272-3517e1ba195dn%40googlegroups.com?utm_medium=email&utm_source=footer> > . > > -- > -- > v8-dev mailing list > [email protected] > http://groups.google.com/group/v8-dev > --- > You received this message because you are subscribed to the Google Groups > "v8-dev" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to [email protected]. > To view this discussion visit > https://groups.google.com/d/msgid/v8-dev/05d4ae9b-c6ac-4fbd-8579-2bdef0c8853fn%40googlegroups.com > <https://groups.google.com/d/msgid/v8-dev/05d4ae9b-c6ac-4fbd-8579-2bdef0c8853fn%40googlegroups.com?utm_medium=email&utm_source=footer> > . > -- -- v8-dev mailing list [email protected] http://groups.google.com/group/v8-dev --- You received this message because you are subscribed to the Google Groups "v8-dev" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. To view this discussion visit https://groups.google.com/d/msgid/v8-dev/CAHZxHpiO_26zLZn3tRbzSUnuEOXV1-376yUjLVJ4ZV1043oiqw%40mail.gmail.com.
