swapoff 怎么这么慢?

swapoff 怎么这么慢?

不知何故,我碰巧换出了 14 GB 内存。杀死罪魁祸首后,我又拥有了大量的空闲内存,所以我想我可以引入重要数据再次。因此,使用了 32 GB 中的 5 GB 和 14 GB 交换空间,我运行了swapoff -a......4 小时后,大约完成了一半的工作。

这意味着不到 1 MB/秒,而我可以轻松复制 200 MB/秒。我的交换区已加密,但所有普通分区也是如此埃斯尼它不会导致明显的 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

读取分区的一部分不会比读取全部慢。然而,阅读大约 1/10 的时间大约要花 100 倍的时间。

我观察到,在swapoff这两个过程中,CPU 大部分处于空闲状态(可能是一个核心的 10%),磁盘也是如此(通过 LED“测量”)。我还看到交换空间相继被关闭。

答案1

首先,让我们看看您对硬盘有何期望。您的硬盘可以达到 200 MB/s依次地。当您考虑寻道时间时,它可以是很多慢点。举一个任意的例子,看看 Seagate 的一款现代 3TB 磁盘的规格,ST3000DM001:

  • 最大持续数据速率:210 MB/s

  • 寻道平均读取:<8.5 ms

  • 每个扇区的字节数:4,096

如果您从不需要查找,并且您的交换位于磁盘边缘附近,则您可以期望看到最大速率 =210MB/秒

但是,如果您的交换数据完全分散,在最坏的情况下,您需要四处寻找您读取的每个扇区。这意味着每 8.5 毫秒只能读取 4 KB,即 4 KB / 0.0085 =470 KB/秒

所以马上,它不是不可思议您实际上正在运行硬盘驱动器的速度。


也就是说,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

其中一条回复是:

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

该错误报告已关闭,未解决。

梅尔·戈尔曼的书《了解 Linux 虚拟内存管理器”有点过时了,但同意这是一个缓慢的操作:

不出所料,负责停用某个区域的函数称为sys_swapoff()。该功能主要涉及更新swap_info_struct.每个调出页中分页的主要任务是其职责try_to_unuse()极其昂贵的。

从 2007 年开始,linux-kernel 邮件列表上就主题“进行了更多讨论”加速交换“——尽管他们讨论的速度比你看到的要高一些。


这是一个有趣的问题,可能会被普遍忽视,因为swapoff很少使用。我认为,如果您真的想追踪它,第一步就是尝试更仔细地观察您的磁盘使用模式(也许使用atopiostat,甚至更强大的工具,如perfsystemtap)。要查找的内容可能是过度查找、小 I/O 操作、不断重写和数据移动等。

答案2

我的笔记本电脑也遇到了同样的问题,它配备了 SSD,所以寻道时间应该不是问题。

我发现另一种解释。以下是摘录

按照现在的工作方式,swapoff 查看交换分区中每个换出的内存页面,并尝试找到所有使用它的程序。如果它不能立即找到它们,它将查看正在运行的每个程序的页表来查找它们。在最坏的情况下,它将检查分区中每个换出页面的所有页表。没错——相同的页表会被一遍又一遍地检查。

所以这是一个内核问题而不是其他问题。

答案3

是的,这个swapoff机制效率极其低下。解决方法很简单:迭代进程,而不是迭代交换的页面。使用这个 python 脚本(我不隶属):

git clone https://github.com/wiedemannc/deswappify-auto
cd ./deswappify-auto
sudo python3 deswappify_auto.py -d -v info

请注意,守护进程操作模式仅适用于经常休眠的台式机/笔记本电脑。我不会将它作为服务器系统上的守护进程运行 - 只需运行它一段时间,等到它报告它处理了一些进程,然后停止它并尝试:

swapoff /dev/x

由于大多数页面现在都存在于交换区和内存中,因此swapoff几乎没有什么可做的,而且现在应该非常快(我看到数百MB/s)。

前面的历史部分

前面提到的 python 脚本基于这个答案的其余部分,这反过来又是我的改进这个较旧的答案作者:吉龙。由于脚本更安全,我建议只尝试我的其余答案作为最后一道防线:

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

这可能会运行 2 秒,实际上不会做任何事情,只是列出前 10 个内存段(实际上它会打印更多一行行;是的,我喜欢俏皮话;只需检查命令,接受风险,复制并粘贴到您的 shell 中即可;这些实际上将从交换中读取)。

...Paste the generated one-liners...
swapoff /your/swap    # much faster now

主要的一行代码(对我来说)是安全的,除了它读取大量 /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" 
        }
      }
    }
  }
}

这个 perl 脚本的输出是一系列gdb命令dump memory (range),这些命令将交换的页面调用到内存中。

输出从大小开始,因此很容易通过它来| sort -Vr | head获取按大小 (SSIZE) 排列的前 10 个最大的段。代表-V版本号适合的排序,但它适合我的目的。我不知道如何进行数字排序。

答案4

在交换期间,如果检测到正在使用的交换槽,则内核首先交换该页面。然后,函数 unuse_process() 尝试查找与刚刚换入的页面相对应的所有页表条目,并对页表进行必要的更新。搜索是详尽且非常耗时的:它访问(整个系统的)每个内存描述符并一一检查其页表条目。

请参考《了解Linux内核第3版》第724页。

相关内容