解決策 1:
少なくとも Linux では、負荷平均と CPU 使用率は実際には 2 つの異なるものです。負荷平均は、一定期間にわたってカーネル実行キューで待機しているタスクの数 (CPU 時間だけでなくディスク アクティビティも含む) の測定値です。 CPU 使用率は、CPU が現在どれだけビジーであるかの尺度です。 1 分間 100% にペグされた単一の CPU スレッドが 1 分間の負荷平均に「寄与」できる最大の負荷は 1 です。 1 分間の負荷平均。
多くの場合、これら 2 つの数値には相互に相関するパターンがありますが、同じとは考えられません。シングル スレッド プロセスを実行している場合、CPU 使用率がほぼ 0% の高負荷 (大量の IO データが待機状態でスタックしている場合など) と、1 および 100% の CPU 負荷が発生する可能性があります。フルチルト。また、CPU 使用率が 100% 近くになることも短期間ですが、平均メトリックがまだ「追い付いていない」ため、負荷はまだ 1 を下回っています。
サーバーの負荷が 15,000 を超えており (これはタイプミスではありません)、CPU % が 0% に近い場合があります。これは、Samba 共有に問題があり、多くのクライアントが IO 待機状態でスタックし始めたために発生しました。対応する CPU アクティビティがなく、定期的に高負荷の数値が表示される場合は、何らかのストレージの問題が発生している可能性があります。仮想マシンでは、これは、同じ VM ホスト上でストレージ リソースをめぐって激しく競合している他の VM が存在することを意味する場合もあります。
また、高負荷は必ずしも悪いことではありません。ほとんどの場合、システムが最大限に活用されているか、処理能力を超えていることを意味します (負荷数がプロセッサ コアの数よりも高い場合)。私が以前システム管理者を務めていた場所では、プライマリ システムの負荷平均を Nagios よりも近くで監視している人がいました。負荷が高いときは、SMTP と言うよりも 24 時間年中無休で電話してくれました。ほとんどの場合、実際には何も問題はありませんでしたが、負荷番号を何かがおかしいと関連付けて、タカのように監視していました。確認した後、私の応答は通常、システムが機能しているだけだというものでした。もちろん、これは負荷が 15000 を超えた同じ場所でした (同じサーバーではありません)。システムの目的を考慮する必要があります。主力の場合は、負荷が自然に高くなることを期待してください。
解決策 2:
負荷は非常に欺瞞的な数値です。一粒の塩でそれを取ってください。
非常に迅速に完了する多くのタスクを非常に連続して生成する場合、実行キュー内のプロセスの数が少なすぎて、それらの負荷を登録できません (カーネルは 5 秒ごとに負荷をカウントします)。
この例を考えてみましょう。8 つの論理コアを持つ私のホストでは、この python スクリプトは CPU 使用率が高く (約 85%)、負荷はほとんどありません。
import os, sys
while True:
for j in range(8):
parent = os.fork()
if not parent:
n = 0
for i in range(10000):
n += 1
sys.exit(0)
for j in range(8):
os.wait()
別の実装、これは wait
を回避します 8 のグループで (テストをゆがめます)。ここで、親は常に子の数をアクティブな CPU の数に維持しようとします。そのため、最初の方法よりもはるかにビジーになり、できればより正確になります。
/* Compile with flags -O0 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <err.h>
#include <errno.h>
#include <sys/signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#define ITERATIONS 50000
int maxchild = 0;
volatile int numspawned = 0;
void childhandle(
int signal)
{
int stat;
/* Handle all exited children, until none are left to handle */
while (waitpid(-1, &stat, WNOHANG) > 0) {
numspawned--;
}
}
/* Stupid task for our children to do */
void do_task(
void)
{
int i,j;
for (i=0; i < ITERATIONS; i++)
j++;
exit(0);
}
int main() {
pid_t pid;
struct sigaction act;
sigset_t sigs, old;
maxchild = sysconf(_SC_NPROCESSORS_ONLN);
/* Setup child handler */
memset(&act, 0, sizeof(act));
act.sa_handler = childhandle;
if (sigaction(SIGCHLD, &act, NULL) < 0)
err(EXIT_FAILURE, "sigaction");
/* Defer the sigchild signal */
sigemptyset(&sigs);
sigaddset(&sigs, SIGCHLD);
if (sigprocmask(SIG_BLOCK, &sigs, &old) < 0)
err(EXIT_FAILURE, "sigprocmask");
/* Create processes, where our maxchild value is not met */
while (1) {
while (numspawned < maxchild) {
pid = fork();
if (pid < 0)
err(EXIT_FAILURE, "fork");
else if (pid == 0) /* child process */
do_task();
else /* parent */
numspawned++;
}
/* Atomically unblocks signal, handler then picks it up, reblocks on finish */
if (sigsuspend(&old) < 0 && errno != EINTR)
err(EXIT_FAILURE, "sigsuspend");
}
}
この動作の理由は、アルゴリズムが実際のタスクの実行よりも子プロセスの作成に多くの時間を費やしているためです (10000 までカウント)。まだ作成されていないタスクは「実行可能」状態にカウントできませんが、生成されると CPU 時間で %sys を消費します。
したがって、実際の答えは、どのような作業が行われても、多数のタスク (スレッドまたはプロセス) が立て続けに生成されるということです。
解決策 3:
負荷平均があまり増加しない場合は、ハードウェアの仕様と処理されるタスクの性質により、全体的なスループットが良好になり、タスクがしばらくの間タスク キューに積み上げられるのを回避していることを意味します。
たとえば、タスクの平均複雑度が高すぎる、またはタスクの平均処理時間が CPU サイクルを多く消費するなどの理由で競合現象が発生した場合は、負荷平均が増加します。
更新:
元の回答では明確でない可能性があるため、今明確にしています:
負荷平均計算の正確な式は次のとおりです:loadvg = tasks running + tasks waiting (for cores) + tasks blocked
.
確実に優れたスループットを実現し、負荷平均を 24 に近づけることができますが、タスクの処理時間にペナルティはありません。一方で、2 ~ 4 個の周期的なタスクが十分な速さで完了しない場合、(CPU サイクルの) 待機中のタスクの数が増加し、最終的に高い負荷平均に達することになります。発生する可能性があるもう 1 つのことは、未処理の同期 I/O 操作を実行しているタスクがコアをブロックし、スループットが低下し、待機中のタスク キューが大きくなることです (その場合、iowait
が表示される場合があります)。 指標の変更)
解決策 4:
Matthew Ife の回答は非常に役に立ち、正しい方向に導いてくれましたが、私たちの場合の動作の原因は正確ではありませんでした。私たちの場合、スレッド プーリングを使用するマルチスレッド Java アプリケーションがあります。実際のタスクを作成する作業が行われないのはなぜですか。
ただし、スレッドが行う実際の作業は短期間であり、IO 待機または同期待機が含まれます。 Matthew が回答で述べているように、負荷平均は OS によってサンプリングされるため、短期間のタスクを見逃す可能性があります。
その動作を再現する Java プログラムを作成しました。次の Java クラスは、サーバーの 1 つで 28% (スタック 650%) の CPU 使用率を生成します。これを行っている間、負荷平均は約 1.3 です。ここで重要なのは、スレッド内の sleep() です。それがなければ、負荷計算は正しくなります。
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class MultiThreadLoad {
private ThreadPoolExecutor e = new ThreadPoolExecutor(200, 200, 0l, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(1000), new ThreadPoolExecutor.CallerRunsPolicy());
public void load() {
while (true) {
e.execute(new Runnable() {
@Override
public void run() {
sleep100Ms();
for (long i = 0; i < 5000000l; i++)
;
}
private void sleep100Ms() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
}
}
public static void main(String[] args) {
new MultiThreadLoad().load();
}
}
要約すると、この理論は、アプリケーションのスレッドは頻繁にアイドル状態になり、その後短時間の作業を実行するため、負荷平均計算でタスクが正しくサンプリングされない理由です。
解決策 5:
負荷平均には、ディスク IO でブロックされているタスクが含まれているため、10 個のタスクすべてが非常に遅いディスクから読み取ろうとするだけで、CPU 使用率がゼロになり、負荷平均が 10 になります。したがって、ビジー状態のサーバーがディスクのスラッシングを開始し、すべてのシークによって多くのブロックされたタスクが発生し、負荷平均が上昇するのはよくあることですが、ディスク上ですべてのタスクがブロックされるため、CPU 使用率が低下します。