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

Dmi*_*try 11 c linux cpu kernel 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%的内核?

由于ref-upd10ref-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**)我获得了正常的性能(例如50ms来初始化32MB的已分配数据).
  5. 缓存内存减少到900MB
  6. 测试总结:我认为linux将用作FS缓存的页面回收到已分配的内存中是没有问题的.

upd11:很多脏页案例.

  1. 项目清单

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

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

  4. 我停下来HowMongoDdWorks跑了我的int main(char**).

  5. 这是结果的一部分:

    init 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. 测试摘要:durty页面的丢失显着减慢了对分配内存的首次访问(公平地说,只有当总应用程序内存开始与整个OS内存相当时才开始发生,即如果你有16 GB的8个可用内存,那么分配1GB,从3GB左右减速start是没问题的).

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

开发机配置:

  1. Linux 2.6.32-220.13.1.el6.x86_64 - Scientific Linux发行版6.1(Carbon)
  2. 内存:15.55 GB
  3. CPU:1 X Intel(R)Core(TM)i5-2300 CPU @ 2.80GHz(4线程)(物理)

由FS缓存中的大量durty页面引起的问题是99.9%.这是在脏页面上创建批量的应用程序:

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

        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这里是测试应用程序,它导致内核空间中的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;
}
Run Code Online (Sandbox Code Playgroud)

在以前的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
...
Run Code Online (Sandbox Code Playgroud)

我保留在这条线下面的所有东西只是为了历史性.


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

upd8(ref):新的测试用例和潜在的解释见尾.

这是我的测试用例(我也测试过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;
}
Run Code Online (Sandbox Code Playgroud)

dev机器上的输出(部分):

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
...
Run Code Online (Sandbox Code Playgroud)

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

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
...
Run Code Online (Sandbox Code Playgroud)

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

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

upd2:所有机器都安装了相同的centos linux 2.6,我使用ssh与它们一起工作.

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

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

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

我有两个问题:

  1. 为什么会这样?

  2. 怎么解决?

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;
}
Run Code Online (Sandbox Code Playgroud)

结果如下:

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...
Run Code Online (Sandbox Code Playgroud)

事实我从这个测试中学到了什么.

  1. 内存分配本身很快.
  2. 首次访问分配的内存很快(因此它不是一个惰性缓冲区分配问题).
  3. 我将分配的缓冲区分成几部分(测试中为8).
  4. 并使用值0填充每个缓冲区部分,然后使用值1填充打印消耗的时间.
  5. 第二缓冲部分填充总是很快.
  6. 但是,第一次缓冲部分填充总是慢一点填充(我相信一些额外的工作是在第一页访问时完成我的内核).
  7. 有些时候,第一次使用值填充缓冲区部分需要花费更长的时间.

我试过建议anwser,它似乎有帮助.我将重新检查并稍后再次发布结果.

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

Nom*_*mal 8

sudo sh -c 'sync ; echo 3 > /proc/sys/vm/drop_caches ; sync'
Run Code Online (Sandbox Code Playgroud)

在您的开发机器上.这是一种安全,非破坏性的方法来确保您的缓存是空的.(即使您碰巧在同一时间保存或写入磁盘,也不会通过运行上述命令丢失任何数据.这确实很安全.)

然后,确保您没有运行任何Java内容,并重新运行上面的命令只是为了确定.例如,您可以检查是否有任何Java运行

ps axu | sed -ne '/ sed -ne /d; /java/p'
Run Code Online (Sandbox Code Playgroud)

它什么都不输出.如果是,请先关闭Java内容.

现在,重新运行您的应用程序测试.您的开发机器现在也会出现同样的减速吗?

如果你想以任何方式离开评论,德米特里,我很乐意进一步探讨这个问题.

编辑添加:我怀疑减速确实发生,并且是由于Java本身引起的大启动延迟.这是一个非常常见的问题,基本上内置于Java,这是其架构的结果.对于较大的应用程序,启动延迟通常是一小部分,无论机器有多快,只是因为Java必须加载和准备类(大多数是串行的,因此添加内核也无济于事).

换句话说,我认为责任归咎于Java,而不是Linux; 恰恰相反,因为Linux通过内核级缓存设法减少了开发机器的延迟 - 而且这只是因为你几乎一直在运行这些Java组件,所以内核知道要缓存它们.

编辑2:在启动应用程序时,查看Java环境访问哪些文件非常有用.你可以这样做strace:

strace -f -o trace.log -q -tt -T -e trace=open COMMAND...
Run Code Online (Sandbox Code Playgroud)

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

strace -f -o trace -ff -q -tt -T -e trace=open COMMAND...
Run Code Online (Sandbox Code Playgroud)

比较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
Run Code Online (Sandbox Code Playgroud)

检查应用程序使用的文件有多碎片; 它报告有多少文件只使用一个或多个扩展区.请注意,它不包含原始的可执行文件(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
Run Code Online (Sandbox Code Playgroud)

如果问题不是缓存问题,那么我认为这两个安装很可能并不是真正等效的.如果是,那我就检查碎片.在那之后,我会-e trace=open在两个环境中进行完整的跟踪(省略)以查看差异的确切位置.


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

在你的prod环境中,内核页面缓存主要是脏的,即大多数缓存的东西都是要写入磁盘的东西.

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

在第一次访问时,内核首先找到一个空闲页面 - 通常是一个包含"干净"缓存数据的页面,即从磁盘读取但未修改的内容.然后,它将其清除为零,以避免进程之间的信息泄漏.(当使用C库分配工具malloc()等,而不是直接mmap()的函数系列时,库可能会使用/重用映射的一部分.虽然内核确实将页面清除为零,但库可能会"弄脏"它们.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;
}
Run Code Online (Sandbox Code Playgroud)

实际的数组分配,释放和填充(每个重复循环)在work()定义的函数中完成work.c.