web-dev-qa-db-ja.com

LinuxスケジューラからCPUを保護する方法(スレッドをそのCPUにスケジューリングしないようにする)

sched_setaffinityを使用してスレッドをCPUに固定し、パフォーマンスを向上させることができます(状況によっては)

Linuxのmanページから:

プロセスを単一のCPUで実行するように制限すると、プロセスが1つのCPUで実行を停止し、別のCPUで実行を再開したときに発生するキャッシュの無効化によって引き起こされるパフォーマンスコストも回避されます。

さらに、よりリアルタイムの応答が必要な場合は、そのスレッドのスケジューラポリシーをSCHED_FIFOに変更し、優先度をいくつかの高い値(最大sched_get_priority_max)に上げることができます。準備ができると、質問は常にそのCPUで実行中の他のスレッドを横取りする必要があります。

ただし、この時点で、リアルタイムスレッドがプリエンプトしたばかりのCPUで実行されているスレッドが、リアルタイムスレッドのレベル1キャッシュエントリの多くを追い出した可能性があります。

私の質問は次のとおりです。

  1. スケジューラが特定のCPUにスレッドをスケジュールするのを防ぐことは可能ですか? (例:CPUをスケジューラから完全に隠すか、または他の方法で)
  2. そのCPUで絶対に実行できなければならないいくつかのスレッドはありますか? (例:カーネルスレッド/割り込みスレッド)
  3. そのCPUでカーネルスレッドを実行する必要がある場合、カーネルスレッドが不足しないように、使用するのに妥当な最大優先度の値はどれですか。
24
Steve Lorimer

答えは cpusets を使用することです。 python cpusetユーティリティ を使用すると、設定が簡単になります。

基本的な概念

