<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Farseerfc的小窩 - swappiness</title><link href="//farseerfc.me/" rel="alternate"></link><link href="//farseerfc.me/feeds/tag-swappiness.atom.xml" rel="self"></link><id>//farseerfc.me/</id><updated>2020-10-06T13:45:00+09:00</updated><entry><title>關於 swap 的一些補充</title><link href="//farseerfc.me/followup-about-swap.html" rel="alternate"></link><published>2020-10-06T13:45:00+09:00</published><updated>2020-10-06T13:45:00+09:00</updated><author><name>farseerfc</name></author><id>tag:farseerfc.me,2020-10-06:/followup-about-swap.html</id><summary type="html">&lt;p&gt;上週翻譯完 &lt;a class="reference external" href="//farseerfc.me/in-defence-of-swap.html"&gt;【譯】替 swap 辯護：常見的誤解&lt;/a&gt;
之後很多朋友們似乎還有些疑問和誤解，於是寫篇後續澄清一下。事先聲明我不是內核開發者，
這裏說的只是我的理解，
&lt;a class="reference external" href="https://www.kernel.org/doc/gorman/html/understand/understand005.html"&gt;基於內核文檔中關於物理內存的描述&lt;/a&gt;
，新的內核代碼的具體行爲可能和我的理解有所出入，歡迎踊躍討論。&lt;/p&gt;
&lt;div class="panel panel-default"&gt;
&lt;div class="panel-heading"&gt;
&lt;a class="reference external" href="https://www.youtube.com/watch?v=7aONIVSXiJ8"&gt;Introduction to Memory Management in Linux&lt;/a&gt;&lt;/div&gt;
&lt;div class="panel-body"&gt;
&lt;div align="left" class="youtube embed-responsive embed-responsive-16by9"&gt;&lt;iframe allow="fullscreen" class="embed-responsive-item" frameborder="0" src="https://www.youtube.com/embed/7aONIVSXiJ8"&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="id3"&gt;
&lt;h2&gt;誤解1: swap 是虛擬內存，虛擬內存肯定比物理內存慢嘛&lt;/h2&gt;
&lt;p&gt;這種誤解進一步的結論通常是：「使用虛擬內存肯定會減慢系統運行時性能，如果物理內存足夠爲什麼還要用虛擬的？」
這種誤解是把虛擬內存和交換區的實現方式類比於「虛擬磁盤」或者「虛擬機」等同的方式，
也隱含「先用物理內存，用完了之後用虛擬內存」也即下面的「誤解3」的理解。&lt;/p&gt;
&lt;p&gt;首先，交換區（swap） &lt;strong&gt;不是&lt;/strong&gt; 虛擬內存。操作系統中說「物理內存」還是「虛擬內存」的時候在指程序代碼
尋址時使用的內存地址方式，使用物理地址空間時是在訪問物理內存，使用虛擬地址空間時是在訪問虛擬內存 …&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;上週翻譯完 &lt;a class="reference external" href="//farseerfc.me/in-defence-of-swap.html"&gt;【譯】替 swap 辯護：常見的誤解&lt;/a&gt;
之後很多朋友們似乎還有些疑問和誤解，於是寫篇後續澄清一下。事先聲明我不是內核開發者，
這裏說的只是我的理解，
&lt;a class="reference external" href="https://www.kernel.org/doc/gorman/html/understand/understand005.html"&gt;基於內核文檔中關於物理內存的描述&lt;/a&gt;
，新的內核代碼的具體行爲可能和我的理解有所出入，歡迎踊躍討論。&lt;/p&gt;
&lt;div class="panel panel-default"&gt;
&lt;div class="panel-heading"&gt;
&lt;a class="reference external" href="https://www.youtube.com/watch?v=7aONIVSXiJ8"&gt;Introduction to Memory Management in Linux&lt;/a&gt;&lt;/div&gt;
&lt;div class="panel-body"&gt;
&lt;div align="left" class="youtube embed-responsive embed-responsive-16by9"&gt;&lt;iframe allow="fullscreen" class="embed-responsive-item" frameborder="0" src="https://www.youtube.com/embed/7aONIVSXiJ8"&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="id3"&gt;
&lt;h2&gt;誤解1: swap 是虛擬內存，虛擬內存肯定比物理內存慢嘛&lt;/h2&gt;
&lt;p&gt;這種誤解進一步的結論通常是：「使用虛擬內存肯定會減慢系統運行時性能，如果物理內存足夠爲什麼還要用虛擬的？」
這種誤解是把虛擬內存和交換區的實現方式類比於「虛擬磁盤」或者「虛擬機」等同的方式，
也隱含「先用物理內存，用完了之後用虛擬內存」也即下面的「誤解3」的理解。&lt;/p&gt;
&lt;p&gt;首先，交換區（swap） &lt;strong&gt;不是&lt;/strong&gt; 虛擬內存。操作系統中說「物理內存」還是「虛擬內存」的時候在指程序代碼
尋址時使用的內存地址方式，使用物理地址空間時是在訪問物理內存，使用虛擬地址空間時是在訪問虛擬內存。
現代操作系統在大部分情況下都在使用虛擬地址空間尋址， &lt;strong&gt;包括&lt;/strong&gt; 在執行內核代碼的時候。&lt;/p&gt;
&lt;p&gt;並且，交換區 &lt;strong&gt;不是&lt;/strong&gt; 實現虛擬內存的方式。操作系統使用內存管理單元（MMU，Memory Management
Unit）做虛擬內存地址到物理內存地址的地址翻譯，現代架構下 MMU 通常是 CPU
的一部分，配有它專用的一小塊存儲區叫做地址轉換旁路緩存（TLB，Translation Lookaside Buffer），
只有在 TLB 中沒有相關地址翻譯信息的時候 MMU 纔會以缺頁中斷的形式調用操作系統內核幫忙。
除了 TLB 信息不足的時候，大部分情況下使用虛擬內存都是硬件直接實現的地址翻譯，沒有軟件模擬開銷。
實現虛擬內存不需要用到交換區，交換區只是操作系統實現虛擬內存後能提供的一個附加功能，
即便沒有交換區，操作系統大部分時候也在用虛擬內存，包括在大部分內核代碼中。&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="id4"&gt;
&lt;h2&gt;誤解2: 但是沒有交換區的話，虛擬內存地址都有物理內存對應嘛&lt;/h2&gt;
&lt;p&gt;很多朋友也理解上述操作系統實現虛擬內存的方式，但是仍然會有疑問：「我知道虛擬內存和交換區的區別，
但是沒有交換區的話，虛擬內存地址都有物理內存對應，不用交換區的話就不會遇到讀虛擬內存需要讀寫磁盤
導致的卡頓了嘛」。&lt;/p&gt;
&lt;p&gt;這種理解也是錯的，禁用交換區的時候，也會有一部分分配給程序的虛擬內存不對應物理內存，
比如使用 &lt;code class="code"&gt;
mmap&lt;/code&gt;
 調用實現內存映射文件的時候。實際上即便是使用 &lt;code class="code"&gt;
read/​write&lt;/code&gt;

讀寫文件， Linux 內核中（可能現代操作系統內核都）在底下是用和 &lt;code class="code"&gt;
mmap&lt;/code&gt;
 相同的機制建立文件
到虛擬地址空間的地址映射，然後實際讀寫到虛擬地址時靠缺頁中斷把文件內容載入頁面緩存（page cache
）。內核加載可執行程序和動態鏈接庫的方式也是通過內存映射文件。甚至可以進一步說，
用戶空間的虛擬內存地址範圍內，除了匿名頁之外，其它虛擬地址都是文件後備（backed by file
），而匿名頁通過交換區作爲文件後備。上篇文章中提到的別的類型的內存，比如共享內存頁面（shm
）是被一個內存中的虛擬文件系統後備的，這一點有些套娃先暫且不提。於是事實是無論有沒有交換區，
缺頁的時候總會有磁盤讀寫從慢速存儲加載到物理內存，這進一步引出上篇文章中對於交換區和頁面緩存這兩者的討論。&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="id5"&gt;
&lt;h2&gt;誤解3： 不是內存快用完的時候才會交換的麼？&lt;/h2&gt;
&lt;p&gt;簡短的答案可以說「是」，但是內核理解的「內存快用完」和你理解的很可能不同。
也可以說「不是」，就算按照內核理解的「內存快用完」的定義，內存快用完的時候內核的行爲是去回收內存，
至於回收內存的時候內核會做什麼有個複雜的啓發式經驗算法，實際上真的內存快滿的時候根本來不及做
swap ，內核可能會嘗試丟棄 page cache 甚至丟棄 vfs cache (dentry cache / inode cache)
這些不需要磁盤I/O就能更快獲取可用內存的動作。&lt;/p&gt;
&lt;p&gt;深究這些內核機制之前，我在思考爲什麼很多朋友會問出這樣的問題。可能大部分這麼問的人，學過編程，
稍微學過基本的操作系統原理，在腦海裏對內核分配頁面留着這樣一種印象（C僞代碼）：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="code-line"&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;////////////////////  userspace space  ////////////////&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;malloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;    &lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;pages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mmap&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt;                                    &lt;span class="c1"&gt;// 從內核分配內存頁&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;alloc_from_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                        &lt;span class="c1"&gt;// 從拿到的內存頁細分&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;&lt;span class="c1"&gt;////////////////////  kernel space  //////////////////&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;SYSCALL&lt;/span&gt; &lt;span class="nf"&gt;do_mmap&lt;/span&gt;&lt;span class="p"&gt;(...){&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;   &lt;span class="c1"&gt;//...&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;kmalloc_pages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nr_page&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;kmalloc_pages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;available_mem&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;    &lt;span class="c1"&gt;// 可用內存不夠了！嘗試搞點內存&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;    &lt;span class="n"&gt;page_frame_info&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;least_accessed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lru_pop_page_frame&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;     &lt;span class="c1"&gt;// 找出最少訪問的頁面&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;least_accessed&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pf_type&lt;/span&gt; &lt;span class="p"&gt;){&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nl"&gt;PAGE_CACHE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;drop_page_cache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;least_accessed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// 丟棄文件緩存&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nl"&gt;SWAP&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;       &lt;span class="n"&gt;swap_out&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;least_accessed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// &amp;lt;- 寫磁盤，所以系統卡了！&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;      &lt;span class="c1"&gt;// ... 別的方式回收 least_accessed&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;    &lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;    &lt;span class="n"&gt;append_free_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;free_page_list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;least_accessed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;           &lt;span class="c1"&gt;// 回收到的頁面加入可用列表&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;    &lt;span class="n"&gt;available_mem&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;least_accessed&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;  &lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;  &lt;span class="c1"&gt;// 搞到內存了！返回給程序&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;  &lt;span class="n"&gt;available_mem&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;phy_addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;take_from_free_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;free_page_list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;assign_virtual_addr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phy_addr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;這種邏輯隱含三層 &lt;strong&gt;錯誤的&lt;/strong&gt; 假設：&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;分配物理內存是發生在從內核分配內存的時候的，比如 &lt;code class="code"&gt;
malloc/​mmap&lt;/code&gt;
 的時候。&lt;/li&gt;
&lt;li&gt;內存回收是發生在進程請求內存分配的上下文裏的，換句話說進程在等內核的內存回收返回內存，
不回收到內存，進程就得不到內存。&lt;/li&gt;
&lt;li&gt;交換出內存到 swap 是發生在內存回收的時候的，會阻塞內核的內存回收，進而阻塞程序的內存分配。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;這種把內核代碼當作「具有特權的庫函數調用」的看法，可能很易於理解，
甚至早期可能的確有操作系統的內核是這麼實現的，但是很可惜現代操作系統都不是這麼做的。
上面三層假設的錯誤之處在於：&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;在程序請求內存的時候，比如 &lt;code class="code"&gt;
malloc/​mmap&lt;/code&gt;
 的時候，內核只做虛擬地址分配，
記錄下某段虛擬地址空間對這個程序是可以合法訪問的，但是不實際分配物理內存給程序。
在程序第一次訪問到虛擬地址的時候，才會實際分配物理內存。這種叫 &lt;strong&gt;惰性分配（lazy allocation）&lt;/strong&gt; 。&lt;/li&gt;
&lt;li&gt;在內核感受到內存分配壓力之後，早在內核內存用盡之前，內核就會在後臺慢慢掃描並回收內存頁。
內存回收通常不發生在內存分配的時候，除非在內存非常短缺的情況下，後臺內存回收來不及滿足當前分配請求，
纔會發生 &lt;strong&gt;直接回收(direct reclamation)&lt;/strong&gt; 。&lt;/li&gt;
&lt;li&gt;同樣除了直接回收的情況，大部分正常情況下換出頁面是內存管理子系統調用 DMA 在後臺慢慢做的，
交換頁面出去不會阻塞內核的內存回收，更不會阻塞程序做內存分配（malloc
）和使用內存(實際訪問惰性分配的內存頁)。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;也就是說，現代操作系統內核是高度並行化的設計，內存分配方方面面需要消耗計算資源或者 I/O
帶寬的場景，都會儘量並行化，最大程度利用好計算機所有組件（CPU/MMU/DMA/IO）的吞吐率，
不到緊要關頭需要直接回收的場合，就不會阻塞程序的正常執行流程。&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="id6"&gt;
&lt;h2&gt;惰性分配有什麼好處？&lt;/h2&gt;
&lt;p&gt;或許會有人問：「我讓你分配內存，你給我分配了個虛擬的，到用的時候還要做很多事情才能給我，這不是騙人嘛」，
或者會有人擔心惰性分配會對性能造成負面影響。&lt;/p&gt;
&lt;p&gt;這裏實際情況是程序從分配虛擬內存的時候，「到用的時候」，這之間有段時間間隔，可以留給內核做準備
。程序可能一下子分配一大片內存地址，然後再在執行過程中解析數據慢慢往地址範圍內寫東西。
程序分配虛擬內存的速率可以是「突發」的，比如一個系統調用中分配 1GiB 大小，而實際寫入數據的速率會被
CPU 執行速度等因素限制，不會短期內突然寫入很多頁面。
這個分配速率導致的時間差內內核可以完成很多後臺工作，比如回收內存，
比如把回收到的別的進程用過的內存頁面初始化爲全0，這部分後臺工作可以和程序的執行過程並行，
從而當程序實際用到內存的時候，需要的準備工作已經做完了，大部分場景下可以直接分配物理內存出來。&lt;/p&gt;
&lt;p&gt;如果程序要做實時響應，想避免因爲惰性分配造成的性能不穩定，可以使用 &lt;code class="code"&gt;
mlock/​mlockall&lt;/code&gt;

