サーバー上でJavaアプリケーションをブートストラップしているときに、LinuxカーネルによるCPU消費量が多いという問題があります。この問題は本番環境でのみ発生します。開発サーバーではすべてが光速です。
upd9:この問題について2つの質問がありました:
それを修正する方法は?-名目上の動物はすべてを同期してドロップすることを提案しました、そしてそれは本当に役に立ちます。
sudo sh -c 'sync ; echo 3 > /proc/sys/vm/drop_caches ;
動作します。upd12:しかし確かsync
に十分です。なぜこれが起こっているのですか?-まだ開いています。耐久性のあるページをディスクにフラッシュすると、カーネルのCPUとIO時間が消費されることは理解していますが、これは正常な動作です。しかし、ストラジとは何ですか。「C」で記述されたシングルスレッドアプリケーションでさえ、カーネル空間ですべてのコアを100%ロードするのはなぜですか。
ref-upd10とref -upd11echo 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**)
すると、通常のパフォーマンスが得られました(32MBの割り当てられたデータを初期化するための50msなど)。 - キャッシュされたメモリが900MBに削減されました
- テストの概要:LinuxがFSキャッシュとして使用されているページを割り当てられたメモリに再利用することは問題ないと思います。
upd11:ダーティページがたくさんある場合。
リストアイテム
HowMongoDdWorks
コメント部分を使用して例を実行し、read
しばらくすると/proc/meminfo
2.8GBはでDirty
、3.6GBはCached
です。停止
HowMongoDdWorks
して実行しましたint main(char**)
。結果の一部は次のとおりです。
init 15、時間0.00s x0[試行1/パート0]時間1.11sx1[試行2/パート0]時間0.04sx0[試行1/パート1]時間1.04sx1[試行2/パート1]時間0.05sx 0[試行1/パート2]時間0.42秒x1[試行2/パート2]時間0.04秒
テストによる要約:耐久性のあるページが失われると、割り当てられたメモリへの最初のアクセスが大幅に遅くなります(公平を期すために、これは、アプリケーションメモリの合計がOSメモリ全体に匹敵し始めたときにのみ発生します。つまり、16GBのうち8GBが空いている場合は1GBを割り当てるのに問題はありません。3GB程度から最も遅くなります)。
これで、開発環境でこの状況を再現できたので、ここに新しい詳細を示します。
開発マシンの構成:
- Linux 2.6.32-220.13.1.el6.x86_64-Scientific Linuxリリース6.1(カーボン)
- RAM:15.55 GB
- CPU:1 X Intel(R)Core(TM)i5-2300 CPU @ 2.80GHz(4スレッド)(物理)
FSキャッシュ内の大量の耐久性のあるページが原因で発生する問題は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アクティビティは発生していません。
- dev〜4コア、16 GM RAM、〜8GB無料
- 本番環境〜12コア、24 GB RAM、〜16 GB空き容量(8〜10 GMは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
...
開発マシンでこのテストを実行している間、すべてのコアがhtopで5%未満の使用率であるように、CPU使用率はグラウンドから上昇することさえありません。
しかし、本番マシンでこのテストを実行すると、すべてのコアで最大100%のCPU使用率が見られ(平均負荷は12コアマシンで最大50%上昇します)、それはすべてカーネル時間です。
upd2:すべてのマシンに同じcentos linux 2.6がインストールされています。私は、sshを使用してそれらを操作します。
upd3: A:スワップしている可能性は低く、テスト中にディスクアクティビティは確認されていません。また、十分なRAMも空いています。(また、descriptinが更新されます)。–ドミトリー9分前
upd4: htopは、カーネルによるHI CPU使用率、すべてのコアによる最大100%の使用率(製品上)を示します。
upd5:初期化が完了した後、CPU使用率は落ち着きますか?私の簡単なテストでは-はい。実際のアプリケーションの場合、新しいプログラムを開始するために他のすべてを停止するのに役立つだけです(これはナンセンスです)。
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...
このテストから学んだ事実。
- メモリ割り当て自体は高速です。
- 割り当てられたメモリへの最初のアクセスは高速です(したがって、遅延バッファ割り当ての問題ではありません)。
- 割り当てられたバッファをパーツに分割しました(テストでは8)。
- そして、各バッファ部分に値0を入力し、次に値1を入力して、消費時間を印刷します。
- 2番目のバッファー部分の充填は常に高速です。
- しかし、最も遠いバッファ部分の塗りつぶしは、常に2番目の塗りつぶしよりも少し遅くなります(最初のページへのアクセス時にカーネルで追加の作業が行われると思います)。
- バッファ部分を最初に値で埋めるのにかなり長い時間がかかる場合があります。
提案されたanwserを試してみましたが、役に立ったようです。後で再確認して結果を投稿します。
Linuxは割り当てられたページをdurtyファイルシステムのキャッシュページにマップしているように見え、ページを1つずつディスクにフラッシュするのに長い時間がかかります。ただし、完全同期は高速に機能し、問題を排除します。