CPUの処理量を減らすコーディング(組み込み)(1/2)

解決済みの質問

CPUの処理量を減らすコーディング(組み込み)

処理が重くてプログラムが回らないので、どうにかして実行量を下げ
ようとしています。
CPUはSH2(80MHz)、内蔵メモリ(ROM、RAM)と、
外付けのフラッシュROM、RAMがあります。
OSはuItron仕様のRTOSで、プログラムはC(一部アセンブラ)
で記述しています。
作成開始前の想定では、昔、68000の12MHzで動かしていた
ソフトにシリアル通信が少し増えた程度なので、CPUの能力不足で
困るなどとは想像してなかったのですが、CPUは高速化したものの
外付けメモリの速度が足りないので外部のバスアクセスは実質10M
Hz程度しか出ないということが後で分かりました。
内蔵メモリだけで動かせば外部使用時の数倍のパフォーマンスが出る
のですが、容量が少なくてソフト全体の20%ほどしか使えません。
I2Cバス(400kbps)と子局機器とのシリアル通信(115
kbps)がメインなので、これらの割り込み処理だけでも内蔵メモ
リで駆動できないかと模索中です。
他には、関数の引数(スタック待避・復帰の処理量)を減らすなどを
試していますが、これといった効果が出ていません。
ソースは10万行くらいあり、コーディング流儀の微細な変更でも、
”積もれば山”の可能性があります。
何かいい案があったら教えてください。

投稿日時 - 2007-07-23 18:53:56

QNo.3192999

困ってます

質問者が選んだベストアンサー

私も今月から SH7085 + NORTi を使い始めました.
昔8ビットマイコンを自作したことはありますが,本格的な組込みの仕事は初めてです.

新しい A/D,D/A 変換基板用に別の人が作りかけていたソフトと,
その元になったソフト (別の基板用に基板メーカが作成したもの) を渡されて,
「まずハードウェアの評価をしないといけないので,とりあえず至急これを動かしてくれ」
と言われ,ようやく先日完成しました.

その過程で,あまり効率の良くないコードがたくさん目に付いたので,
製品版のソフトを開発する際には大幅に書き直してやろうと考えています.
以下,改良できそうな点のリストです.組込の専門家には常識と思いますが,
(自分用の備忘録を兼ねて) 一応ご参考まで.
(上記のソフトも組込みの専門家が書いたはずなんですが….
 組込みは初めてなので,どの程度効くかはわかりませんし,
 間違っているところもあるかもしれません.)

(1) 割込みハンドラに余計な仕事をさせている.
  複数のアナログ入力を A/D 変換したデータを多重化し,SSU で受信している (約1ms周期).
  その受信割り込みハンドラで,データのチェックとフォーマット変換を行って受信バッファに
  書き込んでいた.1ms の割込みハンドラにそんなことまでさせるなよ!
  受信データをそのままバッファに書き込んで,あとはデータ収集タスクにやらせりゃええやん!

(2) I/O レジスタを操作するのにビットフィールドを使い,
  複数のビットをセット (または複数のビットをリセット) している.例えば,

    #define SCI0 ((volatile t_SCI*)0xFFFFC000)

    // 受信エラーリセット
    SCI0.SCSSR.BIT.ORER = 0; // Overrun Error
    SCI0.SCSSR.BIT.FER = 0; // Framing Error
    SCI0.SCSSR.BIT.PER = 0; // Parity Error

   これでは SCSSR が6回もアクセスされてしまう (read/write 各3回).
   I/O レジスタのため volatile が付いており,最適化はされない.
   これを次のようにすれば2回のアクセスですむ (read/write 各1回).

    SCI0.SCSSR.BYTE &= ~(SCSSR_ORER | SCSSR_FER | SCSSR_PER);