將得到的虛擬內存鎖定在物理內存中，鎖的過程中內核會做物理內存分配。不過要區分「性能不穩定」和「低性能」，
預先分配內存可以避免實際使用內存時分配物理頁面的額外開銷，但是會拖慢整體吞吐率，所以要謹慎使用。&lt;/p&gt;
&lt;p&gt;很多程序分配了很大一片地址空間，但是實際並不會用完這些地址，直到程序執行結束這些虛擬地址也一直
處於沒有對應物理地址的情況。惰性分配可以避免爲這些情況浪費物理內存頁面，使得很多程序可以無憂無慮地
隨意分配內存地址而不用擔心性能損失。這種分配方式也叫「超額分配（overcommit）」。飛機票有超售，
VPS 提供商劃分虛擬機有超售，操作系統管理內存也同樣有這種現象，合理使用超額分配能改善整體系統效率。&lt;/p&gt;
&lt;p&gt;內核要高效地做到惰性分配而不影響程序執行效率的前提之一，在於程序真的用到內存的時候，
內核能不做太多操作就立刻分配出來，也就是說內核需要時時刻刻在手上留有一部分空頁，
滿足程序執行時內存分配的需要。換句話說，內核需要早在物理內存用盡之前，就開始回收內存。&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="id7"&gt;
&lt;h2&gt;那麼內核什麼時候會開始回收內存？&lt;/h2&gt;
&lt;p&gt;首先一些背景知識：物理內存地址空間並不是都平等，因爲一些地址範圍可以做
&lt;a class="reference external" href="https://en.wikipedia.org/wiki/Direct_memory_access"&gt;DMA&lt;/a&gt; 而另一些不能，以及
&lt;a class="reference external" href="https://en.wikipedia.org/wiki/Non-uniform_memory_access"&gt;NUMA&lt;/a&gt;
等硬件環境傾向於讓 CPU 訪問其所在 NUMA 節點內存範圍。在 32bit
系統上內核的虛擬地址空間還有低端內存和高端內存的區分，他們會傾向於使用不同屬性的物理內存，到
64bit 系統上已經沒有了這種限制。&lt;/p&gt;
&lt;p&gt;硬件限制了內存分配的自由度，於是內核把物理內存空間分成多個 Zone
，每個 Zone 內各自管理可用內存， Zone 內的內存頁之間是相互平等的。&lt;/p&gt;
&lt;div class="panel panel-default"&gt;
&lt;div class="panel-heading"&gt;
zone 內水位線&lt;/div&gt;
&lt;div class="panel-body"&gt;
&lt;img alt="ditaa diagram" class="ditaa img-responsive" src="//farseerfc.me/uml/c995609e.png"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;一個 Zone 內的頁面分配情況可以右圖描繪。
除了已用內存頁，剩下的就是空閒頁（free pages），空閒頁範圍中有三個水位線（watermark
）評估當前內存壓力情況，分別是高位（high）、低位（low）、最小位（min）。&lt;/p&gt;
&lt;p&gt;當內存分配使得空閒頁水位低於低位線，內核會喚醒 &lt;code class="code"&gt;
kswapd&lt;/code&gt;
 後臺線程， &lt;code class="code"&gt;
kswapd&lt;/code&gt;

負責掃描物理頁面的使用情況並挑選一部分頁面做回收，直到可用頁面數量恢復到水位線高位（high）以上。
如果 &lt;code class="code"&gt;
kswapd&lt;/code&gt;
 回收內存的速度慢於程序執行實際分配內存的速度，
可用空閒頁數量可能進一步下降，降至低於最小水位（min）之後，內核會讓內存分配進入
&lt;strong&gt;直接回收(direct reclamation)&lt;/strong&gt; 模式，在直接回收模式下，程序分配某個物理頁的請求（
第一次訪問某個已分配虛擬頁面的時候）會導致在進程上下文中阻塞式地調用內存回收代碼。&lt;/p&gt;
&lt;p&gt;除了內核在後臺回收內存，進程也可以主動釋放內存，比如有程序退出的時候就會釋放一大片內存頁，
所以可用頁面數量可能會升至水位線高位以上。有太多可用頁面浪費資源對整體系統運行效率也不是好事，
所以系統會積極緩存文件讀寫，所有 page cache 都留在內存中，直到可用頁面降至低水位以下觸發
&lt;code class="code"&gt;
kswapd&lt;/code&gt;
 開始工作。&lt;/p&gt;
&lt;p&gt;設置最小水位線（min）的原因在於，內核中有些硬件也會突然請求大量內存，比如來自網卡接收到的數據包，
預留出最小水位線以下的內存給內核內部和硬件使用。&lt;/p&gt;
&lt;p&gt;設置高低兩個控制 &lt;code class="code"&gt;
kswapd&lt;/code&gt;
 開關的水位線是基於控制理論。喚醒 &lt;code class="code"&gt;
kswapd&lt;/code&gt;

掃描內存頁面本身有一定計算開銷，於是每次喚醒它幹活的話就讓它多做一些活（ high - low
），避免頻繁多次喚醒。&lt;/p&gt;
&lt;p&gt;因爲有這些水位線，系統中根據程序請求內存的「速率」，整個系統的內存分配在宏觀的一段時間內可能處於以下幾種狀態：&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;strong&gt;不回收：&lt;/strong&gt; 系統中的程序申請內存速度很慢，或者程序主動釋放內存的速度很快，
（比如程序執行時間很短，不怎麼進行文件讀寫就馬上退出，）此時可用頁面數量可能一直處於低水位線以上，
內核不會主動回收內存，所有文件讀寫都會以頁面緩存的形式留在物理內存中。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;後臺回收：&lt;/strong&gt; 系統中的程序在緩慢申請內存，比如做文件讀寫，
比如分配並使用匿名頁面。系統會時不時地喚醒 &lt;code class="code"&gt;
kswapd&lt;/code&gt;
 在後臺做內存回收，
不會干擾到程序的執行效率。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;直接回收：&lt;/strong&gt; 如果程序申請內存的速度快於 &lt;code class="code"&gt;
kswapd&lt;/code&gt;
 後臺回收內存的速度，
空閒內存最終會跌破最小水位線，隨後的內存申請會進入直接回收的代碼路徑，從而極大限制內存分配速度。
在直接分配和後臺回收的同時作用下，空閒內存可能會時不時回到最小水位線以上，
但是如果程序繼續申請內存，空閒內存量就會在最小水位線附近上下徘徊。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;殺進程回收：&lt;/strong&gt; 甚至直接分配和後臺回收的同時作用也不足以拖慢程序分配內存的速度的時候，
最終空閒內存會完全用完，此時觸發 OOM 殺手幹活殺進程。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;系統狀態處於 &lt;strong&gt;1. 不回收&lt;/strong&gt;  的時候表明分配給系統的內存量過多，比如系統剛剛啓動之類的時候。
理想上應該讓系統長期處於 &lt;strong&gt;2. 後臺回收&lt;/strong&gt; 的狀態，此時最大化利用緩存的效率而又不會因爲內存回收
減緩程序執行速度。如果系統引導後長期處於 &lt;strong&gt;1. 不回收&lt;/strong&gt; 的狀態下，那麼說明沒有充分利用空閒內存做
文件緩存，有些 unix 服務比如 &lt;a class="reference external" href="https://wiki.archlinux.org/index.php/preload"&gt;preload&lt;/a&gt; 可用來提前填充文件緩存。&lt;/p&gt;
&lt;p&gt;如果系統頻繁進入 &lt;strong&gt;3. 直接回收&lt;/strong&gt; 的狀態，表明在這種工作負載下系統需要減慢一些內存分配速度，
讓 &lt;code class="code"&gt;
kswapd&lt;/code&gt;
 有足夠時間回收內存。就如前一篇翻譯中 Chris
所述，頻繁進入這種狀態也不一定代表「內存不足」，可能表示內存分配處於非常高效的利用狀態下，
系統充分利用慢速的磁盤帶寬，爲快速的內存緩存提供足夠的可用空間。
&lt;strong&gt;直接回收&lt;/strong&gt; 是否對進程負載有負面影響要看具體負載的特性。
此時選擇禁用 swap 並不能降低磁盤I/O，反而可能縮短 &lt;strong&gt;2. 後臺回收&lt;/strong&gt; 狀態能持續的時間，
導致更快進入 &lt;strong&gt;4. 殺進程回收&lt;/strong&gt; 的極端狀態。&lt;/p&gt;
&lt;p&gt;當然如果系統長期處於 &lt;strong&gt;直接回收&lt;/strong&gt; 的狀態的話，則說明內存總量不足，需要考慮增加物理內存，
或者減少系統負載了。如果系統進入 &lt;strong&gt;4. 殺進程回收&lt;/strong&gt; 的狀態，不光用空間的進程會受影響，
並且還可能導致內核態的內存分配受影響，產生網絡丟包之類的結果。&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="id8"&gt;
&lt;h2&gt;微調內存管理水位線&lt;/h2&gt;
&lt;p&gt;可以看一下運行中的系統中每個 Zone 的水位線在哪兒。比如我手上這個 16GiB 的系統中：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="code-line"&gt;&lt;span&gt;&lt;/span&gt;$ cat /proc/zoneinfo&lt;/span&gt;
&lt;span class="code-line"&gt;Node &lt;span class="m"&gt;0&lt;/span&gt;, zone      DMA&lt;/span&gt;
&lt;span class="code-line"&gt;   pages free     &lt;span class="m"&gt;3459&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;         min      &lt;span class="m"&gt;16&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;         low      &lt;span class="m"&gt;20&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;         high     &lt;span class="m"&gt;24&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;         spanned  &lt;span class="m"&gt;4095&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;         present  &lt;span class="m"&gt;3997&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;         managed  &lt;span class="m"&gt;3975&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;Node &lt;span class="m"&gt;0&lt;/span&gt;, zone    DMA32&lt;/span&gt;
&lt;span class="code-line"&gt;   pages free     &lt;span class="m"&gt;225265&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;         min      &lt;span class="m"&gt;3140&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;         low      &lt;span class="m"&gt;3925&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;         high     &lt;span class="m"&gt;4710&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;         spanned  &lt;span class="m"&gt;1044480&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;         present  &lt;span class="m"&gt;780044&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;         managed  &lt;span class="m"&gt;763629&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;Node &lt;span class="m"&gt;0&lt;/span&gt;, zone   Normal&lt;/span&gt;
&lt;span class="code-line"&gt;   pages free     &lt;span class="m"&gt;300413&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;         min      &lt;span class="m"&gt;13739&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;         low      &lt;span class="m"&gt;17173&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;         high     &lt;span class="m"&gt;20607&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;         spanned  &lt;span class="m"&gt;3407872&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;         present  &lt;span class="m"&gt;3407872&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;         managed  &lt;span class="m"&gt;3328410&lt;/span&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;因爲不是 NUMA 系統，所以只有一個 NUMA node，其中根據 DMA 類型共有 3 個 Zone 分別叫 DMA,
DMA32, Normal 。三個 Zone 的物理地址範圍（spanned）加起來大概有
&lt;span class="math"&gt;\(4095+1044480+3407872\)&lt;/span&gt; 大約 17GiB 的地址空間，而實際可訪問的地址範圍（present
）加起來有 &lt;span class="math"&gt;\(3997+780044+3407872\)&lt;/span&gt; 大約 16GiB 的可訪問物理內存。&lt;/p&gt;
&lt;p&gt;其中空閒頁面有 &lt;span class="math"&gt;\(3459+762569+1460218\)&lt;/span&gt; 大約 8.5GiB ，三條水位線分別在：
&lt;span class="math"&gt;\(\texttt{high} = 24+4710+20607 = 98\texttt{MiB}\)&lt;/span&gt; ，
&lt;span class="math"&gt;\(\texttt{low} = 20+3925+17173 = 82\texttt{MiB}\)&lt;/span&gt; ，
&lt;span class="math"&gt;\(\texttt{min} = 16+3140+13739 = 65\texttt{MiB}\)&lt;/span&gt; 的位置。&lt;/p&gt;
&lt;p&gt;具體這些水位線的確定方式基於幾個 sysctl 。首先 min 基於 &lt;code class="code"&gt;
vm.min_free_kbytes&lt;/code&gt;

默認是基於內核低端內存量的平方根算的值，並限制到最大 64MiB 再加點餘量，比如我這臺機器上
&lt;code class="code"&gt;
vm.min_free_kbytes = 67584&lt;/code&gt;
 ，於是 min 水位線在這個位置。
其它兩個水位線基於這個計算，在 min 基礎上增加總內存量的 &lt;code class="code"&gt;
vm.watermark_scale_factor /​ 10000&lt;/code&gt;

比例（在小內存的系統上還有額外考慮），默認 &lt;code class="code"&gt;
vm.watermark_scale_factor = 10&lt;/code&gt;

在大內存系統上意味着 low 比 min 高 0.1% ， high 比 low 高 0.1% 。&lt;/p&gt;
&lt;p&gt;可以手動設置這些值，以更早觸發內存回收，比如將 &lt;code class="code"&gt;
vm.watermark_scale_factor&lt;/code&gt;
 設爲 100:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="code-line"&gt;&lt;span&gt;&lt;/span&gt;$ &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; sudo tee /proc/sys/vm/watermark_scale_factor&lt;/span&gt;
