トップ 最新 追記

Orz日記 by Akio Morita

ToDo:

  • 15 SAD Fit[]回りの障害事例の解析
  • 10 smart pointer版PEGクラスの再実装(Left Recursionまわり)
2006|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|06|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|07|08|09|10|11|12|
2013|01|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|06|07|08|10|12|
2016|01|02|03|05|06|08|10|11|
2017|01|02|03|04|05|06|07|09|10|11|12|
2018|01|02|03|04|06|07|08|09|10|11|12|
2019|01|03|04|05|07|08|09|10|11|12|
2020|01|02|03|04|05|06|07|08|09|10|11|12|
2021|01|02|03|04|05|06|07|08|09|10|11|12|
2022|01|02|03|04|05|06|07|08|09|10|11|12|
2023|01|02|03|04|05|06|07|08|09|10|11|12|
2024|01|02|03|04|

2017-12-01 [長年日記]

_ [SAD]mmap_utils module導入

Fortran pointer経由で、mmap(2)経由で割り当てた匿名共有メモリを扱うためのユーティリティmoduleを導入した

局所スコープで、共有メモリ配列を割り当てているコード群を近代化する

まずは、tlspect@src/tbrad.fとtftrack@src/tftack.fのmapalloc/mapfreeを置き換えた

mapalloc依存するヘルパー関数

OpenShared系は暫く放置するとして…

  • itmmapp/tmunmapp @ src/tffsmatch.f - 空の共有メモリ配列 or 局所配列を確保・開放する
    • src/tffsa.f及び src/tffsmatch.fで利用されている
    • 確保された配列は、tfsscalc@src/tffscalc.fに渡っている
  • itfmalocp @ src/itfmalc.f
    • 要素がReal型で構成されるList及び二重Listをベクトル・行列として格納するFortran配列を割り当て、中身を複製・展開する
    • map引数がかつCOMMON変数nparallelが1より大きいとき、mapalloc経由で領域を割り当てる
      • それ以外は、italoc経由でプロセスローカルなメモリを割り当てる

2017-12-02 [長年日記]

_ [SAD]tffsmatchのutwiss arrayの動的割り当てをFortran pointerベースへ更新

itmmapp経由でrlist内に割り当ててる動的配列utwissの実装をFortran pointerベースの実装に置換した

tffsmatch内部に残っているのは、ifqu/ifqu0でindexしている、 実数型2次元配列 qu(nqcol,nvar)である

rlistから参照するコードなので、読みづらい…

関連するsubroutine

  • tffssetupqu
    • 呼び出し箇所は、数値微分フロー内の並列化直前のみ
      • 数値微分フロー内でnqcolは変更されない
    • ifqu/ifqu0が指す配列をリサイズする(再割り当てする)
    • 配列への格納は、並列workerのループによる
  • tffsqu
    • 呼び出し箇所は、導関数フロー内のみ
      • というか、導関数計算エンジン?
    • 内部で並列化されている
    • tffssetupqu相当のコードを含んでいる
      • 割り当て失敗時は、計算を中断しエラーを返す
    • nqcolを変更しない

関連するコードフロー内で、nvarは定数であり、nqcolはtffscale等で動的に決定され最大値はmaxcond(正確にはmaxcondを越えるとエラー)

技術的には、条件毎の値を保管する配列が長さmaxcondで確保されたものが渡ってくる事に起因する

Fortran pointer渡しにすれば動的に拡張可能となるが、関連するfunction/subroutineのインターフェースを 明示的に宣言することが必要(読み出し・格納のみでリサイズを行わない場合は、宣言なしの配列渡しが可能 Ex. tfshow)

配列渡しによる参照側が(nqcol,nvar)で参照しているので、nqcolが変更された場合アドレッシングレイアウトが変更されるために、アドレッシングの整合性を維持するためにnqcolが減少するケースでもpointerの再割り当てが必要になる

また、mmap_utilsのAPI設計上、munmapに必要な割付長が配列sizeに相関する必要がある

可変長割り当てを考慮すると(nvar,nqcol)の方が良いが、バックエンドに存在する線形代数ルーチンでJacobianを解く際のインターフェースに依存性があり転置するのは面倒が多い

メモリ再割り当て動作を減らすには、mmap_shared/munmap APIを直に使い自前でpointer割り当てを管理するのが最適?

2種類の導関数計算エンジンの外側で、配列割り当てを行う形にコードを整理すべきと思われる

必要なもの

  • 二次元配列ポインタ qu/qu0
  • mmapからが返すtype(C_PTR)値 pqu/pqu0
  • mmapで割り当て済みのinteger(C_SIZE_T)長さ lqu/lqu0
  • 割り当てが一ヶ所ならコードをベタ書きで良い

2017-12-04 [長年日記]

_ [SAD]tffsmatch.f書き換え完了

qu/qu0 arrayのallocation/deallocationをtffsmatch()へ局所化、mmap_sharedベースに書き換え完了

qu/qu0配列の受け渡しを見ていると…なんか変な感じがするというか、バグってるぽぃ 要調査

tgradtfsolvで勾配配列と作業領域を渡す順番が異なっている上に、subroutine側の仮引数名の逆になっているという極めて紛らわしい設計になっていた

更に、tgradtfsolvの呼び出しは、並列スコープ外なので、作業領域(qu0 @ tffsmatch)が共有メモリ上にある必要が無い

mapallocの残り

  • src/trackd.f - intlmに nzp0 x nzpなinteger 2次元配列
  • src/tffscalc.f - iutmに 2*nfam+1 x 4なreal 2次元配列(おそらく-nafm:nfam)
  • src/itfmaloc.f - itfmalocp()のmapモード
    • 参照元は、src/temap.f, src/tfdot.f, src/tffswake.f, src/tftrack.f
    • 全てがmapモードを使う訳ではないので要調査
      • map=.false.なインターフェースを別途設けるべきか?
    • Listを参照して、内容を展開した配列をヒープから割り当てているので、module化して、real 2次元配列 pointerを受け取るインターフェースを実装すべきか?
  • src/tfshared.f - OpenSharedなので後回し

2017-12-06 [長年日記]

