linux高内核cpu用法对内存初始化

时间:2012-09-28 22:09:56

标签: c linux kernel cpu allocation

Linux内核存在高CPU消耗的问题,同时在服务器上引导我的java应用程序。此问题仅在生产中发生,在开发服务器上,一切都是光速。

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

  1. 如何解决? - Nominal Animal 建议同步并删除所有内容,这确实有帮助。 sudo sh -c 'sync ; echo 3 > /proc/sys/vm/drop_caches ;有效。 upd12:但确实sync就够了。

  2. 为什么会发生这种情况? - 它仍然对我开放,我明白将durty页面刷新到磁盘会占用内核CPU和IO时间,这很正常。 但是什么是strage,为什么甚至单线程应用程序写在" C"我在内核空间中将所有核心加载100%?

  3. 由于ref- upd10 和ref- upd11 ,我知道不需要echo 3 > /proc/sys/vm/drop_caches来解决内存缓慢分配问题。 它应该足以运行`sync' 之前 启动耗尽内存的应用程序。 可能会在生产中尝试这个tommorow并在此发布结果。

    upd10:丢失了FS缓存页面案例:

    1. 我执行了cat 10GB.fiel > /dev/null,然后
    2. sync可以肯定,没有durty页面(cat /proc/meminfo |grep ^Dirty显示184kb。
    3. 检查cat /proc/meminfo |grep ^Cached我得到了:4GB缓存
    4. 正在运行int main(char**)我获得了正常的性能(例如50毫秒来初始化32MB的已分配数据)。
    5. 缓存内存减少到900MB
    6. 测试摘要: 我认为linux将用作FS缓存的页面回收到已分配的内存中是没有问题的。
    7. upd11:很多脏页案例。

      1. 列出项目

      2. 我使用评论HowMongoDdWorks部分运行我的read示例,过了一段时间

      3. /proc/meminfo表示2.8GB为Dirty而3.6GB为Cached

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

      5. 以下是结果的一部分:

        init 15,时间0.00s x 0 [尝试1 /部分0]时间1.11s x 1 [尝试2 /部分0]时间0.04秒 x 0 [尝试1 /部分1]时间1.04s x 1 [尝试2 /部分1]时间0.05s x 0 [尝试1 /部分2]时间0.42秒 x 1 [尝试2 /第2部分]时间0.04s

      6. 测试摘要:丢失的durty页面显着减慢了对分配内存的首次访问(公平地说,只有当总应用程序内存开始与整个OS内存相当时才开始发生,即如果你有16 GB中的8个免费,分配1GB没有问题,从3GB左右减速start。

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

        开发机器配置:

        1. Linux 2.6.32-220.13.1.el6.x86_64 - Scientific Linux版本6.1(Carbon)
        2. RAM:15.55 GB
        3. CPU:1 X Intel(R)Core(TM)i5-2300 CPU @ 2.80GHz(4线程)(物理)
        4. 99.9%的问题是由FS缓存中的大量durty页面引起的。这是在脏页面上创建批次的应用程序:

          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);
                      }
          
                  }
              }
          }
          

          这里是测试应用程序,它导致内核空间中的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 :开发和生产系统都是这项测试的重中之重。 upd7 :它不是分页,至少我在问题时间没有看到任何存储IO活动。

          1. dev~4核心,16 GM RAM,~8 GB免费
          2. 生产~12核,24 GB RAM,大约16 GB免费(从8到10 GM在FS Cache下,但没有 差异,即使所有16GM都是完全免费的,结果相同),这台机器也是由CPU加载的,但不要太高~10%。
          3. upd8(ref):新的测试用例和潜在的解释见尾巴。

            这是我的测试用例(我也测试了java和python,但是&#34; c&#34;应该最清楚):

            #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
            ...
            

            在开发机器上运行此测试时,CPU使用率甚至没有从gound上升,就像所有内核在htop中的使用率低于5%。

            但是在生产机器上运行此测试,我发现所有内核的CPU使用率高达100%(12内核机器的平均负载上升高达50%),并且它都是内核时间。

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

            upd3:答:它不太可能交换,在我的测试中没有看到任何磁盘活动,而且大量的RAM也是免费的。 (另外,descriptin已更新)。 - 德米特里9分钟前

            upd4: htop说内核的HI CPU利用率,al核心利用率高达100%(在prod上)。

            upd5:初始化完成后CPU利用率是否稳定下来?在我的简单测试中 - 是的。对于实际应用,它只是帮助阻止其他一切来启动一个新程序(这是无稽之谈)。

            我有两个问题:

            1. 为什么会这样?

            2. 如何解决?

            3. upd8:改进测试并解释。

              #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...
              

              事实我从这次测试中学到了。

              1. 内存分配本身很快。
              2. 首次访问分配的内存很快(因此它不是一个惰性缓冲区分配问题)。
              3. 我将已分配的缓冲区拆分为多个部分(测试中为8个)。
              4. 用值0填充每个缓冲区部分,然后用值1填充打印消耗的时间。
              5. 第二个缓冲区部分填充总是很快。
              6. 但是,第一次缓冲部分填充总是慢一点填充(我相信一些额外的工作是在第一页访问时完成我的内核)。
              7. 有些时候,第一次使用值填充缓冲区部分需要更长的时间。
              8. 我尝试过建议anwser,它似乎有所帮助。我将在稍后再次检查并发布结果。

                看起来linux将分配的页面映射到durty文件系统缓存页面,并且需要花费大量时间将页面逐个刷新到磁盘。但总同步速度很快,消除了问题。

2 个答案:

答案 0 :(得分:8)

运行

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...

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

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环境中,内核页面缓存主要是脏的,即大多数缓存的东西都是要写入磁盘的东西。

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

在第一次访问时,内核首先找到一个空闲页面 - 通常是一个包含&#34; clean&#34;缓存数据,即从磁盘读取但未修改的内容。然后,它将其清除为零,以避免进程之间的信息泄漏。 (当使用像malloc()等C库分配工具而不是直接mmap()系列函数时,库可能会使用/重用映射的一部分。虽然内核确实将页面清除为零,图书馆可能会&#34;脏&#34;他们。使用mmap()来获取匿名页面,你会把它们归零。)

如果内核没有合适的干净页面,则必须先将一些最旧的脏页首先刷新到磁盘。 (内核中有进程将页面刷新到磁盘,并将它们标记为干净,但如果服务器负载使页面不断变脏,通常需要使用大多数脏页而不是大多数干净页 - 服务器获取这样做的工作量更多。不幸的是,这也意味着你现在遇到的第一页访问延迟会增加。)

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

最后,可能会有一些编译器优化对您的基准测试造成严重破坏。我建议您为基准测试main()编写单独的源文件,并在单独的文件中为每次迭代完成实际工作。单独编译它们,并将它们链接在一起,以确保编译器不会重新排列时间函数wrt。实际完成的工作。基本上,在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()中定义的work.c函数中完成实际数组分配,释放和填充(每个重复循环)。

答案 1 :(得分:2)

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

如果你并行运行一个微基准测试,那么说一个程序只是不断地反复弄脏一个非常大的映射,并测量CPU时间(如果在x86或x86-64上使用GCC,则为__builtin_ia32_rdtsc())而不调用任何系统调用,你应该看到,即使内核看起来正在吃东西,这个也会获得足够的CPU时间#34;所有&#34; CPU时间。只有当一个进程调用内部需要一些内存的内核函数(系统调用)时,才会调用&#34;阻塞&#34;,在内核中等待页面刷新以产生新的页面。

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

当您在生产环境中发现由于内核刷新脏页而导致延迟开始变得过大时 - 我认为它以最大设备速度执行,可能会导致应用程序I / O速度出现问题 - ,你可以调整内核脏页面刷新机制。基本上,您可以告诉内核在磁盘上尽快刷新脏页,并确保在任何时间点都不会有那么多脏页(如果可能的话)。

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

就个人而言,我还使用低延迟的可预占内核和1000 Hz定时器用于多媒体工作站(如果我现在有任何数据,则用于多媒体服务器)。这些内核以较短的切片运行用户进程,并且通常提供更好的延迟,尽管最大计算能力略低。如果您的生产服务对延迟敏感,我建议您将生产服务器切换到此类内核。

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