&lt;span class="code-line"&gt;$ cat /proc/zoneinfo&lt;/span&gt;
&lt;span class="code-line"&gt;Node &lt;span class="m"&gt;0&lt;/span&gt;, zone      DMA&lt;/span&gt;
&lt;span class="code-line"&gt;   pages free     &lt;span class="m"&gt;3459&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;         min      &lt;span class="m"&gt;16&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;         low      &lt;span class="m"&gt;55&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;         high     &lt;span class="m"&gt;94&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;         spanned  &lt;span class="m"&gt;4095&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;         present  &lt;span class="m"&gt;3997&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;         managed  &lt;span class="m"&gt;3975&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;   Node &lt;span class="m"&gt;0&lt;/span&gt;, zone    DMA32&lt;/span&gt;
&lt;span class="code-line"&gt;   pages free     &lt;span class="m"&gt;101987&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;         min      &lt;span class="m"&gt;3149&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;         low      &lt;span class="m"&gt;10785&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;         high     &lt;span class="m"&gt;18421&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;         spanned  &lt;span class="m"&gt;1044480&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;         present  &lt;span class="m"&gt;780044&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;         managed  &lt;span class="m"&gt;763629&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;   Node &lt;span class="m"&gt;0&lt;/span&gt;, zone   Normal&lt;/span&gt;
&lt;span class="code-line"&gt;   pages free     &lt;span class="m"&gt;61987&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;         min      &lt;span class="m"&gt;13729&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;         low      &lt;span class="m"&gt;47013&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;         high     &lt;span class="m"&gt;80297&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;         spanned  &lt;span class="m"&gt;3407872&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;         present  &lt;span class="m"&gt;3407872&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;         managed  &lt;span class="m"&gt;3328410&lt;/span&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;得到的三條水位線分別在 &lt;span class="math"&gt;\(\texttt{min} = 16+3149+13729 = 66\texttt{MiB}\)&lt;/span&gt;
， &lt;span class="math"&gt;\(\texttt{low} = 55+10785+47013 = 226\texttt{MiB}\)&lt;/span&gt;
， &lt;span class="math"&gt;\(\texttt{high} = 94+18421+80297 = 386\texttt{MiB}\)&lt;/span&gt; ，
從而 low 和 high 分別比 min 提高 160MiB 也就是內存總量的 1% 左右。&lt;/p&gt;
&lt;p&gt;在 swap 放在 HDD 的系統中，因爲換頁出去的速度較慢，除了上篇文章說的降低
&lt;code class="code"&gt;
vm.swappiness&lt;/code&gt;
 之外，還可以適當提高 &lt;code class="code"&gt;
vm.watermark_scale_factor&lt;/code&gt;

讓內核更早開始回收內存，這雖然會稍微降低緩存命中率，但是另一方面可以在進入直接回收模式之前
有更多時間做後臺換頁，也將有助於改善系統整體流暢度。&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="id9"&gt;
&lt;h2&gt;只有 0.1% ，這不就是說內存快用完的時候麼？&lt;/h2&gt;
&lt;p&gt;所以之前的「誤解3」我說答案可以說「是」或者「不是」，但是無論回答是或不是，都代表了認爲「swap
就是額外的慢速內存」的錯誤看法。當有人在強調「swap 是內存快用完的時候才交換」的時候，
隱含地，是在把系統總體的內存分配看作是一個靜態的劃分過程：打個比方這就像在說，我的系統裏存儲空間有快速
128GiB SSD 和慢速 HDD 的 1TiB ，同樣內存有快速的 16GiB RAM 和慢速 16GiB 的 swap 。
這種靜態劃分的類比是錯誤的看待方式，因爲系統回收內存進而做頁面交換的方式是動態平衡的過程，
需要考慮到「時間」和「速率」而非單純看「容量」。&lt;/p&gt;
&lt;p&gt;假設 swap 所在的存儲設備可以支持 5MiB/s 的吞吐率（ HDD 上可能更慢， SSD
上可能更快，這裏需要關注數量級），相比之下 DDR3 大概有 10GiB/s 的吞吐率，DDR4 大概有 20GiB/s
，無論多快的 SSD 也遠達不到這樣的吞吐（可能 Intel Optane 這樣的
&lt;a class="reference external" href="https://lwn.net/Articles/717953/"&gt;DAX&lt;/a&gt; 設備會改變這裏的狀況）。從而把 swap
當作慢速內存的視角來看的話，加權平均的速率是非常悲觀的，「 16G 的 DDR3 + 16G 的 swap 會有
&lt;span class="math"&gt;\(\frac{16 \times 10 \times 1024 + 16 \times 5}{16+16} = 5 \texttt{GiB/s}\)&lt;/span&gt;
的吞吐？所以開 swap 導致系統速度降了一半？」顯然不能這樣看待。&lt;/p&gt;
&lt;p&gt;動態的看待方式是， swap 設備能提供 5MiB/s 的吞吐，這意味着：如果我們能把未來 10
分鐘內不會訪問到的頁面換出到 swap ，那麼就相當於有
&lt;span class="math"&gt;\(10 \times 60 \texttt{s} \times 5 \texttt{MiB/s} = 3000 \texttt{MiB}\)&lt;/span&gt;
的額外內存，用來放那 10 分鐘內可能會訪問到的頁面緩存。
10 分鐘只是隨口說的一段時間，可以換成 10 秒或者 10 小時，重要的是只要頁面交換發生在後臺，
不阻塞前臺程序的執行，那麼 swap 設備提供的額外吞吐率相當於一段時間內提供了更大的物理內存，
總是能提升頁面緩存的命中，從而改善系統性能。&lt;/p&gt;
&lt;p&gt;當然系統內核不能預知「未來 10 分鐘內需要的頁面」，只能根據歷史上訪問內存的情況預估之後可能會訪問的情況，
估算不準的情況下，比如最近10分鐘內用過的頁面緩存在之後10分鐘內不再被使用的時候，
爲了把最近這10分鐘內訪問過的頁面留在物理內存中，可能會把之後10分鐘內要用到的匿名頁面換出到了交換設備上。
於是會有下面的情況：&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="swap-swap"&gt;
&lt;h2&gt;但是我開了 swap 之後，一旦複製大文件，系統就變卡，不開 swap 不會這樣的&lt;/h2&gt;
&lt;p&gt;大概電腦用戶都經歷過這種現象，不限於 Linux 用戶，包括 macOS 和 Windows 上也是。
在文件管理器中複製了幾個大文件之後，切換到別的程序系統就極其卡頓，複製已經結束之後的一段時間也會如此。
複製的過程中系統交換區的使用率在上漲，複製結束後下降，顯然 swap 在其中有重要因素，並且禁用
swap 或者調低 swappiness 之後就不會這樣了。於是網上大量流傳着解釋這一現象，並進一步建議禁用
swap 或者調低 swappiness 的文章。我相信不少關心系統性能調優的人看過這篇「
&lt;a class="reference external" href="https://rudd-o.com/linux-and-free-software/tales-from-responsivenessland-why-linux-feels-slow-and-how-to-fix-that"&gt;Tales from responsivenessland: why Linux feels slow, and how to fix that&lt;/a&gt;
」或是它的轉載、翻譯，用中文搜索的話還能找到更多錯誤解釋 swappiness 目的的文章，比如
&lt;a class="reference external" href="http://blog.itpub.net/29371470/viewspace-1250975"&gt;這篇將 swappiness 解釋成是控制內存和交換區比例的參數&lt;/a&gt; 。&lt;/p&gt;
&lt;p&gt;除去那些有技術上謬誤的文章，這些網文中描述的現象是有道理的，不單純是以訛傳訛。
桌面環境中內存分配策略的不確定性和服務器環境中很不一樣，複製、下載、解壓大文件等導致一段時間內
大量佔用頁面緩存，以至於把操作結束後需要的頁面攆出物理內存，無論是交換出去的方式還是以丟棄頁面緩存的方式，
都會導致桌面響應性降低。&lt;/p&gt;
&lt;p&gt;不過就像前文 Chris 所述，這種現象其實並不能通過禁止 swap 的方式緩解：禁止 swap 或者調整
swappiness 讓系統儘量避免 swap 只影響回收匿名頁面的策略，不影響系統回收頁面的時機，
也不能避免系統丟棄將要使用的頁面緩存而導致的卡頓。&lt;/p&gt;
&lt;p&gt;以前在 Linux 上也沒有什麼好方法能避免這種現象。 macOS 轉用 APFS 作爲默認文件系統之後，
從文件管理器（Finder）複製文件默認啓用 file clone 快速完成，這操作不實際複製文件數據，
一個隱含優勢在不需要讀入文件內容，從而不會導致大量頁面緩存失效。 Linux 上同樣可以用支持
reflink 的文件系統比如 btrfs 或者開了 reflink=1 的 xfs 達到類似的效果。
不過 reflink 也只能拯救複製文件的情況，不能改善解壓文件、下載文件、計算文件校驗等情況下，
一次性處理大文件對內存產生的壓力。&lt;/p&gt;
&lt;p&gt;好在最近幾年 Linux 有了 cgroup ，允許更細粒度地調整系統資源分配。進一步現在我們有了 cgroup
v2 ，前面 Chris 的文章也有提到 cgroup v2 的 &lt;code class="code"&gt;
memory.low&lt;/code&gt;
 可以某種程度上建議內存子系統
儘量避免回收某些 cgroup 進程的內存。&lt;/p&gt;
&lt;p&gt;於是有了 cgroup 之後，另一種思路是把複製文件等大量使用內存而之後又不需要保留頁面緩存的程序單獨放入
cgroup 內限制它的內存用量，用一點點複製文件時的性能損失換來整體系統的響應流暢度。&lt;/p&gt;
&lt;div class="panel panel-default"&gt;
&lt;div class="panel-heading"&gt;
關於 cgroup v1 和 v2&lt;/div&gt;
&lt;div class="panel-body"&gt;
&lt;p&gt;稍微跑題說一下 cgroup v2 相對於 v1 帶來的優勢。這方面優勢在
&lt;a class="reference external" href="https://www.youtube.com/watch?v=ikZ8_mRotT4"&gt;Chris Down 另一個關於 cgroup v2 演講&lt;/a&gt;
中有提到。老 cgroup v1 按控制器區分 cgroup 層級，從而內存控制器所限制的東西和 IO
控制器所限制的東西是獨立的。在內核角度來看，頁面寫回（page writeback）和交換（swap）正是
夾在內存控制器和IO控制器管理的邊界上，從而用 v1 的 cgroup 難以同時管理。 v2
通過統一控制器層級解決了這方面限制。具體見下面 Chris Down 的演講。&lt;/p&gt;
&lt;div align="left" class="youtube embed-responsive embed-responsive-16by9"&gt;&lt;iframe allow="fullscreen" class="embed-responsive-item" frameborder="0" src="https://www.youtube.com/embed/ikZ8_mRotT4"&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="cgroup-v2"&gt;
&lt;h2&gt;用 cgroup v2 限制進程的內存分配&lt;/h2&gt;
&lt;p&gt;實際上有了 cgroup v2 之後，還有更多控制內存分配的方案。 &lt;a class="reference external" href="https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html#memory"&gt;cgroup v2 的內存控制器&lt;/a&gt;
可以對某個 cgroup 設置這些閾值：&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;memory.min&lt;/strong&gt; : 最小內存限制。內存用量低於此值後系統不會回收內存。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;memory.low&lt;/strong&gt; : 低內存水位。內存用量低於此值後系統會儘量避免回收內存。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;memory.high&lt;/strong&gt; : 高內存水位。內存用量高於此值後系統會積極回收內存，並且會對內存分配節流（throttle）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;memory.max&lt;/strong&gt; : 最大內存限制。內存用量高於此值後系統會對內存分配請求返回 ENOMEM，或者在 cgroup 內觸發 OOM 。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可見這些設定值可以當作 per-cgroup 的內存分配水位線，作用於某一部分進程而非整個系統。
針對交換區使用情況也可設置這些閾值：&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;memory.swap.high&lt;/strong&gt; : 高交換區水位，交換區用量高於此值後會對交換區分配節流。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;memory.swap.max&lt;/strong&gt; : 最大交換區限制，交換區用量高於此值後不再會發生匿名頁交換。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;到達這些 cgroup 設定閾值的時候，還可以設置內核回調的處理程序，從用戶空間做一些程序相關的操作。&lt;/p&gt;
&lt;p&gt;Linux 有了 cgroup v2 之後，就可以通過對某些程序設置內存用量限制，避免他們產生的頁面請求把別的
程序所需的頁面擠出物理內存。使用 systemd 的系統中，首先需要
&lt;a class="reference external" href="https://wiki.archlinux.org/index.php/Cgroups#Switching_to_cgroups_v2"&gt;啓用 cgroup v2&lt;/a&gt;
，在內核引導參數中加上 &lt;code class="code"&gt;
systemd.unified_cgroup_hierarchy=1&lt;/code&gt;

。然後開啓用戶權限代理：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="code-line"&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# systemctl edit user@1000.service&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;&lt;span class="o"&gt;[&lt;/span&gt;Service&lt;span class="o"&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;&lt;span class="nv"&gt;Delegate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;yes&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;然後可以定義用戶會話的 slice （slice 是 systemd 術語，用來映射 cgroup ），比如創建一個叫
&lt;code class="code"&gt;
limit-mem&lt;/code&gt;
 的 slice ：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="code-line"&gt;&lt;span&gt;&lt;/span&gt;$ cat ~/.config/systemd/user/limit-mem.slice&lt;/span&gt;
