linux高内核cpu使用率在内存初始化上

我有一个由Linux内核高CPU消耗的问题,而引导我的Java应用程序在服务器上。 这个问题只发生在生产中,在开发服务器上一切都是光速的。

upd9:关于这个问题有两个问题:

  1. 如何解决它? – 名义动物build议同步和放下一切,这真的有帮助。 sudo sh -c 'sync ; echo 3 > /proc/sys/vm/drop_caches ; 作品。 upd12:但确实sync就足够了。

  2. 为什么这发生? – 对我来说还是开放的,我知道刷到磁盘的消耗CPU和IO的时间是正常的。 但是,为什么即使用“C”编写的单线程应用程序,我的内核空间中的所有核心都加载了100%?

由于ref-upd10ref-upd11,我有一个想法, echo 3 > /proc/sys/vm/drop_caches不需要解决我的问题与缓慢的内存分配。 开始消耗内存的应用程序之前 ,运行“sync”应该足够了。 大概会在这里尝试这个明天的生产和发布结果。

upd10: FScaching失去页面大小写:

  1. 然后我执行cat 10GB.fiel > /dev/null
  2. sync确定,没有durty页面( cat /proc/meminfo |grep ^Dirty显示184kb。
  3. 检查cat /proc/meminfo |grep ^Cached我得到:4GBcaching
  4. 运行int main(char**)我得到了正常的性能(如50ms初始化32MB的分配数据)。
  5. caching内存减less到900MB
  6. testing总结: 我认为linux将回收用作FScaching的页面分配到分配的内存是没有问题的。

upd11:大量脏页面案例。

  1. 项目清单

  2. 我运行了我的HowMongoDdWorks示例和评论read部分,并在一段时间后

  3. /proc/meminfo说,2.8GB是Dirty ,3.6GB是Cached

  4. 我停止了HowMongoDdWorks并运行我的int main(char**)

  5. 这是结果的一部分:

    初始化15,时间0.00sx 0 [尝试1 /部分0]时间1.11sx 1 [尝试2 /部分0]时间0.04sx 0 [尝试1 /部分1]时间1.04sx 1 [尝试2 /部分1]时间0.05sx 0 [尝试1 /第2部分]时间0.42sx 1 [尝试2 /第2部分]时间0.04s

  6. 通过testing总结: 丢失页面大大减缓了第一次访问分配的内存(公平地说,这只有当总的应用程序内存开始与整个OS内存相比时才会发生,也就是说如果你有8 GB的空闲空间,分配1GB是没有问题的,从3GB左右放慢starst)。

现在我设法在我的开发环境中重现这种情况,所以这里是新的细节。

开发机器configuration

  1. Linux 2.6.32-220.13.1.el6.x86_64 – 科学Linux版本6.1(碳)
  2. 内存:15.55 GB
  3. CPU:1 X Intel(R)Core(TM)i5-2300 CPU @ 2.80GHz(4线程)(物理)

FScaching中大量的durty页面导致的问题的99.9%。 这是在脏页面上创build大量的应用程序:

 import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; import java.util.Random; /** * @author dmitry.mamonov * Created: 10/2/12 2:53 PM */ public class HowMongoDdWorks{ public static void main(String[] args) throws IOException { final long length = 10L*1024L*1024L*1024L; final int pageSize = 4*1024; final int lengthPages = (int) (length/pageSize); final byte[] buffer = new byte[pageSize]; final Random random = new Random(); System.out.println("Init file"); final RandomAccessFile raf = new RandomAccessFile("random.file","rw"); raf.setLength(length); int written = 0; int readed = 0; System.out.println("Test started"); while(true){ { //write. random.nextBytes(buffer); final long randomPageLocation = (long)random.nextInt(lengthPages)*(long)pageSize; raf.seek(randomPageLocation); raf.write(buffer); written++; } { //read. random.nextBytes(buffer); final long randomPageLocation = (long)random.nextInt(lengthPages)*(long)pageSize; raf.seek(randomPageLocation); raf.read(buffer); readed++; } if (written % 1024==0 || readed%1024==0){ System.out.printf("W %10d R %10d pages\n", written, readed); } } } } 

这里是testing应用程序,它会导致内核空间中的HI(高达100%的所有内核)CPU负载(与以下相同,但我将再次复制它)。

 #include<stdlib.h> #include<stdio.h> #include<time.h> int main(char** argv){ int last = clock(); //remember the time for(int i=0;i<16;i++){ //repeat test several times int size = 256 * 1024 * 1024; int size4=size/4; int* buffer = malloc(size); //allocate 256MB of memory for(int k=0;k<2;k++){ //initialize allocated memory twice for(int j=0;j<size4;j++){ //memory initialization (if I skip this step my test ends in buffer[j]=k; 0.000s } //printing printf(x "[%d] %.2f\n",k+1, (clock()-last)/(double)CLOCKS_PER_SEC); stat last = clock(); } } return 0; } 

当以前的HowMongoDdWorks程序正在运行时, int main(char** argv)将显示如下结果:

 x [1] 0.23 x [2] 0.19 x [1] 0.24 x [2] 0.19 x [1] 1.30 -- first initialization takes significantly longer x [2] 0.19 -- then seconds one (6x times slowew) x [1] 10.94 -- and some times it is 50x slower!!! x [2] 0.19 x [1] 1.10 x [2] 0.21 x [1] 1.52 x [2] 0.19 x [1] 0.94 x [2] 0.21 x [1] 2.36 x [2] 0.20 x [1] 3.20 x [2] 0.20 -- and the results is totally unstable ... 

我只把这一行放在历史以下。


upd1 :开发和生产系统都足够用于这个testing。 upd7 :它不是分页,至less我在问题时间内没有看到任何存储IO活动。

  1. dev〜4个内核,16个GM RAM,〜8 GB免费
  2. 生产〜12核,24 GB的RAM,〜16 GB的免费(从8到10通用是在FScaching下,但没有区别,即使所有16GM是完全免费的结果相同),也是本机加载CPU,但不太高〜10%。

upd8(ref):新的testing用例和可能的解释见尾部。

这里是我的testing用例(我也testing过java和python,但“c”应该是最清楚的):

 #include<stdlib.h> #include<stdio.h> #include<time.h> int main(char** argv){ int last = clock(); //remember the time for(int i=0;i<16;i++){ //repeat test several times int size = 256 * 1024 * 1024; int size4=size/4; int* buffer = malloc(size); //allocate 256MB of memory for(int k=0;k<2;k++){ //initialize allocated memory twice for(int j=0;j<size4;j++){ //memory initialization (if I skip this step my test ends in buffer[j]=k; 0.000s } //printing printf(x "[%d] %.2f\n",k+1, (clock()-last)/(double)CLOCKS_PER_SEC); stat last = clock(); } } return 0; } 

开发机器上的输出(部分):

 x [1] 0.13 --first initialization takes a bit longer x [2] 0.12 --then second one, but the different is not significant. x [1] 0.13 x [2] 0.12 x [1] 0.15 x [2] 0.11 x [1] 0.14 x [2] 0.12 x [1] 0.14 x [2] 0.12 x [1] 0.13 x [2] 0.12 x [1] 0.14 x [2] 0.11 x [1] 0.14 x [2] 0.12 -- and the results is quite stable ... 

生产机器上的输出(部分):

 x [1] 0.23 x [2] 0.19 x [1] 0.24 x [2] 0.19 x [1] 1.30 -- first initialization takes significantly longer x [2] 0.19 -- then seconds one (6x times slowew) x [1] 10.94 -- and some times it is 50x slower!!! x [2] 0.19 x [1] 1.10 x [2] 0.21 x [1] 1.52 x [2] 0.19 x [1] 0.94 x [2] 0.21 x [1] 2.36 x [2] 0.20 x [1] 3.20 x [2] 0.20 -- and the results is totally unstable ... 

在开发机器上运行这个testing时,CPU使用率甚至没有从goundboost,就像所有内核在htop中的使用率都不到5%一样。

但是在生产机器上运行这个testing,我发现所有核心的CPU使用率高达100%(在12核心机器上的平均负载上升到50%),这都是核心时间。

upd2:所有机器都安装了相同的centos linux 2.6,我使用ssh使用它们。

upd3:答:这不太可能是交换,在我的testing中没有看到任何磁盘活动,并且大量的RAM也是免费的。 (同样,描述符被更新)。 – 德米特里9分钟前

upd4: htop表示内核的HI CPU利用率,al内核的最高利用率为100%(在产品上)。

upd5:初始化完成后CPU利用率是否稳定? 在我的简单testing中 – 是的。 对于真正的应用程序来说,它只是帮助停止其他所有事情来开始一个新的程序(这是无稽之谈)。

我有两个问题:

  1. 为什么这发生?

  2. 如何解决它?

upd8:改进的testing和解释。

 #include<stdlib.h> #include<stdio.h> #include<time.h> int main(char** argv){ const int partition = 8; int last = clock(); for(int i=0;i<16;i++){ int size = 256 * 1024 * 1024; int size4=size/4; int* buffer = malloc(size); buffer[0]=123; printf("init %d, time %.2fs\n",i, (clock()-last)/(double)CLOCKS_PER_SEC); last = clock(); for(int p=0;p<partition;p++){ for(int k=0;k<2;k++){ for(int j=p*size4/partition;j<(p+1)*size4/partition;j++){ buffer[j]=k; } printf("x [try %d/part %d] time %.2fs\n",k+1, p, (clock()-last)/(double)CLOCKS_PER_SEC); last = clock(); } } } return 0; } 

结果如下所示:

 init 15, time 0.00s -- malloc call takes nothing. x [try 1/part 0] time 0.07s -- usually first try to fill buffer part with values is fast enough. x [try 2/part 0] time 0.04s -- second try to fill buffer part with values is always fast. x [try 1/part 1] time 0.17s x [try 2/part 1] time 0.05s -- second try... x [try 1/part 2] time 0.07s x [try 2/part 2] time 0.05s -- second try... x [try 1/part 3] time 0.07s x [try 2/part 3] time 0.04s -- second try... x [try 1/part 4] time 0.08s x [try 2/part 4] time 0.04s -- second try... x [try 1/part 5] time 0.39s -- BUT some times it takes significantly longer then average to fill part of allocated buffer with values. x [try 2/part 5] time 0.05s -- second try... x [try 1/part 6] time 0.35s x [try 2/part 6] time 0.05s -- second try... x [try 1/part 7] time 0.16s x [try 2/part 7] time 0.04s -- second try... 

我从这个testing中学到的事实。

  1. 内存分配本身很快。
  2. 首先访问分配的内存很快(所以它不是一个懒惰的缓冲区分配问题)。
  3. 我将分配的缓冲区分成几部分(testing中的8个)。
  4. 然后用值0填充每个缓冲区部分,然后用值1填充打印耗用的时间。
  5. 第二个缓冲区部分填充总是很快。
  6. 但furst缓冲区部分填充总是比第二次填充慢一点(我相信一些额外的工作是我的内核在第一页访问完成)。
  7. 有些时候,在第一次填充缓冲区部分时需要很长的时间。

我试过build议anwser,它似乎帮助。 稍后再重新检查并发布结果。

看起来像是linux映射将页面分配给durty文件系统caching页面,并且将页面逐个刷新到磁盘需要很长时间。 但总同步运行速度快,消除了问题。

 sudo sh -c 'sync ; echo 3 > /proc/sys/vm/drop_caches ; sync' 

在你的开发机器上。 这是一个安全的,无损的方式来确保你的缓存是空的。 (即使您碰巧在同一时间保存或写入磁盘,也不会因为执行上述命令丢失任何数据,这确实是安全的。)

然后,确保你没有运行任何Java的东西,并重新运行上述命令只是为了确保。 您可以检查是否有任何Java运行的例子

 ps axu | sed -ne '/ sed -ne /d; /java/p' 

它什么都不输出。 如果是这样,请先关闭Java的东西。

现在,重新运行你的应用程序测试。 你的开发机器现在是否也出现同样的减速?

如果你不管怎么说,德米特里,我都乐意进一步探讨这个问题。

编辑补充说:我怀疑减速确实发生,这是由于Java本身引起的大启动延迟。 这是一个非常普遍的问题,基本上内置于Java,这是其架构的结果。 对于更大的应用程序来说,无论机器多快,启动延迟通常都是重要的一小部分,因为Java必须加载和准备类(主要是串行化,所以添加内核也无济于事)。

换句话说,我认为应该归咎于Java而不是Linux; 完全相反,因为Linux通过内核级高速缓存来减少开发机器上的延迟,而且只是因为你几乎一直在运行这些Java组件,所以内核知道要缓存它们。

编辑2:当您的应用程序启动时,查看Java环境访问的文件将非常有用。 你可以用strace做到这一点:

 strace -f -o trace.log -q -tt -T -e trace=open COMMAND... 

创建包含由COMMAND...启动的任何进程完成的open()系统调用的文件trace.log 。 要将输出保存到每个进程的trace.PIDCOMMAND...开始使用

 strace -f -o trace -ff -q -tt -T -e trace=open COMMAND... 

比较你的dev和prod安装的输出结果会告诉你它们是否真的相同。 其中一个可能会有额外的或缺少的库,影响启动时间。

如果安装旧,系统分区已满,那么这些文件可能已经碎片化,导致内核花费更多的时间等待I / O完成。 (请注意,I / O数量保持不变;只有当文件碎片时,才会增加完成所需的时间。)可以使用命令

 LANG=C LC_ALL=C sed -ne 's|^[^"]* open("\(.*\)", O[^"]*$|\1|p' trace.* \ | LANG=C LC_ALL=C sed -ne 's|^[^"]* open("\(.*\)", O[^"]*$|\1|p' \ | LANG=C LC_ALL=C xargs -r -d '\n' filefrag \ | LANG=C LC_ALL=C awk '(NF > 3 && $NF == "found") { n[$(NF-2)]++ } END { for (i in n) printf "%d extents %d files\n", i, n[i] }' \ | sort -g 

检查您的应用程序使用的文件是多么零散; 它会报告有多少文件只使用一个或多个扩展盘区。 请注意,它不包含原始可执行文件( COMMAND... ),只包含它所访问的文件。

如果您只想获取单个命令访问的文件的碎片统计信息,则可以使用

 LANG=C LC_ALL=C strace -f -q -tt -T -e trace=open COMMAND... 2>&1 \ | LANG=C LC_ALL=C sed -ne 's|^[0-9:.]* open("\(.*\)", O[^"]*$|\1|p' \ | LANG=C LC_ALL=C xargs -r filefrag \ | LANG=C LC_ALL=C awk '(NF > 3 && $NF == "found") { n[$(NF-2)]++ } END { for (i in n) printf "%d extents %d files\n", i, n[i] }' \ | sort -g 

如果问题不是由于高速缓存造成的,那么我认为这两个设备最有可能不是真正的等同。 如果是的话,我会检查碎片。 之后,我会在两种环境下做一个完整的跟踪(省略-e trace=open ),以查看差异究竟在哪里。


我相信我现在明白你的问题/情况。

在您的prod环境中,内核页面缓存大部分都是脏的,即大多数缓存的内容是要写入磁盘的内容。

当应用程序分配新的页面时,内核只设置页面映射,实际上并不立即给物理内存。 这只发生在每个页面的第一次访问。

在第一次访问时,内核首先找到一个空闲页面 – 通常是一个包含“干净的”缓存数据的页面,即从磁盘读取但未被修改的页面。 然后,将其清除为零,以避免进程之间的信息泄漏。 (当使用像malloc()等C库分配工具而不是直接mmap()系列函数时,库可以使用/重用部分映射。尽管内核将页面清零,但是该库可能“脏”他们使用mmap()来获得匿名页面,你会得到他们清零。)

如果内核没有合适的干净的页面,它必须首先将一些最老的脏页面刷新到磁盘。 (在内核中有进程将页面刷新到磁盘,并将其标记为干净的,但是如果服务器负载是这样的,页面不断被弄脏,通常需要大部分脏页面,而不是大部分干净的页面 – 服务器获取更多的工作是以这种方式完成的,不幸的是,这也意味着现在遇到的首页访问延迟增加了。

每页是sysconf(_SC_PAGESIZE)字节长,对齐。 换句话说,当且仅当((long)p % sysconf(_SC_PAGESIZE)) == 0 ,指针p指向页面的开始。 我相信大多数内核实际上在大多数情况下填充页面组而不是单个页面,因此增加了第一次访问(到每组页面)的延迟。

最后,可能会有一些编译器优化对基准测试造成严重破坏。 我建议您为基准测试main()编写一个单独的源文件,并在每个迭代中在单独的文件中完成实际的工作。 单独编译它们,并将它们链接在一起,以确保编译器不重新排列时间函数。 实际完成的工作。 基本上,在benchmark.c

 #define _POSIX_C_SOURCE 200809L #include <time.h> #include <stdio.h> /* in work.c, adjust as needed */ void work_init(void); /* Optional, allocations etc. */ void work(long iteration); /* Completely up to you, including parameters */ void work_done(void); /* Optional, deallocations etc. */ #define PRIMING 0 #define REPEATS 100 int main(void) { double wall_seconds[REPEATS]; struct timespec wall_start, wall_stop; long iteration; work_init(); /* Priming: do you want caches hot? */ for (iteration = 0L; iteration < PRIMING; iteration++) work(iteration); /* Timed iterations */ for (iteration = 0L; iteration < REPEATS; iteration++) { clock_gettime(CLOCK_REALTIME, &wall_start); work(iteration); clock_gettime(CLOCK_REALTIME, &wall_stop); wall_seconds[iteration] = (double)(wall_stop.tv_sec - wall_start.tv_sec) + (double)(wall_stop.tv_nsec - wall_start.tv_nsec) / 1000000000.0; } work_done(); /* TODO: wall_seconds[0] is the first iteration. * Comparing to successive iterations (assuming REPEATS > 0) * tells you about the initial latency. */ /* TODO: Sort wall_seconds, for easier statistics. * Most reliable value is the median, with half of the * values larger and half smaller. * Personally, I like to discard first and last 15.85% * of the results, to get "one-sigma confidence" interval. */ return 0; } 

work.c定义的work()函数中进行实际的数组分配,取消分配和填充(每个重复循环)。

当内核用完干净的页面时,它必须将脏页面刷新到磁盘。 将大量脏页面清理到磁盘看起来像是一个很高的CPU负载,因为大多数内核方面的东西需要一个或多个页面(临时)工作。 从本质上说,内核正在等待I / O完成,即使用户空间应用程序称为非I / O相关的内核函数。

如果你并行运行一个微基准测试程序,说一个程序只是不断地把一个非常大的映射反复地扫描一遍,然后测量CPU时间(如果在x86或者x86-64上使用GCC的话,就是__builtin_ia32_rdtsc() ),你应该看到即使内核似乎吃了“全部”CPU时间,也能获得足够的CPU时间。 只有当进程调用内部需要一些内存的内核函数(系统调用)时,才会调用“block”,等待内核中的页面刷新以产生新的页面。

运行基准测试时,通常只需运行sudo sh -c 'sync ; echo 3 >/proc/sys/vm/drop_caches ; sync' sudo sh -c 'sync ; echo 3 >/proc/sys/vm/drop_caches ; sync' sudo sh -c 'sync ; echo 3 >/proc/sys/vm/drop_caches ; sync'几次,然后再运行基准测试,以确保基准测试期间不会有过度的内存压力。 我从来没有在生产环境中使用它。 (虽然运行是安全的,即不会丢失数据,但这就像使用大锤杀死蚊子一样:这是工作的错误工具。)

当你在生产环境中发现由于内核刷新脏页面而导致你的等待时间开始变得太大时 – 我认为它在最大的设备速度下工作,可能导致应用程序I / O速度的一个呃逆 – 你可以调整内核脏页面的冲洗机制。 基本上,你可以告诉内核很快将脏页面刷新到磁盘上,并确保在任何时间点(如果可能的话)都不会有那么多脏页面。

格雷戈里·史密斯(Gregory Smith)在这里写了关于冲洗机理的理论和调整。 简而言之, /proc/sys/vm/包含可修改的内核可调参数。 它们在引导时重置为默认值,但是您可以轻松地编写一个简单的init脚本,以便在引导时将所需的值echo显到文件中。 如果生产计算机上运行的进程执行繁重的I / O操作,那么也可以查看文件系统可调参数。 至少应该使用relatime标志挂载文件系统(请参阅/etc/fstab ),以便在文件被修改或状态更改之后,文件访问时间仅针对首次访问进行更新。

就我个人而言,我也使用低时延的可用内核,1000赫兹定时器用于多媒体工作站(如果我有任何现在的话,也可以用于多媒体服务器)。 这样的内核以更短的时间片运行用户进程,并且通常提供更好的延迟,尽管最大计算能力稍低。 如果你的生产服务是延迟敏感的,我建议你把生产服务器切换到这样的内核。

许多发行版本已经提供了这样的内核,但是我发现重新编译发行版内核或者甚至切换到kernel.org内核要简单得多。 该过程很简单:您需要安装内核开发和工具(在Debian变体上, make-kpkg非常有用)。 要升级内核,您需要获取新的源代码,在重新引导之前配置内核(通常使用当前配置作为基础 – make oldconfig ),构建新内核并安装软件包。 大多数人确实发现,只是升级硬件比重新编写发行版内核更具成本效益,但我发现自己很容易地重新编译内核。 我不会自动重启内核升级,所以在重启之前添加一个简单的步骤(通过运行一个脚本触发)对我来说不是太费劲。