NI製品ディスカッション

キャンセル
次の結果を表示 
次の代わりに検索 
もしかして: 

1ループでの時間制御限界

解決済み
解決策を見る

現在LabVIEWを使って高速制御を行いたいと考えております。

しかし、以前調べた際に1ループでの時間制御は1msecが限界との記述がありました。

LabVIEWで1msecより早い時間の正確な時間制御をできないのでしょうか?

なにか、方法がありましたら、お願いします。

 

また、もし時間の指定を外した状態でループを回すと

もっとも軽い演算(一回の足し算等)で何secのスピードで処理するのでしょうか?

時間を表示させていると、1msec以下で処理しているように見えるので。

 

LabVIEWは8.6を使用しています。

 

 

初心者でもうしわけありませんが、

よろしくお願いします。

0 件の賞賛
メッセージ1/9
9,368件の閲覧回数
解決策
トピック作成者ニックSが受理

「1ループでの時間制御は1msecが限界」というよりも、「LabVIEWで計測可能な時間単位が1msec」

ということなので、1回の足し算とか、軽いループの回る時間などは、1msecよりはるかに短いです。

 

私のマシンの場合ですが、空っぽのForループの1回分の時間は、10~11nsec(ナノ秒)くらいでした。

 

ただし、明示した時間単位で制御しようとすると、「次のミリ秒まで待機(Wait Until Next Millisec)」関数などを

使うことになるので、どうしてもその最小単位の1msec単位での処理になってしまいます。計測する時間差も

1msecなので、みかけのばらつきも(量子化誤差)も1msec単位で生じてしまいます。

(つまり、ほんとは一定速度だとしても、20msec、21msec、20msec、・・・と1msec単位でばらついて見えたり)

 

さて、1msec以下でループの回る速度を制御する方法ですが、APIのQueryPerformanceCounter()と

QueryPerformanceFrequency()を使うと、一応、最小制御時間単位として1μsec(マイクロ秒)以下にすることもできます。

 

添付に4つのサンプルと、上記の基本APIを含むllb、およびいくつかのdllを付けました。ただし、ver.6です。

 

100806-sample1-(Comparison).vi

  LabVIEWの(1)Wait Until Next Millisec関数、(2)Wait関数、および(3)APIのQueryPerformanceCounter()を

  使った1秒単位のループの比較をしています。ジッタが少ないのは(3)ですが、ほかに走っているプロセスの

  負荷にも依存するのでご注意ください。

 

100806-sample2-(LoopExecVariance).vi

  10000回のForループの個々のループの時間差をグラフに表し、ヒストグラムも表示します。

  他のプロセスの負荷の影響を受けるなどで、10000回のうちに回る速度が変化しているようです。

 

100806-sample3-(EmptyLoopExec).vi

  空っぽのForを100万回まわし、その前後の時間差から空のループ1回分の平均時間を計算します。

  LabVIEWの関数ではミリ秒単位なので、あまり精度よく測れませんが、API関数を使うと

  数1000~10000倍以上の精度で計測可能です。

  (実行すると値がパラパラ変わるのは、「100万回のループ」をさらに何度も繰り返しているため)

 

100806-sample4-(0.25msecLoop).vi

  0.25msec(250μsec)単位でループを回しています。実際の時間差も表示しているので、0.25msec単位で

  回っていることがわかると思います。

  数値上は、1usecとかも指定できますが、ループ内の処理の合計がそれを上回れば、当然1usecでは回せません。

  実際、カウント値を読み出すオーバーヘッドだけでsample2のように数10μ秒はかかるようなので、

  100usecくらいが安定して回せる限界かも知れません。

 

QueryPerformance.llb

  APIのQueryPerformanceCounter()とQueryPerformanceFrequency()を呼んでいるllbです。

  なお、これらのAPI関数にはクセがあるらしく、オーバークロックや、動作中に動的にクロックを変化させるようなものでは、

  きちんと測れないこともあるらしいです。そういう場合は、別途LabVIEWのタイマ関数などで「1秒」のカウント値を

  数えておき、自分で校正する、という手もあります。

 

SmallTick.dll

  マイクロ秒単位のWait(Wait Until Next Usec)を提供する簡易的な自作のdllです。

  sample1とsample4で使っています。

  なお、これらの呼び出しに「U32が2個」のクラスタをつないでいますが、これは、LabVIEW7.1以下では

  64ビット整数が使えないためで、LabVIEW8以上では、64ビット整数が使えると思いますので

  Call Library Functionにつながっているクラスタは、すべて64ビット整数をつなげばOKと思います。

 