(3) 排他制御
  ・排他制御の粒度が粗すぎる.受信割込みハンドラが書き込んだバッファの内容をを別の
   場所にコピーする間ずっと,バッファ全体を排他制御をしている.これではオーバーラン
   エラーが多発するはず.リングバッファであれば,書き込みポインタが読み出しポインタ
   を追い越さないようにするだけでいいはず (iTRON でどうやればいいのかまだわからないが).
   いやそれ以前に,コピーすること自体を見直した方がよい.

  ・排他制御すべきコード範囲が非常に短く,すぐ終わる処理ならば,セマフォなどを使わず
   一時的に割込み禁止にするだけの方が効率いいはず (マルチプロセッサではないので).
   例えば送受信などにリングバッファを使用している部分があるが,リングバッファの排他
   制御期間は非常に短くてすむので,これをセマフォではなく割込み禁止で行う.

(4) SH7085 (または SH-2) 固有の問題
  ・データキャッシュがない
   SH7085 には命令キャッシュはあるようだが,データキャッシュはないそうなので,
   (特に外部) メモリアクセスの回数を極力減らすことが必要.逆に参照の局所性は問題に
   ならず,ランダムアクセスを多用してもペナルティは発生しない (はず).したがって
   メモリアクセス順序がランダムになっても (特に外部) メモリアクセス回数を減らす
   方法があれば,そうする方がよい.(キャッシュがある場合とは逆)

  ・除算命令がない
   SH-2 には除算命令がない (1ステップ除算命令というのはあるが,これは商の1ビット
   だけを計算するものであり,商のビット数分繰り返す必要があるらしい) ので,
   特に高速化が必要な部分 (短周期割込み用ハンドラなど) ではできるだけ除算を避ける.
   ちなみに上記の 1ms 受信割り込みハンドラでは除算を (複数回) 使用していた.(-_-#)

  ・乗算命令の問題
   SH-2 の乗算命令は2ステート (32bit 乗算の場合) または4ステート (64bit の場合)
   で実行されるが,その間パイプラインがストールするうえメモリアクセス競合も発生する
   そうなので,使わなくてすむなら使わない方がよさそう.

  ・シフト命令
   ・SH-2 にはバレルシフタがない.→ 右辺が定数でないシフト演算子はなるべく避ける.
   ・SH-2 のシフト命令は,シフト数が 1/2/8/16 のものだけ.
    ただし算術シフト命令は1ビット用だけ.
    →・シフト演算子の右辺がなるべく 1/2/8/16 になるようにする.
      (ビットフィールドの配置を工夫するなど.)
     ・符号付整数のシフトはなるべく避ける.

  ・外部メモリ上のバイトまたはワード (2バイト) データのうち,同時にアクセスする
   ことが多いものはできるだけ同じ1ロングワード (4バイト) 内にまとめ,1回の
   メモリアクセスで読み書きできるようにする.
   (ただし SH7085 の外部データバスは 8/16/32 ビットから選択できるようなので,
    32ビット・データバスでなければ効果がない.)

(5) (特に多次元) 配列は添字ではなくポインタでアクセスする.
  受信バッファとして,チャネル番号,サンプル番号等を添字とする多次元配列が多用され,
  それらを添字でアクセスしている.多次元になるほど,添字から配列要素のアドレスを
  計算する手間がかかるので,なるべく添字ではなくポインタでアクセスする.

(6) パイプライン的なデータ処理の高速化
  A/D 変換したデータに対し,ノイズ除去,移動平均,平滑化などの信号処理をパイプライ
  ン的に行っている.これらは別々の関数として実装されており,したがって別々のループ
  で処理されるが,わかりやすく再利用しやすい反面,実行速度はイマイチ (のはず).

  これらの処理を1つのループに融合し,入力データから一気に最終データまで計算する
  方が,途中結果をいちいち (外部) メモリに書き出して後でまた読み込む,ということを
  何度も繰り返す必要がなくなる.また,CPU 内の演算パイプラインが効くようになるはず
  なので,その分の高速化も期待できそう.
  (余談:参照の局所性も高まるので,データキャッシュのある CPU ならさらに効果的.
   以前同様のことを画像処理 (多数の画像を演算して1枚の画像を作成する) で行った
   ことがある.)

(7) 不必要なバッファコピーが多い.
  処理の都合上,受信バッファから最終的な出力バッファの間に中間バッファが存在し,
  コピーを複数回行っている.中間バッファを使用せず,受信バッファから直接出力バッ
  ファにコピーできないか.

(8) 同じような処理がやたら多い.
  多数のバッファのそれぞれについて,それらをリングバッファとして使うためのコード
  が書かれている.リングバッファの処理を共通化すれば,コードサイズが小さくなって
  ROM の使用量が減るとともに,命令キャッシュの効きが良くなるかもしれない.おまけに
  ソースもわかりやすくなるはず.それ以外にも,共通化できる処理はできるだけ共通化する.

(9) 内蔵 RAM (32KB) に配置するデータは極力無駄を省き,できるだけ多くのデータを内蔵
  RAM に置けるようにする.ブール型の変数1個に4バイト整数丸ごと使うなんて論外!

調べればまだ他にも改善点が出てきそうですが,とりあえずこの辺で.

投稿日時 - 2007-07-30 03:14:23

お礼

色々とありがとうございます。
いくつか期待できそうなので、さっそく試してみます。
で、近況ですが、おかげさまで、いくつか試してなんとか
予定域まで効率の引き上げつつあります。
やったことは、
1)欲しい演算処理を割り込みの中で行う。
下層タスクで演算させると、途中に割り込まれる度にジャンプ処理
とスタック操作に時間を食われて演算完了が遅れるので、いっその
こと他に影響のない範囲で押し込んでしまえばと試したら最終目的
には近づきました。(その分他がとばっちりですが)
2)頻度の多い処理だけを厳選して内蔵ROMに。
デバッグライトをソフト各部に埋め込んで1分あたりの実行回数を
計測し、上位10%を選んで内蔵ROMに詰め込んでみるとかなり
の効果がありました。
今分析結果を基に呼び元の処理を見直し中です。