3 cpusets

  • root:すべての構成に存在し、すべてのCPUを含む(unshielded
  • system:システムタスクに使用されるCPUが含まれます-実行する必要があるが「重要」ではない(unshielded
  • user:「重要な」タスクに使用されるCPUが含まれます-「リアルタイム」モードで実行するタスク(shielded

shieldコマンドは、これら3つのcpusetsを管理します。

セットアップ中に、すべての移動可能なタスクをシールドされていないcpuset(system)に移動し、ティアダウン中にすべての移動可能なタスクをroot cpusetに移動します。セットアップ後、サブコマンドを使用すると、タスクをshielduser)cpusetに移動できます。さらに、特別なタスク(カーネルスレッド)をrootからsystemに(したがって、 user cpuset)。

コマンド:

まず、シールドを作成します。当然、シールドのレイアウトはマシン/タスクに依存します。たとえば、4コアの非NUMAマシンがあるとします。シールドに専用コア3コアを割り当て、重要でないタスク用の1つのコア;これは非NUMAなので、メモリノードパラメータを指定する必要はなく、カーネルスレッドをroot cpusetで実行したままにします(つまり、すべてのCPUで)。

$ cset shield --cpu 1-3

一部のカーネルスレッド(特定のCPUにバインドされていないもの)は、system cpusetに移動できます。 (一般に、特定のCPUにバインドされているカーネルスレッドを移動することはお勧めしません)

$ cset shield --kthread on

シールド(user)またはシールドされていない(system)cpusetsで実行されているものをリストしましょう:(-vは、プロセス名をリストします)(2番目の-vを追加して、80文字以上を表示します)

$ cset shield --shield -v
$ cset shield --unshield -v -v

シールドを止めたい場合(ティアダウン)

$ cset shield --reset

シールドでプロセスを実行しましょう('--'に続くコマンドは、csetではなく、実行するコマンドに渡されます)

$ cset shield --exec mycommand -- -arg1 -arg2

シールドに移動したい実行中のプロセスが既にある場合(コンマ区切りのリストまたは範囲を渡すことで複数のプロセスを移動できることに注意してください(ギャップがある場合でも、範囲内のすべてのプロセスが移動されます))

$ cset shield --shield --pid 1234
$ cset shield --shield --pid 1234,1236
$ cset shield --shield --pid 1234,1237,1238-1240

高度な概念

cset set/proc-これらにより、cpusetsをより細かく制御できます

セット

Cpusetsの作成、調整、名前変更、移動、破棄

コマンド

Cpu 1-3を使用してcpusetを作成し、NUMAノード1を使用して「my_cpuset1」と呼びます。

$ cset set --cpu=1-3 --mem=1 --set=my_cpuset1

「my_cpuset1」を変更して、CPU 1と3のみを使用する

$ cset set --cpu=1,3 --mem=1 --set=my_cpuset1

Cpusetを破棄する

$ cset set --destroy --set=my_cpuset1

既存のcpusetの名前を変更する

$ cset set --set=my_cpuset1 --newname=your_cpuset1

階層的なcpusetを作成する

$ cset set --cpu=3 --mem=1 --set=my_cpuset1/my_subset1

既存のcpusetsのリスト(深さ1)

$ cset set --list

既存のcpusetとその子をリストする

$ cset set --list --set=my_cpuset1

すべての既存のcpusetsをリストして下さい

$ cset set --list --recurse

Proc

スレッドとプロセスを管理する

コマンド

Cpusetで実行中のタスクを一覧表示する

$ cset proc --list --set=my_cpuset1 --verbose

Cpusetでタスクを実行する

$ cset proc --set=my_cpuset1 --exec myApp -- --arg1 --arg2

タスクを移動する

$ cset proc --toset=my_cpuset1 --move --pid 1234
$ cset proc --toset=my_cpuset1 --move --pid 1234,1236
$ cset proc --toset=my_cpuset1 --move --pid 1238-1340

タスクとそのすべての兄弟を移動する

$ cset proc --move --toset=my_cpuset1 --pid 1234 --threads

すべてのタスクを1つのCPUセットから別のCPUセットに移動する

$ cset proc --move --fromset=my_cpuset1 --toset=system

固定されていないカーネルスレッドをcpusetに移動する

$ cset proc --kthread --fromset=root --toset=system

カーネルスレッド(特定のcpuに固定されているスレッドを含む)をcpusetに強制的に移動します(注:これはシステムに悲惨な結果をもたらす可能性があります-何をしているのかを確認してください)

$ cset proc --kthread --fromset=root --toset=system --force

階層の例

階層的なcpusetsを使用して、優先順位付けされたグループを作成できます

  1. 1 CPU(0)のsystem cpusetを作成します
  2. 1 cpuでprio_low cpusetを作成します(1)
  3. 2 cpusでprio_met cpusetを作成します(1-2)
  4. 3 cpus(1-3)のprio_high cpusetを作成する
  5. prio_all cpusetを4つすべてのCPU(0〜3)で作成します(これはrootと同じであることに注意してください。rootから分離することをお勧めします)

上記を実現するには、prio_allを作成してから、prio_allの下にサブセットprio_highを作成します。

$ cset set --cpu=0 --set=system
$ cset set --cpu=0-3 --set=prio_all
$ cset set --cpu=1-3 --set=/prio_all/prio_high
$ cset set --cpu=1-2 --set=/prio_all/prio_high/prio_med
$ cset set --cpu=1 --set=/prio_all/prio_high/prio_med/prio_low
41
Steve Lorimer

これを行うには、他に2つの方法が考えられます(ただし、Red Hatからの素晴らしいレベルのサポートはないようですが、csetほどエレガントではありません)。

1)PID 1を含むすべてのタスクセット-素晴らしくて簡単です(しかし、私自身は問題を見たことがないので、スケジューラーの非効率性を引き起こす可能性があります)。以下のスクリプト(rootとして実行する必要があります)は、init(pid 1)を含む実行中のすべてのプロセスでタスクセットを実行します。これにより、実行中のすべてのプロセスが1つ以上の「ジャンクコア」に固定されます。また、initを固定することで、将来のプロセスも「ジャンクコア」のリストで確実に開始されます。

#!/bin/bash

if [[ -z $1 ]]; then
  printf "Usage: %s '<csv list of cores to set as junk in double quotes>'", $0
  exit -1;
fi

for i in `ps -eLfad |awk '{ print $4 } '|grep -v PID | xargs echo `; do 
   taskset -pc $1 $i;
done

2)isolcpusカーネルパラメータを使用します(これは https://www.kernel.org/doc/Documentation/kernel-parameters.txt のドキュメントです):

isolcpus=   [KNL,SMP] Isolate CPUs from the general scheduler.
            Format:
            <cpu number>,...,<cpu number>
            or
            <cpu number>-<cpu number>
            (must be a positive range in ascending order)
            or a mixture
            <cpu number>,...,<cpu number>-<cpu number>

        This option can be used to specify one or more CPUs
        to isolate from the general SMP balancing and scheduling
        algorithms. You can move a process onto or off an
        "isolated" CPU via the CPU affinity syscalls or cpuset.
        <cpu number> begins at 0 and the maximum value is
        "number of CPUs in system - 1".

        This option is the preferred way to isolate CPUs. The
        alternative -- manually setting the CPU mask of all
        tasks in the system -- can cause problems and
        suboptimal load balancer performance.

私はいくつかのプロジェクトでこれら2つとcsetメカニズムを使用しました(偶然にも、露骨な自己宣伝を許してください:-))、私は Pontus Vision ThreadManager というツールの特許を申請しました特定のx86プラットフォームの特定のソフトウェアワークロードへの最適なピン留め戦略を実行します。お客様のサイトでテストした後、私は本当に良い結果(ピークレイテンシの270%削減)を得たので、ピン留めとCPU分離を行う価値は十分にあります。

