Linuxがスワップファイルを使用する理由、パート2

仮想メモリサブシステムについての少しの「リッピング」の最初の部分、 mmapメカニズム、共有ライブラリ、およびキャッシュの接続は、実際に研究を続けることに抵抗できないほどの白熱した議論を引き起こしました。



したがって、今日は.. 。小さな実験室の仕事。スワップの有無にかかわらず、実際に作成、コンパイル、テストする小さなCプログラムの形式で。



プログラムは非常に単純なことを行います-それはメモリの大きなチャンクを要求し、それにアクセスし、そしてそれを積極的に操作します。ライブラリのロードに悩まされないようにするために、共有ライブラリをロードするときのシステムと同じように、メモリにマップされる大きなファイルを作成するだけです。



そして、このようなmmapファイルから読み取ることにより、この「ライブラリ」からのコードの呼び出しをエミュレートするだけです。



プログラムは複数の反復を行い、各反復で「コード」と大きなデータセグメントのセクションの1つに同時にアクセスします。



また、不要なコードを記述しないために、「コードセグメント」のサイズとRAMの合計サイズを決定する2つの定数を定義します。



  • MEM_GBYTES-テスト用のRAMのサイズ
  • LIB_GBYTES-「コード」サイズ


私たちが持っている「データ」の量は、物理メモリの量よりも少ないです。



  • DATA_GBYTES = MEM_GBYTES-2


「コード」と「データ」の合計量は、物理メモリの量よりもわずかに多くなります。



  • DATA_GBYTES + LIB_GBYTES = MEM_GBYTES + 1


ラップトップでのテストでは、MEM_GBYTES = 16を使用し、次の特性を取得しました。



  • MEM_GBYTES = 16
  • DATA_GBYTES = 14-「データ」が14GB、つまり「十分なメモリ」になることを意味します
  • スワップサイズ= 16GB


プログラムテキスト



#include <sys/mman.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
 
#define GB              1073741824l
 
#define MEM_SIZE        16
#define LIB_GBYTES      3
#define DATA_GBYTES     (MEM_SIZE - 2)
 
long random_read(char * code_ptr, char * data_ptr, size_t size) {
   long rbt = 0;
   for (unsigned long i=0 ; i<size ; i+=4096) {
       rbt += code_ptr[(8l * random() % size)] + data_ptr[i];
   }
   return rbt;
}
 