投稿日時 - 2007-07-30 08:45:30

ANo.10

0人が「このQ&Aが役に立った」と投票しています

[  前へ  |  ]

ベストアンサー以外の回答(9件中 1~5件目)

ANo.9

使ったことはありませんが、シリアル調歩同期にDTC(Data Transfer Controller)を
利用していますか? どうも32byteに1回CPUが処理すればいい(割り込み)みたいです。
Renesasのアプリケーションノートにありました。
URLを参照ください。

参考URL:http://documentation.renesas.com/jpn/products/mpumcu/apn/rjj06b0705_sh7080ap.pdf

投稿日時 - 2007-07-26 18:59:42

ANo.8

>セマフォの資源操作を実行するとカーネルの重い処理が走るのかな
>と考えているのですが、そのへんご存じないですか?

セマフォからのタスクスケジュールは、そのITRONの純粋にスケジューラ
(ディスパッチ)の性能かと思います。
といいますか、セマフォ処理は、あまり工夫しようのない部分ですね。
ミューテックスなら話は別ですが,,,

タイムラグがあるということですが、どの程度なのでしょうか?

ミスポさんはわかりませんが、itronベンダーに問い合わせれば、カーネル
性能データを出してくれるところもあります。最近は、なぜか削除されて
いますが、古いバージョンでは、マニュアルに記載しているものもあります。

ハードウエアに関係せずに純粋に測るならHEWのシミュレータでも
計測できると思います。

それからベンダーによるかもしれませんが、
1)速度をあげたい とか
2)使用メモリを減らしたい
とか要望を出すと、対応したライブラリを出してくる場合があります。
問い合わせみるのもいいかもしれません。

それから、いろいろ対応されているようなので対処済みかもしれませんが、
Nortiのマニュアルに記載がありましたが
「静的エラーチェックしないライブラリ(カーネルのことでしょう)」がある
ようですね。使っていなければ切り替えてみましょう。

投稿日時 - 2007-07-26 03:02:44

ANo.7

>TIC_NUMEを10程度にするとisig_timの周期が10分の1になります。
10倍の間違いでした。 訂正します。

投稿日時 - 2007-07-25 10:36:08

ANo.6

タイムティックが、1/1 ms(スタンダードプロファイル標準)なら 10ms程度に
間隔をあけてみるのも負荷を減らせますね。