&lt;span class="code-line"&gt;&lt;span class="o"&gt;[&lt;/span&gt;Slice&lt;span class="o"&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class="code-line"&gt;&lt;span class="nv"&gt;MemoryHigh&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3G&lt;/span&gt;
&lt;span class="code-line"&gt;&lt;span class="nv"&gt;MemoryMax&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4G&lt;/span&gt;
&lt;span class="code-line"&gt;&lt;span class="nv"&gt;MemorySwapMax&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2G&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;然後可以用 systemd-run 限制在某個 slice 中打開一個 shell：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="code-line"&gt;&lt;span&gt;&lt;/span&gt;$ systemd-run --user --slice&lt;span class="o"&gt;=&lt;/span&gt;limit-mem.slice --shell&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;或者定義一個 shell alias 用來限制任意命令：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="code-line"&gt;&lt;span&gt;&lt;/span&gt;$ &lt;span class="nb"&gt;type&lt;/span&gt; limit-mem&lt;/span&gt;
&lt;span class="code-line"&gt;limit-mem is an &lt;span class="nb"&gt;alias&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; /usr/bin/time systemd-run --user --pty --same-dir --wait --collect --slice&lt;span class="o"&gt;=&lt;/span&gt;limit-mem.slice&lt;/span&gt;
&lt;span class="code-line"&gt;$ limit-mem cp some-large-file dest/&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;實際用法有很多，可以參考 systemd 文檔
&lt;a class="reference external" href="http://www.jinbuguo.com/systemd/systemd.resource-control.html"&gt;man systemd.resource-control&lt;/a&gt;
， &lt;a class="reference external" href="/links.html#xuanwo"&gt;xuanwo&lt;/a&gt; 也 &lt;a class="reference external" href="https://xuanwo.io/2018/10/30/tips-of-systemd/"&gt;有篇博客介紹過 systemd 下資源限制&lt;/a&gt;
， &lt;a class="reference external" href="/links.html#lilydjwg"&gt;lilydjwg&lt;/a&gt; 也 &lt;a class="reference external" href="https://blog.lilydjwg.me/2019/3/2/use-cgroups-to-limit-memory-usage-for-specific-processes.214196.html"&gt;寫過用 cgroup 限制進程內存的用法&lt;/a&gt;
和 &lt;a class="reference external" href="https://blog.lilydjwg.me/2020/5/11/priority-and-nice-value-in-linux.215304.html"&gt;用 cgroup 之後對 CPU 調度的影響&lt;/a&gt;
。&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="id12"&gt;
&lt;h2&gt;未來展望&lt;/h2&gt;
&lt;p&gt;最近新版的 gnome 和 KDE 已經開始爲桌面環境下用戶程序的進程創建 systemd scope 了，
可以通過 &lt;code class="code"&gt;
systemd-cgls&lt;/code&gt;
 觀察到，每個通過桌面文件（.desktop）開啓的用戶空間程序
都有個獨立的名字叫 &lt;code class="code"&gt;
app-APPNAME-HASH.scope&lt;/code&gt;
 之類的 systemd scope 。
有了這些 scope 之後，事實上用戶程序的資源分配某種程度上已經相互獨立，
不過默認的用戶程序沒有施加多少限制。&lt;/p&gt;
&lt;p&gt;今後可以展望，桌面環境可以提供用戶友好的方式對這些桌面程序施加公平性的限制。
不光是內存分配的大小限制，包括 CPU 和 IO 佔用方面也會更公平。
值得一提的是傳統的 ext4/xfs/f2fs 之類的文件系統雖然支持
&lt;a class="reference external" href="https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html#writeback"&gt;cgroup writeback 節流&lt;/a&gt;
但是因爲他們有額外的 journaling 寫入，難以單獨針對某些 cgroup 限制 IO
寫入帶寬（對文件系統元數據的寫入難以統計到具體某組進程）。
而 btrfs 通過 CoW 避免了 journaling ，
&lt;a class="reference external" href="https://facebookmicrosites.github.io/btrfs/docs/btrfs-facebook.html#io-control-with-cgroup2"&gt;在這方面有更好的支持&lt;/a&gt;
。相信不遠的將來，複製大文件之類常見普通操作不再需要手動調用加以限制，
就能避免單個程序佔用太多資源影響別的程序。&lt;/p&gt;
&lt;/div&gt;
&lt;script type='text/javascript'&gt;if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {
    var align = "center",
        indent = "0em",
        linebreak = "false";

    if (false) {
        align = (screen.width &lt; 768) ? "left" : align;
        indent = (screen.width &lt; 768) ? "0em" : indent;
        linebreak = (screen.width &lt; 768) ? 'true' : linebreak;
    }

    var mathjaxscript = document.createElement('script');
    var location_protocol = (false) ? 'https' : document.location.protocol;
    if (location_protocol !== 'http' &amp;&amp; location_protocol !== 'https') location_protocol = 'https:';
    mathjaxscript.id = 'mathjaxscript_pelican_#%@#$@#';
    mathjaxscript.type = 'text/javascript';
    mathjaxscript.src = location_protocol + '//cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML';
    mathjaxscript[(window.opera ? "innerHTML" : "text")] =
        "MathJax.Hub.Config({" +
        "    config: ['MMLorHTML.js']," +
        "    TeX: { extensions: ['AMSmath.js','AMSsymbols.js','noErrors.js','noUndefined.js'], equationNumbers: { autoNumber: 'AMS' } }," +
        "    jax: ['input/TeX','input/MathML','output/HTML-CSS']," +
        "    extensions: ['tex2jax.js','mml2jax.js','MathMenu.js','MathZoom.js']," +
        "    displayAlign: '"+ align +"'," +
        "    displayIndent: '"+ indent +"'," +
        "    showMathMenu: true," +
        "    messageStyle: 'normal'," +
        "    tex2jax: { " +
        "        inlineMath: [ ['\\\\(','\\\\)'] ], " +
        "        displayMath: [ ['$$','$$'] ]," +
        "        processEscapes: true," +
        "        preview: 'TeX'," +
        "    }, " +
        "    'HTML-CSS': { " +
        "        styles: { '.MathJax_Display, .MathJax .mo, .MathJax .mi, .MathJax .mn': {color: 'inherit ! important'} }," +
        "        linebreaks: { automatic: "+ linebreak +", width: '90% container' }," +
        "    }, " +
        "}); " +
        "if ('default' !== 'default') {" +
            "MathJax.Hub.Register.StartupHook('HTML-CSS Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax['HTML-CSS'].FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
            "MathJax.Hub.Register.StartupHook('SVG Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax.SVG.FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
        "}";
    (document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
}
&lt;/script&gt;</content><category term="tech"></category><category term="swap"></category><category term="mm"></category><category term="memory management"></category><category term="swappiness"></category><category term="kswapd"></category><category term="watermark"></category><category term="watermark_scale_factor"></category><category term="cgroup v2"></category></entry><entry><title>【譯】替 swap 辯護：常見的誤解</title><link href="//farseerfc.me/in-defence-of-swap.html" rel="alternate"></link><published>2020-09-30T13:45:00+09:00</published><updated>2020-09-30T13:45:00+09:00</updated><author><name>farseerfc</name></author><id>tag:farseerfc.me,2020-09-30:/in-defence-of-swap.html</id><summary type="html">&lt;p&gt;這篇翻譯自 Chris Down 的博文
&lt;a class="reference external" href="https://chrisdown.name/2018/01/02/in-defence-of-swap.html"&gt;In defence of swap: common misconceptions&lt;/a&gt;
。 &lt;a class="reference external" href="https://github.com/cdown/chrisdown.name/blob/master/LICENSE"&gt;原文的協議&lt;/a&gt;
是 &lt;a class="reference external" href="https://creativecommons.org/licenses/by-sa/4.0/"&gt;CC BY-SA 4.0&lt;/a&gt;
，本文翻譯同樣也使用 &lt;a class="reference external" href="https://creativecommons.org/licenses/by-sa/4.0/"&gt;CC BY-SA 4.0&lt;/a&gt; 。其中加入了一些我自己的理解作爲旁註，所有譯註都在側邊欄中。&lt;/p&gt;
&lt;p&gt;翻譯這篇文章是因爲經常看到朋友們（包括有經驗的程序員和 Linux 管理員）對 swap 和 swappiness
有諸多誤解，而這篇文章正好澄清了這些誤解，也講清楚了 Linux 中這兩者的本質。值得一提的是本文討論的
swap 針對 Linux 內核，在別的系統包括 macOS/WinNT 或者 Unix 系統中的交換文件可能有不同一樣的行爲，
需要不同的調優方式。比如在 &lt;a class="reference external" href="https://www.freebsd.org/doc/handbook/bsdinstall-partitioning.html#configtuning-initial"&gt;FreeBSD …&lt;/a&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;這篇翻譯自 Chris Down 的博文
&lt;a class="reference external" href="https://chrisdown.name/2018/01/02/in-defence-of-swap.html"&gt;In defence of swap: common misconceptions&lt;/a&gt;
。 &lt;a class="reference external" href="https://github.com/cdown/chrisdown.name/blob/master/LICENSE"&gt;原文的協議&lt;/a&gt;
是 &lt;a class="reference external" href="https://creativecommons.org/licenses/by-sa/4.0/"&gt;CC BY-SA 4.0&lt;/a&gt;
，本文翻譯同樣也使用 &lt;a class="reference external" href="https://creativecommons.org/licenses/by-sa/4.0/"&gt;CC BY-SA 4.0&lt;/a&gt; 。其中加入了一些我自己的理解作爲旁註，所有譯註都在側邊欄中。&lt;/p&gt;
&lt;p&gt;翻譯這篇文章是因爲經常看到朋友們（包括有經驗的程序員和 Linux 管理員）對 swap 和 swappiness
有諸多誤解，而這篇文章正好澄清了這些誤解，也講清楚了 Linux 中這兩者的本質。值得一提的是本文討論的
swap 針對 Linux 內核，在別的系統包括 macOS/WinNT 或者 Unix 系統中的交換文件可能有不同一樣的行爲，
需要不同的調優方式。比如在 &lt;a class="reference external" href="https://www.freebsd.org/doc/handbook/bsdinstall-partitioning.html#configtuning-initial"&gt;FreeBSD handbook&lt;/a&gt;
中明確建議了 swap 分區通常應該是兩倍物理內存大小，這一點建議對 FreeBSD 系內核的內存管理可能非常合理，
而不一定適合 Linux 內核，FreeBSD 和 Linux 有不同的內存管理方式尤其是 swap 和 page cache 和
buffer cache 的處理方式有諸多不同。&lt;/p&gt;
&lt;p&gt;經常有朋友看到系統卡頓之後看系統內存使用狀況觀察到大量 swap 佔用，於是覺得卡頓是來源於 swap
。就像文中所述，相關不蘊含因果，產生內存顛簸之後的確會造成大量 swap 佔用，也會造成系統卡頓，
但是 swap 不是導致卡頓的原因，關掉 swap 或者調低 swappiness 並不能阻止卡頓，只會將 swap
造成的 I/O 轉化爲加載文件緩存造成的 I/O 。&lt;/p&gt;
&lt;p&gt;以下是原文翻譯：&lt;/p&gt;

&lt;hr class="docutils"/&gt;
&lt;p&gt;這篇文章也有 &lt;a class="reference external" href="https://chrisdown.name/ja/2018/01/02/in-defence-of-swap.html"&gt;日文&lt;/a&gt;
和 &lt;a class="reference external" href="https://softdroid.net/v-zashchitu-svopa-rasprostranennye-zabluzhdeniya"&gt;俄文&lt;/a&gt;
翻譯。&lt;/p&gt;
&lt;a aria-controls="a01b6caa" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#a01b6caa" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="a01b6caa"&gt;
&lt;p&gt;tl;dr:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Having swap is a reasonably important part of a well functioning system.
Without it, sane memory management becomes harder to achieve.&lt;/li&gt;
&lt;li&gt;Swap is not generally about getting emergency memory, it's about making
memory reclamation egalitarian and efficient. In fact, using it as
"emergency memory" is generally actively harmful.&lt;/li&gt;
&lt;li&gt;Disabling swap does not prevent disk I/O from becoming a problem
under memory contention, it simply shifts the disk I/O thrashing from
anonymous pages to file pages. Not only may this be less efficient,
as we have a smaller pool of pages to select from for reclaim, but it
may also contribute to getting into this high contention state in
the first place.&lt;/li&gt;
&lt;li&gt;The swapper on kernels before 4.0 has a lot of pitfalls,
and has contributed to a lot of people's negative perceptions about
swap due to its overeagerness to swap out pages. On kernels &amp;gt;4.0,
the situation is significantly better.&lt;/li&gt;
&lt;li&gt;On SSDs, swapping out anonymous pages and reclaiming file pages are
essentially equivalent in terms of performance/latency.
On older spinning disks, swap reads are slower due to random reads,
so a lower &lt;code class="code"&gt;
vm.swappiness&lt;/code&gt;
 setting makes sense there
(read on for more about &lt;code class="code"&gt;
vm.swappiness&lt;/code&gt;
).&lt;/li&gt;
&lt;li&gt;Disabling swap doesn't prevent pathological behaviour at near-OOM,
although it's true that having swap may prolong it. Whether the
system global OOM killer is invoked with or without swap, or was invoked
sooner or later, the result is the same: you are left with a system in an
unpredictable state. Having no swap doesn't avoid this.&lt;/li&gt;
&lt;li&gt;You can achieve better swap behaviour under memory pressure and prevent
thrashing using &lt;code class="code"&gt;
memory.low&lt;/code&gt;
 and friends in cgroup v2.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;太長不看：&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;對維持系統的正常功能而言，有 swap 是相對挺重要的一部分。沒有它的話會更難做到合理的內存管理。&lt;/li&gt;
&lt;li&gt;swap 的目的通常並不是用作緊急內存，它的目的在於讓內存回收能更平等和高效。
事實上把它當作「緊急內存」來用的想法通常是有害的。&lt;/li&gt;
&lt;li&gt;禁用 swap 在內存壓力下並不能避免磁盤I/O造成的性能問題，這麼做只是讓磁盤I/O顛簸的範圍從
匿名頁面轉化到文件頁面。這不僅更低效，因爲系統能回收的頁面的選擇範圍更有限了，
而且這種做法還可能是加重了內存壓力的原因之一。&lt;/li&gt;
&lt;li&gt;內核 4.0 版本之前的交換進程（swapper）有一些問題，導致很多人對 swap 有負面印象，
因爲它太急於（overeagerness）把頁面交換出去。在 4.0 之後的內核上這種情況已經改善了很多。&lt;/li&gt;
&lt;li&gt;在 SSD 上，交換出匿名頁面的開銷和回收文件頁面的開銷基本上在性能/延遲方面沒有區別。
在老式的磁盤上，讀取交換文件因爲屬於隨機訪問讀取所以會更慢，於是設置較低的 &lt;code class="code"&gt;
vm.swappiness&lt;/code&gt;

可能比較合理（繼續讀下面關於 &lt;code class="code"&gt;
vm.swappiness&lt;/code&gt;
 的描述）。&lt;/li&gt;
&lt;li&gt;禁用 swap 並不能避免在接近 OOM 狀態下最終表現出的症狀，儘管的確有 swap
的情況下這種症狀持續的時間可能會延長。在系統調用 OOM 殺手的時候無論有沒有啓用 swap
，或者更早/更晚開始調用 OOM 殺手，結果都是一樣的：整個系統留在了一種不可預知的狀態下。
有 swap 也不能避免這一點。&lt;/li&gt;
&lt;li&gt;可以用 cgroup v2 的 &lt;code class="code"&gt;
memory.low&lt;/code&gt;
 相關機制來改善內存壓力下 swap 的行爲並且
避免發生顛簸。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr class="docutils"/&gt;
&lt;a aria-controls="053c68ad" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#053c68ad" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="053c68ad"&gt;
As part of my work improving kernel memory management and cgroup v2,
I've been talking to a lot of engineers about attitudes towards memory
management, especially around application behaviour under pressure and
operating system heuristics used under the hood for memory management.&lt;/blockquote&gt;
&lt;p&gt;我的工作的一部分是改善內核中內存管理和 cgroup v2 相關，所以我和很多工程師討論過看待內存管理的態度，
尤其是在壓力下應用程序的行爲和操作系統在底層內存管理中用的基於經驗的啓發式決策邏輯。&lt;/p&gt;
&lt;a aria-controls="8dedecec" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#8dedecec" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="8dedecec"&gt;
A repeated topic in these discussions has been swap.
Swap is a hotly contested and poorly understood topic,
even by those who have been working with Linux for many years.
Many see it as useless or actively harmful: a relic of a time where
memory was scarce, and disks were a necessary evil to provide much-needed
space for paging. This is a statement that I still see being batted
around with relative frequency in recent years, and I've had many
discussions with colleagues, friends, and industry peers to help them
understand why swap is still a useful concept on modern computers with
significantly more physical memory available than in the past.&lt;/blockquote&gt;
&lt;p&gt;在這種討論中經常重複的話題是交換區（swap）。交換區的話題是非常有爭議而且很少被理解的話題，甚至包括那些在
Linux 上工作過多年的人也是如此。很多人覺得它沒什麼用甚至是有害的：它是歷史遺蹟，從內存緊缺而
磁盤讀寫是必要之惡的時代遺留到現在，爲計算機提供在當年很必要的頁面交換功能作爲內存空間。
最近幾年我還經常能以一定頻度看到這種論調，然後我和很多同事、朋友、業界同行們討論過很多次，
幫他們理解爲什麼在現代計算機系統中交換區仍是有用的概念，即便現在的電腦中物理內存已經遠多於過去。&lt;/p&gt;
&lt;a aria-controls="b1c86306" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#b1c86306" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="b1c86306"&gt;
There's also a lot of misunderstanding about the purpose of swap –
many people just see it as a kind of "slow extra memory" for use in emergencies,
but don't understand how it can contribute during normal load to the healthy
operation of an operating system as a whole.&lt;/blockquote&gt;
&lt;p&gt;圍繞交換區的目的還有很多誤解——很多人覺得它只是某種爲了應對緊急情況的「慢速額外內存」，
但是沒能理解在整個操作系統健康運作的時候它也能改善普通負載的性能。&lt;/p&gt;
&lt;a aria-controls="3603c7fa" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#3603c7fa" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="3603c7fa"&gt;
Many of us have heard most of the usual tropes about memory:
" &lt;a class="reference external" href="https://www.linuxatemyram.com/"&gt;Linux uses too much memory&lt;/a&gt; ",
" &lt;a class="reference external" href="https://superuser.com/a/111510"&gt;swap should be double your physical memory size&lt;/a&gt;
", and the like. While these are either trivial to dispel,
or discussion around them has become more nuanced in recent years,
the myth of "useless" swap is much more grounded in heuristics and
arcana rather than something that can be explained by simple analogy,
and requires somewhat more understanding of memory management to reason about.&lt;/blockquote&gt;
&lt;p&gt;我們很多人也聽說過描述內存時所用的常見說法： 「 &lt;a class="reference external" href="https://www.linuxatemyram.com/"&gt;Linux 用了太多內存&lt;/a&gt;
」，「 &lt;a class="reference external" href="https://superuser.com/a/111510"&gt;swap 應該設爲物理內存的兩倍大小&lt;/a&gt; 」，或者類似的說法。
雖然這些誤解要麼很容易化解，或者關於他們的討論在最近幾年已經逐漸變得瑣碎，但是關於「無用」交換區
的傳言有更深的經驗傳承的根基，而不是一兩個類比就能解釋清楚的，並且要探討這個先得對內存管理有
一些基礎認知。&lt;/p&gt;
&lt;a aria-controls="ff09462c" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#ff09462c" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="ff09462c"&gt;
This post is mostly aimed at those who administrate Linux systems and
are interested in hearing the counterpoints to running with
undersized/no swap or running with vm.swappiness set to 0.&lt;/blockquote&gt;
&lt;p&gt;本文主要目標是針對那些管理 Linux 系統並且有興趣理解「讓系統運行於低/無交換區狀態」或者「把
&lt;code class="code"&gt;
vm.swappiness&lt;/code&gt;
 設爲 0 」這些做法的反論。&lt;/p&gt;
&lt;div class="section" id="id6"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#id23"&gt;背景&lt;/a&gt;&lt;/h2&gt;
&lt;a aria-controls="283c5b39" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#283c5b39" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="283c5b39"&gt;
It's hard to talk about why having swap and swapping out pages are good
things in normal operation without a shared understanding of some of
the basic underlying mechanisms at play in Linux memory management,
so let's make sure we're on the same page.&lt;/blockquote&gt;
&lt;p&gt;如果沒有基本理解 Linux 內存管理的底層機制是如何運作的，就很難討論爲什麼需要交換區以及交換出頁面
對正常運行的系統爲什麼是件好事，所以我們先確保大家有討論的基礎。&lt;/p&gt;
&lt;div class="section" id="id7"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#id24"&gt;內存的類型&lt;/a&gt;&lt;/h3&gt;
&lt;a aria-controls="b93d3e73" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#b93d3e73" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="b93d3e73"&gt;
There are many different types of memory in Linux, and each type has its
own properties. Understanding the nuances of these is key to understanding
why swap is important.&lt;/blockquote&gt;
&lt;p&gt;Linux 中內存分爲好幾種類型，每種都有各自的屬性。想理解爲什麼交換區很重要的關鍵一點在於理解這些的細微區別。&lt;/p&gt;
&lt;a aria-controls="ea5c81cf" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#ea5c81cf" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="ea5c81cf"&gt;
For example, there are &lt;strong&gt;pages ("blocks" of memory, typically 4k)&lt;/strong&gt;
responsible for holding the code for each process being run on your computer.
There are also pages responsible for caching data and metadata related to
files accessed by those programs in order to speed up future access.
These are part of the &lt;strong&gt;page cache&lt;/strong&gt; , and I will refer to them as file memory.&lt;/blockquote&gt;
&lt;p&gt;比如說，有種 &lt;strong&gt;頁面（「整塊」的內存，通常 4K）&lt;/strong&gt; 是用來存放電腦裏每個程序運行時各自的代碼的。
也有頁面用來保存這些程序所需要讀取的文件數據和元數據的緩存，以便加速隨後的文件讀寫。
這些內存頁面構成 &lt;strong&gt;頁面緩存（page cache）&lt;/strong&gt;，後文中我稱他們爲文件內存。&lt;/p&gt;
&lt;a aria-controls="a8e582f2" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#a8e582f2" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="a8e582f2"&gt;
There are also pages which are responsible for the memory allocations
made inside that code, for example, when new memory that has been allocated
with &lt;code class="code"&gt;
malloc&lt;/code&gt;
 is written to, or when using &lt;code class="code"&gt;
mmap&lt;/code&gt;
's
&lt;code class="code"&gt;
MAP_ANONYMOUS&lt;/code&gt;
 flag. These are "anonymous" pages –
so called because they are not backed by anything –
and I will refer to them as anon memory.&lt;/blockquote&gt;
&lt;p&gt;還有一些頁面是在代碼執行過程中做的內存分配得到的，比如說，當代碼調用 &lt;code class="code"&gt;
malloc&lt;/code&gt;

能分配到新內存區，或者使用 &lt;code class="code"&gt;
mmap&lt;/code&gt;
 的 &lt;code class="code"&gt;
MAP_ANONYMOUS&lt;/code&gt;
 標誌分配的內存。
這些是「匿名(anonymous)」頁面——之所以這麼稱呼它們是因爲他們沒有任何東西作後備——
後文中我稱他們爲匿名內存。&lt;/p&gt;
&lt;a aria-controls="38d36a1d" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#38d36a1d" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="38d36a1d"&gt;
There are other types of memory too –
shared memory, slab memory, kernel stack memory, buffers, and the like –
but anonymous memory and file memory are the most well known and
easy to understand ones, so I will use these in my examples,
although they apply equally to these types too.&lt;/blockquote&gt;
&lt;p&gt;還有其它類型的內存——共享內存、slab內存、內核棧內存、文件緩衝區（buffers），這種的——
但是匿名內存和文件內存是最知名也最好理解的，所以後面的例子裏我會用這兩個說明，
雖然後面的說明也同樣適用於別的這些內存類型。&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="id8"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#id25"&gt;可回收/不可回收內存&lt;/a&gt;&lt;/h3&gt;
&lt;a aria-controls="896d577c" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#896d577c" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="896d577c"&gt;
One of the most fundamental questions when thinking about a particular type
of memory is whether it is able to be reclaimed or not.
"Reclaim" here means that the system can, without losing data,
purge pages of that type from physical memory.&lt;/blockquote&gt;
&lt;p&gt;考慮某種內存的類型時，一個非常基礎的問題是這種內存是否能被回收。
「回收（Reclaim）」在這裏是指系統可以，在不丟失數據的前提下，從物理內存中釋放這種內存的頁面。&lt;/p&gt;
&lt;a aria-controls="c8915ce3" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#c8915ce3" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="c8915ce3"&gt;
For some page types, this is typically fairly trivial. For example,
in the case of clean (unmodified) page cache memory,
we're simply caching something that we have on disk for performance,
so we can drop the page without having to do any special operations.&lt;/blockquote&gt;
&lt;p&gt;對一些內存類型而言，是否可回收通常可以直接判斷。比如對於那些乾淨（未修改）的頁面緩存內存，
我們只是爲了性能在用它們緩存一些磁盤上現有的數據，所以我們可以直接扔掉這些頁面，
不需要做什麼特殊的操作。&lt;/p&gt;
&lt;a aria-controls="543b6f9b" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#543b6f9b" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="543b6f9b"&gt;
For some page types, this is possible, but not trivial. For example,
in the case of dirty (modified) page cache memory, we can't just drop the page,
because the disk doesn't have our modifications yet.
As such we either need to deny reclamation or first get our changes back to
disk before we can drop this memory.&lt;/blockquote&gt;
&lt;p&gt;對有些內存類型而言，回收是可能的，但是不是那麼直接。比如對髒（修改過）的頁面緩存內存，
我們不能直接扔掉這些頁面，因爲磁盤上還沒有寫入我們所做的修改。這種情況下，我們可以選擇拒絕回收，
或者選擇先等待我們的變更寫入磁盤之後才能扔掉這些內存。&lt;/p&gt;
&lt;a aria-controls="5111480d" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#5111480d" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="5111480d"&gt;
For some page types, this is not possible. For example,
in the case of the anonymous pages mentioned previously,
they only exist in memory and in no other backing store,
so they have to be kept there.&lt;/blockquote&gt;
&lt;p&gt;對還有些內存類型而言，是不能回收的。比如前面提到的匿名頁面，它們只存在於內存中，沒有任何後備存儲，
所以它們必須留在內存裏。&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="id9"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#id26"&gt;說到交換區的本質&lt;/a&gt;&lt;/h2&gt;
&lt;a aria-controls="99551744" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#99551744" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="99551744"&gt;
&lt;p&gt;If you look for descriptions of the purpose of swap on Linux,
you'll inevitably find many people talking about it as if it is merely
an extension of the physical RAM for use in emergencies. For example,
here is a random post I got as one of the top results from typing
"what is swap" in Google:&lt;/p&gt;
&lt;blockquote&gt;
Swap is essentially emergency memory; a space set aside for times
when your system temporarily needs more physical memory than you
have available in RAM. It's considered "bad" in the sense that
it's slow and inefficient, and if your system constantly needs
to use swap then it obviously doesn't have enough memory. […]
If you have enough RAM to handle all of your needs, and don't
expect to ever max it out, then you should be perfectly safe
running without a swap space.&lt;/blockquote&gt;
&lt;/blockquote&gt;
&lt;p&gt;如果你去搜 Linux 上交換區的目的的描述，肯定會找到很多人說交換區只是在緊急時用來擴展物理內存的機制。
比如下面這段是我在 google 中輸入「什麼是 swap」 從前排結果中隨機找到的一篇：&lt;/p&gt;
&lt;blockquote&gt;
交換區本質上是緊急內存；是爲了應對你的系統臨時所需內存多餘你現有物理內存時，專門分出一塊額外空間。
大家覺得交換區「不好」是因爲它又慢又低效，並且如果你的系統一直需要使用交換區那說明它明顯沒有足夠的內存。
［……］如果你有足夠內存覆蓋所有你需要的情況，而且你覺得肯定不會用滿內存，那麼完全可以不用交換區
安全地運行系統。&lt;/blockquote&gt;
&lt;a aria-controls="fe4c8bc1" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#fe4c8bc1" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="fe4c8bc1"&gt;
To be clear, I don't blame the poster of this comment at all for the content
of their post – this is accepted as "common knowledge" by a lot of
Linux sysadmins and is probably one of the most likely things that you will
hear from one if you ask them to talk about swap. It is unfortunately also,
however, a misunderstanding of the purpose and use of swap, especially on
modern systems.&lt;/blockquote&gt;
&lt;p&gt;事先說明，我不想因爲這些文章的內容責怪這些文章的作者——這些內容被很多 Linux 系統管理員認爲是「常識」，
並且很可能你問他們什麼是交換區的時候他們會給你這樣的回答。但是也很不幸的是，
這種認識是使用交換區的目的的一種普遍誤解，尤其在現代系統上。&lt;/p&gt;
&lt;a aria-controls="77407587" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#77407587" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="77407587"&gt;
Above, I talked about reclamation for anonymous pages being "not possible",
as anonymous pages by their nature have no backing store to fall back to
when being purged from memory – as such, their reclamation would result in
complete data loss for those pages. What if we could create such a
store for these pages, though?&lt;/blockquote&gt;
&lt;p&gt;前文中我說過回收匿名頁面的內存是「不可能的」，因爲匿名內存的特點，把它們從內存中清除掉之後，
沒有別的存儲區域能作爲他們的備份——因此，要回收它們會造成數據丟失。但是，如果我們爲這種內存頁面創建
一種後備存儲呢？&lt;/p&gt;
&lt;a aria-controls="75be861f" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#75be861f" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="75be861f"&gt;
Well, this is precisely what swap is for. Swap is a storage area for these
seemingly "unreclaimable" pages that allows us to page them out to
a storage device on demand. This means that they can now be considered as
equally eligible for reclaim as their more trivially reclaimable friends,
like clean file pages, allowing more efficient use of available physical memory.&lt;/blockquote&gt;
&lt;p&gt;嗯，這正是交換區存在的意義。交換區是一塊存儲空間，用來讓這些看起來「不可回收」的內存頁面在需要的時候
可以交換到存儲設備上。這意味着有了交換區之後，這些匿名頁面也和別的那些可回收內存一樣，
可以作爲內存回收的候選，就像乾淨文件頁面，從而允許更有效地使用物理內存。&lt;/p&gt;
&lt;a aria-controls="aa3951ca" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#aa3951ca" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="aa3951ca"&gt;
&lt;strong&gt;Swap is primarily a mechanism for equality of reclamation,&lt;/strong&gt;
&lt;strong&gt;not for emergency "extra memory". Swap is not what makes your application&lt;/strong&gt;
&lt;strong&gt;slow – entering overall memory contention is what makes your application slow.&lt;/strong&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;交換區主要是爲了平等的回收機制，而不是爲了緊急情況的「額外內存」。使用交換區不會讓你的程序變慢——&lt;/strong&gt;
&lt;strong&gt;進入內存競爭的狀態才是讓程序變慢的元兇。&lt;/strong&gt;&lt;/p&gt;
&lt;a aria-controls="526740fe" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#526740fe" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="526740fe"&gt;
So in what situations under this "equality of reclamation"
scenario would we legitimately choose to reclaim anonymous pages?
Here are, abstractly, some not uncommon scenarios:&lt;/blockquote&gt;
&lt;p&gt;那麼在這種「平等的可回收機遇」的情況下，讓我們選擇回收匿名頁面的行爲在何種場景中更合理呢？
抽象地說，比如在下述不算罕見的場景中：&lt;/p&gt;
&lt;a aria-controls="a12ad6df" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#a12ad6df" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="a12ad6df"&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;During initialisation, a long-running program may allocate and
use many pages. These pages may also be used as part of shutdown/cleanup,
but are not needed once the program is "started" (in an
application-specific sense). This is fairly common for daemons which
have significant dependencies to initialise.&lt;/li&gt;
&lt;li&gt;During the program's normal operation, we may allocate memory which is
only used rarely. It may make more sense for overall system performance
to require a &lt;strong&gt;major fault&lt;/strong&gt; to page these in from disk on demand,
instead using the memory for something else that's more important.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;程序初始化的時候，那些長期運行的程序可能要分配和使用很多頁面。這些頁面可能在最後的關閉/清理的
時候還需要使用，但是在程序「啓動」之後（以具體的程序相關的方式）持續運行的時候不需要訪問。
對後臺服務程序來說，很多後臺程序要初始化不少依賴庫，這種情況很常見。&lt;/li&gt;
&lt;li&gt;在程序的正常運行過程中，我們可能分配一些很少使用的內存。對整體系統性能而言可能比起讓這些內存頁
一直留在內存中，只有在用到的時候才按需把它們用 &lt;strong&gt;缺頁異常（major fault）&lt;/strong&gt; 換入內存，
可以空出更多內存留給更重要的東西。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="panel panel-default"&gt;
&lt;div class="panel-heading"&gt;
&lt;a class="reference external" href="https://www.youtube.com/watch?v=ikZ8_mRotT4"&gt;cgroupv2: Linux's new unified control group hierarchy (QCON London 2017)&lt;/a&gt;&lt;/div&gt;
&lt;div class="panel-body"&gt;
&lt;div align="left" class="youtube embed-responsive embed-responsive-16by9"&gt;&lt;iframe allow="fullscreen" class="embed-responsive-item" frameborder="0" src="https://www.youtube.com/embed/ikZ8_mRotT4"&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="id10"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#id27"&gt;考察有無交換區時會發生什麼&lt;/a&gt;&lt;/h2&gt;
&lt;a aria-controls="fc974b28" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#fc974b28" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="fc974b28"&gt;
Let's look at typical situations, and how they perform with and without
swap present. I talk about metrics around "memory contention" in my
&lt;a class="reference external" href="https://www.youtube.com/watch?v=ikZ8_mRotT4"&gt;talk on cgroup v2&lt;/a&gt; .&lt;/blockquote&gt;
&lt;p&gt;我們來看一些在常見場景中，有無交換區時分別會如何運行。
在我的 &lt;a class="reference external" href="https://www.youtube.com/watch?v=ikZ8_mRotT4"&gt;關於 cgroup v2 的演講&lt;/a&gt;
中探討過「內存競爭」的指標。&lt;/p&gt;
&lt;div class="section" id="id11"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#id28"&gt;在無/低內存競爭的狀態下&lt;/a&gt;&lt;/h3&gt;
&lt;a aria-controls="a577bbc2" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#a577bbc2" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="a577bbc2"&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;With swap:&lt;/strong&gt; We can choose to swap out rarely-used anonymous memory that
may only be used during a small part of the process lifecycle,
allowing us to use this memory to improve cache hit rate,
or do other optimisations.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Without swap&lt;/strong&gt; We cannot swap out rarely-used anonymous memory,
as it's locked in memory. While this may not immediately
present as a problem, on some workloads this may represent
a non-trivial drop in performance due to stale,
anonymous pages taking space away from more important use.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;有交换区:&lt;/strong&gt; 我們可以選擇換出那些只有在進程生存期內很小一部分時間會訪問的匿名內存，
這允許我們空出更多內存空間用來提升緩存命中率，或者做別的優化。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;無交換區:&lt;/strong&gt; 我們不能換出那些很少使用的匿名內存，因爲它們被鎖在了內存中。雖然這通常不會直接表現出問題，
但是在一些工作條件下這可能造成卡頓導致不平凡的性能下降，因爲匿名內存佔着空間不能給
更重要的需求使用。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="panel panel-default"&gt;
&lt;div class="panel-heading"&gt;
譯註：關於 &lt;strong&gt;內存熱度&lt;/strong&gt; 和 &lt;strong&gt;內存顛簸（thrash）&lt;/strong&gt;&lt;/div&gt;
&lt;div class="panel-body"&gt;
&lt;p&gt;討論內核中內存管理的時候經常會說到內存頁的 &lt;strong&gt;冷熱&lt;/strong&gt; 程度。這裏冷熱是指歷史上內存頁被訪問到的頻度，
內存管理的經驗在說，歷史上在近期頻繁訪問的 &lt;strong&gt;熱&lt;/strong&gt; 內存，在未來也可能被頻繁訪問，
從而應該留在物理內存裏；反之歷史上不那麼頻繁訪問的 &lt;strong&gt;冷&lt;/strong&gt; 內存，在未來也可能很少被用到，
從而可以考慮交換到磁盤或者丟棄文件緩存。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;顛簸（thrash）&lt;/strong&gt; 這個詞在文中出現多次但是似乎沒有詳細介紹，實際計算機科學專業的課程中應該有講過。
一段時間內，讓程序繼續運行所需的熱內存總量被稱作程序的工作集（workset），估算工作集大小，
換句話說判斷進程分配的內存頁中哪些屬於 &lt;strong&gt;熱&lt;/strong&gt; 內存哪些屬於 &lt;strong&gt;冷&lt;/strong&gt; 內存，是內核中
內存管理的最重要的工作。當分配給程序的內存大於工作集的時候，程序可以不需要等待I/O全速運行；
而當分配給程序的內存不足以放下整個工作集的時候，意味着程序每執行一小段就需要等待換頁或者等待
磁盤緩存讀入所需內存頁，產生這種情況的時候，從用戶角度來看可以觀察到程序肉眼可見的「卡頓」。
當系統中所有程序都內存不足的時候，整個系統都處於顛簸的狀態下，響應速度直接降至磁盤I/O的帶寬。
如本文所說，禁用交換區並不能防止顛簸，只是從等待換頁變成了等待文件緩存，
給程序分配超過工作集大小的內存才能防止顛簸。&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="id12"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#id29"&gt;在中/高內存競爭的狀態下&lt;/a&gt;&lt;/h3&gt;
&lt;a aria-controls="266f0b15" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#266f0b15" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="266f0b15"&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;With swap:&lt;/strong&gt; All memory types have an equal possibility of being reclaimed.
This means we have more chance of being able to reclaim pages
successfully – that is, we can reclaim pages that are not quickly
faulted back in again (thrashing).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Without swap&lt;/strong&gt; Anonymous pages are locked into memory as they have nowhere to go.
The chance of successful long-term page reclamation is lower,
as we have only some types of memory eligible to be reclaimed
at all. The risk of page thrashing is higher. The casual
reader might think that this would still be better as it might
avoid having to do disk I/O, but this isn't true –
we simply transfer the disk I/O of swapping to dropping
hot page caches and dropping code segments we need soon.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;有交换区:&lt;/strong&gt; 所有內存類型都有平等的被回收的可能性。這意味着我們回收頁面有更高的成功率——
成功回收的意思是說被回收的那些頁面不會在近期內被缺頁異常換回內存中（顛簸）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;無交換區:&lt;/strong&gt; 匿名內存因爲無處可去所以被鎖在內存中。長期內存回收的成功率變低了，因爲我們成體上
能回收的頁面總量少了。發生缺頁顛簸的危險性更高了。缺乏經驗的讀者可能覺得這某時也是好事，
因爲這能避免進行磁盤I/O，但是實際上不是如此——我們只是把交換頁面造成的磁盤I/O變成了扔掉熱緩存頁
和扔掉代碼段，這些頁面很可能馬上又要從文件中讀回來。&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="id13"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#id30"&gt;在臨時內存佔用高峰時&lt;/a&gt;&lt;/h3&gt;
&lt;a aria-controls="bf276ac9" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#bf276ac9" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="bf276ac9"&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;With swap:&lt;/strong&gt; We're more resilient to temporary spikes, but in cases of
severe memory starvation, the period from memory thrashing beginning
to the OOM killer may be prolonged. We have more visibility into the
instigators of memory pressure and can act on them more reasonably,
and can perform a controlled intervention.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Without swap&lt;/strong&gt; The OOM killer is triggered more quickly as anonymous
pages are locked into memory and cannot be reclaimed. We're more likely to
thrash on memory, but the time between thrashing and OOMing is reduced.
Depending on your application, this may be better or worse. For example,
a queue-based application may desire this quick transfer from thrashing
to killing. That said, this is still too late to be really useful –
the OOM killer is only invoked at moments of severe starvation,
and relying on this method for such behaviour would be better replaced
with more opportunistic killing of processes as memory contention
is reached in the first place.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;有交换区:&lt;/strong&gt; 我們對內存使用激增的情況更有抵抗力，但是在嚴重的內存不足的情況下，
從開始發生內存顛簸到 OOM 殺手開始工作的時間會被延長。內存壓力造成的問題更容易觀察到，
從而可能更有效地應對，或者更有機會可控地干預。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;無交換區:&lt;/strong&gt; 因爲匿名內存被鎖在內存中了不能被回收，所以 OOM 殺手會被更早觸發。
發生內存顛簸的可能性更大，但是發生顛簸之後到 OOM 解決問題的時間間隔被縮短了。
基於你的程序，這可能更好或是更糟。比如說，基於隊列的程序可能更希望這種從顛簸到殺進程的轉換更快發生。
即便如此，發生 OOM 的時機通常還是太遲於是沒什麼幫助——只有在系統極度內存緊缺的情況下才會請出
OOM 殺手，如果想依賴這種行爲模式，不如換成更早殺進程的方案，因爲在這之前已經發生內存競爭了。&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="id14"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#id31"&gt;好吧，所以我需要系統交換區，但是我該怎麼爲每個程序微調它的行爲？&lt;/a&gt;&lt;/h3&gt;
&lt;a aria-controls="7f9321c9" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#7f9321c9" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="7f9321c9"&gt;
You didn't think you'd get through this entire post without me plugging cgroup v2, did you? ;-)&lt;/blockquote&gt;
&lt;p&gt;你肯定想到了我寫這篇文章一定會在哪兒插點 cgroup v2 的安利吧？ ;-)&lt;/p&gt;
&lt;a aria-controls="3fcbadba" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#3fcbadba" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="3fcbadba"&gt;
Obviously, it's hard for a generic heuristic algorithm to be right all the time,
so it's important for you to be able to give guidance to the kernel.
Historically the only tuning you could do was at the system level,
using &lt;code class="code"&gt;
vm.swappiness&lt;/code&gt;
 . This has two problems: &lt;code class="code"&gt;
