LoginSignup
2
5

More than 3 years have passed since last update.

pythonのリアルタイム性能

Last updated at Posted at 2021-05-01

pythonでsleep(遅延)したときに、実際にどれ位の時間スリープするかという話です。マイクロ秒単位のリアルタイム処理が可能みたいです。ここで「リアルタイム性」とは、狙った時刻に処理を行えるという性質です。OSに依存する話で、以下の話はラズパイ4B上のリアルタイムパッチが当てられたDebian 11 BullseyeのLinuxカーネル普通のカーネル実験しました。x86_64でも同じことをやって傾向は同じでした。

更新: qemu内でも同じことを行って同じ傾向を得たので追記しました

Linux上のプログラムの実行優先度の変更

nice

ナイス値(-20 〜 +19)を変化させます。ナイス値が小さいほうが優先して実行されるます。最も優先して実行するには nice --20 やりたいこと のようにプログラムを起動します。

chrt

ナイス値の調整よりもさらに優先度を上げたい場合や下げたい使います。割り込み処理も含めてあらゆる処理を押しのけて実行させたい場合 chrt --fifo 99 やりたいこと とし、他に何もやることが無い場合にだけ実行させたい場合 chrt --idle 0 やりたいこと とします。

実験

実験の下準備

  1. apt-get install stress-ng linux-cpupower
  2. cpupower frequency-set -g performance でCPU動作周波数を最高値に固定します
  3. stress-ng --timeout 300 --parallel 0 --class pipe & でOSとすべてのCPUに負荷を掛けます。pipeinterrupt に変えるとよりキビシクなります。

実験結果

狙った遅延と平均の誤差をマイクロ秒単位で表示しています。RTと標準とQEMUはそれぞれLinuxのカーネルの種類を表します。QEMUはQEMU内のDebian 11標準amd64カーネルです。QEMUを動かしているときにホストOSはほぼアイドル状態でした。nice -19 および chrt --idle 0 で起動したら遅延の計測プログラムが終了しなかったので、結果を載せていません。

実行優先度・OS種別\狙った遅延 100 1000 10000 100000 1000000 10000000
普通に起動・標準 47403.7 1216.6 14996.0 164.6 1061.6 8126.5
普通に起動・RT 79.9 69.1 85.5 223.6 1082.1 10211.5
普通に起動・QEMU 15575.3 4335.5 134.1 9424.7 1068.2 9318.9
nice --20・標準 2404.2 85.8 32285.5 162.5 982.1 7887.1
nice --20・RT 132.0 89.1 120.8 174.0 3862.4 10071.6
nice --20・QEMU 3532.0 1384.8 5940.3 174.6 911.7 7940.7
chrt --fifo 99・標準 30.6 38.5 64.3 61.8 69.7 63.1
chrt --fifo 99・RT 31.6 21.0 44.6 56.1 64.7 66.4
chrt --fifo 99・QEMU 166.9 58.4 58.4 69.4 78.3 73.7

リアルタイム性が必要な場合は chrt --fifo 99 で起動するのがよいです。リアルタイムパッチを当てなくても chrt --fifo 99 でかなりのリアルタイム応答性が実現できるようです。
上記のQEMU内での実験時には、ホストLinuxの負荷はほぼゼロでしたが、ホストLinuxの負荷が高い場合にはQEMUの実行が優先されるようにchrt --fifoでQEMUが実行されるようにしないとQEMU内でのリアルタイム性能は保証されないはずです。libvirtvirt-manager から起動するQEMUの実行優先度の設定はvcpusched, iothreadsched ならびに emulatorsched で行うことができます。

実験用プロググラム

以下のプログラムを用いて狙った遅延と実際の遅延の差(誤差)をマイクロ秒単位で測っています。time_ns() がPython 3.7で導入されたため、Python 3.7かそれ以降ではないと動作しません。使用したPythonバージョンはDebian 11標準の3.9です。

import time

for 狙った遅延 in (0.0001, 0.001, 0.01, 0.1, 1.0, 10.0):
  print('狙った遅延=' + str(狙った遅延))
  誤差の合計 = 0
  for i in range(3):
    前の時刻 = time.time_ns()
    time.sleep(狙った遅延)
    後の時刻 = time.time_ns()
    誤差 = (後の時刻 - 前の時刻) / 1000000000 - 狙った遅延
    print('誤差={:.1f}マイクロ秒'.format(誤差*1000000))
    誤差の合計 += 誤差
  print('平均の誤差={:.1f}マイクロ秒'.format(1000000 * 誤差の合計 / 3))

上記のpythonプログラムを以下のシェルスクリプトで起動しました

#!/bin/sh

export LANG=C.UTF-8
exec </dev/null >>log.txt 2>&1
set -ex
date
uname -a
cpupower frequency-set -g performance
# 次の行で pipe を interrupt に変えるとよりキビシクなります
stress-ng --timeout 300 --parallel 0 --class pipe &
python3 sleep-test.py
nice --20 python3 sleep-test.py
chrt --fifo 99 nice --20 python3 sleep-test.py
nice -19 python3 sleep-test.py
#chrt --idle 0 nice -19 python3 sleep-test.py
kill -TERM $!
sleep 3
chrt --fifo 99 pkill stress
cpupower frequency-set -g schedutil
2
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
5