Add performance enhancements for SkRegion::op When looking at pprof for a particularly gnarly set of consecutive ops (see new benchmark), a curious hotspot was distance_to_sentinel. We can look at the item just before the current run to see how long it is instead of using a loop to walk the entire way. This had a ~15% improvement. Before: ``` $ ./control_nanobench --match region curr/maxrss loops min median mean max stddev samples config bench 91/88 MB 497 16.2ns 16.2ns 16.2ns 16.2ns 0% █▁▁▁▁▁▁▁▁▃ nonrendering region_contains_sect 92/88 MB 1 1.37s 1.46s 1.5s 1.77s 8% ▁▃▄▄█▅▂▁▃▂ nonrendering region_checkerboard 91/88 MB 1 1.23ms 1.26ms 1.27ms 1.35ms 3% █▃▂▄▁▁▅▁▁▆ nonrendering region_setRects_10000_sorted 91/88 MB 1 301µs 301µs 302µs 307µs 1% █▂▂█▁▁▁▁▁▁ nonrendering region_setRects_2500_sorted 91/88 MB 4 59.1µs 59.1µs 60.1µs 69.1µs 5% █▁▁▁▁▁▁▁▁▁ nonrendering region_setRects_500_sorted 91/88 MB 42 5.63µs 5.65µs 5.74µs 6.59µs 5% ▁▁▁▁▁▁█▁▁▁ nonrendering region_setRects_50_sorted 91/88 MB 1 5.5ms 5.51ms 5.52ms 5.55ms 0% █▂▃█▂▁▄▁█▃ nonrendering region_setRects_10000 91/88 MB 1 1.37ms 1.39ms 1.39ms 1.42ms 1% ▇▁▄▇▂▃█▁▁▇ nonrendering region_setRects_2500 91/88 MB 2 266µs 266µs 268µs 285µs 2% ▂▂▁▁█▁▁▁▁▁ nonrendering region_setRects_500 91/88 MB 12 16.9µs 16.9µs 17.6µs 22.5µs 10% ▂▁▁▁█▁▁▁▁▂ nonrendering region_setRects_50 ``` After: ``` $ ./nanobench --match region curr/maxrss loops min median mean max stddev samples config bench 91/88 MB 394 16.2ns 16.2ns 16.2ns 16.5ns 1% █▂▁▂▁▂▂▂▂▂ nonrendering region_contains_sect 92/88 MB 1 1.07s 1.1s 1.12s 1.26s 7% ▃▂█▄█▁▁▁▁▂ nonrendering region_checkerboard 92/88 MB 1 1.08ms 1.09ms 1.11ms 1.16ms 2% █▂▆▂▁▆▂▂▁▅ nonrendering region_setRects_10000_sorted 91/88 MB 2 266µs 268µs 270µs 285µs 2% ▁▃▂▂▁█▁▃▁▂ nonrendering region_setRects_2500_sorted 91/88 MB 5 52µs 52.3µs 53.1µs 59.1µs 4% ▂▁▁▁█▃▁▁▁▂ nonrendering region_setRects_500_sorted 91/88 MB 49 5µs 5.01µs 5.03µs 5.19µs 1% ▂▁▁▁▁▁▁█▂▂ nonrendering region_setRects_50_sorted 91/88 MB 1 4.85ms 4.86ms 4.87ms 4.91ms 0% ▄▃▃▆▂▁▂█▁▁ nonrendering region_setRects_10000 91/88 MB 1 1.22ms 1.22ms 1.23ms 1.26ms 1% █▁▁█▂▁▂█▂▂ nonrendering region_setRects_2500 91/88 MB 2 238µs 239µs 246µs 282µs 6% ▆▁▁▁▁▁▁▁█▁ nonrendering region_setRects_500 91/88 MB 13 14.6µs 14.7µs 14.8µs 15.6µs 2% █▂▁▄▁▁▁▁▁▁ nonrendering region_setRects_50 ``` Bug: 511952524 Change-Id: I9c496ba621c61e62790cca62793de1fefee3a85e Reviewed-on: https://skia-review.googlesource.com/c/skia/+/1235636 Auto-Submit: Kaylee Lubick <kjlubick@google.com> Reviewed-by: Florin Malita <fmalita@google.com> Commit-Queue: Florin Malita <fmalita@google.com>
diff --git a/bench/RegionBench.cpp b/bench/RegionBench.cpp index 7c887d0..74c12038 100644 --- a/bench/RegionBench.cpp +++ b/bench/RegionBench.cpp
@@ -180,3 +180,33 @@ DEF_BENCH(return new RegionSetRectsBench(500, true);) DEF_BENCH(return new RegionSetRectsBench(2500, true);) DEF_BENCH(return new RegionSetRectsBench(10000, true);) + +class RegionCheckerboardBench : public Benchmark { +public: + RegionCheckerboardBench() {} + +protected: + const char* onGetName() override { return "region_checkerboard"; } + + bool isSuitableFor(Backend backend) override { + return backend == Backend::kNonRendering; + } + + void onDraw(int loops, SkCanvas* canvas) override { + for (int i = 0; i < loops; ++i) { + SkRegion rgn; + // Simulate a 300x200 grid of tiles where every other tile is missing. + // This creates 30,000 non-merging rectangles with high coordinate values + // similar to b/511952524. + for (int y = 0; y < 300; ++y) { + for (int x = 0; x < 200; ++x) { + if ((x + y) % 2 == 0) { + rgn.op(SkIRect::MakeXYWH(x * 256, y * 256, 256, 256), SkRegion::kUnion_Op); + } + } + } + } + } +}; + +DEF_BENCH(return new RegionCheckerboardBench();)
diff --git a/src/core/SkRegion.cpp b/src/core/SkRegion.cpp index effae18..59cf109 100644 --- a/src/core/SkRegion.cpp +++ b/src/core/SkRegion.cpp
@@ -798,10 +798,20 @@ } }; +/** + * Returns the distance to the next X-sentinel. + * + * The runs pointer points to the first interval [Left, Right]. + * The value at runs[-1] is the number of intervals for this scanline. + * This is safe because the caller has already advanced the runs pointer + * past the interval-count. + * + * The scanline layout is: [Bottom-Y] [IntervalCount] [L1, R1, L2, R2, ...] [X-Sentinel] + */ static int distance_to_sentinel(const SkRegionPriv::RunType* runs) { - const SkRegionPriv::RunType* ptr = runs; - while (*ptr != SkRegion_kRunTypeSentinel) { ptr += 2; } - return ptr - runs; + int intervals = runs[-1]; + SkASSERT_RELEASE(runs[intervals * 2] == SkRegion_kRunTypeSentinel); + return intervals * 2; } static int operate_on_span(const SkRegionPriv::RunType a_runs[],