vm.swappiness&lt;/code&gt;

is incredibly hard to reason about because it only feeds in as
a small part of a larger heuristic system, and it also is system-wide
instead of being granular to a smaller set of processes.&lt;/blockquote&gt;
&lt;p&gt;顯然，要設計一種對所有情況都有效的啓發算法會非常難，所以給內核提一些指引就很重要。
歷史上我們只能在整個系統層面做這方面微調，通過 &lt;code class="code"&gt;
vm.swappiness&lt;/code&gt;
 。這有兩方面問題：
&lt;code class="code"&gt;
vm.swappiness&lt;/code&gt;
 的行爲很難準確控制，因爲它只是傳遞給一個更大的啓發式算法中的一個小參數；
並且它是一個系統級別的設置，沒法針對一小部分進程微調。&lt;/p&gt;
&lt;a aria-controls="dc0990d3" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#dc0990d3" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="dc0990d3"&gt;
You can also use &lt;code class="code"&gt;
mlock&lt;/code&gt;
 to lock pages into memory, but this requires
either modifying program code, fun with &lt;code class="code"&gt;
LD_PRELOAD&lt;/code&gt;
 , or doing
horrible things with a debugger at runtime.
In VM-based languages this also doesn't work very well, since you
generally have no control over allocation and end up having to
&lt;code class="code"&gt;
mlockall&lt;/code&gt;
 , which has no precision towards the pages
