在服务器上引导我的 java 应用程序时,我遇到了 linux 内核高 CPU 消耗的问题。这个问题只发生在生产中,在开发服务器上一切都是光速的。
upd9:关于这个问题有两个问题:
如何解决?- Nominal Animal建议同步和删除所有内容,这真的很有帮助。
sudo sh -c 'sync ; echo 3 > /proc/sys/vm/drop_caches ;
作品。upd12:但确实sync
足够了。为什么会这样?- 它对我来说仍然是开放的,我知道将 durty pages 刷新到磁盘会消耗内核 CPU 和 IO 时间,这是正常的。但是什么是策略,为什么即使是用“C”编写的单线程应用程序我也会在内核空间中 100% 加载所有内核?
由于ref-upd10和ref-upd11 ,我有一个想法echo 3 > /proc/sys/vm/drop_caches
不需要解决内存分配缓慢的问题。在启动消耗内存的应用程序之前运行“同步”就足够了。明天可能会在生产中尝试这个并在这里发布结果。
upd10:丢失 FS 缓存页面案例:
- 我执行了
cat 10GB.fiel > /dev/null
,然后 sync
可以肯定的是,没有任何页面(cat /proc/meminfo |grep ^Dirty
显示 184kb.- 检查
cat /proc/meminfo |grep ^Cached
我得到:4GB缓存 - 运行
int main(char**)
我得到了正常的性能(比如初始化 32MB 的分配数据需要 50 毫秒)。 - 缓存内存减少到 900MB
- 测试总结:我觉得linux把用作FS缓存的页面回收到分配的内存是没有问题的。
upd11:很多脏页案例。
项目清单
HowMongoDdWorks
我用注释部分运行我的示例read
,一段时间后/proc/meminfo
说2.8GB是Dirty
,一个3.6GB是Cached
。我停下来
HowMongoDdWorks
运行我的int main(char**)
.以下是部分结果:
初始化 15,时间 0.00s x 0 [try 1/part 0] time 1.11s x 1 [try 2/part 0] time 0.04s x 0 [try 1/part 1] time 1.04s x 1 [try 2/part 1] time 0.05s x 0 [尝试 1/第 2 部分] 时间 0.42 秒 x 1 [尝试 2/第 2 部分] 时间 0.04 秒
测试总结:丢失的 durty pages 显着减慢了对分配内存的首次访问(公平地说,这仅在应用程序总内存开始与整个 OS 内存相当时才开始发生,即如果您有 8 个 16 GB 空闲,它分配1GB没问题,从3GB左右开始减速)。
现在我设法在我的开发环境中重现了这种情况,所以这里有新的细节。
开发机器配置:
- Linux 2.6.32-220.13.1.el6.x86_64 - 科学 Linux 版本 6.1(碳)
- 内存:15.55 GB
- CPU:1 X Intel(R) Core(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 活动。
- dev ~ 4 核,16 GM RAM,约 8 GB 免费
- 生产 ~ 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;
}
开发机器上的输出(部分):
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 表示内核的 HI CPU 利用率,al 内核的利用率高达 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文件系统缓存页面,将页面一一刷新到磁盘需要很多时间。但总同步工作得很快并消除了问题。