int main() {
   size_t libsize = LIB_GBYTES * GB;
   size_t datasize = DATA_GBYTES * GB;
   int fd;
   char * dataptr;
   char * libptr;
 
   srandom(256);
   if ((fd = open("library.bin", O_RDONLY)) < 0) {
       printf("Required library.bin of size %ld\n", libsize);
       return 1;
   }
 
   if ((libptr = mmap(NULL, libsize,
                     PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) {
       printf("Failed build libptr due %d\n", errno);
       return 1;
   }
 
   if ((dataptr = mmap(NULL, datasize,
                       PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS,
                       -1, 0)) == MAP_FAILED) {
       printf("Failed build dataptr due %d\n", errno);
       return 1;
   }
 
   printf("Preparing test ...\n");
   memset(dataptr, 0, datasize);
   printf("Doing test ...\n");
 
   unsigned long chunk_size = GB;
   unsigned long chunk_count = (DATA_GBYTES - 3) * GB / chunk_size;
   for (unsigned long chunk=0 ; chunk < chunk_count; chunk++) {
       printf("Iteration %d of %d\n", 1 + chunk, chunk_count);
       random_read(libptr, dataptr + (chunk * chunk_size), libsize);
   }
   return 0;
}

      
      





スワップを使用せずにテストする



vm.swappines = 0を指定してスワップを無効にし、テストを実行します

$ time ./swapdemo 
Preparing test ...
Killed

real 0m6,279s
user 0m0,459s
sys 0m5,791s
      
      







何が起こった?swappiness value = 0は、スワップを無効にしました。匿名ページはスワップにプッシュされなくなりました。つまり、データは常にメモリにあります。問題は、残りの2GBがChromeとVSCodeをバックグラウンドで実行するには不十分であり、OOM-killerがテストプログラムを強制終了したことです。同時に、メモリの不足により、この記事を書いたChromeタブが埋もれてしまいました。そして、自動保存が機能したとしても、私はそれが気に入らなかった。私のデータが埋もれているとき、私はそれが好きではありません。



スワップが含まれています



Set vm_swappines = 60(デフォルト)

テストを実行します。



$ time ./swapdemo 
Preparing test ...
Doing test ...
Iteration 1 of 11
Iteration 2 of 11
Iteration 3 of 11
Iteration 4 of 11
Iteration 5 of 11
Iteration 6 of 11
Iteration 7 of 11
Iteration 8 of 11
Iteration 9 of 11
Iteration 10 of 11
Iteration 11 of 11

real 1m55,291s
user 0m2,692s
sys 0m20,626s

      
      





フラグメントトップ:



Tasks: 298 total,   2 running, 296 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0,6 us,  3,1 sy,  0,0 ni, 85,7 id, 10,1 wa,  0,5 hi,  0,0 si,  0,0 st
MiB Mem :  15670,0 total,    156,0 free,    577,5 used,  14936,5 buff/cache
MiB Swap:  16384,0 total,  12292,5 free,   4091,5 used.   3079,1 avail Mem

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
  10393 viking    20   0   17,0g  14,2g  14,2g D  17,3  93,0   0:18.78 swapdemo
    136 root      20   0       0      0      0 S   9,6   0,0   4:35.68 kswapd0

      
      





悪い、悪いLinux !!! 14ギガバイトのキャッシュと3ギガバイトが利用可能ですが、ほぼ4ギガバイトのスワップを使用します。Linuxの設定が間違っています!悪いoutlingo、悪い古い管理者、彼らは何も理解していません、彼らはスワップを有効にするように言いました、そして今彼らはシステムをスワップさせて私のためにひどく働きます。彼らは何をすべきかを正確に知っているので、はるかに若くて有望なインターネットの専門家のアドバイスに従ってスワップを無効にする必要があります!



ええと...そうです。専門家のアドバイスで可能な限りスワップをオフにしましょう。



ほとんどスワップなしでテスト



vm_swappines = 1に設定し



ます。この値は、匿名ページのスワップが他に方法がない場合にのみ実行されるという事実につながります。



クリス・ダウンは素晴らしいエンジニアだと思います。スワップファイルによってシステムのパフォーマンスが向上すると説明したとき、彼の言うことを知っているからです。したがって、「何か」が「うまくいかず」、システムがひどく非効率的に動作する可能性があることを期待して、事前に確認してテストプログラムを実行し、タイマーで制限して、少なくとも異常終了を確認しました。



最初に一番上の出力を見てみましょう:



Tasks: 302 total,   1 running, 301 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0,2 us,  4,7 sy,  0,0 ni, 84,6 id, 10,0 wa,  0,4 hi,  0,0 si,  0,0 st
MiB Mem :  15670,0 total,    162,8 free,   1077,0 used,  14430,2 buff/cache
MiB Swap:  20480,0 total,  18164,6 free,   2315,4 used.    690,5 avail Mem

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
   6127 viking    20   0   17,0g  13,5g  13,5g D  20,2  87,9   0:10.24 swapdemo
    136 root      20   0       0      0      0 S  17,2   0,0   2:15.50 kswapd0

      
      





やったー?スワップは2.5ギガバイトでのみ使用されます。これは、スワップを有効にしたテスト(およびswappiness = 60)のほぼ2分の1です。スワップの使用量は少なくなります。空きメモリも少なくなります。そして、私たちはおそらく若い専門家に安全に勝利を与えることができます。しかし、ここに奇妙なことがあります-私たちのプログラムは、2(TWO!)分で1(ONE!)の反復を完了することさえできませんでした:



$ { sleep 120 ; killall swapdemo ; } &
[1] 6121
$ time ./swapdemo
Preparing test …
Doing test …
Iteration 1 of 11
[1]+  Done                    { sleep 120; killall swapdemo; }
Terminated

real	1m58,791s
user	0m0,871s
sys	0m23,998s
      
      





繰り返します-プログラムは2分で1回の反復を完了できませんでしたが、前のテストでは2分で11回の反復を実行しました-つまり、スワップがほとんど無効になっているため、プログラムの実行速度は10(!)倍以上遅くなります。



しかし、1つのプラスがあります-単一のChromeタブが害を受けたわけではありません。そして、これは良いことです。



スワップを完全に無効にしてテストする



しかし、swappinessを介してスワップを「クラッシュ」させるだけでは不十分であり、完全に無効にする必要がありますか?当然、この理論もテストする必要があります。私たちはテストを行うためにここに来ました、または何ですか?



これは理想的なケースです。



  • スワップはなく、すべてのデータはメモリ内で保証されます
  • スワップは存在しないため、誤って使用されることはありません。


そして今、私たちのテストは電光石火の速さで終わります、老人は彼らが値する場所に行き、カートリッジを交換します-若者のための方法。



残念ながら、テストプログラムを実行した結果は似ており、1回の反復も完了していません。



トップ出力:



Tasks: 217 total,   1 running, 216 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0,0 us,  2,2 sy,  0,0 ni, 85,2 id, 12,6 wa,  0,0 hi,  0,0 si,  0,0 st
MiB Mem :  15670,0 total,    175,2 free,    331,6 used,  15163,2 buff/cache
MiB Swap:      0,0 total,      0,0 free,      0,0 used.    711,2 avail Mem

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
    136 root      20   0       0      0      0 S  12,5   0,0   3:22.56 kswapd0
   7430 viking    20   0   17,0g  14,5g  14,5g D   6,2  94,8   0:14.94 swapdemo

      
      





なぜこうなった



説明は非常に簡単です。mmap(libptr)を介して接続する「コードセグメント」はキャッシュにあります。したがって、何らかの方法でスワップを禁止(またはほぼ禁止)する場合、スワップを物理的に無効にするか、vm.swappines = 0 | 1を使用して、常に同じシナリオで終了します-mmapファイルをフラッシュしますキャッシュから、次にディスクからロードします。また、ライブラリはmmapを介して正確にロードされます。これを確認するには、ls -l / proc // map_filesを実行するだけです。



$ ls -l /proc/8253/map_files/ | head -n 10
total 0
lr-------- 1 viking viking 64   7 12:58 556799983000-55679998e000 -> /usr/libexec/gnome-session-binary
lr-------- 1 viking viking 64   7 12:58 55679998e000-5567999af000 -> /usr/libexec/gnome-session-binary
lr-------- 1 viking viking 64   7 12:58 5567999af000-5567999bf000 -> /usr/libexec/gnome-session-binary
lr-------- 1 viking viking 64   7 12:58 5567999c0000-5567999c4000 -> /usr/libexec/gnome-session-binary
lr-------- 1 viking viking 64   7 12:58 5567999c4000-5567999c5000 -> /usr/libexec/gnome-session-binary
lr-------- 1 viking viking 64   7 12:58 7fb22a033000-7fb22a062000 -> /usr/share/glib-2.0/schemas/gschemas.compiled
lr-------- 1 viking viking 64   7 12:58 7fb22b064000-7fb238594000 -> /usr/lib/locale/locale-archive
lr-------- 1 viking viking 64   7 12:58 7fb238594000-7fb2385a7000 -> /usr/lib64/gvfs/libgvfscommon.so
lr-------- 1 viking viking 64   7 12:58 7fb2385a7000-7fb2385c3000 -> /usr/lib64/gvfs/libgvfscommon.so

      
      





そして、記事の最初の部分で検討したように、システムは、実際にメモリが不足している状態で、匿名ページのスワップが無効になっている場合、スワップを無効にした所有者が残した唯一のオプションを選択します。そして、このオプションは、mmapがロードされたライブラリのデータによって占められていた空白ページを再利用(解放)します。



結論



「Itakeallthing with me」ソフトウェア配布方法(flatpak、snap、docker image)を積極的に使用すると、mmapを介して接続されるコードの量が大幅に増加します。



これは、スワップの設定/無効化に関連する「極端な最適化」の使用が完全に予期しない影響をもたらす可能性があるという事実につながる可能性があります。スワップファイルはメモリ不足の条件下で仮想メモリサブシステムを最適化するメカニズムであり、使用可能なメモリは完全に「未使用メモリ」ではなく、キャッシュと空きメモリの合計です。



スワップファイルを無効にすることで、「間違ったオプションを削除」するのではなく、「オプションを残さない」ことになります。



プロセスのメモリ消費データ(VSSとRSS)を解釈するときは、十分に注意する必要があります。それらは「現在の状態」を表し、「最適な状態」ではありません。



システムにスワップを使用させたくない場合は、システムに メモリを追加しますが、スワップを無効にしないでください。しきい値レベルでスワップを無効にすると、システムが少しスワップされた場合よりも状況が大幅に悪化します。



PS:ディスカッションでは、「ただし、zramを介してメモリ圧縮を有効にした場合は...」という質問が定期的に行われます。私は興味を持ち、適切なテストを実行しました。Fedoraでデフォルトで行われているように、zramとswapを有効にすると、ランタイムは約1分に加速します。



ただし、この理由は、ゼロのあるページは非常によく圧縮されているため、実際にはデータはスワップされませんが、圧縮された形式でRAMに保存されます。データセグメントをランダムで圧縮率の低いデータで埋めると、画像の見栄えが悪くなり、テストの実行時間は再び2分に増加します。これは、「正直な」スワップファイルの場合と同等(さらにはわずかに悪い)です。



All Articles