Kompresja RAMu i SWAPu
Kategoria: Artykuły, etykiety: zram, swap, ram, performance
Dodany: 2014-02-19 02:28
(zmodyfikowany: 2014-02-19 02:30)
Przez: morfik
Wyświetleń: 7324
Jakiś czas temu, ktoś na forum napisał posta o kompresji danych w pamięci RAM, dodatkowo była też wzmianka o SWAPie. Jako, że wszyscy zachwalają ten sposób z kompresją danych, zwłaszcza na słabych maszynach, tych z małą ilością RAM, to postanowiłem sprawdzić na ile są wiarygodne te wierzenia w rzekomy lepszy performance, no jakby nie patrzeć ciężko znaleźć kogoś kto ma mniej RAMu ode mnie.
Na sam początek trochę słów o moim sprzęcie, a konkretnie o RAMie i procesorze, bo głównie od tych dwóch czynników będzie zależał wynik końcowy. Zgodnie z tym co pokazuje inxi, RAM i procek prezentuje się następująco:
# inxi -F -c 21
...
CPU: Dual core Intel Pentium D CPU (-MCP-) cache: 2048 KB flags: (lm nx sse sse2 sse3)
Clock Speeds: 1: 3000.00 MHz 2: 3000.00 MHz
...
Info: Processes: 227 Uptime: 10:24 Memory: 829.6/1001.2MB Client: Shell (bash) inxi: 1.9.17
Czyli mam dwurdzeniowy procesor i 1GiB pamięci RAM, czyli skompresowany RAM i SWAP powinny przypaść mi do gustu. Spróbujmy zatem wdrożyć tę technologię.
Kernel musi posiadać odpowiedni moduł, by w ogóle była możliwa zabawa ze ZRAMem:
# cat /boot/config-3.12-1-amd64 | grep -i zram
CONFIG_ZRAM=m
# CONFIG_ZRAM_DEBUG is not set
Bez tego ani rusz i jeśli nasz kernel nie posiada tego modułu, konieczna będzie rekompilacja kernela. Kolejna sprawa to załadowanie tego modułu na starcie systemu, zatem dopisujemy w /etc/modules poniższą linijkę:
zram num_devices=2
Trzeba tylko dobrać odpowiednią liczbę urządzeń, a zależy ona od ilości rdzeni procesora, mój procek ma 2 rdzenie, więc parametr num_devices przyjął wartość 2. Ale samo załadowanie modułu nic nam nie da, trzeba jeszcze stworzyć odpowiednie RAM dyski. Te RAM dyski to nic innego jak sformatowane na SWAP urządzenia zamontowane w RAMie. Mamy dwie opcje -- albo stworzyć skrypt startowy, albo zaprzęgnąć do tego celu udeva. Jeśli chcemy skrypt, to wygląda on mniej więcej tak:
#!/bin/bash
### BEGIN INIT INFO
# Provides: zram
# Required-Start: $syslog $remote_fs
# Required-Stop: $syslog $remote_fs
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: zram devices
# Description: zram devices
### END INIT INFO
# Author: Mikhail Morfikov morfikov[at]gmail.com
PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/sbin
SCRIPTNAME="zram"
zram_start () {
modprobe zram num_devices=2
SIZE="250"
echo $(($SIZE*1024*1024)) > /sys/block/zram0/disksize
echo $(($SIZE*1024*1024)) > /sys/block/zram1/disksize
mkswap -L zram0 /dev/zram0
mkswap -L zram1 /dev/zram1
swapon /dev/zram0 -p 70
swapon /dev/zram1 -p 70
}
zram_stop () {
swapoff /dev/zram0
swapoff /dev/zram1
echo 1 > /sys/block/zram0/reset
echo 1 > /sys/block/zram1/reset
modprobe -r zram
}
case "$1" in
start)
zram_start || exit 1
;;
stop)
zram_stop || exit 1
;;
force-reload|restart)
zram_stop && sleep 1
zram_start || exit 1
;;
*)
echo "Usage: $SCRIPTNAME {start|stop|restart}"
exit 1
;;
esac
exit 0
Można również skorzystać z udeva. To wymaga stworzenia pliku z regułami /etc/udev/rules.d/70-zram.rules o treści:
KERNEL=="zram0", SUBSYSTEM=="block", DRIVER=="", ACTION=="add", ATTR{disksize}=="0", ATTR{disksize}="150M", RUN+="/sbin/mkswap $env{DEVNAME}"
KERNEL=="zram1", SUBSYSTEM=="block", DRIVER=="", ACTION=="add", ATTR{disksize}=="0", ATTR{disksize}="150M", RUN+="/sbin/mkswap $env{DEVNAME}"
Kluczowe jest tutaj ustalenie wartości ATTR{disksize}, która to określa rozmiar jednego RAM dysku. Teoretycznie nie powinno się stosować wartości większej niż 50% dostępnego RAMu, czyli jeśli ja mam 1GiB, to maksymalna wartość jaką mógłbym użyć na ZRAM to 512MiB, po 256MiB na każdy z rdzeni procesora. Ale przy takiej wartości, komp trochę zaczynał mulić i zadziałało to zjadliwie przy wartości 300MiB, czyli 150MiB na każdy rdzeń.
Dodatkowo trzeba dodać do /etc/fstab dwa wpisy, które zamontują nam te urządzenia ZRAM przy starcie systemu:
/dev/zram0 swap swap defaults,pri=70 0 0
/dev/zram1 swap swap defaults,pri=70 0 0
UUID=3f9f24d7-86d1-4e21-93e9-f3c181d05cf0 swap swap defaults,pri=10 0 0
Powyżej jest również pokazany mój zwykły SWAP. Różni się on jedynie priorytetem -- trzeba je nadać tak by wyższy priorytet przypadł urządzeniom ZRAM, bo w takim przypadku dane pierw będą zrzucane do tych RAM dysków, a gdy zabranie w nich miejsca, dane polecą do tradycyjnego SWAPa.
Pozostała jeszcze kwestia skompresowanego SWAPu, tego zwykłego. Tutaj sprawa ma się dość prosto, trzeba dopisać jeden parametr do linijki kernela -- zswap.enabled=1 . Edytujemy zatem konfigurację extlinuxa (lub gruba), plik /boot/extlinux/extlinux.conf i dopisujemy ten parametr:
APPEND root=/dev/mapper/debian_crypt-root cgroup_enable=memory zswap.enabled=1 fbcon=scrollback:124k ro
Po resecie maszyny sprawdzamy czy aby wszytko zostało utworzone i zamontowane prawidłowo:
# swapon -s
Filename Type Size Used Priority
/dev/mapper/debian_crypt-swap partition 2097148 398368 10
/dev/zram0 partition 153596 153480 70
/dev/zram1 partition 153596 153500 70
Jak widać powyżej, mam zapchane oba urządzenia ZRAM, łącznie prawie 700MiB siedzi w tych SWAPach. Samo zachowanie się urządzeń ZRAM można monitorować przez pliki w katalogu /sys/block/zram0 oraz /sys/block/zram1 . Dodatkowo znalazłem skrypt, który pokazuje stopień kompresji danych:
#!/bin/sh -eu
TOTAL_DATA=0
TOTAL_COMP=0
HAS_ZRAM=
# Iterate through swap devices searching for compressed ones
while read NAME _; do
# Filter zram swaps and let's hope your ordinary swap doesn't have
# "zram" in its name :D
case $NAME in
*zram*) ;;
*) continue
esac
DIR=/sys`udevadm info --query=path --name=$NAME`
read DATA <$DIR/orig_data_size
read COMP <$DIR/mem_used_total
TOTAL_DATA=$((TOTAL_DATA + DATA))
TOTAL_COMP=$((TOTAL_COMP + COMP))
HAS_ZRAM=1
done </proc/swaps
# Extract physical memory in kibibytes and scale back to bytes
{
read _ MEM_KIB _
read _ FREE_KIB _
read _ BUF_KIB _
read _ CACHE_KIB _
} </proc/meminfo
/usr/bin/printf "\
Physical memory: %'17d bytes
Buffers and cache: %'17d bytes / %4.1f%% of total memory
Unallocated: %'17d bytes / %4.1f%% of total memory
" `echo "scale=6; 1024*$MEM_KIB; 1024*($BUF_KIB+$CACHE_KIB); 100*($BUF_KIB+$CACHE_KIB)/$MEM_KIB; 1024*$FREE_KIB; 100*$FREE_KIB/$MEM_KIB"|bc`
if test "$HAS_ZRAM"; then
/usr/bin/printf "\
Compressed: %'17d bytes / %4.1f%% of total memory
Uncompressed size: %'17d bytes / %.3f compression ratio
" `echo "scale=6; $TOTAL_COMP; 100*$TOTAL_COMP/$MEM_KIB/1024; scale=6; $TOTAL_DATA; $TOTAL_DATA/$TOTAL_COMP"|bc`
else
echo "Compressed: 0 bytes / zram not in use"
fi
Po wywołaniu tego skryptu dostałem poniższy wynik:
# ./zraminfo.sh
Physical memory: 1,049,821,184 bytes
Buffers and cache: 116,097,024 bytes / 11.1% of total memory
Unallocated: 81,043,456 bytes / 7.7% of total memory
Compressed: 121,118,720 bytes / 11.5% of total memory
Uncompressed size: 346,472,448 bytes / 2.861 compression ratio
Jeśli dobrze odczytuję te wartości, to kompresja jest na poziomie prawie 300% tylko co nam po tych 300% skoro większość danych to cache? Przynajmniej tak wynika z przeprowadzonego przeze mnie testu (dostępny pod tym linkiem). Ten cache zamiast rezydować sobie normalnie w RAMie, gdy ten nie jest używany, zapycha urządzenia co po paru minutach sprawia, że z tego 1 GiB ramu robi się 700, bo ten cache nie jest uwalniany...
Póki co nie udało mi się znaleźć informacji na temat tego czy takie zachowanie jest normalne czy też nie, bo jakby nie patrzeć wyrzucanie danych z RAMu na dysk by zrobić miejsce dla cache niezbyt pomaga słabym maszynom. Niemniej jednak, warto wiedzieć, że tego typu mechanizm jak ZRAM istnieje, a mi może kiedyś uda się rozwiązać tę zagadkę.