_ [SAD]CaMonitorバックエンドのオーバーホール

  • ポインタ型のchid/evidをintptr_tへキャストしてdoubleへ放り込む実装をid登録テーブルとそのハンドルに置き換え
  • PutCBで呼び出すEPICS\$PutCB[]をFortranで再実装
  • ValueCBでのdbr_*_t型のvalue取り出し部を再実装
    • マクロによるコード生成に移行
    • 型変換をFortran側へ移譲(void *渡し・call C_F_POINTER)

現状、CaMonitorのValueCB経由の読み出しは、フル実装になったはず

残っているのは、

  • EPICS\$CaPut, EPICS\$CaPutCBの再実装
    • 現状、DBF_STRINGと DBF_DOUBLEしか使えない
  • src/tfefun2.f側の実装
    • CaOpen2にて、chidのリソースリーク
    • CaRead2にて、issueパートとretireパート間でwriteback bufferポインタをintptr_tキャストしてdoubleに放り込んで渡している
      • 生成したList要素をスタックに積み上げているので、vstk側の隙間にktastk経由(integer64 == intptr_t)で詰め込めるはず
      • さもなくば、Native型で追加のstack frameを確保して積み込むべし
  • evidテーブルリソースリーク
    • EPICS\$CaClearEventを行わず、EPICS\$CaClearChannelされるとchidテーブルは開放されるが、evidテーブルが開放されない
    • ca_clear_channelのマニュアルを読む限り、chidに紐付けされたevidは開放されるっぽぃ
      • 従って、今回導入したevidテーブルエントリー(8byte)のリーク
    • EPICS CA APIに、ca_evid_to_chidという関数が有るので、evidテーブルをスキャンして、chidとヒットをとることは出来る
      • evid → chid → chid-slot-numberは定数時間でたどれるが、逆にたどるテーブルは用意されていないので、真面目にやると線形スキャン
      • 最善のケースでは、evidが開放済みなので検索が無駄になる。特に、登録数が多いときにもったいない
      • chidに対して、evidの割り当ての有無が分かると効率化出来る
        • 線形スキャン中に全数回収した時点で終了出来る
        • CaMonitorクラスの実装では、基本的にchid辺りのevidはたかだか1個なので、ポインタ型のアライメントによる空き領域に埋め込めるが…
        • 呼び出し元を限定出来ないので、カウンタを設けるべきであろう
        • chid_tableの実装を構造体にするか2テーブルにするかは選択の余地あり
          • アライメントの厳しいマシンでは、構造体にするとパディングロスが増える
          • 2テーブルだとreallocationのコードでのエラーチェックで面倒が増える

2017-12-07 [長年日記]

_ [SAD]CaMonitorオーバーホール

  • EPICS\$CaPut, EPICS\$CaPutCBの再実装完了
    • 引数型 Real/Stringと ca_field_typeが整合しないケースは、旧実装に合わせてあるが、もっと適切な処理が有るか?
      • Real引数に対して DBF_STRING fieldの場合、DBR_DOUBLEで送信
      • String引数に対して DBF_STRING以外のfieldの場合も、DBR_STRINGで送信

evidテーブルリソースリークへの追加の考察

  • ca_evid_to_chid APIが使えるには、evidが指すクラスオブジェクトが生存している必要が有る
  • ca_clear_channel APIで、chidに紐付けされたevidオブジェクトが破壊されると、API呼び出し後には evidchid変換出来ない
    • ca_clear_channel呼び出し前に、evidテーブルのスキャンを行うもしくは、evidテーブル側に紐付けされているchidを格納しておく必要が有る
    • EPICS\$CaAddEvent(ca_create_subscription)で生成されるevidに紐付けされるchidは唯一なので、evidテーブルへの追加情報は固定長(ポインタ)で良い
    • chidの結びついたevid数が未知の場合、evidテーブルの全エントリをスキャンする必要が有る
    • ca_clear_channel前にevidを開放する戦略の場合、ca_clear_channelの呼び出しが失敗するケースを想定する必要は?
      • ca_clear_channelの失敗時の返り値は、ECA_BADCHIDのみ
      • 従って、失敗するケースはchidテーブルの登録情報が破損している or 何らかのframework外からの操作でオブジェクトが破壊されているケースなので、考慮する必要はないと思われる

従って、以下の方針で修正する

  • chidテーブルに、evidテーブルからの参照数を記録する
  • chidテーブル経由でca_clear_channelする際に、evidテーブルからの参照が残っている場合は、evidテーブルをスキャンしてca_clear_subscriptionを行う
    • ca_clear_channelの呼び出しは、CaClose1の実装側からもあることに注意
      • 最終的には、CaOpen1/'CaOpen2/CaClose1のバックエンド実装をEPICS\$CaOpen/EPICS\$CaClearChannelと統合すべきである
        • 扱い的には、EPICS\$CaPut系と同様にCallbackフラグ付きの内部インターフェースを設ける?
  • CaMonitorのDestructorでは、Stopメソッド(EPICS\$CaClearEvent)を実行してからEPICS\$CaClearChannelする
    • evidテーブルの線形検索時間を短縮するため

_ [SAD]CaOpen1/CaOpen2系の動作

CaOpen系とCaClose系のコードは、EPICS\CaOpenとEPICS\CaClearChannelに統合できそうなので、動作仕様の違いをまとめておく

ca_create_channel系(CaOpen1/CaOpen2)

関数引数の数callbackca_pend_ioretrychidリーク
CaOpen11なしありなしca_pend_ioタイムアウト時
CaOpen21 or moreなしありありca_pend_ioタイムアウト・リトライ時
EPICS\$CaOpen1ありなしなしなし
  • CaOpen系は、ca_pend_ioレベルでタイムアウトする(レコードが規定時間以内に接続しない)場合にMessageを返すため、ca_create_channelしたchid構造体が破壊されない
  • 特に、CaOpen2では、リトライ動作があるために存在しないレコードに対するアクセスで、複数個のchidがリークする
    • 引数中にエラーとなるレコードがあると正常に接続できたレコードのchidも複数個生産・リークする
  • 実装統合にあたり
    • chidのリークは直すべきバグと思われる
    • タイムアウトしきい値とリトライ動作に関しては要検討

ca_clear_channel系(CaClose1)

関数引数の数ca_pend_io
CaClose11あり
EPICS\$CaClearChannel1なし
  • CaClose1のca_pend_ioにリトライコードがあるが、リトライする必要があるのか不明
    • =ca_clear_channel時点で、当該chidのデストラクタ呼び出し要求は通っているはず=
    • =このケースでの、タイムアウトの長い ca_pend_ioと短い ca_pend_ioの複数回呼び出しの違いが分からない=
    • ca_clear_channel要求は、ca_flush_ioで送出されるので、ca_pend_ioは不要(2017/12/11追記)

