Closed Bug 664642 Opened 11 years ago Closed 11 years ago

Freeing decoded images doesn't reduce memory usage on Linux

Categories

(Core :: Graphics: ImageLib, defect)

All
Linux
defect
Not set
normal

Tracking

()

RESOLVED INVALID

People

(Reporter: justin.lebar+bug, Unassigned)

Details

(Whiteboard: [MemShrink])

Attachments

(5 files, 1 obsolete file)

This may be a bug with the allocator and not imagelib.

 * Set image.mem.discard_timeout_ms to 3000
 * Load the attached page (full-size pictures from [1]).  Thank Wikimedia for their bandwidth.
 * Load about:memory without knocking the images out of memory (e.g. with the attached page focused, type "about:memory" into the location bar and press alt+enter to open in a new tab).
 * Switch to about:memory tab and copy-paste the stats somewhere.
 * Wait a few seconds for the images to be discarded, then refresh about:memory.
 * Switch back to images tab and make sure that images were discarded before you refreshed.
 * Switch back to about:memory tab and compare results from after discard to before discard.

My about:memory from before and after the discard is included below.  Notice that although imagelib does free the relevant memory, it's never returned to the OS.

I understand that jemalloc may hold on to free'd memory, and that it's now available for other uses.  But if ever there's a time to return memory to the OS, it's right after we free 380mb of image data.  The perception that Firefox grabs on to memory and never lets go doesn't seem so far off here.

The numbers in about:memory don't add up, and I'm not sure how to interpret that.  For instance, in the before discard case, it claims 460m of image data plus 220m of JS equals 510m of explicit allocations.  The decoded image data has size 390m, but after discard only 170m is "heap-unclassified".  Where'd the rest of the space go?  Maybe njn can shed some light on this.

***** Before discard:

508.56 MB (100.0%) -- explicit
├──460.65 MB (90.58%) -- images
│  ├──460.55 MB (90.56%) -- content
│  │  ├──460.55 MB (90.56%) -- used
│  │  │  ├──388.87 MB (76.46%) -- uncompressed
│  │  │  └───71.68 MB (14.09%) -- raw
│  │  └────0.00 MB (00.00%) -- (1 omitted)
│  └────0.11 MB (00.02%) -- (1 omitted)
├──217.99 MB (42.86%) -- js
│  ├──149.00 MB (29.30%) -- gc-heap
│  ├───33.28 MB (06.54%) -- mjit-code
│  ├───23.22 MB (04.57%) -- tjit-data
│  │   ├──17.50 MB (03.44%) -- allocators-reserve
│  │   └───5.72 MB (01.12%) -- allocators-main
│  ├────8.00 MB (01.57%) -- stack
│  ├────2.99 MB (00.59%) -- mjit-data
│  └────1.50 MB (00.29%) -- (1 omitted)
├───34.95 MB (06.87%) -- storage
│   └──34.95 MB (06.87%) -- sqlite
│      ├──29.56 MB (05.81%) -- places.sqlite
│      │  ├──29.10 MB (05.72%) -- cache-used
│      │  └───0.46 MB (00.09%) -- (2 omitted)
│      └───5.38 MB (01.06%) -- (13 omitted)
├────6.23 MB (01.23%) -- layout
│    ├──6.23 MB (01.22%) -- all
│    └──0.00 MB (00.00%) -- (1 omitted)
└──-211.26 MB (-41.54%) -- (1 omitted)

Other Measurements
1,350.72 MB -- vsize
  703.00 MB -- heap-committed
  562.39 MB -- resident
  465.78 MB -- heap-used
  237.22 MB -- heap-unused
    3.30 MB -- heap-dirty
    1.44 MB -- canvas-2d-pixel-bytes
    0.05 MB -- gfx-surface-image

**** After discard:

502.15 MB (100.0%) -- explicit
├──215.50 MB (42.91%) -- js
│  ├──149.00 MB (29.67%) -- gc-heap
│  ├───31.78 MB (06.33%) -- mjit-code
│  ├───22.62 MB (04.51%) -- tjit-data
│  │   ├──17.08 MB (03.40%) -- allocators-reserve
│  │   └───5.55 MB (01.10%) -- allocators-main
│  ├────8.00 MB (01.59%) -- stack
│  ├────2.72 MB (00.54%) -- mjit-data
│  └────1.38 MB (00.27%) -- (1 omitted)
├──173.62 MB (34.58%) -- heap-unclassified
├───71.86 MB (14.31%) -- images
│   ├──71.75 MB (14.29%) -- content
│   │  ├──71.75 MB (14.29%) -- used
│   │  │  ├──71.68 MB (14.27%) -- raw
│   │  │  └───0.08 MB (00.01%) -- (1 omitted)
│   │  └───0.00 MB (00.00%) -- (1 omitted)
│   └───0.11 MB (00.02%) -- (1 omitted)
├───34.95 MB (06.96%) -- storage
│   └──34.95 MB (06.96%) -- sqlite
│      ├──29.56 MB (05.89%) -- places.sqlite
│      │  ├──29.10 MB (05.79%) -- cache-used
│      │  └───0.46 MB (00.09%) -- (2 omitted)
│      └───5.38 MB (01.07%) -- (13 omitted)
└────6.23 MB (01.24%) -- layout
     ├──6.23 MB (01.24%) -- all
     └──0.00 MB (00.00%) -- (1 omitted)

