プロが教える店舗&オフィスのセキュリティ対策術

Windows PC向けにPCIデバイスの制御を行うドライバを作成しているのですが、コンフィグレーションレジスタへのアクセス速度が非常に遅く、なぜ遅いのか原因が分からず悩んでいます。

PCIバスのスペックではPCIコンフィグレーションサイクルが6サイクルなので、PCIバスのクロックが33MHzでウェイトがなければ1サイクル180nsで完了するはずなのですが、テストプログラムを作成して速度を計測したところ、PCIデバイスのコンフィグレーションレジスタへのアクセスに920nsを要しています。プログラム上のオーバーヘッドは、レジスタアクセス1回あたり約3nsで、ほとんど無視できる程度です。

いくらなんでもあんまり遅いので、これはPCIデバイス自体あるいはPCI-PCIブリッジに問題があるのかと思い、比較のためノースブリッジ内にあるデバイスにアクセスしてみたのですが、CPUにいちばん近く、かつPCIクロックに制限されないデバイスにもかかわらずサイクルタイムは271nsまでしか縮みませんでした。

こんな速度では大昔のISAのプログラムI/Oデバイスよりも遅いことになってしまうので、何かやり方に問題があるのかと思うのですが、どう変更すればいいものか全く見当がつきません。(ループの中はI/Oアクセス命令しかないような状態です。)

原因あるいは解法がお分かりの方ありましたら、教えていただけませんでしょうか。

なおテスト環境はCPU P4-2.8GHz (FSB800)、メモリ1.5GB、OSはWindows XPです。

※質問の文字数制限のためサンプルプログラムは掲載しませんでしたが、いただいた回答への補足として掲載可能です。

A 回答 (5件)

一回ごとの値を計測する件ですが、たとえ計測精度に30%の誤差があっても、推定値と実測値の数倍の差を埋める情報は得られる可能性があるかと考えます。


1000回実行の平均時間は必ずしも一回の実行時間を反映するわけではないからです。同じ平均900nsでも、900ns×1000回と、200ns×900回+7200ns×100回では随分と違うでしょう。
実際問題としてI/Oレジスタを1000回(100万回)連続で参照するパターンがあまり一般的じゃないので、それゆえに遅いという可能性もあるわけです。

あと、カウンタがTSCじゃないとすると外部をみているのでしょうか。そっちのオーバーヘッドも気になるところです。
    • good
    • 0
この回答へのお礼

全くおっしゃるとおりです。特異的なばらつきがあるかもしれないという可能性をすっかり失念していました。

さっそく、1回の計測では1回のI/Oだけを行い、それを200回行って分布を調べるプログラムを作成して試してみたところ

  19カウント: 7回
  20カウント: 143回
  21カウント: 49回

という結果になりました(端数があっていないのは、2回以下を表示しないようにしたからです)。1回の計測を「1回のI/Oを100回試行した最小値」と変更すると

  19カウント: 200回

となりますので、これと比較的よく符号する結果です。

また、カウンタへのアクセス(やその他計測系そのもの)のオーバーヘッドについても同様に調べてみたところ、こんな感じでした。(I/Oを行う命令だけを削除)

   7カウント: 19回
   8カウント: 78回
   9カウント: 60回
  10カウント: 30回
  11カウント: 11回

残念ながらどちらも特異な分布は示さず、アクセスが遅い原因に迫ることはできませんでしたが、推測のあらをまた1つ潰したという点で意味がありました。

アドバイスありがとうございました。

お礼日時:2005/07/01 17:03

あまり自信はないですが、本当に測りたい時間を測れてるのかちょっと疑問に思います。


フリーランのカウンタってのはTSCで良いのかな? 70nsの分解度なら1000回ループなんてせずに一回ごとの時間を測ってみては?
あと100回の最小値でなく100回それぞれの時間を記録して分布を見るべきかと。
    • good
    • 0
この回答へのお礼

コメントありがとうございます。

TSCを使用すると分解能がCPUのコアクロックによって変化してしまいCPUごとに調整が必要となる等不便ですので、今回は使用しておりません。

1000回ループではなく1回ごとの時間を計測してはとのご意見ですが、1回の実行のみで計測するのでは計測制度が70nsになってしまい、仮に最短のPCIバスサイクルとなった場合には計測誤差が30%にもなってしまいます。また計測そのものに要する時間が誤差として加わるわけですが、これも1000回ループ時と比較して1回ごとに計測すると影響が1000倍になってしまいます。

次に経過時間の分布を見てはとのご意見ですが、分布を見ることによって原因を類推するヒントになるのではないかという意味合いでしたらおっしゃるとおりかと思います。ここに掲載するテストプログラムのコードは最短としたかったため、実際の実行時間に最も近い値となるのが確実である、最小値を求めるコードとしました。実際には分布を見るテストも念のため行っておりますが、その結果、やはり最小値あるいはその周辺が最も高頻度となっておりました。

