繁体   English   中英

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

[英]linux high kernel cpu usage on memory initialization

我有一个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. 我用注释的read部分运行我的HowMongoDdWorks示例,并在一段时间后

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

        }
    }
}

这里是测试应用程序,它导致内核空间中的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%。

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

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

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

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

结果如下:

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. 有些时候,第一次使用值填充缓冲区部分需要花费更长的时间。

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

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

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其中包含由COMMAND...启动的任何进程完成的open()系统调用。 要将输出保存到每个进程的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环境中,内核页面缓存主要是脏的,即大多数缓存的东西都是要写入磁盘的东西。

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

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

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

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

最后,可能会有一些编译器优化会对您的基准测试造成严重破坏。 我建议您为基准测试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.c定义的work()函数中完成实际的数组分配,释放和填充(每个重复循环)。

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

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

运行基准测试时,通常只需运行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速度出现问题 - 您可以调整内核脏页面刷新机制。 基本上,您可以告诉内核在磁盘上更快地刷新脏页,并确保在任何时间点都不会有那么多脏页(如果可能的话)。

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM