gpt4 book ai didi

c - 内存初始化时linux高内核cpu使用率

转载 作者:IT王子 更新时间:2023-10-29 00:00:33 25 4
gpt4 key购买 nike

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

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

  • 如何解决? - 名义动物 建议同步并删除所有内容,这确实有帮助。 sudo sh -c 'sync ; echo 3 > /proc/sys/vm/drop_caches ;作品。 upd12:但确实sync足够。
  • 为什么会发生这种情况? - 它对我来说仍然是开放的,我知道将durty页面刷新到磁盘会消耗内核CPU和IO时间,这是正常的。 但是什么是奇怪,为什么即使是用“C”编写的单线程应用程序也会在内核空间中 100% 加载所有内核?

  • 由于引用- upd10 和引用- upd11 我有一个想法 echo 3 > /proc/sys/vm/drop_caches不需要用缓慢的内存分配来解决我的问题。
    运行`sync'应该就足够了 之前 启动消耗内存的应用程序。
    明天可能会在生产中尝试这个并在此处发布结果。

    upd10:丢失 FS 缓存页面的情况:
  • 我执行了 cat 10GB.fiel > /dev/null ,然后
  • sync可以肯定的是,没有垃圾页面( cat /proc/meminfo |grep ^Dirty 显示 184kb。
  • 正在检查 cat /proc/meminfo |grep ^Cached我得到:4GB 缓存
  • 运行 int main(char**)我得到了正常的性能(比如 50 毫秒来初始化 32MB 的分配数据)。
  • 缓存内存减少到 900MB
  • 测试摘要:我认为 linux 将用作 FS 缓存的页面回收到分配的内存中是没有问题的。

  • upd11:很多脏页案例。
  • 列表项
  • 我运行我的 HowMongoDdWorks带注释的示例 read部分,一段时间后
  • /proc/meminfo说 2.8GB 是 Dirty 3.6GB 是 Cached .
  • 我停了HowMongoDdWorks并运行我的 int main(char**) .
  • 这是结果的一部分:

    初始化 15,时间 0.00 秒
    x 0 [尝试 1/part 0] 时间 1.11s
    x 1 [尝试 2/part 0] 时间 0.04s
    x 0 [尝试 1/part 1] 时间 1.04s
    x 1 [尝试 2/part 1] 时间 0.05s
    x 0 [尝试 1/第 2 部分] 时间 0.42s
    x 1 [尝试 2/第 2 部分] 时间 0.04 秒
  • 测试总结:丢失的 durty 页面显着减慢了对分配内存的首次访问(公平地说,只有当应用程序总内存开始与整个操作系统内存相当时才会发生这种情况,即如果您有 8 个 16 GB 可用空间,则没有问题分配 1GB,从 3GB 左右开始减速)。

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

    开发机配置:
  • Linux 2.6.32-220.13.1.el6.x86_64 - Scientific Linux 6.1 版(碳)
  • 内存:15.55 GB
  • CPU:1 X Intel(R) Core(TM) i5-2300 CPU @ 2.80GHz(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 事件。
  • dev ~ 4 核,16 GM RAM,~8 GB 空闲
  • 生产 ~ 12 核,24 GB
    RAM,约 16 GB 可用空间(8 到 10 GM 在 FS 缓存下,但没有
    不同,即使所有16GM都是完全免费的结果相同),这台机器也有CPU加载,但不会太高~10%。

  • upd8(引用):新的测试用例和潜在的解释见尾部。

    这是我的测试用例(我也测试了 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
    ...

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

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

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

    upd3: A:不太可能发生交换,在我的测试期间没有看到任何磁盘事件,并且还有大量 RAM 可用。 (此外,descriptin 已更新)。 – 德米特里 9 分钟前

    upd4: htop 说内核的 CPU 利用率很高,所有内核的利用率高达 100%(在生产上)。

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

    我有两个问题:
  • 为什么会发生这种情况?
  • 如何解决?

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

    我从这次测试中学到的事实。
  • 内存分配本身很快。
  • 第一次访问分配的内存很快(所以它不是一个懒惰的缓冲区分配问题)。
  • 我将分配的缓冲区拆分为多个部分(测试中为 8 个)。
  • 并用值0填充每个缓冲区部分,然后用值1填充打印消耗的时间。
  • 第二个缓冲区部分填充总是很快。
  • 但是,第一个缓冲区部分填充总是比第二个填充慢一点(我相信我的内核在第一页访问时完成了一些额外的工作)。
  • 有时,第一次用值填充缓冲区部分需要更长的时间。

  • 我尝试了建议的 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包含 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 )以查看差异的确切位置。

    我相信我现在了解您的问题/情况。

    在您的生产环境中,内核页面缓存大多是脏的,即大多数缓存的内容是将要写入磁盘的内容。

    当您的应用程序分配新页面时,内核仅设置页面映射,实际上并不会立即提供物理 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;
    }

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

    关于c - 内存初始化时linux高内核cpu使用率,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12648023/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com