Set MMAP_THRESHOLD to a fixed value (128K)
authorDietmar Maurer <dietmar@proxmox.com>
Wed, 26 Jan 2022 06:10:59 +0000 (07:10 +0100)
committerThomas Lamprecht <t.lamprecht@proxmox.com>
Wed, 26 Jan 2022 13:10:54 +0000 (14:10 +0100)
glibc's malloc has a misguided heuristic to detect transient allocations that
will just result in allocation sizes below 32 MiB never using mmap.

That it turn means that those relatively big allocations are on the heap where
cleanup and returning memory to the OS is harder to do and easier to be blocked
by long living, small allocations at the top (end) of the heap.

Observing the malloc size distribution in a file-level backup run:

@size:
[0]                   14 |                                                    |
[1]                25214 |@@@@@                                               |
[2, 4)              9090 |@                                                   |
[4, 8)             12987 |@@                                                  |
[8, 16)            93453 |@@@@@@@@@@@@@@@@@@@@                                |
[16, 32)           30255 |@@@@@@                                              |
[32, 64)          237445 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
[64, 128)          32692 |@@@@@@@                                             |
[128, 256)         22296 |@@@@                                                |
[256, 512)         16177 |@@@                                                 |
[512, 1K)           5139 |@                                                   |
[1K, 2K)            3352 |                                                    |
[2K, 4K)             214 |                                                    |
[4K, 8K)            1568 |                                                    |
[8K, 16K)             95 |                                                    |
[16K, 32K)          3457 |                                                    |
[32K, 64K)          3175 |                                                    |
[64K, 128K)          161 |                                                    |
[128K, 256K)         453 |                                                    |
[256K, 512K)          93 |                                                    |
[512K, 1M)            74 |                                                    |
[1M, 2M)             774 |                                                    |
[2M, 4M)             319 |                                                    |
[4M, 8M)             700 |                                                    |
[8M, 16M)             93 |                                                    |
[16M, 32M)            18 |                                                    |

We see that all allocations will be on the heap, and that while most
allocations are small, the relatively few big ones will still make up most of
the RSS and if blocked from being released back to the OS result in much higher
peak and average usage for the program than actually required.

Avoiding the "dynamic" mmap-threshold increasement algorithm and fixing it at
the original default of 128 KiB reduces RSS size by factor 10-20 when running
backups. As with memory mappings other mappings or the heap can never block
freeing the memory fully back to the OS.

But, the drawback of using mmap is more wasted space for unaligned or small
allocation sizes, and the fact that the kernel allegedly zeros out the data
before giving it to user space. The former doesn't really matter for us when
using it only for allocations bigger than 128 KiB, and the latter is a
trade-off, using 10 to 20 times less memory brings its own performance
improvement possibilities for the whole system after all ;-)

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
 [ Thomas: added to comment & commit message + extra-empty-line fixes ]
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
pbs-tools/src/lib.rs
proxmox-backup-client/src/main.rs
proxmox-restore-daemon/src/main.rs
src/bin/proxmox-backup-api.rs
src/bin/proxmox-backup-proxy.rs

index 053b3c0ca1469ab92b06d230e26f9a2aa8fa9958..939ba7e6b16bba0eb6ea03f31b2bae3e0ebdf4a4 100644 (file)
@@ -8,3 +8,22 @@ pub mod sha;
 pub mod ticket;
 
 pub mod async_lru_cache;
+
+/// Set MMAP_THRESHOLD to a fixed value (128 KiB)
+///
+/// This avoids the "dynamic" mmap-treshold logic from glibc's malloc, which seems misguided and
+/// effectively avoids using mmap for all allocations smaller than 32 MiB. Which, in combination
+/// with the allocation pattern from our/tokio's complex async machinery, resulted in very large
+/// RSS sizes due to defragmentation and long-living (smaller) allocation on top of the heap
+/// avoiding that the (big) now free'd allocations below couldn't get given back to the OS. This is
+/// not an issue with mmap'd memeory chunks, those can be given back at any time.
+///
+/// Lowering effective MMAP treshold to 128 KiB allows freeing up memory to the OS better and with
+/// lower latency, which reduces the peak *and* average RSS size by an order of magnitude when
+/// running backup jobs. We measured a reduction by a factor of 10-20 in experiments and see much
+/// less erratic behavior in the overall's runtime RSS size.
+pub fn setup_libc_malloc_opts() {
+    unsafe {
+        libc::mallopt(libc::M_MMAP_THRESHOLD, 4096*32);
+    }
+}
index 5068263728905698f8882647d481b719e1206e3a..406f2d180412336ff1075a02e738a441bb1bee41 100644 (file)
@@ -1464,6 +1464,7 @@ impl ReadAt for BufferedDynamicReadAt {
 }
 
 fn main() {
+    pbs_tools::setup_libc_malloc_opts();
 
     let backup_cmd_def = CliCommand::new(&API_METHOD_CREATE_BACKUP)
         .arg_param(&["backupspec"])
index c2be942ba18e41711a5116c3c05071d65a3ec7c1..47790a7db86c814d2d22f2a7c11e4891e5c81e80 100644 (file)
@@ -38,6 +38,8 @@ lazy_static! {
 
 /// This is expected to be run by 'proxmox-file-restore' within a mini-VM
 fn main() -> Result<(), Error> {
+    pbs_tools::setup_libc_malloc_opts();
+
     if !Path::new(VM_DETECT_FILE).exists() {
         bail!(
             "This binary is not supposed to be run manually, use 'proxmox-file-restore' instead."
index e6fc5f23e107e705b6a1056b9a93579e4515ca5d..ee037a3bb30574df163fdd9310d9ad4845bd1611 100644 (file)
@@ -19,6 +19,8 @@ use proxmox_backup::auth_helpers::*;
 use proxmox_backup::config;
 
 fn main() {
+    pbs_tools::setup_libc_malloc_opts();
+
     proxmox_backup::tools::setup_safe_path_env();
 
     if let Err(err) = proxmox_async::runtime::main(run()) {
index 523966cff57235a1bf31c9f3123169e2e2497575..30b730ef983db6a392807d88f41f38df880b945c 100644 (file)
@@ -73,6 +73,8 @@ use proxmox_backup::server::do_verification_job;
 use proxmox_backup::server::do_prune_job;
 
 fn main() -> Result<(), Error> {
+    pbs_tools::setup_libc_malloc_opts();
+
     proxmox_backup::tools::setup_safe_path_env();
 
     let backup_uid = pbs_config::backup_user()?.uid;