Int64.dll

  64ビット整数を演算するための簡易的な自作dllです。LabVIEW8以上は、普通に64ビット整数で演算して下さい。

 

(同時にたくさん添付できないようなので、いくつかに分割して投稿します)

 

 

ただ、基本的に実行速度はWindows起因でいくらでも変わってしまうので、たとえば0.25msecでループを

回すように書いた場合、処理が軽ければだいたいば0.25msecで回ってくれますが、他のプロセスが重くなると

すぐ遅延が生じますので(これは普通に待機関数を使った場合も同じ)、その点はもともと保証されていないことを

ご了承ください。上記はあくまで1msec以下の計測と制御を試みた例ということで。

 

すべてをダウンロード
メッセージ2/9
9,332件の閲覧回数

添付の追加分です。

 

すべてをダウンロード
0 件の賞賛
メッセージ3/9
9,331件の閲覧回数

・・・・よく考えたら、初めから7つのファイルを1つのzipにして添付すれば良かったですね。。。

(蛇足ながら)

 

メッセージ4/9
9,329件の閲覧回数

M.Shiraishi 様

 

ナイス突込み。

 

 

 

 

いつも勉強になります。有難うございます。

 

0 件の賞賛
メッセージ5/9
9,293件の閲覧回数

M.Shiraishi 様

 

助かりました。

ありがとうございます。

 

こんなAPIがあったのですね。

勉強になりました。

また、機会がありましたら御教授ねがいます。

ありがとうございました。

0 件の賞賛
メッセージ6/9
9,286件の閲覧回数

xyzz様、ニックS様

 

 どうもありがとうございます。

 

なお、SmallTick.dllですが、一応、どう実装したかソースを書いておきます。たいしたものではございませんが・・・

 


SmallTick.cpp

#include <stdio.h>

#include <windows.h>

__declspec(dllexport) void _stdcall WaitUntilNextUsec(unsigned long Usec,LARGE_INTEGER *Tick);
__declspec(dllexport) double _stdcall PerformanceFrequencyInfo(LARGE_INTEGER *Frequency);

void _stdcall WaitUntilNextUsec(unsigned long Usec,LARGE_INTEGER *Tick)
{
    long long Count,Quot;
    LARGE_INTEGER Frequency;

    QueryPerformanceFrequency(&Frequency);
    Count=(*(long long *)(&Frequency))*Usec/1000000; // Frequency*Usec/1000000  指定時間内のカウント

    QueryPerformanceCounter(Tick);
    Quot=(*(long long *)Tick)/Count;
    do {
        QueryPerformanceCounter(Tick);
    } while((*(long long *)Tick)/Count==Quot);
    return;
}

double _stdcall PerformanceFrequencyInfo(LARGE_INTEGER *Frequency)
{
    QueryPerformanceFrequency(Frequency);
    return((*(long long *)Frequency)*((double)1.0)); // [Hz]
}


 

WaitUntilNextUsec() は、第1引数でusec単位の待機時間を入力し、第2引数に待機完了時のカウント値(64bit)を返します。

(入力が待機時間、出力が待機後のTick値なのは、標準の Wait や WaitUntilNextMillisec と同様のつくり)

 

中身の do ... while 文は、CPUに負荷をかけるかも知れません。

実際、この関数を繰り返し呼ぶと、私のPCはファンの音が大きくなるような・・・使いすぎてPCが壊れてもいけないので念のため。

(うまく負荷を回避しつつ、精度を維持できる書き方があれば知りたい)

 

関数内のローカル変数 Frequency は、引数の *Tick の領域で代用可能で、わずかにメモリ節約します。

 

PerformanceFrequencyInfo() のほうは、単に QueryPerformanceFrequency() が返す64bitの「1秒あたりカウント数」を、

64bit整数と double の両方で返します。[Hz]単位の数値で簡便に扱いたい場合に使います。

double は64bit整数のすべては扱えませんが(bit数が同じなので)、QueryPerformanceFrequency() が返す値は

そんなに大きくないので(まず32bitに収まっているので)、doubleで返して、以降の扱いがしやすいようにしています。

 

以上、ご参考下さい。

 

メッセージ7/9
9,274件の閲覧回数

M.Shiraishi

 

本当に詳しくありがとうございます。

ぜひ参考にします。

ちょうど最近winAPIをかじっり初めたところなので

ちょうど上記に非常に興味をもっておりました。

ソースコードまで教えていただき

しっかり勉強させていただきます。

ありがとうございます。

0 件の賞賛
メッセージ8/9
9,270件の閲覧回数

重複投稿のため削除しました。

0 件の賞賛
メッセージ9/9
9,270件の閲覧回数