问题描述
我在Linux内核中遇到了高CPU cunsumpoting的问题,同时引导我的Java应用程序在服务器上.此问题仅在生产中发生,在开发服务器上,一切都是轻速的.
upd9:关于这个问题有两个问题:
-
如何修复它? - 名义动物建议同步和放弃所有东西,这确实有帮助. sudo sh -c 'sync ; echo 3 > /proc/sys/vm/drop_caches ;有效. upd12:但是确实sync就足够了.
-
为什么会发生这种情况? - 对我来说仍然是开放的,我确实知道,浮出水面的页面以消耗盘子的盘子会消耗核CPU和io时间,这是正常的. 但是,什么是Strage,为什么即使在" C"中写下的单线螺纹应用程序在内核空间中100%均为100%的核心?
由于ref- upd11 和ref- upd11 我想到,echo 3 > /proc/sys/vm/drop_caches不需要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 CACHE的页面收回到分配的内存中是没有问题的.
upd11:很多肮脏的页面案例.
-
列表项目
-
我运行我的HowMongoDdWorks示例带有评论read零件,一段时间后
-
/proc/meminfo说2.8GB是Dirty,一个3.6GB为Cached.
-
我停了下来HowMongoDdWorks并运行我的int main(char**).
-
这是结果的一部分:
init 15,时间0.00 x 0 [尝试1/part 0]时间1.11 x 1 [尝试2/part 0]时间0.04S x 0 [尝试1/第1部分]时间1.04S x 1 [尝试2/第1部分]时间0.05S x 0 [尝试1/第2部分]时间0.42S x 1 [尝试2/第2部分]时间0.04S
-
通过测试摘要:丢失的腐烂页面显着降低了首先访问分配的内存(公平地说,只有当总应用程序内存开始与整个OS内存相当时,才会发生这种情况,即您有16 GB中的8个免费,分配1GB,从3GB左右分配降低了Starst是没有问题的).
现在我设法在开发环境中重现了这种情况,因此这里有新的细节.
开发机配置:
- Linux 2.6.32-220.13.1.el6.x86_64-科学Linux版本6.1(碳)
- RAM:15.55 GB
- cpu:1 x intel(r)核心(TM)i5-2300 cpu @ 2.80GHz(4个线程)(物理)(物理)
这是99.9%的问题,是由FS缓存中大量腐烂页面引起的.这是在肮脏页面上创建大量的应用程序:
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活动.
- 开发〜4内核,16 gm ram,〜8 GB免费
- 生产〜12核,24 GB RAM,〜16 GB免费(从8到10克处于FS缓存,但没有 差异,即使所有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; }
开发机上的输出(部分):
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%.
但是在生产机器上运行此测试,我看到所有内核最多100%的CPU使用(平均负载率上升到12枚核心机器上最高可达50%),这都是内核时间.
upd2:所有机器都安装了相同的CentOS Linux 2.6,我使用SSH.
upd3: a:不太可能交换,在我的测试过程中没有看到任何磁盘活动,而且很多RAM也是免费的. (另外,描述已更新). - dmitry 9分钟前
upd4: htop say hi cpu用内核,最多100%利用Al core(on Prod).
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填充,打印时间.
- 第二缓冲区填充始终很快.
- 但是furst缓冲端部件填充总是比第二填充要慢一些(我相信第一页上的内核完成了一些额外的工作).
- 有时首次用值填充缓冲区部分需要更长的时间.
我尝试了建议的anwser,似乎有所帮助.我将稍后再进行重新检查并再次发布结果.
看起来像Linux地图分配给了液文件系统缓存页面,并且要花很多时间才能刷新页面才能一个一个一个一个一个.但是总同步工作很快并消除了问题.
推荐答案
运行
sudo sh -c 'sync ; echo 3 > /proc/sys/vm/drop_caches ; sync'
在您的开发机上.这是一种安全,无损的方法,可确保您的缓存空虚. (即使您碰巧在同一时间保存或写入磁盘,您也会通过运行上述命令丢失任何数据.这确实是安全的.)
然后,请确保您没有任何Java的内容,并重新运行上述命令以确保.您可以检查是否运行任何Java,例如
ps axu | sed -ne '/ sed -ne /d; /java/p'
它不应输出.如果这样做,请先关闭Java的东西.
现在,重新运行您的申请测试.现在,开发机上也会发生同样的放缓吗?
如果您愿意发表评论,Dmitry,我很乐意进一步探索问题.
编辑要添加:我怀疑确实会发生放缓,这是由于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() SYSCALL.要将输出保存到每个过程trace.PID COMMAND...开始,请使用
strace -f -o trace -ff -q -tt -T -e trace=open COMMAND...
比较开发和产品安装上的输出将告诉您它们是否确实等效.其中一个可能有额外或缺少的库,影响启动时间.
如果安装旧,并且系统分区合理地完整,则这些文件可能已被分散,从而导致内核花费更多的时间等待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()获取匿名页面,您确实将它们归零.)
如果内核没有合适的清洁页面,则首先必须首先将一些最古老的肮脏页面冲洗到磁盘上. (内核内部有一些过程可以融合磁盘,并标记它们清洁,但是如果服务器负载是连续污垢的,通常需要大部分具有肮脏的页面而不是大部分干净的页面 - 服务器获取 - 该服务器获得了这样做的更多工作.不幸的是,这也意味着您现在遇到的第一页访问延迟的增加.)
每个页面都是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.c中定义的work()函数中完成的实际数组分配,交易和填充(每次重复循环).
其他推荐答案
当内核用尽可用的干净页面时,它必须将脏页卷到磁盘上.将许多肮脏的页面冲洗到磁盘上看起来像是高CPU负载,因为大多数内核侧东西都需要一个或多个页面(暂时)工作.本质上,即使在称为non-i/o相关的内核函数的用户空间应用程序时,内核也正在等待I/O完成.
如果您并行运行微学分,请说一个程序,该程序只是一遍又一遍地弄脏一个非常大的映射,并测量CPU时间(__builtin_ia32_rdtsc()如果在X86或X86-64上使用GCC),而无需调用任何SYSCALLS,任何SYSCALLS,,您应该看到,即使内核似乎在吃了所有CPU时间,这也有很多CPU时间.只有当一个过程调用内部需要一些内存的内核函数(SYSCALL)时,该调用"块"会粘在内核中等待页面潮红以产生新页面.
运行基准时,通常足以在运行基准之前几次运行sudo sh -c 'sync ; echo 3 >/proc/sys/vm/drop_caches ; sync',以确保基准期间不应有不适当的内存压力.我从不在生产环境中使用它. (虽然可以安全地运行,即不会丢失数据,但这就像使用大锤杀死蚊子:作业的错误工具.)
当您在生产环境中发现您的潜伏期由于毛绒污垢的肮脏页面而开始增长太大 - 我相信它会以最大的设备速度效果,这可能会在应用程序I/O速度中引起打ic-您可以调整内核肮脏的页面冲洗机构.基本上,您可以告诉内核更快地刷新脏页,并确保在任何时间点都不会有那么多脏页(如果可能的话).
格雷戈里·史密斯(Gregory Smith)撰写了有关冲洗机制的理论和调整在这里/a>.简而言之,/proc/sys/vm/包含可以修改的内核调谐物.它们已重置为启动时的默认值,但是您可以轻松地将一个简单的初始脚本写入echo启动时的文件所需值.如果在生产机上运行的流程非常重I/O,则可能会查看文件系统调谐物.至少,您应使用relatime标志安装文件系统(请参见/etc/fstab),以便仅在修改文件或更改文件状态后,仅对第一个访问进行文件访问时间.
.就个人而言,我还使用低延迟的预先空位内核,其中包括1000 Hz计时器用于多媒体工作站(如果我现在有任何使用,则使用多媒体服务器).此类内核以较短的切片运行用户流程,并且通常提供更好的潜伏期,尽管最大计算能力略低.如果您的生产服务对延迟敏感,我建议您将您的生产服务器切换到此类内核.
许多发行版确实已经提供了这样的内核,但是我发现重新编译发行核更简单,甚至切换到kernel.org bernels.该过程很简单:您需要安装内核开发和工具(在Debian变体上,make-kpkg非常有用).要升级内核,您可以获得新来源,配置内核(通常使用当前配置作为基础 - make oldconfig),构建新内核并在重新启动之前安装软件包.大多数人确实发现仅升级硬件比重新编译发行核更具成本效益,但是我发现重新编译内核很轻松.无论如何,我不会自动重新启动内核升级,因此在重新启动之前添加一个简单的步骤(通过运行单个脚本触发)对我来说并不是太多的努力.