TIC_NUMEを10程度にするとisig_timの周期が10分の1になります。
ただし、Nortiは、タイムアウトの仕様が厳密に4.0仕様に合致していなかった
ようなので、若干注意が必要かと思います。

投稿日時 - 2007-07-25 10:26:07

お礼

ありがとうございます。
うちのシステムの計時周期は10msで、これをいじるのは無理
みたいです。
セマフォ待ちでタスクを休眠させると、待ち解除した時にスケジュ
ーラの定時起動タイミング外でもすぐに起床しますが、パラレルポ
ートから信号出してタイミングを測ると、資源解放から起床までに
少しタイムラグがあります。
セマフォの資源操作を実行するとカーネルの重い処理が走るのかな
と考えているのですが、そのへんご存じないですか?

投稿日時 - 2007-07-25 18:57:34

ANo.5

はずしていると思いますが、自分でもミスった経験あることとしては、
「CPUの分周をセットしていない」とかであれば、いきなり今の4倍で動いたりします。
ですが、外部メモリのボトルネックということで、これはないでしょうね。

次に、微々たる?対応方法についてですが、
ITRONカーネル速度を上げる一般的な方法として(Nortiがそうかはよく知りません)

1)タスク優先度レベルを小さくする。
  たとえば、優先度を5飛ばしや10飛ばしで定義している場合
  例) タスクA:10 タスクB:20 タスクC:30
できるだけ、最大値を小さく、詰めて定義します。
     -> タスクA:1 タスクB:2 タスクC:3
  理由:スケジューラのアルゴリズムにもよりますが、優先度別キューの上位から
     の検索を行うアルゴリズムなら、優先度200なら200回先頭キューを比較しなければ
     スケジュールしない場合が考えられるから。
     何ゆえ、最大優先度をコンフュギュレータで指定するかと言うと、固定最大値だと
     遅くなるためです。ばかばかしいかもしれませんが、実際これで回避した
     ケースの報告を聞いたことがあります。

2)優先度待ち(TA_TPRI)をやめて、できるだけFIFO待ちにする。
  優先度待ちのアルゴリズムは大抵先頭から順番に比較するというあまり賢くないもの
  です。優先度待ちに多く並んでいた場合にさらに待つとカーネルが多くのCPU時間を
  使います。
TOPPERS/JSP FI4もこれです。というよりそうでないものは知りません。

3)周期ハンドラアラームハンドラのID数を減らします。
  周期ハンドラの定義数分だけ、isig_tim時に分まわります。(そうでないのもあります,TOPPERSはその辺賢いです)
  定義だけというのもやめて必要数だけ生成する。

4)可変長メモリプール使わず、できるだけ固定長メモリプールでサイズを分けましょう
  可変長メモリプールのメモリが返されたときのマージ処理は非常に重い場合があります。

5)多重割り込みを禁止できるなら禁止にする。
  割り込みハンドラや、周期、アラームハンドラなど、多重割り込みが可能にしてあるなら
  禁止にする。そうすることで多重割り込みにかかわる負荷が減ります。ただし、割り込み
  処理が逐次処理されるので割り込みが間に合わない場合は禁止できないです。

6)カーネル管理外割り込みを利用する
  サービスコールを使わない割り込み処理でいいならカーネル管理外割り込みとすれば
  カーネルが動作しない分だけ処理が軽くなります。

投稿日時 - 2007-07-25 00:42:14

お礼

ありがとうございます。
1~3は実施済み、4は返却しないのでカーネルに任せず自主管理。
5は間に合わないので無理、6もカーネルを介してません。
TOPPERSはまだ使ったことないんですが、やはり対処は同じような
感じなんですね。

投稿日時 - 2007-07-25 18:48:19

あわせてチェックしたい
  • C to 68000アセンブラ ...
  • 組み込みアセンブラとは何ですか? ...
  • μITRONのスタック見積もりについて ...
PR

OKWaveのオススメ

教えて弁護士さん!

お金の悩みQ&A特集はこちら