パフォーマンス Tips
高頻度に焦点を更新するなど, スループットが要求される場面で性能を引き出すための指針.
複合コマンドと低レベルコマンド
Section titled “複合コマンドと低レベルコマンド”Pattern はバッファ書き込み・再生設定・バンク切替をまとめて行う複合コマンドだが, これらは 1 フレームに融合して送られる.
そのためフレーム数は低レベルコマンド 1 つと同じであり, 単に焦点を更新するだけなら Pattern をそのまま高頻度で送ってよい.
ただし, バンクを設定してしまえば, 毎フレームの焦点更新で必要なのはバッファの書き換え, すなわち WritePatternBuffer だけである.
WritePatternBuffer のみを送ると, 毎フレームの再生設定とバンク切替を省けるため, デバイス側の 1 フレームあたりの処理が軽くなる.
最初に一度だけバンクを設定しておく. (最初はPatternを使っても良い.)
let mut builder = client.datagram_builder();builder .push(SetSilencer::disable()) .push(WritePatternBuffer { bank: PatternBank::B0, index: 0, emissions: &patterns, }) .push(ConfigPattern { bank: PatternBank::B0, config: SamplingConfig::FREQ_40K, size: 1, loop_behavior: LoopBehavior::Infinite, });for frame in &builder.build()? { client.send_checked(frame).await?;}builder = client.datagram_builder()builder.push(SetSilencer.disable())builder.push( WritePatternBuffer( bank=PatternBank.B0, index=0, emissions=patterns, ))builder.push( ConfigPattern( bank=PatternBank.B0, config=SamplingConfig.FREQ_40K, size=1, loop_behavior=LoopBehavior.Infinite, ))for frame in builder.build(): await client.send_checked(frame)var builder = client.DatagramBuilder();builder.Push(SetSilencer.Disable());builder.Push(new WritePatternBuffer( bank: PatternBank.B0, index: 0, emissions: patterns));builder.Push(new ConfigPattern( bank: PatternBank.B0, config: SamplingConfig.Freq40k, size: 1, loopBehavior: LoopBehavior.Infinite));foreach (var frame in builder.Build()){ await client.SendCheckedAsync(frame);}以降のホットループでは WritePatternBuffer のみを送る.
let mut buf = Frames::default();let mut pending: VecDeque<ResponseFuture> = VecDeque::with_capacity(MAX_INFLIGHT);for i in 0..NUM_POINTS { let theta = 2.0 * PI * i as f32 / NUM_POINTS as f32; let target = center + offset( RADIUS_MM * theta.cos() * mm, RADIUS_MM * theta.sin() * mm, 0.0 * mm, ); autd3_rs_pattern::focus( &geometry, target, wavelength, &autd3_rs_pattern::FocusOption::default(), &mut patterns, );
let mut builder = client.datagram_builder(); builder.push(WritePatternBuffer { bank: PatternBank::B0, index: 0, emissions: &patterns, }); builder.build_into(&mut buf)?; for frame in &buf { if pending.len() >= MAX_INFLIGHT { pending.pop_front().expect("non-empty").await?.check()?; } pending.push_back(client.send(frame).await?); }}while let Some(fut) = pending.pop_front() { fut.await?.check()?;}pending = collections.deque()for i in range(NUM_POINTS): theta = 2.0 * math.pi * i / NUM_POINTS target = center + np.array([RADIUS_MM * math.cos(theta), RADIUS_MM * math.sin(theta), 0.0]) pattern.focus( geometry, target, wavelength, pattern.FocusOption(), patterns, ) builder = client.datagram_builder() builder.push( WritePatternBuffer( bank=PatternBank.B0, index=0, emissions=patterns, ) ) for frame in builder.build(): if len(pending) >= MAX_INFLIGHT: (await pending.popleft()).check() pending.append(await client.send(frame))while pending: (await pending.popleft()).check()var pending = new Queue<ResponseToken>();for (var i = 0; i < NumPoints; i++){ var theta = 2.0f * MathF.PI * i / NumPoints; var target = center + new Vector3(RadiusMm * MathF.Cos(theta), RadiusMm * MathF.Sin(theta), 0.0f); Pattern.Focus( geometry, target, wavelength, new FocusOption(), patterns );
var hotBuilder = client.DatagramBuilder(); hotBuilder.Push(new WritePatternBuffer( bank: PatternBank.B0, index: 0, emissions: patterns )); foreach (var frame in hotBuilder.Build()) { if (pending.Count >= Client.MaxInflight) { (await pending.Dequeue()).Check(); } pending.Enqueue(await client.SendAsync(frame)); }}while (pending.Count > 0){ (await pending.Dequeue()).Check();}streaming 送信
Section titled “streaming 送信”上のホットループでは, 応答を待たずに次々と送るstreamingを使っている.
send でフレームをキューに積み, MAX_INFLIGHT を超えたら古い応答から回収する.
往復レイテンシが隠蔽されてスループットがさらに向上するため, 高頻度更新では stop-and-wait より streaming を選ぶ.
バッファの再利用
Section titled “バッファの再利用”毎フレームの確保を避けるため, バッファは使い回す.
pattern_buffer()で得たバッファは, Pattern 計算関数の出力先として毎フレーム上書きできる.- Rust では
Framesもbuild_intoで同じバッファへ書き出せば,buildのたびに確保せずに済む. Python/C# ではbuild_intoは提供されない.
OS チューニング
Section titled “OS チューニング”EtherCAT のリアルタイム性は OS の設定にも左右される. スレッドの優先度・CPU アフィニティ・タイマ精度などを調整することで, ジッタを抑えてより安定した高レート送信ができる可能性がある.
これらは ClientConfig の rt_priority / rt_policy / rt_affinity, および Link Option の sync0_period / sync0_shift として設定できる.
適切な値は環境によって異なるため, 実測して決めるのがよい.
Windows のドライバ設定
Section titled “Windows のドライバ設定”Windows では NIC のドライバを最新のものにしたうえで, 以下のとおりに設定することを推奨する.
デバイスマネージャーのアダプタのプロパティ → 詳細設定で
- 割り込み調節 (Interrupt Moderation) → 無効
- 割り込み調節率 (Interrupt Throttle Rate) → 無効
- DMA Coalescing → 無効
- 省電力イーサネット (Energy Efficient Ethernet) → 無効
- フロー制御 (Flow Control) → 無効
リポジトリには, 性能の計測とチューニングのためのツールが用意されている.
cargo xtask tool perftest: スループットとレイテンシを計測するcargo xtask tool synctune: 同期の安定性を計測し最適なパラメータを探索する
詳しい使い方は各ツールの --help を参照.