6
Leo

これは、cgroupを使用して、昔ながらの方法で行う方法です。私はFedora 28マシンを持っていて、RedHat/Fedoraがsystemd-runを使用することを望んでいますが、そこにこの機能を見つけることができませんでした。誰かが私を啓蒙したいのであれば、systemd-runを使用してそれを行う方法を知りたいです。

(CPU 0〜3の)4番目のCPUをスケジューリングから除外し、既存のすべてのプロセスをCPU 0〜2に移動するとします。次に、CPU 3にプロセスをすべて単独で配置したいと思います。

Sudo su -
cgcreate -g cpuset:not_cpu_3
echo 0-2 > /sys/fs/cgroup/cpuset/not_cpu_3/cpuset.cpus
# This "0" is the memory node. See https://utcc.utoronto.ca/~cks/space/blog/linux/NUMAMemoryInfo
# for more information *
echo 0 > /sys/fs/cgroup/cpuset/not_cpu_3/cpuset.mems
  • 具体的には、マシン上で/proc/zoneinfo/sys/devices/system/node階層を確認する必要があります。適切なノード情報を取得することは、読者のための演習として残します。

これでcgroupができたので、分離されたCPU 3 cgroupを作成する必要があります。

cgcreate -g cpuset:cpu_3
echo 3 > /sys/fs/cgroup/cpuset/cpu_3/cpuset.cpus
# Again, the memory node(s) you want to specify.
echo 0 > /sys/fs/cgroup/cpuset/cpu_3/cpuset.mems

すべてのプロセス/スレッドをnot_cpu_3 cgroupに配置します。

for pid in $(ps -eLo pid) ; do cgclassify -g cpuset:not_cpu_3 $pid; done

レビュー:

ps -eL k psr o psr,pid,tid,args | sort | cut -c -80

注意!現在スリープ中のプロセス移動しません。スケジューラーがそれらを別のCPUに置くために、それらは起こされなければなりません。これを確認するには、上記のリストからお気に入りのスリーププロセスを選択します。CPU0〜2にあるはずのプロセス(Webブラウザなど)はまだ3のままです。上記のリストのスレッドIDを使用して、以下を実行します。

kill -CONT <thread_id>

kill -CONT 9812

Psコマンドを再実行し、それが別のCPUに移動されていることを確認します。

ダブルノート!一部のカーネルスレッドできないで、移動しません。たとえば、すべてのCPUにカーネルスレッド[kthreadd]があることに注意してください。プロセスをcgroupsに割り当てることは、カーネルスレッドではなく、ユーザー空間プロセスに対して機能します。これがマルチタスクの世界での生活です。

次に、プロセスとそのすべての子を制御グループcpu_3に移動します。

pid=12566 # for example
cgclassify -g cpuset:cpu_3 $pid
taskset -c -p 3 $pid

繰り返しますが、$pidがスリープしている場合は、CPUの移動を実際に実行するためにスリープを解除する必要があります。

これをすべて元に戻すには、作成したcgroupを削除するだけです。誰もがルートcgroupに戻ります。

cgdelete -r cpuset:cpu_3
cgdelete -r cpuset:not_cpu_3

再起動する必要はありません。

(申し訳ありませんが、元のポスターの3番目の質問は理解できません。コメントはできません。)

0
Mike S

Rhelインスタンスを使用している場合は、これにTunaを使用できます(他のLinuxディストリビューションでも使用できる場合がありますが、それについては不明です)。 yumコマンドから簡単にインストールできます。 TunaはCPUコアを分離するために使用でき、特定のCPUで実行されているプロセスを隣接するCPUに動的に移動します。 CPUコアを分離するコマンドは次のとおりです。

# tuna --cpus=CPU-LIST --isolate

htopを使用すると、マグロがリアルタイムでCPUコアを分離する方法を確認できます。

0
root