お礼日時:2005/07/01 13:27

なるほど、そういう事ですか。


恐らく、連続したアドレスにwriteトランザクションを掛ければバースト転送をしてくれると思うので早くなる可能性があります。
readトランザクションを早くするには、、、対象のPCIデバイスがDMA機能を持っていれば早くなると思います。
その時のコードの書き方は以下を参照。
http://support.microsoft.com/default.aspx?scid=k …

う~ん、適当なデバイスが無いのでテストできないのがモドカシイ。FPGAが載っている安いPCIボードが欲しいですね。
    • good
    • 0
この回答へのお礼

PCIのスペック上、メモリサイクルはコンバインやプリフェッチが許されるので昇順に連続すれば速くなる可能性があるのですが(そして普通は速くなるわけですが)、I/Oサイクルはコンバイン・プリフェッチとも許されない(Strong Order)なので、残念ながら、単独で遅ければ連続しても遅い可能性が高いです。

動作自体に特に問題はないので、激しく困ったというほど切羽詰ってもいないのがまた困るところ(笑)

お礼日時:2005/07/01 01:00

なるほどぉ。

。。
もしかしたらIRQLを上げて実行してみるとちょっとは改善するかも。
後は、PCIのバスの状態をロジアナとかバスアナで見て原因を考えるとか。。。
読み込んでるデータ自体は正常なんですよね?
そもそもコンフィグレーション空間へそんなに頻繁にアクセスするかなぁって気もしますが。

これ以上は分かりそうもないです。スンマセン。
    • good
    • 0
この回答へのお礼

コメントありがとうございます。

念のためIRQLを上げることも試してはみたのですが、得られる数字は変わりませんでした。いちばん頻繁に発生する割り込みであるタイマー割り込みでも間隔が10msあり、1ms以内で終わる1回の試行に影響を及ぼす頻度は少ないと考えられるので、納得できる結果ではあります。

確かにおっしゃるとおり、コンフィグレーション空間は通常、大量にアクセスするわけではないので、少々遅かろうが実用上は差し支えありません。が、コンフィグレーション空間へのアクセスの次はメモリサイクルないしI/Oサイクルでのアクセスがあるわけですが、実はI/Oサイクルもまた同じくらい遅いのです。(I/Oサイクルでは完全にデバイス固有の話になってしまって質問しづらい(質問しても回答してもらいにくい)のでコンフィグレーションレジスタに関する質問にしました。)

メモリサイクル以外は全部遅いのが(今回使用している)チップセットの仕様!とでも判明すれば楽なんですけどね(苦笑)

ちなみにチップセットはインテルのE7221(915のサーバ版)+ICH6Rです。データシートにはソフト的な記述は豊富なのに、ハード的な記述(特にタイミング関係)はほとんど載ってないのが辛いところです。

お礼日時:2005/06/30 21:25

そもそも、どのようにして時間を計ったのでしょうか?

この回答への補足

テストプログラムの主要部分は以下のようなものです。

  ULONG ulTrial = 100; // 試行回数
  ULONG ulNumAccess = 1000; // 試行1回ごとのI/Oアクセス回数

  ULONG ulMinElapsed = -1; // 経過時間:全試行回数の中でもっとも短いもの
  for (ULONG trial = 0; trial < ulTrial; ++trial) {
    ULONG ulBefore = *pRealTimeCounter;
    for (ULONG n = 0; n < ulNumAccess; n += 1) {
      // (1) Accessing PCI Device Configuration Register
      // Cycle Time: 920ns, Frequency: 1.09MHz
      __asm mov dx, 0x0cfc;
      __asm in eax, dx;

      // (2) dummy - no I/O access
      // Cycle Time: 2.9ns, Frequency: 341 MHz
      //volatile PULONG p = &n;

    }
    ULONG ulAfter = *pRealTimeCounter;
    ULONG ulElapsed = ulAfter - ulBefore; // 今回の経過時間
    if (ulElapsed < ulMinElapsed) {
      ulMinElapsed = ulElapsed;
    }
  }

pRealTimeCounterはフリーランのカウンタの値が取得できるポインタで、I/Oアクセスループの前後でカウンタの値を取得し、その差を経過時間として使用しています。カウントアップ間隔が約70nsなので、1000回のI/Oアクセスを行うことで0.1ns単位程度の精度を確保しています。

このプログラムの実行中、割り込みを一切禁止していないため、割り込みが発生するとその回の試行では経過時間が大きく計測されます。その影響を避けるため100回の試行を行い、その中で最も経過時間の短いものを最終結果として使用しています。

補足日時:2005/06/30 16:55
    • good
    • 0

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!