mmap(2) したファイルのページキャッシュの reclaim
sysctl vm.drop_caches=1
は mmap(2) しているファイルのページキャッシュを破棄することはできなかったが、他にはどういう方法があるんだっけ?
( 何か困っているケースがあるわけでなくて挙動を確かめたいという興味・関心で調べている )
memory pressure をかける
1. 大きな無名ページをもつプロセスを作る
ホストの RAM と同程度の無名ページを使うプロセスを作れば、ページキャッシュを追い出せる ( reclaim されると書いた方が正確だろうか )
#include <stdlib.h> int main() { long size = 1L * 4 * 1024 * 1024 * 1024; char *p = malloc(size); for ( long i = 0; i < size; i += 4096) { /* cause minor page fault */ p[i] = '@'; } }
上記のような4GB の無名ページを使うコードを実行して検証する
before
top - 08:46:16 up 6:22, 6 users, load average: 0.39, 0.15, 0.09 Tasks: 1 total, 0 running, 1 sleeping, 0 stopped, 0 zombie %Cpu(s): 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st KiB Mem : 3881976 total, 2708252 free, 51452 used, 1122272 buff/cache KiB Swap: 1572860 total, 1490584 free, 82276 used. 3595604 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 3972 vagrant 20 0 1052728 1.000g 1.000g S 0.0 27.0 0:00.95 .mmap
after
top - 08:47:14 up 6:23, 6 users, load average: 0.32, 0.18, 0.10 Tasks: 1 total, 0 running, 1 sleeping, 0 stopped, 0 zombie %Cpu(s): 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st KiB Mem : 3881976 total, 3786392 free, 49364 used, 46220 buff/cache KiB Swap: 1572860 total, 1488288 free, 84572 used. 3679080 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 3972 vagrant 20 0 1052728 436 436 S 0.0 0.0 0:00.95 .mmap
スワップアウトも起こる状態なので、そりゃそうだよね感
2. 大きなファイルを読み込む
別のページキャッシュを増やしまくることで mmap 分のページキャッシュを追い出せる
before
top - 02:30:28 up 7 min, 3 users, load average: 0.31, 0.16, 0.08 Tasks: 1 total, 0 running, 1 sleeping, 0 stopped, 0 zombie %Cpu(s): 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st KiB Mem : 3881976 total, 114612 free, 106444 used, 3660920 buff/cache KiB Swap: 1572860 total, 1572860 free, 0 used. 3496964 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 2295 vagrant 20 0 1052728 1.000g 1.000g S 0.0 27.0 0:01.06 .mmap
適当につくった巨大なファイルを cat でページキャッシュにのせる
cat /tmp/4000mb.txt > /dev/null cat /tmp/4000mb.txt > /dev/null # 2回実行することで、確実に mmap しているプロセス分のページキャッシュを追い出せる # Inactive(file) LRU の挙動なんだろうけど、正確に説明できないな
after
top - 02:31:31 up 8 min, 3 users, load average: 0.37, 0.21, 0.10 Tasks: 1 total, 0 running, 1 sleeping, 0 stopped, 0 zombie %Cpu(s): 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st KiB Mem : 3881976 total, 144908 free, 107312 used, 3629756 buff/cache KiB Swap: 1572860 total, 1572860 free, 0 used. 3527340 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 2295 vagrant 20 0 1052728 404 320 S 0.0 0.0 0:01.06 .mmap
RssFile もこの通り減っている
[vagrant@localhost ~]$ grep RssFile /proc/$(pgrep mmap)/status RssFile: 424 kB
実行中の sar -B 1
を取ると以下のように数値が変化する
[vagrant@localhost ~]$ sar -B 1 Linux 3.10.0-514.26.2.el7.x86_64 (localhost.localdomain) 2017年08月25日 _x86_64_ (2 CPU) 02時44分11秒 pgpgin/s pgpgout/s fault/s majflt/s pgfree/s pgscank/s pgscand/s pgsteal/s %vmeff 02時44分12秒 0.00 0.00 54.46 0.00 39.60 0.00 0.00 0.00 0.00 02時44分13秒 0.00 0.00 44.00 0.00 38.00 0.00 0.00 0.00 0.00 02時44分14秒 236856.00 0.00 332.00 1.00 165.00 0.00 0.00 0.00 0.00 🔥 1回目の cat 02時44分15秒 483328.00 0.00 18.00 0.00 183.00 0.00 0.00 0.00 0.00 02時44分16秒 491520.00 0.00 18.00 0.00 167.00 0.00 0.00 0.00 0.00 02時44分17秒 466944.00 0.00 18.00 0.00 184.00 0.00 0.00 0.00 0.00 02時44分18秒 450560.00 0.00 18.00 0.00 151.00 0.00 0.00 0.00 0.00 02時44分19秒 458752.00 28.00 20.00 0.00 28676.00 282092.00 8537.00 28497.00 9.81 02時44分20秒 475136.00 0.00 18.00 0.00 109258.00 109109.00 0.00 109109.00 100.00 02時44分21秒 458752.00 0.00 18.00 0.00 123089.00 122964.00 0.00 122964.00 100.00 02時44分22秒 458752.00 22.00 18.00 0.00 109122.00 108966.00 0.00 108966.00 100.00 02時44分23秒 434176.00 0.00 18.00 0.00 116487.00 116299.00 0.00 116299.00 100.00 02時44分24秒 450560.00 0.00 18.00 0.00 109265.00 109082.00 0.00 109082.00 100.00 02時44分25秒 377628.00 0.00 83.00 1.00 95768.00 95537.00 0.00 95533.00 100.00 02時44分26秒 0.00 0.00 18.00 0.00 38.00 0.00 0.00 0.00 0.00 02時44分27秒 0.00 0.00 18.00 0.00 38.00 0.00 0.00 0.00 0.00 02時44分28秒 0.00 0.00 18.00 0.00 38.00 0.00 0.00 0.00 0.00 02時44分29秒 4028.00 0.00 1405.00 25.00 383.00 0.00 0.00 0.00 0.00 02時44分30秒 0.00 0.00 18.00 0.00 31.00 0.00 0.00 0.00 0.00 02時44分31秒 0.00 0.00 83.00 0.00 224.00 0.00 0.00 0.00 0.00 02時44分32秒 384256.00 0.00 330.00 0.00 88813.00 88621.00 0.00 88621.00 100.00 🔥 2回目の cat 02時44分33秒 491520.00 0.00 18.00 0.00 123285.00 123095.00 0.00 123095.00 100.00 02時44分34秒 516096.00 0.00 18.00 0.00 136552.00 136345.00 0.00 136345.00 100.00 02時44分35秒 770048.00 0.00 18.00 0.00 190944.00 190633.00 0.00 190633.00 100.00 02時44分36秒 704404.00 0.00 18.00 0.00 170837.00 170539.00 0.00 170539.00 100.00 02時44分37秒 606208.00 0.00 18.00 0.00 157094.00 156826.00 0.00 156826.00 100.00 02時44分38秒 655360.00 0.00 18.00 0.00 171118.00 170866.00 0.00 170866.00 100.00 02時44分39秒 729980.20 0.00 19.80 0.00 176368.32 176232.67 0.00 176200.99 99.98 02時44分40秒 377628.00 0.00 76.00 1.00 95306.00 95030.00 0.00 95062.00 100.03 02時44分41秒 0.00 0.00 18.00 0.00 38.00 0.00 0.00 0.00 0.00 02時44分42秒 0.00 0.00 18.00 0.00 38.00 0.00 0.00 0.00 0.00 02時44分43秒 0.00 0.00 18.00 0.00 38.00 0.00 0.00 0.00 0.00 02時44分44秒 0.00 0.00 18.00 0.00 38.00 0.00 0.00 0.00 0.00
成功しなかった方法
posix_fadvise(2) + POSIX_FADV_DONTNEED
こちらのエントリを思い出して posix_fadvise(2) + POSIX_FADV_DONTNEED を試してみたが、 mmap(2) しているファイルのページキャッシュは破棄できない
#include <stdlib.h> #include <sys/mman.h> #include <unistd.h> #include <fcntl.h> int main() { int fd = open("/tmp/1000mb.txt", O_RDONLY); if (fd == -1) { perror("open"); exit(1); } if(posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED) == -1) { err(1, "madvise failed"); } exit(0); }
カーネルのソース
Linux カーネルのソースをよむと POSIX_FADV_DONTNEED を指定した際は invalidate_mapping_pages
を呼び出しているのだった。
SYSCALL_DEFINE4(fadvise64_64, int, fd, loff_t, offset, loff_t, len, int, advice) { //... case POSIX_FADV_DONTNEED: if (!bdi_write_congested(mapping->backing_dev_info)) __filemap_fdatawrite_range(mapping, offset, endbyte, WB_SYNC_NONE); /* First and last FULL page! */ start_index = (offset+(PAGE_CACHE_SIZE-1)) >> PAGE_CACHE_SHIFT; end_index = (endbyte >> PAGE_CACHE_SHIFT); if (end_index >= start_index) { unsigned long count = invalidate_mapping_pages(mapping, start_index, end_index); /* * If fewer pages were invalidated than expected then * it is possible that some of the pages were on * a per-cpu pagevec for a remote CPU. Drain all * pagevecs and try again. */ if (count < (end_index - start_index + 1)) { lru_add_drain_all(); invalidate_mapping_pages(mapping, start_index, end_index); ⭐ } } break;
先のエントリで調べたように invalidate_mapping_pages
では mmap(2) している場合は処理されない
感想
ページキャッシュ確保の meory pressure によって reclam されるけど、 syctl vm.drop_caches=1 では破棄されないのが特殊な挙動だなと思った。
巨大なファイルを mmap(2) しているミドルウェア (例えば Elasticsearch ) が動作しているホストなんかで、別の大きなファイル (例. ログ) を cat や less で調査してたりすると、ミドルウェアが mmap(2) で蓄えたページキャッシュを reclaim しちゃうというケースはあるだろうな (それによってどこまでパフォーマンスが下がるのかが主要な問題だけど)