you actually care about.&lt;/blockquote&gt;
&lt;p&gt;你可以用 &lt;code class="code"&gt;
mlock&lt;/code&gt;
 把頁面鎖在內存裏，但是這要麼必須改程序代碼，或者折騰
&lt;code class="code"&gt;
LD_PRELOAD&lt;/code&gt;
 ，或者在運行期用調試器做一些魔法操作。對基於虛擬機的語言來說這種方案也不能
很好工作，因爲通常你沒法控制內存分配，最後得用上 &lt;code class="code"&gt;
mlockall&lt;/code&gt;

，而這個沒有辦法精確指定你實際上想鎖住的頁面。&lt;/p&gt;
&lt;a aria-controls="ee392737" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#ee392737" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="ee392737"&gt;
cgroup v2 has a tunable per-cgroup in the form of &lt;code class="code"&gt;
memory.low&lt;/code&gt;

, which allows us to tell the kernel to prefer other applications for
reclaim below a certain threshold of memory used. This allows us to not
prevent the kernel from swapping out parts of our application,
but prefer to reclaim from other applications under memory contention.
Under normal conditions, the kernel's swap logic is generally pretty good,
and allowing it to swap out pages opportunistically generally increases
system performance. Swap thrash under heavy memory contention is not ideal,
but it's more a property of simply running out of memory entirely than
a problem with the swapper. In these situations, you typically want to
fail fast by self-killing non-critical processes when memory pressure
starts to build up.&lt;/blockquote&gt;
&lt;p&gt;cgroup v2 提供了一套可以每個 cgroup 微調的 &lt;code class="code"&gt;
memory.low&lt;/code&gt;

