群とか環とか体とか、大学で教えてもらった記憶がうっすらありますが、何回見ても忘れます。幸いにもWikipedia先生に割と詳しく解説されているので、まとめておこうと思います。
集合Gと二項演算uの組(G, u)を考えます。uは乗法や加法と呼ばれる場合があるようです。以降u(a, b) をa + bの形で表します。
下にある群は、上の群の性質をすべて満たします(以降も特に断りがなければ同じ)。なので、モノイドは半群の性質を満たしますし、群はモノイドと半群の性質を満たします。
集合Rと2つの二項演算u, tの組(R, u, t) を考えます。(R, u) はアーベル群です。uは加法、tは乗法と呼ばれるようです。以降t(a, b) をa * bの形で表します。
群と違って、環はモノイド(単位元が存在する)である必要はないみたいです。
可換環では零でない零因子という困ったことが起きます。
困る例として挙げられていたのはa * b = 0かつa != 0でもb = 0とは限らない、もしくは、a * b = a * cかつa != 0でも、b = cとは限らない、という例でした。
具体的な例が載ってませんでしたが、行列の和と積を考えると発生しそうに思えます。
A = [1, 1] [0, 0] B = [ 1, 0] [-1, 0] C = [0, -1] [0, 1] A * B = [0, 0] [0, 0] A * C = [0, 0] [0, 0] A != 0だがB, Cは零行列ではない。A * B = A * Cだが、B = Cでもない。
環はこういうパターンが無数に出てきて、さらに条件を厳しくしないと困る場合がありますよ、ということですかね?
変わった名前ですが、環の一種のようです。環で発生する零因子による困った問題を排除しています。
群環体の「体」まで辿り着きたかったのですが、整閉整域、一意分解整域、主イデアル整域、ユークリッド整域、体、有限体、と訳のわからない名前のオンパレードで、力尽きました。また今度調べます。
現在使っているThinkPad E480はUSB Type-C端子とHDMI端子しかありません。先日(2021年2月12日の日記参照)新しく購入したディスプレイ2つに画像を出力するには、USB Type-CのDisplayPort Alternate Mode(VESAの機能紹介のページ(英語))を使う必要があります。接続はこんな感じです。
このときE480とディスプレイを繋ぐUSB Type-Cケーブルは3つの役割を果たします。
たった1本のUSB Type-Cケーブルで3役もこなす凄いヤツです。今まで「ACアダプタ」「DisplayPort」「USB Type-A」の3つに分かれていたコネクタが、USB Type-C 1つに統合できますから、省スペースが命のノートPCにとっては大歓迎の機能でしょう。
そんな凄いヤツを使い始めて数日ですが、既に嫌になってきました。正直な感想として、少なくともユーザーからすると便利と思えないし、使わなくて良いなら使いたくないです。
ケーブルの問題は今後、技術革新で細くなることを祈るしかありません。E480の問題は回避策不明です。HWの制約?USBハブの問題は、E480ならUSB Type-Aコネクタが別にあるので、そちらを使えば回避可能です。USB PDの問題は今の所E480だと困っていません。
しかし将来的にノートPCを買い換えるとUSB PDの問題が起きる可能性があります。回避策はあるでしょうか?USB Type-Cが3つ付いていて、1つはDisplayPort、2つ目はUSB PD電源供給、3つ目はPCに繋ぐ、変なハブ的なものがあれば良い?もはや「ケーブル1本でOK」のコンセプトは完全崩壊だし、ディスプレイ側のUSB PDは死蔵確定です。そんな訳のわからんことになるなら、素直にDisplayPortとUSB Type-Cのコネクタ2個付けてくれよって思います。
そもそもDislayPortとUSB PDなんて全く無関係のものを統合したら、どちらか壊れただけでPCが機能不全になることくらい、聡明なVESAやUSB-IFの面々には明らかなはずですけど、何でこんなデザインにしたんですかね?理解しがたいよ……。
昔SHARP SF1という製品がありました(DIMEの記事)。スーパーファミコンとテレビが1つに合体した、当時小学生だった私には夢のような製品でした。配線なしで見た目スッキリ、場所も取らない、ACアダプタやビデオケーブルを接続する手間も不要、いかにも便利そうじゃないですか?結構お高いのもあって、友達が持っているのを見て羨ましかったです。USB Type-Cも似たような売り文句です。
しかし後から聞くところによれば、テレビが故障するとスーパーファミコンを外せないから他のテレビに繋げなくて困る、逆にスーパーファミコンが故障するとテレビごと修理になって高ぇわテレビがなくなるわで困る、元より不便になっています。元々バラバラの製品を無理やり一蓮托生にしたら、そりゃそうなりますわな……。無関係の機能をデタラメに統合してはいけない、という好例です。
残念なことにUSB Type-CもDisplayPortとUSB PDの機能統合で、似たような落とし穴に落ちています。小学生の私に「21世紀になっても人類はSF1と同じ過ちを繰り返しているよ」って教えてあげたいですね。
COVID-19が流行し始めた昨年2月ころ、在宅勤務が主となりました。当時の気持ちを正直に言えば「すぐ収束して、電車通勤に戻るだろう」で、完全にナメていました。在宅勤務の環境もまったく整えておらず、ダイニングテーブル、ノートPC+8インチのモバイルディスプレイでした。夕食時は邪魔だから、仕事道具をガサガサ片付ける、といった具合です。
そんなこんなで1年間やってきたものの、COVID-19の予想外の長期化、それに加えて会社方針(COVID-19が収束しようとしまいと、今後は在宅勤務)もあって、在宅勤務の環境を改善することにしました。
家には、奥さんが一昨年の在宅勤務で使っていた部屋があって、幅100cmの机と背もたれ付きのオフィスチェアがあります。そのスペースを譲ってもらうことにしました。
すっかり勤務形態が逆転しました。会社のみなさんの話を聞くと、東京の狭い家で夫婦とも在宅勤務、子供まで自粛で家に居て、全く仕事にならん……みたいな地獄化したご家庭もあるみたい。我が家も夫婦同時に在宅勤務だと、スペースの確保がちょっと大変だったと思います。交代で在宅勤務になったのは、今思えば割とラッキーだったのかな?
ダイニングテーブルはしっかりした作りで、間違いなく家で一番上等なテーブルです。一方のダイニングチェアは年中使い続けるような椅子ではなかったらしく、1年間酷使し続けたところ、座面、背面のクッションが潰れ椅子のフレームが腰にガツガツ当たるようになりました。痛ぇよーー。
部屋を移ってスペースが広がったので、大きめのデュアルディスプレイも目指します。幅120cmの机を買い足し100cmの机は横向きに合わせL字にします。
買ったのは山善のAMDT-1260 AMDL-70という一番シンプルな天板と脚だけのテーブルです(Amazonへのリンク)。Amazonで9,000円くらいとお安いです。理由はわからないですが、山善の公式サイトには載っていないんですよね。なぜだろね?
机の幅を考えると27インチx2が載りそうです。ただし、机の奥行きがあまりない(60cm)ので、大きすぎると端が見えなくなって、かえって使いにくいです。という点を勘案して24インチx2にします。
買ったのはEIZO FlexScan EV2480です。Amazonで1台4万円くらい。デュアルディスプレイにしたので、総額8万円くらい掛かりました。非常に高価ですが、これでも中位機種なんです。ちなみに上位機種のEV2495は1台7万円します。強烈!
FlexScanは重たいのが難点(純正スタンドがめちゃ重い)ですが、変な色やら、映らないやらのトラブルは皆無で非常に快適です。以前勤めてた会社(パナソニックやソシオネクスト)でも大変お世話になりました。良いメーカーですよね。
目次: ベンチマーク
先日(2021年2月5日の日記参照)非正規化数(Denormal数)の計算は遅いと書きましたが、いかほどでしょうか?どのくらい遅いのか、デノーマルフラッシュでどの程度速くなるのか、この2点について見ていこうと思います。
おそらく世の中のどのFPUも割り算が一番苦手なはずです。入力を正規化数、非正規化数の2パターン用意して、下記の演算をたくさん実行してみます。
#define SIZE 10000
union uuu {
double f;
long long n;
};
...
void test_speed(union uuu *val)
{
for (int i = 0; i < SIZE; i++) {
dst[i].f =
val[i].f / 1.08f -
val[i].f / 1.07f +
val[i].f / 1.06f -
val[i].f / 1.05f +
val[i].f / 1.04f -
val[i].f / 1.03f +
val[i].f / 1.02f -
val[i].f / 1.01f;
}
}
コードは全部貼り付けると邪魔くさいのでGitHubにおきました(GitHubへのリンク)。前回のFTZ, DAZのテストに使ったコードも一緒に入っています。
まずはx86系のCPUで測ってみましょう。手元にあるのはZen系(AMD Ryzen 7 2700)と、Atom系(Intel Pentium J4205)なので、この2つで測ります。コンパイラはgcc (Debian 10.2.1-6) 10.2.1 20210110、最適化レベルはO2です。
10000 loops Normal ----- FTZ:OFF, DAZ:OFF 0.912859[s] dst : -3.668038e-02(0xbfa2c7c56ca81140) FTZ:OFF, DAZ:ON 0.880695[s] dst : -3.668038e-02(0xbfa2c7c56ca81140) FTZ:ON, DAZ:OFF 0.880633[s] dst : -3.668038e-02(0xbfa2c7c56ca81140) FTZ:ON, DAZ:ON 0.880653[s] dst : -3.668038e-02(0xbfa2c7c56ca81140) Denormal ----- FTZ:OFF, DAZ:OFF 1.806291[s] dst : -4.446591e-323(0x8000000000000009) FTZ:OFF, DAZ:ON 0.783578[s] dst : 0.000000e+00(0x00000000) FTZ:ON, DAZ:OFF 1.513203[s] dst : 0.000000e+00(0x00000000) FTZ:ON, DAZ:ON 0.783183[s] dst : 0.000000e+00(0x00000000)
1000 loops Normal ----- FTZ:OFF, DAZ:OFF 1.024671[s] dst : -3.668038e-02(0xbfa2c7c56ca81140) FTZ:OFF, DAZ:ON 1.025767[s] dst : -3.668038e-02(0xbfa2c7c56ca81140) FTZ:ON, DAZ:OFF 1.025151[s] dst : -3.668038e-02(0xbfa2c7c56ca81140) FTZ:ON, DAZ:ON 1.027414[s] dst : -3.668038e-02(0xbfa2c7c56ca81140) Denormal ----- FTZ:OFF, DAZ:OFF 12.147070[s] dst : -4.446591e-323(0x8000000000000009) FTZ:OFF, DAZ:ON 0.309857[s] dst : 0.000000e+00(0x00000000) FTZ:ON, DAZ:OFF 7.126950[s] dst : 0.000000e+00(0x00000000) FTZ:ON, DAZ:ON 0.312373[s] dst : 0.000000e+00(0x00000000)
Ryzen 7 2700でもPentium J4205でも、Denormal数が計算に出現すると遅くなりますが、J4205はその傾向が顕著で10倍くらい遅いです。FTZ, DAZをONにすると、効果覿面に速くなります。
次にARMで測ってみましょう。手元にあるのはRK3399です。Cortex-A72とCortex-A53が混載されているので、両方で測ります。カーネルはLinux 5.11.0-rc3-next-20210113、コンパイラはgcc (Debian 8.3.0-6) 8.3.0、最適化レベルはO2です。
ARMは仕様上FTZとDAZが分かれていない(FZという両方合わせたような機能がある)ので、片方だけONにした結果はありません。
$ taskset 0x10 ./a.out 10000 ... 10000 loops Normal ----- FTZ:OFF, DAZ:OFF 7.153237[s] dst : -3.668038e-02(0xbfa2c7c56ca81140) FTZ:ON, DAZ:ON 7.118581[s] dst : -3.668038e-02(0xbfa2c7c56ca81140) Denormal ----- FTZ:OFF, DAZ:OFF 8.008282[s] dst : -4.446591e-323(0x8000000000000009) FTZ:ON, DAZ:ON 1.779883[s] dst : 0.000000e+00(0x00000000)
$ taskset 0x1 ./a.out 10000 ... 10000 loops Normal ----- FTZ:OFF, DAZ:OFF 11.693382[s] dst : -3.668038e-02(0xbfa2c7c56ca81140) FTZ:ON, DAZ:ON 11.691307[s] dst : -3.668038e-02(0xbfa2c7c56ca81140) Denormal ----- FTZ:OFF, DAZ:OFF 12.196882[s] dst : -4.446591e-323(0x8000000000000009) FTZ:ON, DAZ:ON 11.697961[s] dst : 0.000000e+00(0x00000000)
Cortex-A72はデノーマルフラッシュの効果抜群ですが、Cortex-A53はデノーマルフラッシュによる変化はほとんどありません。どちらもARMが設計した同世代のCPUにも関わらず、速度の傾向は大きく異なります。詳細な理由はARMにしかわかりませんが、なかなか興味深い結果です。
最近の日本やら先進国やらの迷走を見ていると、民主主義って人口ピラミッドが逆転することを考慮できていないのでは?と疑問を感じます。
人口ピラミッドが逆転して高齢世代が主流派になると、
このような政策が支持されるので、少子化はさらに加速し、先のない高齢世代に最大投資しまくる、まさに今の日本みたいな状態になります。当然ながら経済力は下がる一方ですし、滅亡一直線の国家です。
民主主義のバグとしか思えないですね……。
目次: 車
ディーラーから電話がかかってきて「(車検と一緒にセットで頼んだ)半年点検どうしましょうか?」と聞かれました。実は頼んだことを忘れていたので、確認してもらって助かりました。「半年前の車検で一緒にご依頼されました半年点検なんですが〜」と説明が妙に手慣れた感じだったところを見るに、頼んでおいて忘れてしまう、私みたいな人が多いんだな〜なんて思った。
点検結果はというと、走行系の異常はありませんでした。フロントのウォッシャー液を送るゴムホースが破損していて、フロントのウォッシャー液が出ないうえ、むりやり使ったらエンジンルームにダダ漏れするそうです。え、そうなの??全く気づいていませんでした。ウォッシャー液って全然使わないんだよね……。
部品の取り寄せが要るためすぐには直せず、修理の際はご予約いただきたい、とのことだったので、またどこかの土曜日にでも持っていこうと思います。
私のようなおじさんがディーラーに行くと、新車のカタログをたくさん渡されて「新車どうです?」って話されるんですが、最近のスバルではなーんにも聞かれません。今のスバルはレガシィB4 GTの乗り換え先がないからです。
スバルの現行車種でセダンはインプレッサG4だけです。G4は上級グレードの2.0iでも、2リッターNA(154馬力)です。レガシィB4 GTの2リッターターボ(280馬力)から乗り換えると大幅パワーダウンは否めず、おすすめしづらいのでしょう。現にディーラーでもカタログだけもらったものの、ほとんどプッシュされませんでした。
ちょっと前なら、インプレッサWRX S4 STI Sportがありましたが、生産終了してしまいました。仮に現行車種だったとしても、一般人向けとは思えんし、気軽に「WRX S4いかがですか?」とは言いにくいでしょう。スバルのディーラーにとってセダン乗りは鬼門ですね。
目次: ベンチマーク
浮動小数点数の演算ではIEEE 754という規格があり、x86系のプロセッサはこの規格に基づいた演算を行っています。規格のなかに非正規化数(Denormal NumberもしくはSubnormal Numberとも)という、極めて小さい特殊な数のカテゴリがあります(参考: IEEE 754 - Wikipedia)。
通常はDenormal数も正確に演算しますが、Denormal数の演算は遅いです。計算精度より計算速度が重要な場面ではMXCSRレジスタに特殊なフラグを設定することで、Denormal数の扱いを変更し、高速に演算することができます(参考: FTZフラグとDAZフラグの設定 - インテルC++ コンパイラー18.0デベロッパー・ガイドおよびリファレンス)。
基本的にはDenormal数を無視して0として扱うようにしますが、Intelのプロセッサでは入力側と出力側を別々に扱えるようです。設定可能なフラグは2つあります。デノーマルフラッシュって必殺技の名前っぽいよね。どうでもいいけど。
うーん。わかるような、わからないような。こういうときは実際に動かしてどんな結果になるか試すのが一番良いでしょう。FTZとDAZを有効にする方法はIntelコンパイラの説明から抜粋したものですが、GCCでも全く同じ方法で利用可能です。
#include <stdint.h>
#include <stdio.h>
#include <pmmintrin.h>
double zero = 0.0f;
union {
double f;
long long n;
} a, b, c, d, ab, ac, ca, dc;
void test()
{
a.n = 0x0008000000000000ULL;
b.n = 0x0004000000000000ULL;
c.n = 0x0010000000000000ULL;
d.n = 0x0014000000000000ULL;
ab.f = a.f + b.f;
ac.f = a.f + c.f;
ca.f = c.f - a.f;
dc.f = d.f - c.f;
printf(" a : %e(0x%08llx)\n"
" b : %e(0x%08llx)\n"
" c : %e(0x%08llx)\n"
" d : %e(0x%08llx)\n"
" a+b : %e(0x%08llx)\n"
" a+c : %e(0x%08llx)\n"
" c-a : %e(0x%08llx)\n"
" d-c : %e(0x%08llx)\n"
" a==0: %d\n\n",
a.f, a.n, b.f, b.n, c.f, c.n, d.f, d.n,
ab.f, ab.n, ac.f, ac.n, ca.f, ca.n, dc.f, dc.n,
a.f == zero);
}
int main(int argc, char *argv[])
{
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_OFF);
_MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_OFF);
printf("FTZ:OFF, DAZ:OFF\n");
test();
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_OFF);
_MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON);
printf("FTZ:OFF, DAZ:ON\n");
test();
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
_MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_OFF);
printf("FTZ:ON, DAZ:OFF\n");
test();
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
_MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON);
printf("FTZ:ON, DAZ:ON\n");
test();
return 0;
}
変数aとbはDenormal数です(doubleのexponent部分が0)。変数cは非常に小さいですが通常の数です。最後のa.f == zeroはDenormal数と0を比較したときに、等しければ1、等しくなければ0が出力されます。実行結果とともに説明したほうが良いと思うので、実行結果を示します。
$ gcc a.c && ./a.out FTZ:OFF, DAZ:OFF a : 1.112537e-308(0x8000000000000) b : 5.562685e-309(0x4000000000000) c : 2.225074e-308(0x10000000000000) d : 2.781342e-308(0x14000000000000) a+b : 1.668805e-308(0xc000000000000) ★Denorm1 + Denorm2 = Denorm3 a+c : 3.337611e-308(0x18000000000000) ★Denorm1 + Normal1 = Normal1+α c-a : 1.112537e-308(0x8000000000000) ★Normal1 - Denorm1 = Denorm4 d-c : 5.562685e-309(0x4000000000000) ★Normal2 - Normal1 = Denorm5 a==0: 0 ★Denorm1 == 0 ? いいえ FTZ:OFF, DAZ:ON a : 1.112537e-308(0x8000000000000) b : 5.562685e-309(0x4000000000000) c : 2.225074e-308(0x10000000000000) d : 2.781342e-308(0x14000000000000) a+b : 0.000000e+00(0x00000000) ★Denorm1 + Denorm2 = 0(※1) a+c : 2.225074e-308(0x10000000000000) ★Denorm1 + Normal1 = Normal1 → 入力のDenormalが0扱い = DAZ c-a : 2.225074e-308(0x10000000000000) ★Normal1 - Denorm1 = Normal1 → 入力のDenormalが0扱い = DAZ d-c : 5.562685e-309(0x4000000000000) ★Normal2 - Normal1 = Denorm5 a==0: 1 ★Denorm1 == 0 ? はい → 入力のDenormalが0扱い = DAZ FTZ:ON, DAZ:OFF a : 1.112537e-308(0x8000000000000) b : 5.562685e-309(0x4000000000000) c : 2.225074e-308(0x10000000000000) d : 2.781342e-308(0x14000000000000) a+b : 0.000000e+00(0x00000000) ★Denorm1 + Denorm2 = 0(※1) a+c : 3.337611e-308(0x18000000000000) ★Denorm1 + Normal1 = Normal1+α c-a : 0.000000e+00(0x00000000) ★Normal1 - Denorm1 = 0 → 演算結果のDenormalが0扱い = FTZ d-c : 0.000000e+00(0x00000000) ★Normal2 - Normal1 = 0 → 演算結果のDenormalが0扱い = FTZ a==0: 0 ★Denorm1 == 0 ? いいえ FTZ:ON, DAZ:ON a : 1.112537e-308(0x8000000000000) b : 5.562685e-309(0x4000000000000) c : 2.225074e-308(0x10000000000000) d : 2.781342e-308(0x14000000000000) a+b : 0.000000e+00(0x00000000) ★Denorm1 + Denorm2 = 0(※1) a+c : 2.225074e-308(0x10000000000000) ★Denorm1 + Normal1 = Normal1 → 入力のDenormalが0扱い = DAZ c-a : 2.225074e-308(0x10000000000000) ★Normal1 - Denorm1 = Normal1 → 入力のDenormalが0扱い = DAZ d-c : 0.000000e+00(0x00000000) ★Normal2 - Normal1 = 0 → 演算結果のDenormalが0扱い = FTZ a==0: 1 ★Denorm1 == 0 ? はい → 入力のDenormalが0扱い = DAZ (※1)演算結果だけでは、入力のDenormal数が両方0扱いなのか、結果のDenormal数が0扱いなのか、判別できない。
FTZとDAZが発生する例を示したつもりです。できるだけ頑張って説明してみたんですが、良くわからなかったらごめんなさい。
目次: ベンチマーク
昔(2017年6月14日の日記参照)yesの速度を測ったりして遊んでいましたが、改めてRyzen 7 2700でyes | pv > /dev/nullを実行してみたところ、出力速度が不安定です。
出力速度が不安定なときに、topで各CPUスレッドの負荷を眺めていると、ときどきプロセスが違うCPUスレッドに移動しているようにみえます。コアごとに動作周波数が違うせいか、yesのプロセスが別のコアに移ったとき、移動先のコアが省エネモードから最高周波数に立ち上がるまでのラグが影響しているんでしょうか?どうやって確かめましょうね?特定のスレッドに貼り付けたらエエんかしら??
てなことを最初考えたんですが、実はそんなに難しい話ではなく、単にyesとpvが同じコアに割り付けられたときに、速度的に不利に働いているだけのような気がしてきました。実験するためtasksetを使って適当にスレッドを散らします。
かなり性能が変わります。コアが同じかどうか?はもちろん重要ですが、Zenアーキテクチャはコアコンプレックスの内か外かで性能に大きな違いが出ます。結果が安定しなかったのはプロセスがコアコンプレックス外に行ったり来たりしていたためでしょうね。
Debian TestingのLinux Kernel(現状、5.10.4-1)は、コアコンプレックスまでは考慮してくれないらしく、コアコンプレックス内と外のコアのどちらで実行しても良いよ、という設定にすると、処理が遅くなる方に割り付けてしまいます。
$ taskset 0x110 yes | taskset 0x1 pv > /dev/null [4.59GiB/s] $ top top - 02:05:53 up 16 days, 9:39, 20 users, load average: 0.64, 0.96, 1.06 Tasks: 355 total, 3 running, 349 sleeping, 2 stopped, 1 zombie %Cpu0 : 7.5 us, 75.8 sy, 0.0 ni, 16.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st ★pvはCPU 0で動作する %Cpu1 : 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu2 : 0.3 us, 0.3 sy, 0.0 ni, 99.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu3 : 0.3 us, 0.3 sy, 0.0 ni, 99.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu4 : 0.0 us, 0.3 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu5 : 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu6 : 0.0 us, 0.3 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu7 : 0.3 us, 0.0 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu8 : 6.0 us, 79.1 sy, 0.0 ni, 14.9 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st ★yesはCPU 4のほうが速いはずだが、CPU 8で動作する %Cpu9 : 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu10 : 0.7 us, 0.0 sy, 0.0 ni, 99.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu11 : 0.3 us, 0.0 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu12 : 0.3 us, 0.3 sy, 0.0 ni, 99.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu13 : 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu14 : 0.7 us, 0.3 sy, 0.0 ni, 99.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu15 : 0.0 us, 0.3 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st MiB Mem : 32106.7 total, 582.9 free, 3532.5 used, 27991.3 buff/cache MiB Swap: 0.0 total, 0.0 free, 0.0 used. 27906.8 avail Mem
パッと見、法則性が良くわかりませんでした。なるべくビジーなスレッドから遠い番号のCPUスレッドに割り当てようとする?のかもしれませんね。
(※1)Ryzen 7は1コア2スレッドなので、スレッド (0, 1), (2, 3), (4, 5) のように2スレッドが同じコアで実行されます。
メモ: 技術系?の話はFacebookから転記しておくことにした。後半を加筆。
目次: Zephyr
Zephyrにはリブートを行う関数sys_reboot() が既に実装されており、アーキテクチャごとのリブート用関数sys_arch_reboot() を呼ぶ仕組みです。
// zephyr/subsys/power/reboot.c
void sys_reboot(int type)
{
(void)irq_lock();
#ifdef CONFIG_SYS_CLOCK_EXISTS
sys_clock_disable();
#endif
sys_arch_reboot(type); //★★アーキテクチャごとのリブート関数を呼ぶ
/* should never get here */
printk("Failed to reboot: spinning endlessly...\n");
for (;;) {
k_cpu_idle();
}
}
// zephyr/soc/riscv/riscv-privilege/virt/soc.c
/* Reboot machine */
#define FINISHER_REBOOT 0x7777
void sys_arch_reboot(int type)
{
volatile uint32_t *reg = (uint32_t *)SIFIVE_SYSCON_TEST;
*reg = FINISHER_REBOOT; //★★0x100000に0x00007777をWrite
ARG_UNUSED(type);
}
// zephyr/soc/riscv/riscv-privilege/virt/soc.h
#include <soc_common.h>
#include <devicetree.h>
#define SIFIVE_SYSCON_TEST 0x00100000 //★追加
#define RISCV_MTIME_BASE 0x0200BFF8
#define RISCV_MTIMECMP_BASE 0x02004000
実装は素直にsys_arch_reboot() を追加しただけです。そんなに難しくないですよね。
前回説明した通り、サンプルアプリのshellを使って動作確認します。
$ cmake -G Ninja -DBOARD=qemu_riscv32 ../samples/subsys/shell/shell_module/ $ ninja menuconfig ★★CONFIG_REBOOTとCONFIG_BOOT_BANNERを有効にする $ ninja run [0/1] To exit from QEMU enter: 'CTRL+a, x'[QEMU] CPU: riscv32 *** Booting Zephyr OS build v2.5.0-rc1-276-ge7d3bb714bc2 *** uart:~$ kernel reboot cold ★★↑ここでリブートされる *** Booting Zephyr OS build v2.5.0-rc1-276-ge7d3bb714bc2 *** uart:~$
無事リブートしました。本当にリブートしてるのか不安になるくらい速いです。Zephyrは起動時に何も言わないので、Linuxと比べるとリブートは簡素に見えますね。速いのは良いんだけど、感動がちょっと薄いのが難点かも?
目次: Zephyr
Zephyrでリセットできないんだけど、って言われて調べたので、忘れないうちに記録に残しておきます。
RISC-Vの規格ではリセット処理について何も記述されていませんので、リセット処理はハードウェア依存となります。QEMUのRISC-V virtマシンはどうかというと、SiFiveのナイスガイ達が作ってくれたリセットの仕組みがあります。
// qemu/hw/riscv/virt.c
static const struct MemmapEntry {
hwaddr base;
hwaddr size;
} virt_memmap[] = {
[VIRT_DEBUG] = { 0x0, 0x100 },
[VIRT_MROM] = { 0x1000, 0xf000 },
[VIRT_TEST] = { 0x100000, 0x1000 }, //★アドレス0x00100000にある★
[VIRT_RTC] = { 0x101000, 0x1000 },
...
// qemu/hw/riscv/virt.c
static void virt_machine_init(MachineState *machine)
{
...
/* SiFive Test MMIO device */
sifive_test_create(memmap[VIRT_TEST].base); //★テストデバイスを追加している★
// qemu/hw/misc/sifive_test.c
/*
* Create Test device.
*/
DeviceState *sifive_test_create(hwaddr addr)
{
DeviceState *dev = qdev_new(TYPE_SIFIVE_TEST);
sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr);
return dev;
}
...
static void sifive_test_write(void *opaque, hwaddr addr,
uint64_t val64, unsigned int size)
{
if (addr == 0) {
int status = val64 & 0xffff;
int code = (val64 >> 16) & 0xffff;
switch (status) {
case FINISHER_FAIL:
exit(code);
case FINISHER_PASS:
exit(0);
case FINISHER_RESET:
qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET); //★ここに到達するとリセットが掛かるはず★
return;
default:
break;
}
}
qemu_log_mask(LOG_GUEST_ERROR, "%s: write: addr=0x%x val=0x%016" PRIx64 "\n",
__func__, (int)addr, val64);
}
// qemu/include/hw/misc/sifive_test.h
enum {
FINISHER_FAIL = 0x3333,
FINISHER_PASS = 0x5555,
FINISHER_RESET = 0x7777 //★この値を書けば良さそう★
};
つまり0x100000に0x00007777を4バイトWriteすれば良さそうです。
Zephyrのサンプルshellを使います。理由はリブートするコマンドが簡単に使えるからです。CONFIG_REBOOTを有効にする必要があります。またshellは起動後プロンプトが出るだけで、リセットが掛かったかどうかわかりにくいため、起動時のバナーも有効にしておくと良いです。
CONFIG_REBOOT=y Boot Options ---> [ ] Reboot functionality CONFIG_BOOT_BANNER=y General Kernel Options ---> Kernel Debugging and Metrics ---> [ ] Boot banner
ちょっと長いので一旦切ります。次回は実装と動作確認をします。
< | 2021 | > | ||||
<< | < | 02 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
- | 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | - | - | - | - | - | - |
合計:
本日: