3

Imagine a scenario where 2GiB is swapped out to zram and compresses down to 1GiB.

Once the memory pressure is alleviated and the 2GiB gradually become unswapped, does Linux free the 1GiB of pages that were used to store the compressed zram pages?

If so, does it defragment existing pages?
There must be multiple pages in a compressed page, what happens when all but one are unswapped? Do all pages stay in memory until that last page is freed aswell?

Atemu
  • 584
  • 4
  • 14

3 Answers3

4

I would say no by default, but it can be done so.

$ lsblk --discard /dev/zram0 
NAME  DISC-ALN DISC-GRAN DISC-MAX DISC-ZERO
zram0        0        4K       2T         0

This means zram0 is a discard-capable device.

From man swapon:

-d, --discard[=policy]

Enable swap discards, if the swap backing device supports the discard or trim operation.
[...]
The /etc/fstab mount options discard, discard=once, or discard=pages may also be used to enable discard flags.

One just has to figure out on one's specific Linux distribution where the swapon command or equivalent is done, and add accordingly either the swapon --discard option or the mount discard option at this place (or at least the =pages variant) .

A.B
  • 31,762
  • 2
  • 62
  • 101
2

Short answer: Yes, zram backing pages are automatically freed.

After checking by experiment (kernel 5.10.105), it seems that unused zram storage is automatically freed, even when the zram device is mounted without discard.

Summary: The script below runs a process that allocates a big chunk of memory.
zram usage (checked via zramctl) initially increases, and then goes back to baseline after stopping the process and evicting swapped pages.

# * I've run this on a freshly booted VM *

# zram is mounted with nodiscard to exclude any effects of
# `discard`.
sudo grep zram /etc/fstab
# /dev/zram0 none swap nodiscard,pri=5

# We have ~6 GiB of RAM
grep -i memtotal /proc/meminfo
# MemTotal:        6386852 kB

# Show zram usage.
# `DATA` is the total amount of uncompressed data currently stored in zram.
zramctl
# NAME       ALGORITHM DISKSIZE DATA COMPR TOTAL STREAMS MOUNTPOINT
# /dev/zram0 zstd          3.1G   4K   58B    4K       4 [SWAP]

# Start a process that allocates 10 GiB of RAM
stress-ng -- --vm-bytes $((10*1024**3)) --vm-keep --vm 1 &

# *Wait some time for the stress test command to be swapped out*

# zram usage has gone up from 4 KiB to 3.1 GiB
zramctl
# NAME       ALGORITHM DISKSIZE  DATA COMPR TOTAL STREAMS MOUNTPOINT
# /dev/zram0 zstd          3.1G  3.1G  1.1G  1.2G       4 [SWAP]

# Stop stress test
kill %1

# zram usage decreased from 3.1 GiB to 0.3 GiB
zramctl
# NAME       ALGORITHM DISKSIZE   DATA COMPR TOTAL STREAMS MOUNTPOINT
# /dev/zram0 zstd          3.1G 336.8M 48.6M 57.6M       4 [SWAP]

# Read the first byte of all memory pages of all processes.
# This evicts all non-kernel swapped pages without using `swapoff`, which might
# reset the zram device.
sudo ./read_all_mem_pages.rb

# Now zram usage is almost back to zero
zramctl
# NAME       ALGORITHM DISKSIZE  DATA COMPR TOTAL STREAMS MOUNTPOINT
# /dev/zram0 zstd          3.1G 18.4M  3.5M  5.9M       4 [SWAP]

Source of read_all_mem_pages.rb:

#!/usr/bin/env ruby

def access_all_pages(pid)
  name = File.basename(File.readlink("/proc/#{pid}/exe")) rescue return
  puts "#{pid} (#{name})"
  File.open("/proc/#{pid}/mem", 'r') do |mem|
    for_each_mem_page(pid) do |page_address|
      mem.seek(page_address)
      mem.read(1) rescue nil
    end
  end
end

def for_each_mem_page(pid)
  File.foreach("/proc/#{pid}/maps") do |line|
    fields = line.split
    range, dest = fields[0], fields[-1]
    next if dest == "[vsyscall]"
    start, end_ = range.split('-').map { |x| x.to_i(16) }
    address = start
    while address < end_
      yield address
      address += 4096
    end
  end
end

pids = Dir.children('/proc').grep(/^\d+$/).map(&:to_i)
pids.each { |pid| access_all_pages(pid) }
ens
  • 212
  • 2
  • 10
0

I believe the memory consumed by zRam is never released. See

sudo zramctl --output-all
NAME       DISKSIZE  DATA COMPR ALGORITHM STREAMS ZERO-PAGES TOTAL MEM-LIMIT MEM-USED MIGRATED MOUNTPOINT
/dev/zram0     5,4G 56,6M 26,5M lz4             2        697 28,2M        0B    28,2M       0B [SWAP]

What matters is not 'TOTAL' which increases and decreases but 'MEM-USED' which as far as I tested never goes down. (I have to restart zramswap.service to get it back to zero). You can only get 'MEM-USED' field from zramctl with the '--output-all' switch.