Other Measurements
1,349.09 MB -- vsize
  703.00 MB -- heap-committed
  557.51 MB -- resident
  461.00 MB -- heap-used
  242.00 MB -- heap-unused
    2.13 MB -- heap-dirty
    1.20 MB -- canvas-2d-pixel-bytes
    0.05 MB -- gfx-surface-image

[1] http://commons.wikimedia.org/wiki/Commons:Picture_of_the_Year/2009/Finalists
I'll note I just saw a checkin to fix double-counting of some image allocations for about:memory, so that may be part of it
(In reply to comment #0)
> └──-211.26 MB (-41.54%) -- (1 omitted)

Note that this negative value is weird and somehow biases the total value before discard.
Note that to debug these kind of things, it would be useful to have a view of jemalloc arenas use.
(In reply to comment #0)
> 
> I understand that jemalloc may hold on to free'd memory, and that it's now
> available for other uses.  But if ever there's a time to return memory to
> the OS, it's right after we free 380mb of image data.  The perception that
> Firefox grabs on to memory and never lets go doesn't seem so far off here.

Depends what people are measuring.  This one doesn't worry me that much.  My philosophy is to assume jemalloc knows what it's doing.  It's paging that matters the most, and that memory won't cause paging.  (Address space exhaustion on 32-bit is more of an issue, admittedly.)


> The numbers in about:memory don't add up,
>
> └──-211.26 MB (-41.54%) -- (1 omitted)

The key here is the negative number, which is easy to miss.  Is your repo more than 10 days old?  If so, it is probably bug 658814.  If you can paste the verbose about:memory (click the "more verbose" link at the bottom, or go straight to "about:memory?verbose") it'll be clearer what's happening.
Whiteboard: [MemShrink]
> It's paging that matters the most, and that memory won't cause paging.

If someone else needs the memory, the OS will have to page us out.  It probably won't pick the right pages to kick out, so when we're refocused, we may have to page in before we're responsive.  And even if the OS did choose to page out these inactive pages, as soon as the allocator reuses them for something else, we have to page them in!

This is happening on the 2011-06-15 nightly.  I'll paste verbose stats in a moment.
I constantly run into memory space exhaustion on Win32 (I keep a silly number of tabs open - bad habit) and frequently crash at that point (4.0.1).  I run a clone of that profile on a recent nightly (on unix) and things seem better, but I haven't pushed it yet.  When we get close to exhausted, minor scrolls and other operations will often invoke 10-200 seconds of max system CPU use with a frozen program.
Verbose output.

***** Before discard

Main Process

Explicit Allocations
512,956,810 B (100.0%) -- explicit
├──215,790,800 B (42.07%) -- js
│  ├──153,092,096 B (29.85%) -- gc-heap
│  ├───30,699,520 B (05.98%) -- mjit-code
│  ├───19,239,168 B (03.75%) -- tjit-data
│  │   ├──14,208,000 B (02.77%) -- allocators-reserve
│  │   └───5,031,168 B (00.98%) -- allocators-main
│  ├────8,388,608 B (01.64%) -- stack
│  ├────3,191,760 B (00.62%) -- mjit-data
│  └────1,179,648 B (00.23%) -- tjit-code
├──177,975,496 B (34.70%) -- heap-unclassified
├───76,721,509 B (14.96%) -- images
│   ├──75,775,493 B (14.77%) -- content
│   │  ├──75,771,958 B (14.77%) -- used
│   │  │  ├──75,688,382 B (14.76%) -- raw
│   │  │  └──────83,576 B (00.02%) -- uncompressed
│   │  └───────3,535 B (00.00%) -- unused
│   │          ├──3,535 B (00.00%) -- raw
│   │          └──────0 B (00.00%) -- uncompressed
│   └─────946,016 B (00.18%) -- chrome
│         ├──944,992 B (00.18%) -- used
│         │  ├──944,992 B (00.18%) -- uncompressed
│         │  └────────0 B (00.00%) -- raw
│         └────1,024 B (00.00%) -- unused
│              ├──1,024 B (00.00%) -- uncompressed
│              └──────0 B (00.00%) -- raw
├───36,763,712 B (07.17%) -- storage
│   └──36,763,712 B (07.17%) -- sqlite
│      ├──31,093,552 B (06.06%) -- places.sqlite
│      │  ├──30,608,584 B (05.97%) -- cache-used
│      │  ├─────406,552 B (00.08%) -- stmt-used
│      │  └──────78,416 B (00.02%) -- schema-used
│      ├───1,049,224 B (00.20%) -- other
│      ├─────940,680 B (00.18%) -- cookies.sqlite
│      │     ├──924,968 B (00.18%) -- cache-used
│      │     ├───12,752 B (00.00%) -- stmt-used
│      │     └────2,960 B (00.00%) -- schema-used
│      ├─────633,480 B (00.12%) -- webappsstore.sqlite
│      │     ├──562,144 B (00.11%) -- cache-used
│      │     ├───64,984 B (00.01%) -- stmt-used
│      │     └────6,352 B (00.00%) -- schema-used
│      ├─────558,008 B (00.11%) -- formhistory.sqlite
│      │     ├──396,728 B (00.08%) -- cache-used
│      │     ├──158,616 B (00.03%) -- stmt-used
│      │     └────2,664 B (00.00%) -- schema-used
│      ├─────466,912 B (00.09%) -- extensions.sqlite
│      │     ├──363,704 B (00.07%) -- cache-used
│      │     ├───92,040 B (00.02%) -- stmt-used
│      │     └───11,168 B (00.00%) -- schema-used
│      ├─────402,872 B (00.08%) -- urlclassifier3.sqlite
│      │     ├──297,688 B (00.06%) -- cache-used
│      │     ├──100,944 B (00.02%) -- stmt-used
│      │     └────4,240 B (00.00%) -- schema-used
│      ├─────381,360 B (00.07%) -- signons.sqlite
│      │     ├──330,680 B (00.06%) -- cache-used
│      │     ├───46,160 B (00.01%) -- stmt-used
│      │     └────4,520 B (00.00%) -- schema-used
│      ├─────341,368 B (00.07%) -- addons.sqlite
│      │     ├──264,648 B (00.05%) -- cache-used
│      │     ├───69,760 B (00.01%) -- stmt-used
│      │     └────6,960 B (00.00%) -- schema-used
│      ├─────305,632 B (00.06%) -- content-prefs.sqlite
│      │     ├──264,664 B (00.05%) -- cache-used
│      │     ├───36,920 B (00.01%) -- stmt-used
│      │     └────4,048 B (00.00%) -- schema-used
│      ├─────269,512 B (00.05%) -- chromeappsstore.sqlite
│      │     ├──198,976 B (00.04%) -- cache-used
│      │     ├───64,184 B (00.01%) -- stmt-used
│      │     └────6,352 B (00.00%) -- schema-used
│      ├─────109,168 B (00.02%) -- downloads.sqlite
│      │     ├───99,576 B (00.02%) -- cache-used
│      │     ├────6,768 B (00.00%) -- stmt-used
│      │     └────2,824 B (00.00%) -- schema-used
│      ├─────108,416 B (00.02%) -- permissions.sqlite
│      │     ├───99,584 B (00.02%) -- cache-used
│      │     ├────6,776 B (00.00%) -- stmt-used
│      │     └────2,056 B (00.00%) -- schema-used
│      └─────103,528 B (00.02%) -- search.sqlite
│            ├───99,568 B (00.02%) -- cache-used
│            ├────2,000 B (00.00%) -- schema-used
│            └────1,960 B (00.00%) -- stmt-used
└────5,705,293 B (01.11%) -- layout
     ├──5,704,125 B (01.11%) -- all
     └──────1,168 B (00.00%) -- bidi

Other Measurements
1,413,173,248 B -- vsize
  735,051,776 B -- heap-committed
  574,558,208 B -- resident
  472,689,034 B -- heap-used
  262,361,876 B -- heap-unused
    3,612,672 B -- heap-dirty
    1,255,840 B -- canvas-2d-pixel-bytes
       83,696 B -- gfx-surface-image

***** After discard

515,309,784 B (100.0%) -- explicit
├──216,408,140 B (42.00%) -- js
│  ├──153,092,096 B (29.71%) -- gc-heap
│  ├───31,297,536 B (06.07%) -- mjit-code
│  ├───19,061,272 B (03.70%) -- tjit-data
│  │   ├──14,060,000 B (02.73%) -- allocators-reserve
│  │   └───5,001,272 B (00.97%) -- allocators-main
│  ├────8,388,608 B (01.63%) -- stack
│  ├────3,388,980 B (00.66%) -- mjit-data
│  └────1,179,648 B (00.23%) -- tjit-code
├──179,606,773 B (34.85%) -- heap-unclassified
├───76,732,350 B (14.89%) -- images
│   ├──75,787,358 B (14.71%) -- content
│   │  ├──75,787,358 B (14.71%) -- used
│   │  │  ├──75,688,382 B (14.69%) -- raw
│   │  │  └──────98,976 B (00.02%) -- uncompressed
│   │  └───────────0 B (00.00%) -- unused
│   │              ├──0 B (00.00%) -- raw
│   │              └──0 B (00.00%) -- uncompressed
│   └─────944,992 B (00.18%) -- chrome
│         ├──944,992 B (00.18%) -- used
│         │  ├──944,992 B (00.18%) -- uncompressed
│         │  └────────0 B (00.00%) -- raw
│         └────────0 B (00.00%) -- unused
│                  ├──0 B (00.00%) -- raw
│                  └──0 B (00.00%) -- uncompressed
├───36,796,728 B (07.14%) -- storage
│   └──36,796,728 B (07.14%) -- sqlite
│      ├──31,093,552 B (06.03%) -- places.sqlite
│      │  ├──30,608,584 B (05.94%) -- cache-used
│      │  ├─────406,552 B (00.08%) -- stmt-used
│      │  └──────78,416 B (00.02%) -- schema-used
│      ├───1,049,224 B (00.20%) -- other
│      ├─────940,680 B (00.18%) -- cookies.sqlite
│      │     ├──924,968 B (00.18%) -- cache-used
│      │     ├───12,752 B (00.00%) -- stmt-used
│      │     └────2,960 B (00.00%) -- schema-used
│      ├─────633,480 B (00.12%) -- webappsstore.sqlite
│      │     ├──562,144 B (00.11%) -- cache-used
│      │     ├───64,984 B (00.01%) -- stmt-used
│      │     └────6,352 B (00.00%) -- schema-used
│      ├─────558,008 B (00.11%) -- formhistory.sqlite
│      │     ├──396,728 B (00.08%) -- cache-used
│      │     ├──158,616 B (00.03%) -- stmt-used
│      │     └────2,664 B (00.00%) -- schema-used
│      ├─────466,912 B (00.09%) -- extensions.sqlite
│      │     ├──363,704 B (00.07%) -- cache-used
│      │     ├───92,040 B (00.02%) -- stmt-used
│      │     └───11,168 B (00.00%) -- schema-used
│      ├─────435,888 B (00.08%) -- urlclassifier3.sqlite
│      │     ├──330,704 B (00.06%) -- cache-used
│      │     ├──100,944 B (00.02%) -- stmt-used
│      │     └────4,240 B (00.00%) -- schema-used
│      ├─────381,360 B (00.07%) -- signons.sqlite
│      │     ├──330,680 B (00.06%) -- cache-used
│      │     ├───46,160 B (00.01%) -- stmt-used
│      │     └────4,520 B (00.00%) -- schema-used
│      ├─────341,368 B (00.07%) -- addons.sqlite
│      │     ├──264,648 B (00.05%) -- cache-used
│      │     ├───69,760 B (00.01%) -- stmt-used
│      │     └────6,960 B (00.00%) -- schema-used
│      ├─────305,632 B (00.06%) -- content-prefs.sqlite
│      │     ├──264,664 B (00.05%) -- cache-used
│      │     ├───36,920 B (00.01%) -- stmt-used
│      │     └────4,048 B (00.00%) -- schema-used
│      ├─────269,512 B (00.05%) -- chromeappsstore.sqlite
│      │     ├──198,976 B (00.04%) -- cache-used
│      │     ├───64,184 B (00.01%) -- stmt-used
│      │     └────6,352 B (00.00%) -- schema-used
│      ├─────109,168 B (00.02%) -- downloads.sqlite
│      │     ├───99,576 B (00.02%) -- cache-used
│      │     ├────6,768 B (00.00%) -- stmt-used
│      │     └────2,824 B (00.00%) -- schema-used
│      ├─────108,416 B (00.02%) -- permissions.sqlite
│      │     ├───99,584 B (00.02%) -- cache-used
│      │     ├────6,776 B (00.00%) -- stmt-used
│      │     └────2,056 B (00.00%) -- schema-used
│      └─────103,528 B (00.02%) -- search.sqlite
│            ├───99,568 B (00.02%) -- cache-used
│            ├────2,000 B (00.00%) -- schema-used
│            └────1,960 B (00.00%) -- stmt-used
└────5,765,793 B (01.12%) -- layout
     ├──5,764,625 B (01.12%) -- all
     └──────1,168 B (00.00%) -- bidi

Other Measurements
1,413,779,456 B -- vsize
  735,051,776 B -- heap-committed
  576,462,848 B -- resident
  474,443,992 B -- heap-used
  260,606,918 B -- heap-unused
    4,014,080 B -- heap-dirty
    1,507,008 B -- canvas-2d-pixel-bytes
       83,696 B -- gfx-surface-image
So the memory is still entrained in imagelib? ...
Ack; I screwed up in comment 7.  Here are better results.
We could use MALLOC_STATS output from a case like this too.

je_malloc.c:3337 should be freeing the "large" (>=4K, < 1MB) allocations if it thinks the entire allocation is free - note that "large" chunks are ~1MB it appears, so parts may be shared by other users.  'Huge' allocations >=1MB should be going more directly back to be unmapped.

Note that image data is NOT isolated from general-usage heap/mmap data.  They're indirectly semi-isolated because they tend to be 'large' or 'huge', but large starts at 4k.

It would be interesting (though I probably wouldn't release it this way) to see what happens if the image allocators (gfxsurface(JPEG), PNG, GIF, etc) were temporarily switched to use a separate arena...
I'm going to switch this to Core :: jemalloc for now, because I'm quite sure that imagelib frees the memory it allocates. If that's wrong, please move this bug back.
Component: ImageLib → jemalloc
QA Contact: imagelib → jemalloc
Summary: Discarding images doesn't reduce memory usage → Freeing large chunks of malloc'ed memory (e.g. decoded images) doesn't reduce memory usage
Should be easy to figure out if jemalloc is the culprit ... is this reproducible on Mac?
Attached file Results on mac
Here are the mac results.  Resident size goes down from 862m to 670m (-192m).  about:memory says that the decoded images take up 388m, although if we're double-counting that, then the 192 would account for almost all the space.
I just tested on Windows, and it appears that we do release there too.

On IRC, khuey suggested that the problem might be [1], but stuart is skeptical.

[1] http://hg.mozilla.org/mozilla-central/file/079c171b6323/memory/jemalloc/jemalloc.c#l193
Hardware: x86_64 → All
Summary: Freeing large chunks of malloc'ed memory (e.g. decoded images) doesn't reduce memory usage → Freeing large chunks of malloc'ed memory (e.g. decoded images) doesn't reduce memory usage on Linux
I just built on Linux with #MALLOC_DECOMMIT defined, and it didn't fix the problem.
Moving this back to imagelib.  I wrote a testcase which allocates a large arraybuffer in JS and then frees it and makes sure that freeing reduces RSS. This test passes on Linux, indicating to me that the problem isn't with jemalloc.

I'll attach the testcase in a moment.
Component: jemalloc → ImageLib
QA Contact: jemalloc → imagelib
Summary: Freeing large chunks of malloc'ed memory (e.g. decoded images) doesn't reduce memory usage on Linux → Freeing decoded images doesn't reduce memory usage on Linux
For kicks, I also ran the testcase on Mac with jemalloc; it passes there, too.
If we believe the RSS numbers in this about:memory dump, the decoded image data is never stored in the process's memory at all.

This is corroborated by instrumenting malloc -- I see a 170mb block malloc'ed and almost immediately free'd, even while the image is still onscreen.  The free happens in imgFrame::Optimize.
That last attachment was an encoding fail; this one should work.
Attachment #540544 - Attachment is obsolete: true
Attachment #540547 - Attachment mime type: text/x-patch → text/plain
(In reply to comment #17)
> For kicks, I also ran the testcase on Mac with jemalloc; it passes there,
> too.

Thanks! Is that using the patch from bug 414946?
(In reply to comment #20)
> Thanks! Is that using the patch from bug 414946?

Yep!  Worked great.
I think this is INVALID, per comment 18.

On Linux, about:memory shouldn't be reporting that the decoded images are stored on the heap -- this is bug 664659.  Maybe we should also be reporting in about:memory how much space we're using inside the X server to store these images.  I'll file a bug on that.
Status: NEW → RESOLVED
Closed: 11 years ago
Resolution: --- → INVALID
Filed bug 665952 on reporting the memory we're using in X.
You need to log in before you can comment on or make changes to this bug.