不知何故,我碰巧换掉了 14 GB 的内存。杀了罪魁祸首后,我又有了大量的空闲内存,所以我想我可以再把重要的数据带进来。因此,使用了 32 GB 中的 5 GB 和使用了 14 GB 的交换空间,我跑了swapoff -a…… 4 小时后,大约一半的工作完成了。
这意味着少于 1 MB/s,而我可以轻松复制 200 MB/s。我的交换是加密的,但所有普通分区也是如此,使用aes-ni它不会导致明显的 CPU 负载(填充交换空间只需要几分钟)。我看到优化没有特殊原因swapoff,但是我想知道它怎么会变慢?
只需添加更多数据:我的主内存是 32 GB,我在 4 个硬盘中的每一个上都有 32 GB 的交换空间(当然是矫枉过正,但谁在乎?)。整个交换空间可以在不到 5 分钟的时间内被(解密和)读取:
time -p sudo sh -c 'for i in /dev/mapper/cryptswap?; do md5sum $i & done; wait'
014a2b7ef300e11094134785e1d882af /dev/mapper/cryptswap1
a6d8ef09203c1d8d459109ff93b6627c /dev/mapper/cryptswap4
05aff81f8d276ddf07cf26619726a405 /dev/mapper/cryptswap3
e7f606449327b9a016e88d46049c0c9a /dev/mapper/cryptswap2
real 264.27
Run Code Online (Sandbox Code Playgroud)
读取分区的一部分不会比读取全部更慢。然而,阅读大约 1/10 的时间需要大约 100 倍的时间。
我观察到,在swapoffCPU 大部分时间都处于空闲状态(可能是一个内核的 10%),磁盘也是如此(由 LED“测量”)。我还看到交换空间一个接一个关闭。
Jim*_*ris 65
首先,让我们看看您对硬盘的期望。您的硬盘驱动器可以连续运行200 MB/s 。当你寻求因素在时间,也可以是多慢。要选择任意示例,请查看希捷现代 3TB 磁盘之一ST3000DM001 的规格:
最大持续数据速率:210 MB/s
寻求平均读数:<8.5 毫秒
每个扇区的字节数:4,096
如果您从不需要搜索,并且您的交换位于磁盘边缘附近,您可以期望看到最大速率 = 210 MB/s
但是,如果您的交换数据完全零散,在最坏的情况下,您需要四处寻找您读取的每个扇区。这意味着您每 8.5 毫秒只能读取 4 KB,或 4 KB / 0.0085 = 470 KB/s
因此,立即开始,您实际上正在与硬盘驱动器速度抗衡并非不可想象。
也就是说,swapoff运行如此缓慢并且必须乱序读取页面似乎很愚蠢,特别是如果它们被快速写入(这意味着有序)。但这可能就是内核的工作方式。Ubuntu 错误报告#486666讨论了同样的问题:
The swap is being removed at speed of 0.5 MB/s, while the
hard drive speed is 60 MB/s;
No other programs are using harddrive a lot, system is not under
high load etc.
Ubuntu 9.10 on quad core.
Swap partition is encrypted.
Top (atop) shows near 100% hard drive usage
DSK | sdc | busy 88% | read 56 | write 0 | avio 9 ms |
but the device transfer is low (kdesysguard)
0.4 MiB/s on /dev/sdc reads, and 0 on writes
Run Code Online (Sandbox Code Playgroud)
其中一个答复是:
It takes a long time to sort out because it has to rearrange and flush the
memory, as well as go through multiple decrypt cycles, etc. This is quite
normal
Run Code Online (Sandbox Code Playgroud)
错误报告已关闭未解决。
Mel Gorman 的书“ Understanding the Linux Virtual Memory Manager ”有点过时,但同意这是一个缓慢的操作:
可以预见,负责停用区域的函数称为
sys_swapoff()。此功能主要与更新swap_info_struct. 在每个分页页面中进行分页的主要任务try_to_unuse()是 非常昂贵的责任。
从 2007 年开始,在 linux-kernel 邮件列表上有更多讨论,主题是“加速交换”——尽管他们讨论的速度比您看到的要高一些。
这是一个有趣的问题,可能会被普遍忽略,因为swapoff很少使用。我认为,如果您真的想追踪它,第一步是尝试更仔细地观察您的磁盘使用模式(可能使用atop、iostat,或者甚至更强大的工具,如perf或systemtap)。要查找的内容可能是过度搜索、小的 I/O 操作、不断的重写和数据移动等。
Nic*_*ood 43
我的带有 SSD 的笔记本电脑遇到了同样的问题,因此搜索时间应该不是问题。
我找到了另一种解释。这是摘录
现在的工作方式是,swapoff 查看交换分区中每个换出的内存页,并尝试找到所有使用它的程序。如果它不能立即找到它们,它将查看正在运行的每个程序的页表以找到它们。在最坏的情况下,它将检查分区中每个换出页的所有页表。没错——同样的页表会被一遍又一遍地检查。
所以这是一个内核问题而不是其他任何问题。
kub*_*zyk 29
swapoff是的,这个机制非常低效。解决方法很简单:迭代进程,而不是迭代交换的页面。使用这个 python 脚本(我不隶属于):
git clone https://github.com/wiedemannc/deswappify-auto
cd ./deswappify-auto
sudo python3 deswappify_auto.py -d -v info
Run Code Online (Sandbox Code Playgroud)
请注意,守护进程操作模式仅适用于经常休眠的台式机/笔记本电脑。我不会将它作为服务器系统上的守护进程运行 - 只需运行一段时间,等到它报告它处理了一些进程然后停止它并尝试:
swapoff /dev/x
Run Code Online (Sandbox Code Playgroud)
由于大多数页面现在都存在于交换和内存中,因此swapoff几乎没有什么可做的,现在应该非常快(我看到数百 MB/s)。
前面提到的 python 脚本基于这个答案的其余部分,这反过来是我对jlong撰写的这个旧答案的改进。由于脚本要安全得多,我建议仅尝试将其余答案作为最后一道防线:
perl -we 'for(`ps -e -o pid,args`) { if(m/^ *(\d+) *(.{0,40})/) { $pid=$1; $desc=$2; if(open F, "/proc/$pid/smaps") { while(<F>) { if(m/^([0-9a-f]+)-([0-9a-f]+) /si){ $start_adr=$1; $end_adr=$2; } elsif(m/^Swap:\s*(\d\d+) *kB/s){ print "SSIZE=$1_kB\t gdb --batch --pid $pid -ex \"dump memory /dev/null 0x$start_adr 0x$end_adr\"\t2>&1 >/dev/null |grep -v debug\t### $desc \n" }}}}}' | sort -Vr | head
Run Code Online (Sandbox Code Playgroud)
这可能会运行 2 秒,实际上不会做任何事情,只列出前 10 个内存段(实际上它打印了更多的单行;是的,我确实喜欢单行;只需检查命令,接受风险,复制并粘贴到您的外壳;这些实际上将从交换中读取)。
...Paste the generated one-liners...
swapoff /your/swap # much faster now
Run Code Online (Sandbox Code Playgroud)
主要的单行是安全的(对我来说),除了它读取很多 /proc。
为您手动检查准备的子命令不安全。每个命令将在从交换区读取内存段期间挂起一个进程。因此,不能容忍任何暂停的进程是不安全的。我看到的传输速度大约为每分钟 1 GB。(前面提到的python 脚本消除了该缺陷)。
另一个危险是对系统施加过多的内存压力,因此请检查通常的 free -m
它有什么作用?
for(`ps -e -o pid,args`) {
if(m/^ *(\d+) *(.{0,40})/) {
$pid=$1;
$desc=$2;
if(open F, "/proc/$pid/smaps") {
while(<F>) {
if(m/^([0-9a-f]+)-([0-9a-f]+) /si){
$start_adr=$1;
$end_adr=$2;
} elsif( m/^Swap:\s*(\d\d+) *kB/s ){
print "SSIZE=$1_kB\t gdb --batch --pid $pid -ex \"dump memory /dev/null 0x$start_adr 0x$end_adr\"\t2>&1 >/dev/null |grep -v debug\t### $desc \n"
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
这个 perl 脚本的输出是一系列gdb命令dump memory (range),这些命令将交换的页面重新调用到内存中。
输出从大小开始,因此很容易通过它| sort -Vr | head来获得按大小 (SSIZE) 排列的前 10 个最大段。该-V代表版本号,适当的排序,但它适合我的目的。我无法弄清楚如何使数字排序工作。
小智 11
在交换期间,如果检测到正在使用的交换槽,则内核首先在页面中进行交换。然后函数 unuse_process() 尝试找到与刚刚换入的页面相对应的所有页表条目,并对页表进行必要的更新。搜索是详尽且非常耗时的:它访问(整个系统的)每个内存描述符并逐个检查其页表条目。
请参阅“了解 Linux Kernel 3rd 版本”的第 724 页。