ca_pend_io系(CaPendIO)

  • EPICS\$CaPendIOとエラー時に返すMessageを除いて機能は同一
  • なぜか、短い時間のca_pend_ioを繰り返す実装である
    • ca_pend_ioはタイムアウト時間が長くても、outstanding taskが完了した時点で戻ってくる上に、当該ループで別のイベントハンドラを回すわけでは無いので、必要性が理解できない
    • 単純に、EPICS\$CaPendIOの別名で良いと思われる
    • ハンドラ無しのca_create_channel/ca_get要求が存在しない場合、timeoutが可変な ca_pend_ioを回す必要性が不明(2017/12/11追記)

ca_state系(CaConStatus1)

  • CaMonitor backendには、CaConStatus1の対応物は無い
    • chid構造体の参照動作のみなので、再実装に簡単

ca_get系(CaRead1/CaRead2)

  • CaMonitor backendには、CaRead1/CaRead2の対応物は無い

ca_put系(CaWrite1/CaWrite2)

  • 機能的には、CaMonitor@Put/PutCB相当だが、ca_pend_ioベースのblockingアクセスとなる
  • CaWrite2に関しては、chidとvalueのペアを複数引き受けて、ca_pend_ioによるblockingを一回ですませるmassitve ca_putインターフェースがある

未実装系(src/tfefun2.f)

  • CaChName
    • シンボル登録あり・実装不在
    • 名称から、chid構造体のca_nameによる参照を取ってくるのを期待していると推定される
    • 実装フローは、CaConStatus1と同一(返り値の型が異なる Real vs String)
  • CaStatus2
    • シンボル登録自体がコメントアウトされている
    • CaOpen2系用ca_state参照を意図していたのか?

_ [SAD]CaOpen1/CaOpen2の返り値

調査結果のまとめ

CaOpen1[record]

実装は、nfCaOpen @ src/tfefun2.f (casearch_/capendio_)

  • 戻り値 Real (chidハンドル)
  • エラー時のMessage
    • ca_create_channel - CA::search
    • ca_pend_io - CA:Channel open time out

CaOpen2[record...]

実装は、tfefun2 @ src/tfefun2.f (casearch_/capendio_)

  • 戻り値 {Real...} (chidハンドルのリスト)
  • エラー時のMessage
    • ca_create_channel - CA::open
    • ca_pend_io - CA::open
  • リトライ動作
    • 最大2回(計3回)
    • ca_pend_ioの基本タイムアウト - 0.5~4.0秒 Restrict[0.2 * narg, 0.5, 4.0]
    • リトライ毎に 1.414倍になる
      • 最大リトライ時で、合計 4.413倍
      • 最大待ち時間 17.65秒
引数基本タイムアウト
10.5
20.5
30.6
40.8
81.6
122.4
163.2
204.0
244.0

再実装に関する考察

  • CA::searchをCA::openに統合すべきと思われる
    • CaOpen[] wrapperは、CaOpen2[]に依存しているので、CaOpen1[]の仕様変更はインパクトが小さいはず
  • CaOpen2[]のエラー処理は、どうするべきか?
    • Messageを返す場合は、chidハンドルを返せないので、ca_clear_channelで解体すべきである(リソース管理的に)
    • リトライを実装する場合、接続に失敗しているレコードのみ再要求すべきである
    • CaOpen[] weapperから複数レコードの解決を要求されたケースでは、解決できた分だけでも返すべきでは?
  • CaOpen2[]の返り値は、Real Listなので、itavalocで直接構築すべき
  • CaOpen wrapper(Packages/CaSad.n)では、CaOpen2[]はString型に対する呼び出しのみ

_ [SAD][EPICS]ca_create_channelのエラー

レコード名に長い文字列を与えて、ca_create_channelがECA_BADSTRを返す状況にすると、SEVCHKマクロでSEGVする

当該エラーは、nciu::nciu(コンストラクタ)内のnameLengthTempチェックからthrowされるcacChannel::badString例外に起因している

レコード名の長さがMAX_UDP_SEND - sizeof(caHdr)を超過している際に発生し、libca内の構造体の整合性が破壊されている模様(試験環境でのしきい値は、1008bytes)

ca_create_channelに投げる前に長さをチェックすべき

2017/12/08追記

  • MAX_UDP_SEND - caProto.hにて 1024 octetに設定されている
  • caHdr型 - caProto.hca_hdr構造体 uint16 x4 + uint32 x2なのでパディング無しで 16byte
  • nameLengthTemp - strlen(pNameIn) + 1なので、NULL文字を含むレコード名の長さ

従って、1024 - 16 → 1008文字のレコード名(NULL文字を除く)で cacChannel::badString例外が発生する


2017-12-08 [長年日記]

_ [SAD]CaWrite1/CaWrite2

既存実装の動作解析まとめ

CaWrite1[chid, value]

  • 返り値 - value (ca_put後に ca_getしたものを返す)
  • エラー
    • General::narg - 引数が2個では無い
    • General::wrongtype - 引数の型が正しくない
    • CA::write - valueがReal ListもしくはString Listで無い(ぉぃぉぃ)
      • caputrealarray_/caputstringarray_にてList要素の型検査エラーで発生
      • EPICS CA APIのエラーは、すべて無視されている
  • 実装本体は、nfCaWrite(src/tfefun2.f)及び caputreal_/caputstring_/caputrealarray_/caputstringarray_(src/CaSearch_.c)
    • ca_stateは調べていない
      • ca_put時点で接続状態になければ、ECA_DISCONNが返る
    • ca_put/ca_getの後で、ca_pend_ioする(計2回)ので、Blocking I/Oになっている
      • ca_put後のca_pend_ioは、ca_flush_ioで十分と思われる
    • EPICS CA APIのエラーハンドリングは行われない
    • ca_pend_ioのタイムアウト設定値
関数タイムアウト
caputreal_100
caputsyring_0.1
caputrealarray_0.5
caputstringarray_0.5