，允許我們告訴內核說當使用的內存低於一定閾值之後優先回收別的程序的內存。這可以讓我們不強硬禁止內核
換出程序的一部分內存，但是當發生內存競爭的時候讓內核優先回收別的程序的內存。在正常條件下，
內核的交換邏輯通常還是不錯的，允許它有條件地換出一部分頁面通常可以改善系統性能。在內存競爭的時候
發生交換顛簸雖然不理想，但是這更多地是單純因爲整體內存不夠了，而不是因爲交換進程（swapper）導致的問題。
在這種場景下，你通常希望在內存壓力開始積攢的時候通過自殺一些非關鍵的進程的方式來快速退出（fail fast）。&lt;/p&gt;
&lt;a aria-controls="d789770a" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#d789770a" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="d789770a"&gt;
You can not simply rely on the OOM killer for this. The OOM killer is
only invoked in situations of dire failure when we've already entered
a state where the system is severely unhealthy and may well have been
so for a while. You need to opportunistically handle the situation yourself
before ever thinking about the OOM killer.&lt;/blockquote&gt;
&lt;p&gt;你不能依賴 OOM 殺手達成這個。 OOM 殺手只有在非常急迫的情況下纔會出動，那時系統已經處於極度不健康的
狀態了，而且很可能在這種狀態下保持了一陣子了。需要在開始考慮 OOM 殺手之前，積極地自己處理這種情況。&lt;/p&gt;
&lt;a aria-controls="fe0515e4" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#fe0515e4" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="fe0515e4"&gt;
Determination of memory pressure is somewhat difficult using traditional
Linux memory counters, though. We have some things which seem somewhat related,
but are merely tangential – memory usage, page scans, etc – and from these
metrics alone it's very hard to tell an efficient memory configuration
from one that's trending towards memory contention. There is a group of us
at Facebook, spearheaded by &lt;a class="reference external" href="https://patchwork.kernel.org/project/LKML/list/?submitter=45"&gt;Johannes&lt;/a&gt;
, working on developing new metrics that expose memory pressure more easily
that should help with this in future. If you're interested in hearing more
about this,
&lt;a class="reference external" href="https://youtu.be/ikZ8_mRotT4?t=2145"&gt;I go into detail about one metric being considered in my talk on cgroup v2&lt;/a&gt;.&lt;/blockquote&gt;
&lt;p&gt;不過，用傳統的 Linux 內存統計數據還是挺難判斷內存壓力的。我們有一些看起來相關的系統指標，但是都
只是支離破碎的——內存用量、頁面掃描，這些——單純從這些指標很難判斷系統是處於高效的內存利用率還是
在滑向內存競爭狀態。我們在 Facebook 有個團隊，由
&lt;a class="reference external" href="https://patchwork.kernel.org/project/LKML/list/?submitter=45"&gt;Johannes&lt;/a&gt;
牽頭，努力開發一些能評價內存壓力的新指標，希望能在今後改善目前的現狀。
如果你對這方面感興趣， &lt;a class="reference external" href="https://youtu.be/ikZ8_mRotT4?t=2145"&gt;在我的 cgroup v2 的演講中介紹到一個被提議的指標&lt;/a&gt;
。&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="id16"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#id32"&gt;調優&lt;/a&gt;&lt;/h2&gt;
&lt;div class="section" id="id17"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#id33"&gt;那麼，我需要多少交換空間？&lt;/a&gt;&lt;/h3&gt;
&lt;a aria-controls="5b5c883c" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#5b5c883c" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="5b5c883c"&gt;
In general, the minimum amount of swap space required for optimal
memory management depends on the number of anonymous pages pinned into
memory that are rarely reaccessed by an application, and the value of
reclaiming those anonymous pages. The latter is mostly a question of
which pages are no longer purged to make way for these infrequently
accessed anonymous pages.&lt;/blockquote&gt;
&lt;p&gt;通常而言，最優內存管理所需的最小交換空間取決於程序固定在內存中而又很少訪問到的匿名頁面的數量，
以及回收這些匿名頁面換來的價值。後者大體上來說是問哪些頁面不再會因爲要保留這些很少訪問的匿名頁面而
被回收掉騰出空間。&lt;/p&gt;
&lt;a aria-controls="f070dec0" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#f070dec0" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="f070dec0"&gt;
If you have a bunch of disk space and a recent (4.0+) kernel,
more swap is almost always better than less. In older kernels &lt;code class="code"&gt;
kswapd&lt;/code&gt;
,
one of the kernel processes responsible for managing swap, was historically
very overeager to swap out memory aggressively the more swap you had.
In recent times, swapping behaviour when a large amount of swap space is
available has been significantly improved. If you're running kernel 4.0+,
having a larger swap on a modern kernel should not result in overzealous
swapping. As such, if you have the space, having a swap size of a few GB
keeps your options open on modern kernels.&lt;/blockquote&gt;
&lt;p&gt;如果你有足夠大的磁盤空間和比較新的內核版本（4.0+），越大的交換空間基本上總是越好的。
更老的內核上 &lt;code class="code"&gt;
kswapd&lt;/code&gt;
 ， 內核中負責管理交換區的內核線程，在歷史上傾向於有越多交換空間就
急於交換越多內存出去。在最近一段時間，可用交換空間很大的時候的交換行爲已經改善了很多。
如果在運行 4.0+ 以後的內核，即便有很大的交換區在現代內核上也不會很激進地做交換。因此，
如果你有足夠的容量，現代內核上有個幾個 GB 的交換空間大小能讓你有更多選擇。&lt;/p&gt;
&lt;a aria-controls="d981b13d" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#d981b13d" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="d981b13d"&gt;
If you're more constrained with disk space, then the answer really
depends on the tradeoffs you have to make, and the nature of the environment.
Ideally you should have enough swap to make your system operate optimally
at normal and peak (memory) load. What I'd recommend is setting up a few
testing systems with 2-3GB of swap or more, and monitoring what happens
over the course of a week or so under varying (memory) load conditions.
As long as you haven't encountered severe memory starvation during that week
– in which case the test will not have been very useful – you will probably
end up with some number of MB of swap occupied. As such, it's probably worth
having at least that much swap available, in addition to a little buffer for
changing workloads. &lt;code class="code"&gt;
atop&lt;/code&gt;
 in logging mode can also show you which applications
are having their pages swapped out in the &lt;code class="code"&gt;
SWAPSZ&lt;/code&gt;
 column, so if you don't
already use it on your servers to log historic server state you probably
want to set it up on these test machines with logging mode as part of this
experiment. This also tells you when your application started swapping out
pages, which you can tie to log events or other key data.&lt;/blockquote&gt;
&lt;p&gt;如果你的磁盤空間有限，那麼答案更多取決於你願意做的取捨，以及運行的環境。理想上應該有足夠的交換空間
能高效應對正常負載和高峰（內存）負載。我建議先用 2-3GB 或者更多的交換空間搭個測試環境，
然後監視在不同（內存）負載條件下持續一週左右的情況。只要在那一週裏沒有發生過嚴重的內存不足——
發生了的話說明測試結果沒什麼用——在測試結束的時候大概會留有多少 MB 交換區佔用。
作爲結果說明你至少應該有那麼多可用的交換空間，再多加一些以應對負載變化。用日誌模式跑 &lt;code class="code"&gt;
atop&lt;/code&gt;

可以在 &lt;code class="code"&gt;
SWAPSZ&lt;/code&gt;
 欄顯示程序的頁面被交換出去的情況，所以如果你還沒用它記錄服務器歷史日誌的話
，這次測試中可以試試在測試機上用它記錄日誌。這也會告訴你什麼時候你的程序開始換出頁面，你可以用這個
對照事件日誌或者別的關鍵數據。&lt;/p&gt;
&lt;a aria-controls="c81cfdce" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#c81cfdce" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="c81cfdce"&gt;
Another thing worth considering is the nature of the swap medium.
Swap reads tend to be highly random, since we can't reliably predict
which pages will be refaulted and when. On an SSD this doesn't matter much,
but on spinning disks, random I/O is extremely expensive since it requires
physical movement to achieve. On the other hand, refaulting of file pages
is likely less random, since files related to the operation of a single
application at runtime tend to be less fragmented. This might mean that on
a spinning disk you may want to bias more towards reclaiming file pages
instead of swapping out anonymous pages, but again, you need to test and
evaluate how this balances out for your workload.&lt;/blockquote&gt;
&lt;p&gt;另一點值得考慮的是交換空間所在存儲設備的媒介。讀取交換區傾向於很隨機，因爲我們不能可靠預測什麼時候
什麼頁面會被再次訪問。在 SSD 上這不是什麼問題，但是在傳統磁盤上，隨機 I/O 操作會很昂貴，
因爲需要物理動作尋道。另一方面，重新加載文件緩存可能不那麼隨機，因爲單一程序在運行期的文件讀操作
一般不會太碎片化。這可能意味着在傳統磁盤上你想更多地回收文件頁面而不是換出匿名頁面，但仍舊，
你需要做測試評估在你的工作負載下如何取得平衡。&lt;/p&gt;
&lt;div class="panel panel-default"&gt;
&lt;div class="panel-heading"&gt;
譯註：關於休眠到磁盤時的交換空間大小&lt;/div&gt;
&lt;div class="panel-body"&gt;
原文這裏建議交換空間至少是物理內存大小，我覺得實際上不需要。休眠到磁盤的時候內核會寫回並丟棄
所有有文件作後備的可回收頁面，交換區只需要能放下那些沒有文件後備的頁面就可以了。
如果去掉文件緩存頁面之後剩下的已用物理內存總量能完整放入交換區中，就可以正常休眠。
對於桌面瀏覽器這種內存大戶，通常有很多緩存頁可以在休眠的時候丟棄。&lt;/div&gt;
&lt;/div&gt;
&lt;a aria-controls="34173e8b" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#34173e8b" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="34173e8b"&gt;
For laptop/desktop users who want to hibernate to swap, this also needs to
be taken into account – in this case your swap file should be at least
your physical RAM size.&lt;/blockquote&gt;
&lt;p&gt;對筆記本/桌面用戶如果想要休眠到交換區，這也需要考慮——這種情況下你的交換文件應該至少是物理內存大小。&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="swappiness"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#id34"&gt;我的 swappiness 應該如何設置？&lt;/a&gt;&lt;/h3&gt;
&lt;a aria-controls="02421253" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#02421253" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="02421253"&gt;
First, it's important to understand what &lt;code class="code"&gt;
vm.swappiness&lt;/code&gt;
 does.
&lt;code class="code"&gt;
vm.swappiness&lt;/code&gt;
 is a sysctl that biases memory reclaim either towards
reclamation of anonymous pages, or towards file pages. It does this using two
different attributes: &lt;code class="code"&gt;
file_prio&lt;/code&gt;
 (our willingness to reclaim file pages)
and &lt;code class="code"&gt;
anon_prio&lt;/code&gt;
 (our willingness to reclaim anonymous pages).
&lt;code class="code"&gt;
vm.swappiness`plays into this, as it becomes the default value for
:code:`anon_prio&lt;/code&gt;
, and it also is subtracted from the default value of 200
for &lt;code class="code"&gt;
file_prio&lt;/code&gt;
, which means for a value of &lt;code class="code"&gt;
vm.swappiness = 50&lt;/code&gt;
,
the outcome is that &lt;code class="code"&gt;
anon_prio&lt;/code&gt;
 is 50, and &lt;code class="code"&gt;
file_prio&lt;/code&gt;
 is 150
(the exact numbers don't matter as much as their relative weight compared to the other).&lt;/blockquote&gt;
&lt;p&gt;首先很重要的一點是，要理解 &lt;code class="code"&gt;
vm.swappiness&lt;/code&gt;
 是做什麼的。
&lt;code class="code"&gt;
vm.swappiness&lt;/code&gt;
 是一個 sysctl 用來控制在內存回收的時候，是優先回收匿名頁面，
還是優先回收文件頁面。內存回收的時候用兩個屬性： &lt;code class="code"&gt;
file_prio&lt;/code&gt;
 （回收文件頁面的傾向）
和 &lt;code class="code"&gt;
anon_prio&lt;/code&gt;
 （回收匿名頁面的傾向）。 &lt;code class="code"&gt;
vm.swappiness&lt;/code&gt;
 控制這兩個值，
因爲它是 &lt;code class="code"&gt;
anon_prio&lt;/code&gt;
 的默認值，然後也是默認 200 減去它之後 &lt;code class="code"&gt;
file_prio&lt;/code&gt;
 的默認值。
意味着如果我們設置 &lt;code class="code"&gt;
vm.swappiness = 50&lt;/code&gt;
 那麼結果是 &lt;code class="code"&gt;
anon_prio&lt;/code&gt;
 是 50，
&lt;code class="code"&gt;
file_prio&lt;/code&gt;
 是 150 （這裏數值本身不是很重要，重要的是兩者之間的權重比）。&lt;/p&gt;
&lt;div class="panel panel-default"&gt;
&lt;div class="panel-heading"&gt;
譯註：關於 SSD 上的 swappiness&lt;/div&gt;
&lt;div class="panel-body"&gt;
&lt;p&gt;原文這裏說 SSD 上 swap 和 drop page cache 差不多開銷所以 &lt;code class="code"&gt;
vm.swappiness = 100&lt;/code&gt;

。我覺得實際上要考慮 swap out 的時候會產生寫入操作，而 drop page cache 可能不需要寫入（
要看頁面是否是髒頁）。如果負載本身對I/O帶寬比較敏感，稍微調低 swappiness 可能對性能更好，
內核的默認值 60 是個不錯的默認值。以及桌面用戶可能對性能不那麼關心，反而更關心 SSD
的寫入壽命，雖然說 SSD 寫入壽命一般也足夠桌面用戶，不過調低 swappiness
可能也能減少一部分不必要的寫入（因爲寫回髒頁是必然會發生的，而寫 swap 可以避免）。
當然太低的 swappiness 會對性能有負面影響（因爲太多匿名頁面留在物理內存裏而降低了緩存命中率）
，這裏的權衡也需要根據具體負載做測試。&lt;/p&gt;
&lt;p&gt;另外澄清一點誤解， swap 分區還是 swap 文件對系統運行時的性能而言沒有差別。或許有人會覺得
swap 文件要經過文件系統所以會有性能損失，在譯文之前譯者說過 Linux 的內存管理子系統基本上獨立於文件系統。
實際上 Linux 上的 swapon 在設置 swap 文件作爲交換空間的時候會讀取一次文件系統元數據，
確定 swap 文件在磁盤上的地址範圍，隨後運行的過程中做交換就和文件系統無關了。關於 swap
空間是否連續的影響，因爲 swap 讀寫基本是頁面單位的隨機讀寫，所以即便連續的 swap 空間（swap
分區）也並不能改善 swap 的性能。希疏文件的地址範圍本身不連續，寫入希疏文件的空洞需要
文件系統分配磁盤空間，所以在 Linux 上交換文件不能是希疏文件。只要不是希疏文件，
連續的文件內地址範圍在磁盤上是否連續（是否有文件碎片）基本不影響能否 swapon 或者使用 swap 時的性能。&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;a aria-controls="b2897dd9" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#b2897dd9" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="b2897dd9"&gt;
This means that, in general, &lt;code class="code"&gt;
vm.swappiness&lt;/code&gt;
&lt;strong&gt;is simply a ratio of how&lt;/strong&gt;
&lt;strong&gt;costly reclaiming and refaulting anonymous memory is compared to file memory&lt;/strong&gt;
&lt;strong&gt;for your hardware and workload&lt;/strong&gt;. The lower the value, the more you tell the
kernel that infrequently accessed anonymous pages are expensive to swap out
and in on your hardware. The higher the value, the more you tell the kernel
that the cost of swapping anonymous pages and file pages is similar on your
hardware. The memory management subsystem will still try to mostly decide
whether it swaps file or anonymous pages based on how hot the memory is,
but swappiness tips the cost calculation either more towards swapping or
more towards dropping filesystem caches when it could go either way.
On SSDs these are basically as expensive as each other, so setting
&lt;code class="code"&gt;
vm.swappiness = 100&lt;/code&gt;
 (full equality) may work well.
On spinning disks, swapping may be significantly more expensive since
swapping in generally requires random reads, so you may want to
bias more towards a lower value.&lt;/blockquote&gt;
&lt;p&gt;這意味着，通常來說 &lt;code class="code"&gt;
vm.swappiness&lt;/code&gt;
&lt;strong&gt;只是一個比例，用來衡量在你的硬件和工作負載下，&lt;/strong&gt;
&lt;strong&gt;回收和換回匿名內存還是文件內存哪種更昂貴&lt;/strong&gt; 。設定的值越低，你就是在告訴內核說換出那些不常訪問的
匿名頁面在你的硬件上開銷越昂貴；設定的值越高，你就是在告訴內核說在你的硬件上交換匿名頁和
文件緩存的開銷越接近。內存管理子系統仍然還是會根據實際想要回收的內存的訪問熱度嘗試自己決定具體是
交換出文件還是匿名頁面，只不過 swappiness 會在兩種回收方式皆可的時候，在計算開銷權重的過程中左右
是該更多地做交換還是丟棄緩存。在 SSD 上這兩種方式基本上是同等開銷，所以設成
&lt;code class="code"&gt;
vm.swappiness = 100&lt;/code&gt;
 （同等比重）可能工作得不錯。在傳統磁盤上，交換頁面可能會更昂貴，
因爲通常需要隨機讀取，所以你可能想要設低一些。&lt;/p&gt;
&lt;a aria-controls="916d89d2" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#916d89d2" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="916d89d2"&gt;
The reality is that most people don't really have a feeling about which
their hardware demands, so it's non-trivial to tune this value based on
instinct alone – this is something that you need to test using different
values. You can also spend time evaluating the memory composition of your
system and core applications and their behaviour under mild memory reclamation.&lt;/blockquote&gt;
&lt;p&gt;現實是大部分人對他們的硬件需求沒有什麼感受，所以根據直覺調整這個值可能挺困難的 ——
你需要用不同的值做測試。你也可以花時間評估一下你的系統的內存分配情況和核心應用在大量回收內存的時候的行爲表現。&lt;/p&gt;
&lt;a aria-controls="1716806c" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#1716806c" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="1716806c"&gt;
When talking about &lt;code class="code"&gt;
vm.swappiness&lt;/code&gt;
 , an extremely important change to
consider from recent(ish) times is
&lt;a class="reference external" href="https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/patch/?id=fe35004fbf9eaf67482b074a2e032abb9c89b1dd"&gt;this change to vmscan by Satoru Moriya in 2012&lt;/a&gt;
, which changes the way that &lt;code class="code"&gt;
vm.swappiness = 0&lt;/code&gt;
 is handled
quite significantly.&lt;/blockquote&gt;
&lt;p&gt;討論 &lt;code class="code"&gt;
vm.swappiness&lt;/code&gt;
 的時候，一個極爲重要需要考慮的修改是（相對）近期在
&lt;a class="reference external" href="https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/patch/?id=fe35004fbf9eaf67482b074a2e032abb9c89b1dd"&gt;2012 年左右 Satoru Moriya 對 vmscan 行爲的修改&lt;/a&gt;
，它顯著改變了代碼對 &lt;code class="code"&gt;
vm.swappiness = 0&lt;/code&gt;
 這個值的處理方式。&lt;/p&gt;
&lt;a aria-controls="0d73ddd8" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#0d73ddd8" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="0d73ddd8"&gt;
Essentially, the patch makes it so that we are extremely biased against
scanning (and thus reclaiming) any anonymous pages at all with
&lt;code class="code"&gt;
vm.swappiness = 0&lt;/code&gt;
 , unless we are already encountering severe
memory contention. As mentioned previously in this post, that's generally
not what you want, since this prevents equality of reclamation prior to
extreme memory pressure occurring, which may actually lead to this
extreme memory pressure in the first place. &lt;code class="code"&gt;
vm.swappiness = 1&lt;/code&gt;

is the lowest you can go without invoking the special casing for
anonymous page scanning implemented in that patch.&lt;/blockquote&gt;
&lt;p&gt;基本上來說這個補丁讓我們在 &lt;code class="code"&gt;
vm.swappiness = 0&lt;/code&gt;
 的時候會極度避免掃描（進而回收）匿名頁面，
除非我們已經在經歷嚴重的內存搶佔。就如本文前面所屬，這種行爲基本上不會是你想要的，
因爲這種行爲會導致在發生內存搶佔之前無法保證內存回收的公平性，這甚至可能是最初導致發生內存搶佔的原因。
想要避免這個補丁中對掃描匿名頁面的特殊行爲的話， &lt;code class="code"&gt;
vm.swappiness = 1&lt;/code&gt;
 是你能設置的最低值。&lt;/p&gt;
&lt;a aria-controls="8283fab5" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#8283fab5" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="8283fab5"&gt;
The kernel default here is &lt;code class="code"&gt;
vm.swappiness = 60&lt;/code&gt;
. This value is
generally not too bad for most workloads, but it's hard to have a
general default that suits all workloads. As such, a valuable extension
to the tuning mentioned in the "how much swap do I need" section above
would be to test these systems with differing values for &lt;code class="code"&gt;
vm.swappiness&lt;/code&gt;

, and monitor your application and system metrics under heavy (memory) load.
Some time in the near future, once we have a decent implementation of
&lt;a class="reference external" href="https://youtu.be/ikZ8_mRotT4?t=2145"&gt;refault detection&lt;/a&gt; in the kernel,
you'll also be able to determine this somewhat workload-agnostically by
looking at cgroup v2's page refaulting metrics.&lt;/blockquote&gt;
&lt;p&gt;內核在這裏設置的默認值是 &lt;code class="code"&gt;
vm.swappiness = 60&lt;/code&gt;
 。這個值對大部分工作負載來說都不會太壞，
但是很難有一個默認值能符合所有種類的工作負載。因此，對上面「 &lt;a class="reference internal" href="#id17"&gt;那麼，我需要多少交換空間？&lt;/a&gt;
」那段討論的一點重要擴展可以說，在測試系統中可以嘗試使用不同的 &lt;code class="code"&gt;
vm.swappiness&lt;/code&gt;

，然後監視你的程序和系統在重（內存）負載下的性能指標。在未來某天，如果我們在內核中有了合理的
&lt;a class="reference external" href="https://youtu.be/ikZ8_mRotT4?t=2145"&gt;缺頁檢測&lt;/a&gt; ，你也將能通過 cgroup v2 的頁面缺頁
指標來以負載無關的方式決定這個。&lt;/p&gt;
&lt;div class="panel panel-default"&gt;
&lt;div class="panel-heading"&gt;
&lt;a class="reference external" href="https://www.youtube.com/watch?v=beefUhRH5lU"&gt;SREcon19 Asia/Pacific - Linux Memory Management at Scale: Under the Hood&lt;/a&gt;&lt;/div&gt;
&lt;div class="panel-body"&gt;
&lt;div align="left" class="youtube embed-responsive embed-responsive-16by9"&gt;&lt;iframe allow="fullscreen" class="embed-responsive-item" frameborder="0" src="https://www.youtube.com/embed/beefUhRH5lU"&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="id19"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#id35"&gt;2019年07月更新：內核 4.20+ 中的內存壓力指標&lt;/a&gt;&lt;/h3&gt;
&lt;a aria-controls="2cbb410f" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#2cbb410f" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="2cbb410f"&gt;
The refault metrics mentioned as in development earlier are now in the
kernel from 4.20 onwards and can be enabled with &lt;code class="code"&gt;
CONFIG_PSI=y&lt;/code&gt;

. See my talk at SREcon at around the 25:05 mark:&lt;/blockquote&gt;
&lt;p&gt;前文中提到的開發中的內存缺頁檢測指標已經進入 4.20+ 以上版本的內核，可以通過
&lt;code class="code"&gt;
CONFIG_PSI=y&lt;/code&gt;
 開啓。詳情參見我在 SREcon 大約 25:05 左右的討論。&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="id20"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#id36"&gt;結論&lt;/a&gt;&lt;/h2&gt;
&lt;a aria-controls="01142bf6" aria-expanded="false" class="translate-collapse-btn" data-toggle="collapse" href="#01142bf6" role="button"&gt;
&lt;span class="badge badge-pill badge-light"&gt;&lt;i class="fa fa-language"&gt;&lt;/i&gt; &lt;/span&gt;&lt;/a&gt;&lt;blockquote class="collapse" id="01142bf6"&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Swap is a useful tool to allow equality of reclamation of memory pages,
but its purpose is frequently misunderstood, leading to its negative
perception across the industry. If you use swap in the spirit intended,
though – as a method of increasing equality of reclamation – you'll
find that it's a useful tool instead of a hindrance.&lt;/li&gt;
&lt;li&gt;Disabling swap does not prevent disk I/O from becoming a problem under
memory contention, it simply shifts the disk I/O thrashing from anonymous
pages to file pages. Not only may this be less efficient, as we have
a smaller pool of pages to select from for reclaim, but it may also
contribute to getting into this high contention state in the first place.&lt;/li&gt;
&lt;li&gt;Swap can make a system slower to OOM kill, since it provides another,
slower source of memory to thrash on in out of memory situations – the
OOM killer is only used by the kernel as a last resort, after things have
already become monumentally screwed. The solutions here depend on your system:&lt;ul&gt;
&lt;li&gt;You can opportunistically change the system workload depending on
cgroup-local or global memory pressure. This prevents getting into these
situations in the first place, but solid memory pressure metrics are
lacking throughout the history of Unix. Hopefully this should be
better soon with the addition of refault detection.&lt;/li&gt;
&lt;li&gt;You can bias reclaiming (and thus swapping) away from certain processes
per-cgroup using memory.low, allowing you to protect critical daemons
without disabling swap entirely.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;交換區是允許公平地回收內存的有用工具，但是它的目的經常被人誤解，導致它在業內這種負面聲譽。如果
你是按照原本的目的使用交換區的話——作爲增加內存回收公平性的方式——你會發現它是很有效的工具而不是阻礙。&lt;/li&gt;
&lt;li&gt;禁用交換區並不能在內存競爭的時候防止磁盤I/O的問題，它只不過把匿名頁面的磁盤I/O變成了文件頁面的
磁盤I/O。這不僅更低效，因爲我們回收內存的時候能選擇的頁面範圍更小了，而且它可能是導致高度內存競爭
狀態的元兇。&lt;/li&gt;
&lt;li&gt;有交換區會導致系統更慢地使用 OOM 殺手，因爲在缺少內存的情況下它提供了另一種更慢的內存，
會持續地內存顛簸——內核調用 OOM 殺手只是最後手段，會晚於所有事情已經被搞得一團糟之後。
解決方案取決於你的系統：&lt;ul&gt;
&lt;li&gt;你可以預先更具每個 cgroup 的或者系統全局的內存壓力改變系統負載。這能防止我們最初進入內存競爭
的狀態，但是 Unix 的歷史中一直缺乏可靠的內存壓力檢測方式。希望不久之後在有了
&lt;a class="reference external" href="https://youtu.be/ikZ8_mRotT4?t=2145"&gt;缺頁檢測&lt;/a&gt; 這樣的性能指標之後能改善這一點。&lt;/li&gt;
&lt;li&gt;你可以使用 &lt;code class="code"&gt;
memory.low&lt;/code&gt;
 讓內核不傾向於回收（進而交換）特定一些 cgroup 中的進程，
允許你在不禁用交換區的前提下保護關鍵後臺服務。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr class="docutils"/&gt;
&lt;p&gt;感謝在撰寫本文時 &lt;a class="reference external" href="https://github.com/rahulg"&gt;Rahul&lt;/a&gt; ，
&lt;a class="reference external" href="https://github.com/htejun"&gt;Tejun&lt;/a&gt; 和
&lt;a class="reference external" href="https://patchwork.kernel.org/project/LKML/list/?submitter=45"&gt;Johannes&lt;/a&gt;
提供的諸多建議和反饋。&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"></category><category term="swap"></category><category term="mm"></category><category term="memory management"></category><category term="translate"></category><category term="swappiness"></category></entry></feed>