Closed Bug 1898647 Opened 2 years ago Closed 25 days ago

Seperate the cache of unallocated memory into: dirty, clean, unmapped.

Categories

(Core :: Memory Allocator, enhancement)

enhancement

Tracking

()

RESOLVED FIXED
153 Branch
Tracking Status
firefox153 --- fixed

People

(Reporter: pbone, Assigned: pbone)

References

(Blocks 2 open bugs)

Details

(Whiteboard: [sp3])

Attachments

(2 files)

jemalloc uses a single red-black tree to look for unallocated pages, they're sorted by size so it can to "best fit" allocation. But not by "status". So if in the tree there is a unmapped block of 4 pages before a dirty block of 4 pages. and the allocator is looking for at least two pages, it may map in the new memory (increasing RSS) rather than use 2 dirty pages (decreasing overheads). This is especially true if for example there was a block of 6 dirty pages which will always be sorted after 4 unmapped pages.

We should modify jemalloc to create multiple free lists for: dirty, clean, unmapped and check them in sequence. This could improve Firefox's memory footprint and performance.

Wouldn't changing ArenaAvailTreeTrait to account for the status have roughly the same effect?

(In reply to Mike Hommey [:glandium] from comment #1)

Wouldn't changing ArenaAvailTreeTrait to account for the status have roughly the same effect?

it depends. it's work for the cases where there's a dirty block of 4 pages and an unmapped block of 4 pages.

When there's an unmapped block of 4 pages and a dirty block of 5 pages though I think separate lists would be good. In that case changing the tree trait won't work and I think we'd be happy with that fragmentation rather than mapping in new memory when dirty memory is already there.

What I don't know is if there's a block of 128 pages of dirty memory and we only want to allocate 1 page. Is fragmenting that block okay (we already do that if there's no smaller blocks) Especially since a future allocations are more likely to request a more even 128 pages rather than 127.

Well, that kind of hits the crux of the issue, which is, is it really more beneficial to use that dirty block of 5 pages or that unmapped block of 4 pages, and how often are you likely to have to make this choice? And as you point out, even if it is beneficial, is there a limit to the size of that dirty block over which you get diminished returns.

Agreed. I think the first step here would be to do some kind of logging/test to see how often different things happen. then maybe to estimate or test for the costs.

Whiteboard: [sp3]
Blocks: 2024836

I'm going to separate this bug into two. The first part (this one) will prefer dirty, then fresh/other then decommitted memory within a size. That is it won't prefer allocating 5 dirty pages rather than 4 decommitted pages.

The 2nd part (Bug 2024836) can take this further to prefer dirty memory at the cost of fragmentation.

Depends on: 1932624

We can prefer to allocate from dirty runs by inserting dirty runs at the
head of a size class' list and other runs at the end.

Further refine the order runs are returned by inserting fresh and other
runs in the middle of a size class' list leaving only decommitted and
madvised runs to be inserted at the end.

(In reply to Paul Bone [:pbone] from comment #8)

Here are the pref results: https://perf.compare/compare-results?baseRev=52da6d23edc91dc72046025d9ca19b81533409f5&baseRepo=try&newRev=d1e4ac0cb8ae9683952459ef2cf84373fbd2f80b&newRepo=try&framework=13 This improves on Bug 1932624 by allocating dirty pages first and decommitted pages last.

Interesting. This seems to help old macOS (which did regress in the bug 1932624 runs, so one would need to look at the sum), but slightly regresses Android. Not sure if there can be some interference with OS level optimizations (maybe in some cases the OS maps fresh memory on recently used memory already?). Overall I'd probably think we are less interested in old macOS wrt Android.

Do we have a try run with only pt1 of this stack?

Flags: needinfo?(pbone)

(In reply to Jens Stutte [:jstutte] from comment #10)

Do we have a try run with only pt1 of this stack?

https://treeherder.mozilla.org/jobs?repo=try&revision=a4d65acaaf24a1d44a98c6301b5c2aaf481f32ee

https://perf.compare/compare-results?baseRev=52da6d23edc91dc72046025d9ca19b81533409f5&baseRepo=try&newRev=a4d65acaaf24a1d44a98c6301b5c2aaf481f32ee&newRepo=try&framework=13

it's still processing but it doesn't seem wonderful. So it chooses LIFO for dirty runs and FIFO for everything else. We could instead try LIFO for everything else and FIFO for decommitted runs.

Flags: needinfo?(pbone)
Assignee: nobody → pbone
Attachment #9555452 - Attachment description: WIP: Bug 1898647 - p1. Prefer to allocate dirty runs → Bug 1898647 - p1. Prefer to allocate dirty runs r=glandium
Status: NEW → ASSIGNED
Attachment #9555453 - Attachment description: WIP: Bug 1898647 - p2. Insert fresh runs in the middle of a size class list → Bug 1898647 - p2. Insert fresh runs in the middle of a size class list r=glandium
Status: ASSIGNED → RESOLVED
Closed: 25 days ago
Resolution: --- → FIXED
Target Milestone: --- → 153 Branch
QA Whiteboard: [qa-triage-done-c154/b153]
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: