I wrote up a testcase to stress-test the fixed overhead of a style traversal (as opposed to the actual styling work that happens per-element). It does the following in a loop: (1) Creates an element that matches only a rule. (2) Inserts it into the DOM (3) Forces a style flush (4) Removes the element from the DOM Stylo generally performs very badly on this testcase even in sequential mode (STYLO_THREADS=1) because of high fixed overhead, which is something that I'm looking into separately. But stylo performs about twice as bad when STYLO_THREADS=1 is omitted (i.e. with the parallel thread pool), presumably due to high context switching cost. While looking at this, I noticed that the cumulative sample times in the profiler don't add up, which might be a bug in the profiler. At the very least, it's something I'd like to understand better, especially if it leads us to ways to reduce our context switching overhead. STR on a MBP: (1) Set the profiler intervals to 0.2ms, buffer size to 90megs. (2) Add ",StyleThread" to the list of profiled threads. (3) Load the testcase under the profiler (4) Filter for Servo_TraverseSubtree, and trim the timeline to that interval. On my machine, I see a ~700ms interval with ~350ms in Servo_TraverseSubtree, with about ~250ms in LockLatch::wait, waiting on the thread pool. (5) Look at each of the worker threads, and sum up the time spent in ::execute on each one during that interval. On my machine, this is about ~180ms. So there's about 70ms going missing somehow.
Julian has been digging into the rayon stuff, and is familiar with the profiler, so this might make sense for him to look at. On the other hand, this is probably less urgent than the other stylo work he's been doing. dmajor was also looking to help out on stylo stuff, but presumably isn't as familiar with rayon as jseward.
Oh, and the key point I forgot to mention was that, when profiling with Instruments, the numbers _do_ add up (at IIRC). Would be good to double-check this when reproducing before diving in too deep.