CaWrite2[{chid, value}...]

  • 返り値 - Null
  • エラー
    • General::narg - 引数無しの場合
  • 引数を個別 nfCaWriteNoWait(src/tfefun2.f)で処理する
  • nfCaWriteNoWait側では、itfmessageによるエラー処理を行っているが、ドライバ側(tfefun2)で irtcをクリアし、Nullを返している
    • ca_state(chid)が cs_connであることをca_putの事前条件に設定している
    • ca_putは、DBR_STRING/DBR_DOUBLEにて、ca_put/ca_array_putを実行している(caput*nowait_ @ src/CaSearch_.c)
      • ca_putは、ca_array_putのマクロなので、この部分は EPICS\$CaPutと同様と言える
      • General::nargを返すケースを除外すると、エラーを無視してEPICS\$CaPutをScanする以下のコードと同義
CaWrite2[arguments__] := Scan[Check[EPICS$CaPut@@#, Null]&, {arguments}]

再実装の方針

  • CaWrite1は、EPICS\$CaPutに ca_pend_io→ca_get→ca_pend_ioのシーケンスを追加した派生版として実装可能
    • DBRテンプレートと戻り値生成部の改修で対応可能
    • ca_pend_ioのタイムアウト値は、再検討が必要
      • CaOpen1/CaOpen2/CaRead1/CaRead2にも存在するので、調査の上、合理的な値を設定すべき
  • CaWrite2は、EPICS\$CaPutにエラー時のMessage生成を抑制した派生版で引数列をScanするドライバーとして実装可能

_ [SAD][EPICS]ca_put前のca_state検査

ca_putのマニュアルによれば、ca_put時点でchannelが接続状態になければ、ECA_DISCONNが返るとあるので、ca_putに投げ込む値を組み立てるのにコストが必要な場合は、事前にca_stateで接続状態を確認した方が良いと思われる


2017-12-09 [長年日記]

_ [SAD][EPICS]ca_create_channelとca_getに対するca_pend_ioに付いて

マニュアルを読んだ範囲では、handler無しのca_create_channelとca_getのrequestの完了を待つ際の ca_pend_ioの戻り値に付いてまとめると

ECA_NORMAL

ca_pend_ioに先行するhandler無しの ca_create_channelとca_get要求全ての処理が完了している

ECA_TIMEOUT

ca_pend_ioに先行するhandler無しの ca_create_channelとca_get要求のなかに、未完了のものが含まれる。

=未完了の要求のうち、ca_get要求は再実行されるがca_create_channel要求は再実行されない=(2017/12/11訂正)

ca_pend_io以前に投入されたhandler無しのca_create_channel/ca_get要求は、終了・キャンセルされている。 ただし、CA APIレベルで状態を持たないca_get要求の成否は不明となる (2017/12/11追記)

ca_create_channelに関しては、ca_pend_ioからECA_TIMEOUTで戻った時点で接続状態が確定し、ca_stateが cs_never_connもしくはca_prev_conn状態となっており、ca_clear_channelするまで変わらない。 また、ca_create_channelで生成したchidオブジェクトをca_stateで調べれば、接続確立に失敗しているchannelを特定できる(未接続で確定しているので、以降のca_get/ca_putがECA_DISCONNで失敗するので、ca_clear_channelで捨てる以外することが無いはず)

=一方、ca_get要求は再投入されるとのことなので、ca_getへ渡したbuffer pointerは、CA client library内で生存しており、ca_pend_ioがECA_NORMALを返すまで開放することが出来ない=(ca_pend_ioから戻った時点で、参照が開放される 2017/12/11訂正)

また、どのca_get要求が未完了なのかを調べるAPIが存在しないので、 以前に未完了で放置したca_get要求が有った場合、直前のca_getが成功したか否かを判定するには、DBR_TIME型などの付加情報の付いた要求を行いbufferが上書きされてるかを判定することになる。

=TCP/IPの接続タイムアウトで接続が遮断すれば、channel状態がDISCONNへ移行し、当該channelへのca_get要求が廃棄されるはずであるが、それまでの間、ca_getへ渡したbufferを安全に回収出来ない=(2017/12/11削除)

=従って、外部ネットワークに悪意があると考えた場合、ca_getはcallback経由の実装を行うべきと思われる=(2017/12/11削除)

ca_get with callbackのアイデア(リソースリーク対策には不要2017/12/11)

callbackへ任意の参照を渡せ、通信が途中で切断された場合はcallbackはbad statusを伴って呼び出されるとの記載があるので、 callbackへ以下の情報を記録したヒープ上のオブジェクトを渡す。

  • main programとの同期変数
  • 読み出しデータの書き込み先の情報

同期変数の状態は以下の4種

  1. main programは、受け取り待ち中(書き込み先情報が有効)
  2. main programは、受け取りを放棄(キャンセル)
  3. main programへ、受け渡し済み(get成功)
  4. main programへ、受け渡し済み(get失敗)

callbackは、同期変数が状態(1)であることを確認し後、データを書き込み、同期変数を状態(3)もしくは(4)へ遷移させる

callbackが、同期変数が状態(2)であることを確認した場合は、ヒープ上のオブジェクトを開放する

main programは、ca_get_callback後に同期変数が状態(3)もしくは(4)へ変わるのをca_pend_ioを行いながら待ち、受け取りに成功した場合は、ヒープ上のオブジェクトを開放する

main programがtimeoutした場合は、同期変数を状態(2)へ遷移させ、受け取り先の割り当てを解除する

callback動作が、プリエンプトな場合は同期変数の操作はアトミックである必要があり、main programとcallbackが併走する場合は、callback側が動作中であることを示す遷移状態かロックを追加する必要がある

ISO C11で同期変数を実装するならatomic_intとか?

2017/12/11追記

ca_get_callbackによる要求に対して、ca_pend_ioはブロッキング動作しないとの事なので、同期待ちがspin waitもしくはsignalベースとなりあまりうれしくないことが判明

ca_sg_create/ca_sg_array_put/ca_sg_blockを使うべきか?

ca_cg_resetで、同期グループ内でissueされている要求をキャンセルできるようなので、安全にリソースを回収できる

ただし、同期グループに複数のI/Oが含まれる場合、どのI/Oが未完で終わったかを識別するAPIが存在しない

  • 生データ以外の例外値が存在する追加データが存在するタイプを要求し、メモリの上書き状態を確かめる
  • 複数の同期グループを構成し、ca_sg_testで個別に調べる
    • 特定の同期グループに対して、ca_sg_blockした際に他の同期グループのタスクが進行するか否かが自明ではない
    • 複数の同期グループを同時に待つAPIが存在しない
      • ca_sg_testと経過時間測定を組み合わせて自前で作成するか、ca_pend_ioとca_sg_testの組み合わせか?
  • 少なくともca_pend_ioでは、同期グループのタスクは進行しない(3.16.1での実験結果)
  • ca_sg_blockがTIMEOUTした時点で、ca_sg_array_get要求がキャンセルされる(3.16.1での実験結果)
  • 特定の同期グループに対するca_sg_blockで他の同期グループのタスクが処理されない場合、効率的に実装する手段が存在しない

従って、ca_get要求が処理されたかを個別に確認するには、以下の2種に絞られる

  • 付加データを含めて要求し、付加データの領域に予め有り得ない値を書き込み、書き換えを検出する
    • dbr_time型であれば、stamp.nsecが使いやすい(有効値 [0, 999999999])
  • callbackベースで実装する。ただし、timeout待ちのblocking waitが面倒

2017-12-10 [長年日記]

_ [SAD][EPICS]CaRead1/CaRead2インターフェース仕様

コード解析のまとめ

CaRead1[chid[, count]]

  • 引数は、1 or 2でchidハンドルと追加のcount引数を受け取る
  • 返り値 {pv-value, alarm-status, alarm-severity, time-stamp}
  • エラー
    • 未接続 - CA::Channel
    • タイムアウト - CA::read
    • その他 - CA::read (ヒープ割り当て失敗等を含む)
  • 処理フロー
    • 引数検査
    • ca_state確認
    • ca_array_get
    • ca_pend_io
  • Filedの扱い
    • STRING/CHAR/ENUM - DBR_TIME_STRINGを要求
    • それ以外 - DBR_TIME_DOUBLEを要求

CaRead2[(chid|Rule[chid, count])..]

  • 引数は、1個以上
  • 返り値 {({pv-value, alarm-status, alarm-severity, time-stamp}|Null)..}
    • ca_array_getの発行に失敗した場合は、返り値リストの当該要素は Nullに設定される
  • エラー
    • 未接続 - CA::unconnected
    • タイムアウト - CA::read
  • 処理フロー
    • 引数検査
    • ca_state確認
    • ca_array_get
    • ca_pend_io(リトライあり)

再実装にあたって

  • ca_array_getの発行処理、受信データの格納処理は共通化できる
  • 引数処理が異なるので、専用のフロントエンド作成するか、共通の中間フォーマットを定義する必要がある
  • 引数毎の中間情報は、chid, count, type(callback未使用時), ca_array_getとの共通ポインタを含む必要がある
    • callbackありのケースでは、chidと1:1のslot番号を用いて、itastkへ slot/countを格納し、vstk側にポインタを格納する余地がある(typeは、callback時にevent_handler_argから拾える)
    • 必要な容量はnargオーダーなので、SAD stackframe上に確保出来ると思われる
    • 生のchidを格納する場合は、専用の構造体をSAD stackframe上に作成すべき

2017-12-11 [長年日記]

_ [SAD][EPICS]ca_pend_ioのタイムアウト値

ca_get/ca_pend_ioの動作試験の結果から、タイムアウトを延長しながら、issue/pendを繰り返すので有れば、最初から長いタイムアウトを設定すべきである また、タイムアウト値を調整するインターフェースを設けた方が便利と思われる。

ca_create_channelに関してはリトライで要求前後にIOCがリスタートするケースを拾える可能性があるが、ca_getではIOCとの接続がダウンした時点でconnection handlerが無いCA接続は救えない


2017-12-12 [長年日記]

_ [SAD][EPICS]EPICS CA backendの再実装が一段落

取り合えず、実装の一本化が完了

残っている作業

  • とにかくテスト
    • DBF_DOUBLE scalarの CaRead1/CaRead2/CaMonitor動作は確認済み
    • waveform系・DBF_DOUBLE以外のフィールドでの動作確認が必要
    • CaWrite1/CaWrite2/EPICS\$CaPutCBの動作確認が必要
  • コードの整理
    • 内容が重複するマクロの整理
    • 読み返し動作を含むCaWrite1は、独立させるべき?
      • 読み返し失敗(ca_array_get/ca_pend_ioエラー)の取扱いは?
      • CA::* メッセージを返す or Nullを返す(CaRead2準拠)
  • CA::* メッセージの名称・内容の見直し
  • Dynamic Loader対応
    • 初期化時に、SAD/Tkinterバックエンド不在の処理をどうするか
    • Tkinter moduleと同様に、素直に依存moduleとして要求する?

2017-12-14 [長年日記]

_ [SAD][EPICS]送信バリアの見直し

オリジナル実装の送信バリアのまとめと考察

  • EPICS\$CaOpen - ca_flush_io/ca_pend無し
    • connection handler付きなので、これで正しい
  • EPICS\$CaAddEvent - ca_flush_io/ca_pend無し
    • 次の送信バリアまで、queuingされている
  • EPICS\$CaPut/CaPutCB - ca_flush_io有り
    • 複数のca_putを扱うためにScanすると要素毎に ca_flush_ioが走るのは非効率的
    • 送信バリア付きなら、CaWrite2のようなインターフェースが欲しい
      • 戻り値は、Nullなので引数形式を拡張しても問題ないが、どちらが便利か
        • Apply形式 f[{chid1, val1}, {chid2, val2}...] (CaWrite2の類型)
        • Map形式 f[{{chid1, val1}, {chid2, val2}...}]
    • 送信バリアを外すのも一考
      • CaMonitorクラス側で、lazy EPICS\$CaFlushIOを実装する
      • Tkinterを前提にしているので、AfterIdleを使って送信バリアをIdleTaskまで遅延させる
  • EPICS\$CaClearChannel/CaClearEvent - ca_flush_io/ca_pend無し
    • 次の送信バリアまで、queuingされている
  • CaOpen1/CaOpen2 - ca_pend_io有り(同期接続)
  • CaClose1 - ca_pend_io有り
    • ca_clear_channelなので、ca_flush_ioで十分のはず
  • CaRead1/CaRead2 - ca_pend_io有り
  • CaWrite1 - 暗黙のca_pend_io有り(内部的にca_get動作を含むため)
    • ca_put直後のca_pend_ioは、ca_flush_ioで十分のはず
  • CaWrite2 - ca_flush_io/ca_pend無し
    • API設計的に、ポーリングを期待していないので、ca_flush_ioするべき

_ [SAD][EPICS]API拡張の検討メモ

  • CaClose1/EPICS\$CaClearChannel/EPICS\$CaClearEvent
    • chidを引数に、資源開放を行いNullを返すAPIなので、複数資源の同時開放サポートを自然に導入出来る
  • CaOpen2
    • PV待ちでタイムアウトした場合、例外メッセージを返し確立済みの全接続を破棄するが、接続マネージャのバックエンドに使うには確立済みの接続は返して欲しい
    • 接続失敗を示す返り値に選択の余地有り
      • 0(無効chid slot番号) - 有効な番号と同型(Real)
      • \$Failed - RealQで有効性を判定出来る(典型的なリソース割り当てAPIの失敗戻り値)
    • =後方互換性を考えるとCaOpen3もしくはCaOpen\$を創設すべき?=CaOpen1[{pvname...}]形式でタイムアウトしたpvnameに対して$Failedを返す実装を導入
      • オリジナルのCaOpen1は、String型引数1個のみのサポートなので、引数エラー以外の互換性に影響しない
    • Tkinterを前提にして良いのなら、接続マネージャをEPICS\$CaOpenベースで実装すべき
      • 一時的な接続断に対しても自動での再接続を期待できる
      • Tkinter無しでもEPICS\$CaPendEventとSleepを組み合わせてポーリングで実装する解がある
        • Sleep時間単位で待ち時間が量子化されるので、CPU負荷と効率のバランスが難しいところ
        • デフォルトの最大待ち時間は、EPICS\$CaOpenTimeoutで拾う
        • EPICS\$CaOpenのconnection handlerが暗黙のうちにCaMonitorをロードする
        • CaMonitorパッケージロード時に評価されるCaMonitor@DoPeriodicIO[]内に含まれるAfterから暗黙のうちにTkinterが初期化される
          • 最初のca_create_channel時点で、eca_fd_register callbackが呼び出されるので、この時点でTkinterが存在しない場合、polling用の fdが行方不明になる(TkinterがDynamicLoadのケース)
          • DynamicLoad前提の場合、sadTk_CreateFileHandler wrapperにQueuingが必要となる
          • おそらく、CaMonitorの接続の場合、Tkinterにfd callbackが設定出来れば、ca_pend_ioの定期実行は不要なので、DoPeriodicIOは廃止出来る
          • ただし、EPICS\$CaOpen/CaAddEvent/CaClearEvent/CaClearChannel要求を送出するために遅延した・定期的なca_flush_ioが必要
            • CaMonitorクラスメソッドからの送出で有れば、Lazy EPICS\$CaFlushIOを仕込めば足りる
          • 以下のコードで暗黙のTkinterロード以外は、意図通りに動く
CaOpenX[pv:{__String}] := Module[{chl = EPICS$CaOpen/@pv, dt = 10e-3, timeout = EPICS$CaOpenTimeout[]},
  While[timeout > 0,
    Sleep[dt]; timeout -= dt; EPICS$CaPendEvent[];
    Print[timeout];
    If[And@@(CaConnectedQ/@chl), Break[]]];
  Map[If[CaConnectedQ[#], #, EPICS$CaClearChannel[#]; $Failed]&, chl]];
  • EPICS\$CaOpen
    • 複数のPVを同時に開く拡張が考えられる(上記のCaOpenXの様な事例でMapを一つ減らせる/関数呼び出し分早くなる)
    • 後方互換性を考慮すると、EPICS\$CaOpen[chid..]形式では1引数と2引数以上で戻り値型を_Realと{__Real}で切り替える形式となる
      • 従って、リストを受けるコードを汎用で書くケースでは、Flatten[{EPICS\$CaOpen[pv...]}]となりコードが増える
        • 評価コスト的には、リストの長さ分EPICS\$CaOpenをMapから評価するのに対して、Flatten[]とList[]の評価が増えるだけなので、長いリストでは安くなるが、リスト長に線形にFlattenがSAD stackを消費する
      • 素直に、EPICS\$CaOpen[{pv...}]をサポートする方が簡単?=実装した
        • 内部エンジンをit/iaペアを受ける1引数バックエンド(String/String-Listのみサポート)と引数検査を行うフロントエンドに分割して実装する=
  • CaConState1
    • 戻り値が、独自フォーマットなのが使い難い
    • 接続状態判定だけなら、真偽値で欲しい
      • =新説するなら、=CaConnectedQ=辺りか?=を実装した
      • Query関数にするなら、無効な引数に対しては、Falseを返すべき?

_ [SAD]mmap_utils回りの書き換え済みコードについての覚書

tffsmatchで割り当てた領域がtffscalcに渡しているケースで、tffscalc内部で並列化する場合に、並列リージョンからの書き戻し動作の挙動が変わっていないかソースを精査する必要あり

_ [SAD][EPICS]EPICS backendの残作業まとめ

  • 実働・通信テスト
  • CaOpen/CaClose/CaRead/CaWrite wrapperの再実装(Packages/CaSad.n)
    • 複数レコードへの接続要求の効率的な処理(拡張されたCaOpen1)
    • CaCloseのサポート
      • 現行実装では、チャンネル切断時に二度とアクセス出来なくなる(chidの再生成をサポートしていない)
    • 可能なら、EPICS\$CaOpenベースへ移行する
      • connection handler付きであれば、IOC再起動時などは自動での再接続を期待出来る
  • EPICS\$CaPut/CaPutCBから ca_flush_ioの排除
    • CaMonitorクラスのLazy FlushIO化(Put/PutCB/Start/Stop/Constructor/Destructor)
  • TkinterのDynamic Loading対応
    • Tk_CreateFileHandler wrapperでのQueuingサポート
  • CaMonitorから DoPeriodicIOの排除
    • 送信バッファのca_flush_ioは、Lazy FlushIOが実施
    • ca_pend_eventは、eca_fd_callbackが担当する

2017-12-15 [長年日記]

_ [SAD]tfefundef crash

tfefundef@src/tfefun.f冒頭のget_func_id(ia1)にて、SEGVするケースを発見した

     irtc=0
     narg=isp-isp1
     it1=itastk(1,isp1)
     ia1=itastk(2,isp1)
1    if(it1 .eq. ntfoper)then
       if(ia1 .gt. mtfend .and. get_func_id(ia1) .ge. 1000)then
         call tfefunref(isp1,itx,iax,vx,.true.,irtc)
         if(irtc .eq. 0 .and.
    $         itx .eq. ntfreal .and. iax .ne. 0)then
           call tfclrtparaed
         else
           if(irtc .eq. 0)then
             irtc=itfmessageexp(999,'General::invset',it1,ia1,0.d0)
           endif
         endif
         go to 6900
       elseif(ia1 .eq. mtfatt)then
         call tfatt(isp1,itx,iax,vx,.false.,irtc)
         go to 6900
       else

発生ケースでは、it1が ntfoperにも関わらず ia1が無効なポイントをさしている。 ただし、itastk(2, isp1)は健全な状態で、後続のコードでit1がntfsymbolのケースで、Symbolの参照先がOperatorになっている場合にitastk(*, isp1)とit1を書き換えて、go to 1で再帰するケースで発生する

また、当該コードで、call tfclrtparaedに達するケースは、LINE(tfline)がパラメータブロックの参照をiax経由で返してくる場合で、LINE[...] = valの代入式の左辺値評価を行うケースに相当する。従って、tfefunrefを呼び出す事前条件はもう少し限定出来ると思われる(左辺値参照の伝搬を制限するケースでは、isp1にLINEが来ていないケースの評価が省略できるはず…)

ifブロック内で、General::invsetを返す条件に irtc == 0(tffunrefの評価が成功している)を要求しているのも謎なので要解析

_ [SAD][EPICS]TkinterのDynamic Loading対応について

Tk_CreateFileHandler wrapperに関して、Tkinterのloading前に登録手続きのQeueingサポートを必要と考えていたが、Tcl/Tkのインタプリタインスタンスの生成(Packages/TkinterCore.n)と関係なくlibtkがバインドされていれば十分であることが分かった。

従って、バックエンドモジュールであるSAD/Tkinter.soがロード済みで有れば十分である。

builtinの場合を考えると、sadDefFunc_EPICSCA時点ではLibrary@Requireは要求出来ないので、SADScriptインタープリタからの関数評価で初期化シングルトンeca_initが走ったタイミングで、必要なbackend moduleを要求すれば良い

実際に、ポーリングを走らせる段階ではTkWait/TkSenseを評価したタイミングで、Packages/Tkinter.nがロードされTkイベントループ内からTkinter backendに登録済みの eca_fd_callbackが起動される

必要なものをまとめると

  • =eca_initからSAD/Tkinter.soのロードを要求する=src/sim/sad_tcltk.cからロードを要求する
    • ロード済みか判定する手続きが必要
    • =feature_requireするのが簡単?=src/sim/sad_tcltk.c内ならポインタテーブルの初期化状態で判定出来る
      • Tkinter.soでfeature_provideしておく
  • Tkinter.so内のTclCreateInterpが使うsadXlib_SetNewDisplayホックのバックエンドがSAD/Xlib.so'内にあるので、必要ならこれもchain loadする必要がある
    • =依存性のメカニズムは、Tkinter.soのケースと同じで良い=src/sim/sad_xlib.c側でロード処理を行う

_ [SAD][FreeBSD]SADのSocketサポートとipv6_ipv4mappingについて

CPUS lpqの件で取り上げたnet.inet6.ip6.v6only sysctl変数であるが、net.inet6.ip6.v6only=1(ipv6_ipv4mapping="NO")な運用下では、TCPOpen[0]で生成するServer Side TCP SocketがIPv6専用となり、IPv4で接続してくるクライアントからの接続を受けられなくなる。

TCPOpen[0]が返すポート番号で再度TCPOpen[port]を実行すると、TCP/IPv4ポートが空いていれば割り当てる事が出来る。

ただし、現行のTCPAcceptは単一のSocketのみを受け付けるので、SelectUnit/PollでIPv6/IPv4ソケットを待ち、要求が有ったソケットからTCPAcceptするという処理が必要となる

TCPOpen・TCPAcceptに手を加えて、プロトコル毎のソケットをリストで返す案もあるが、TCPOpen[0]が返すソケットをそのままSelectUnit/Pollに流し込むコードで後方互換性が破壊される。 また、TCPAcceptでBlocking Waitさせるケースでは、TCPAccept内部でselect/pollでBlockingさせることが必要となり面倒が増える

TCPAcceptによるSocket Listのサポートは後方互換性を破壊しないので実装すべきである

影響範囲

  • SADInspect
    • TCPOpen[0]で得たSocketをTCPAccept[]でBlocking Waitしている
  • Network/StreamIO library
    • StreamServerでの待受ポートの作成時 TCPOpen[Port]形式を使っている
    • Server Socketと TkCreateFileHandlerに登録している
    • Server Socketを他の接続Socketと共にPollUnitする際にPrepend[]で繋げている
    • SuperKEKBの運転に影響が出る

Network/StreamIO libraryに関しては、早急に対策が必要

  • Server Socketを生成する部分を生のTCPOpenをラップしたHelper関数を用意する
    • Helper関数内で複数回TCPOpenして全てのプロトコルのSocketを生成する
  • Server Socket変数をSocketのリストとして取り扱う様に改訂する
    • PollUnitの結果からFirst/Restを取る操作を行っているので、可変長にする必要がある

2017-12-16 [長年日記]

_ [SAD]TCPAcceptの拡張

単体ソケットだけでなく、ソケットリストからのaccept(2)を実装

内部的には、poll(2)でブロックしてから、accept(2)を行う

ただし、リスト先頭から有効なソケットを探すので、TCPAcceptの回転が遅い場合、リスト先頭のソケットのキューが優先される公平性の問題がある

ソケットリストの乱数回転もしくはシャッフリングを行うあたりが解か?

_ [SAD]TCPServer

TCPOpen[port]のラッパーで、TCPServer[0]に対しては{socket-list, port}、TCPServer[port] (port > 0)に対してはsocket-listを返す

TCPOpenに失敗した場合は、共に$Failedを返す


2017-12-18 [長年日記]

_ [SAD]残作業のまとめ

  • Gamma/LogGammaの左半面がおかしい
  • tfefundef crash(r5095で修正完了)
  • EPICS CA backend updates
    • CaMonitorへのLazy FlushIO導入(Put/PutCB/Constructor/Destructor)
    • EPICS\$CaPut/CaPutCBからのca_flush_ioの排除
    • CaMonitorから DoPeriodicIOの排除
      • eca_fd_callbackによるca_pend_eventの駆動が期待できない状況(SAD/Tkinter不在)では、DoPeriodicIOがAfterによってca_pend_ioを駆動することも期待できない
    • 各種レコードでの実働試験

2017-12-19 [長年日記]

_ [SAD]インスタンス変数の初期値にUndefinedが使えない

クラス定義で、Undefinedを初期値にしたインスタンス変数を定義すると、当該変数は初期化されない

Packages/Class.nの内部実装で、未定義時の特異値としてUndefinedを使っている模様

_ [SAD]クラス変数にSetDelayedで定数式でない式を定義すると動かない

クラス変数に束縛される式が、Holdに入った状態になる

A = Class[{}, {expr := non-const-expr}, {}, ];
a = A[];
Print[a@expr];
! Hold[non-const-expr]が返る

定数式の場合は、問題ないので、Pacakges/Class.nでの定義展開の問題と思われる

_ [SAD]poll(2) + listen socket覚書

listen socketがreadyになると、POLLINになるがioctl(2)のFIONREADに対しては0を返すので、PollUnit[]やSelectUnit[]の戻り値は、イベント無しのNullからEndOfFileに変わる

戻り値検査が面倒なので、NetworkIO/API 1.003以降の環境で有れば、SelectUnit/PollUnitから起床したらTCPAcceptをnon-blockingで回してしまう方が簡単である


2017-12-21 [長年日記]

_ [SAD][EPICS]EPICS CA Overhaul終了

MAIN trunkへのマージ完了

結果として、作業時間3週間・200 commit越えの大作パッチとなってしまった


2017-12-25 [長年日記]

_ [FreeBSD]x11-fonts/libXfontを1.5.4にアップグレードしてからjisx0208フォントを認識しない

2017/12/18 r456561適用後のportupgrade後にX server再起動でjisx0208フォントを認識しない状況が発生

  • fontconfig+Xftで自前でレンダリングしているfirefoxなどは影響を受けていない
  • X serverのフォントレンダリングに依存するja-ktermやemacsに影響する
    • libXfontをリンクしているのは、X serverなので X serverの再起動後に影響が現れる

更新前のlibXfont-1.5.2へ巻き戻すと元に戻るので、1.5.2から1.5.4の差分で退行している?

1.5.4に入っているCVE-2017-16611の修正で、フォントファイルオープン(src/fontfile/fileio.c)時にO_NOFOLLOWが付与された結果、.pcf.gzがシンボリックリンクなものが表示できなくなっている

portsから入れているフォントに関しては、ports側を修正すべき (copyにするかハードリンクに切り替える)


2017-12-27 [長年日記]

_ [SAD][数学]Gamma,LogGammaの修正完了

特異点近傍の振る舞いと左半面でのLogGamma虚部の連続性の修正を完了

MAIN trunkへバックポート実施

_ [SAD]非標準実装の書き換え

  • IEEE_ARITHMETICモジュール
    • isnan() - IEEE_IS_NANへ置き換え
    • dinfinity - IEEE_VALUE(X, IEEE_POSITIVE_INF)へ置き換え
    • nan - IEEE_VALUE(X, IEEE_QUIET_NAN)へ置き換え

IEEE_VALUEは、intrinsicモジュール内の関数であるが、intrinsic関数では無いので、parameterの初期化式に記述できないのが難点 (現状も初期化は、tfinfint_が行っている)

_ [SAD][Fortran]gfortran floorバグってる

REAL型のXに対して、floor(X)はX以下の最大のINTEGERを返す関数なので、正の無限大に対しては、INTEGER表現域の最大値をを返すべきだと思うのですが、gfortran 6.4だとなぜか-2147483648 = 0x80000000を返してくる

内部的には、REAL->INTEGER変換で符号付き2の補数形式の32bit INTEGERの最小値-2147483648.0d0を変換できるようになっていて、符号付きの浮動小数点なので正の無限大が2147483648.0d0に打ち切られてから整数変換によって0x80000000へ変換が起こってるように考えたのだが、有限値2147483648.0d0のfloorは表現域の上限2147483647 = 0x7fffffffを返している

INTEGER型の表現域に解が存在しないtfloor(-Infinity)に対して2147483647が返ってくるのも謎である

実装バグっぽい気がする

     use IEEE_ARITHMETIC
     use ISO_FORTRAN_ENV
     real(real64) :: a, pinf, ninf, nan
     pinf = IEEE_VALUE(dinf, IEEE_POSITIVE_INF)
     ninf = IEEE_VALUE(dinf, IEEE_NEGATIVE_INF)
     nan = IEEE_VALUE(nan, IEEE_QUIET_NAN)
     a =  2147483648.d0
     b = -2147483649.d0
     write(*,*)a,' -> ',floor(a)
     write(*,*)b,' -> ',floor(b)
     write(*,*)pinf,' -> ',floor(pinf)
     write(*,*)ninf,' -> ',floor(ninf)
     write(*,*)nan,' -> ',floor(nan)
     write(*,*)sign(nan,-1.d0),' -> ',floor(sign(nan,-1.d0))
     end

2017-12-28 [長年日記]

_ [FreeBSD][Fortran]LLVM/flang on FreeBSD

コンパイラドライバ(devel/llvm50側)をゴソゴソ修正して、-Lオプション無しで、libflangmain, libflang, libflangrti等のランタイムライブラリをリンクできる所まで修正完了

まだ、-Iオプション無しでintrinsic moduleが使えないので、コンパイラドライバもしくはflang1/flang2に埋め込まれているmodule include pathに修正が必要な模様…

tools/clang/lib/Driver/ToolChains/FreeBSD.cppに環境依存のintrinsic module pathを追加するコード(flang1に対して-stdincオプションを設定する)を追加して、追加オプション無しでintrinsic module付きのコードをコンパイル・リンクできる所まで完了

SADのコンパイルを試すのは来年かな?


カテゴリー: Admin | Emacs | EPICS | Fortran | FreeBSD | GCC | hgsubversion | IPv6 | KEKB | LHC | Lisp | LLVM | MADX | Ryzen | SAD | samba | tDiary | unix | WWW | YaSAI | お仕事 | イベント | 出張 | 宴会 | 数学 | 艦これ | 買いもの | 追記 | 雑記