C言語で信頼できる時間誤差

このQ&Aのポイント
  • C言語のQueryPerformanceCounterを用いて時間を計測するプログラムを作りました.このとき時間の間隔は必ずしも500m秒になりません.Sleep関数に含まれる誤差とQueryPerformanceCounter関数に含まれる誤差の2つが考えられると思っています.
  • PCはwindows XPでMicrosoft Viusal Stadio 2010で動作させ,その他のアプリケーションは開いていない状況下で実行しました.
  • サンプルプログラムにより実行しましたが,何が原因なのでしょうか?
回答を見る
  • ベストアンサー

C言語で信頼できる時間誤差

C言語で信頼できる時間誤差 C言語のQueryPerformanceCounterを用いて時間を計測するプログラムを作りました. このとき時間の間隔は必ずしも500m秒になりません. 原因としては, 1. Sleep関数に含まれる誤差 2. QueryPerformanceCounter関数に含まれる誤差 の2つが考えられると思っています. そこで,質問ですが,1および2の誤差ってどの程度なのでしょうか? また,その他に誤差が出現するとすれば,それは何でしょうか. ちなみに,PCはwindows XPでMicrosoft Viusal Stadio 2010で動作させ, その他のアプリケーションは開いていない状況下で実行しました. サンプルプログラムにより実行しましたが,何が原因なのでしょうか? **********【サンプルプログラム】********** #include <stdio.h> #include <windows.h> void main (void){ LARGE_INTEGER m_pCounterFreq; LARGE_INTEGER m_pBeforeCounter; LARGE_INTEGER ulCounter; double dPassageSec; double pre =0.0; int counter; printf("取得時間,取得時間間隔\n"); QueryPerformanceFrequency( &m_pCounterFreq ); QueryPerformanceCounter( &m_pBeforeCounter ); for(counter=0;;counter++){ QueryPerformanceCounter( &ulCounter ); dPassageSec = (double)(ulCounter.QuadPart - m_pBeforeCounter.QuadPart) / (double)m_pCounterFreq.QuadPart; if(dPassageSec >= 5.2) break; Sleep(500); printf("%-12f,%-12f\n", dPassageSec, dPassageSec-pre); pre = dPassageSec; } } **********【実行結果一例】********** 取得時間,取得時間間隔 0.000002 ,0.000002 0.486344 ,0.486342←14msも誤差がある 0.986095 ,0.499751 1.486106 ,0.500012 1.986102 ,0.499995 2.486100 ,0.499998 2.986116 ,0.500016 3.486089 ,0.499973 3.986090 ,0.500001←ほぼ500ms 4.486092 ,0.500002 4.986208 ,0.500117

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

  • ベストアンサー
noname#140045
noname#140045
回答No.1

たぶん間違いないと思いますが、MS-WindowsもUNIXやLinuxもリアルタイムOSではありません。 ご存じのように、これらのOSはマルチタスクにより、同時にいろいろな処理が動いています。 そして、Sleepすると実行権を解放します。 その後、Sleep終了後に実行権が与えられるのですが(この場合であれば)500ms後丁度に実行権が与えられることは保証されていません。 確か、NASAはLinuxを使用しているはずですが、これはLinuxをリアルタイムOSに改造したものを使用していると聞きました。 ロケットの打ち上げでは、チョットの時間のズレも一大事になる可能性がありますからね。

puyoer
質問者

お礼

Sleep誤差ですか.参考になりました. NASAのリアルタイムOSは気になりますね! 少し調べてみます.ありがとうございます,

その他の回答 (5)

  • goma1109
  • ベストアンサー率0% (0/2)
回答No.6

Sleep関数の精度についてはMSDNの英語のページに説明があります。 日本語にはありませんが。 http://msdn.microsoft.com/en-us/library/ms686298%28VS.85%29.aspx http://msdn.microsoft.com/ja-jp/library/cc429358.aspx ここも参考になります。 http://hp.vector.co.jp/authors/VA007219/rtc_pic.html 結論としてはSleepは高精度のタイマーとしては使えないと思います。

puyoer
質問者

お礼

英語の文献は少し抵抗があったのですが,参考にさせていただきます. ありがとうございます.

  • rinkun
  • ベストアンサー率44% (706/1571)
回答No.5

Sleepですね。Sleepの動作としてはプロセスの実行権を手放して指定時間を過ぎてから実行可能状態にするだけです。指定時間を過ぎてもカーネル、デバイスドライバなど特権プログラムの実行中だと実行可能状態になるまでに時間が掛かる場合もありますし、実行可能状態になっても他の実行可能プロセスとの競合で実行再開が遅れる場合があります。 従って、再開時間の精度を上げようと思うと以下が必要になります。 1. カーネル、デバイスドライバ等の特権プログラムでの割り込み禁止時間を短くする 2. プロセスを他のプロセスより優先度を高くする このうち2はプロセス起動時の設定などでできますが、1は使う全てのデバイスに関わるため簡単ではありません。 数年前にLinuxで調べたときにはディスク・デバイスドライバに長い割り込み禁止があり時々タイマに数百msを越える遅延がありました。 なお、QueryPerformanceCounterも結構重いですが、待ちは伴わないので数マイクロ秒程度の遅延しかありませんし、取得する値にはほぼ誤差はないはずです。

puyoer
質問者

お礼

このプログラムは予備段階で他に色々とデバイスを読み込んだりするので, windowsの方が都合がいいので,OS変更はできないんです. でも,OSによる違いが生まれるという知識は今後大いに役立つと思います. ありがとうございました.

  • titokani
  • ベストアンサー率19% (341/1726)
回答No.4

>1. Sleep関数に含まれる誤差 >2. QueryPerformanceCounter関数に含まれる誤差 QueryPerformanceCounterの値は、CPUのクロックに使われている水晶発振子からきているので、誤差はほぼゼロと考えてよいです。 この場合の原因はSleepの誤差ですね。 理由は他の方もおっしゃっている通りで、Sleepとはそもそもそんなものと考えて使うべきでしょう。 なるべく正確に500msウェイトしたいなら、Sleepで490msとか480msとか待つようにして、最終調整にQueryPerformanceCounterを使うのがいいと思います。 まあ、それでも他のアプリとの競合次第では、大幅にタイミングが遅れる可能性はあります。 そのあたりをどの程度まで許容できるかですね。 絶対に500msを外せないのであれば、OSの選定から考えなければならないでしょう。

puyoer
質問者

お礼

説明が詳しくてわかりやすくて助かります. なるほど,CPUのクロックに用いられているものなら,間違いないでしょうね. 0.1s以下は正直誤差があっても問題はないのですが, 質問されたときに正確に応えたかったもので... 参考になりました.ありがとうございます.

回答No.3

UNIXやWindowsなどのOSでは、1つのハードウエアのタイマーやクロックはOSが独占使用します。そしてアプリには複数存在するように見せかける、仮想的なソフトウェア・タイマーを搭載しています。その仕組みによって複数のプロセスからの要求を実現しているのです。 従って、その時の状況に応じた値になってしまい、必ずしも正確なものを保証している訳ではありません。

puyoer
質問者

お礼

No2さん同様ですが,プロセス管理や優先度を失念していました. お教えいただき,ありがとうございます.

  • notnot
  • ベストアンサー率47% (4846/10257)
回答No.2

No1の方が書いているように、厳密には無理なわけですが、出来るだけ誤差を減らすためにはそのプロセスのCPUプライオリティを上げればいいです。Win32APIでプロセス起動の時に指定出来たはず。起動してからで良ければタスクマネージャで変更出来ます。 シングルコアで高い優先度にしたプロセスがループすると電源切るしかないので、注意が必要ですが。

puyoer
質問者

お礼

そういえば、プロセスの優先度を高くするっていう方法がありましたね. 失念していました.ありがとうございます.

関連するQ&A

  • QueryPerformanceCounter の使い方の間違いの指摘をお願いします。

    Windowsの起動してからの時間を知りたいのです、QueryPerformanceCounterを使っています。 前は、timeGetTimeを使っていたのですが32ビットで47日以上はオーバーフローになってしまうので上のを使うことにしました。 QueryPerformanceCounterの使い方をいろいろ調べて見よう見まねでやってみたのですが、よく分からずtimeGetTimeのように使ったら訳の分からない値を返してきました。 明らかに、自分が間違っていることは分かるのですが、その致命的な間違いが分かりません。 MSDNより BOOL QueryPerformanceCounter( LARGE_INTEGER *lpPerformanceCount // カウンタの値 ); typedef union _LARGE_INTEGER { struct { DWORD LowPart; DWORD HighPart; }; LONGLONG QuadPart; } LARGE_INTEGER, *PLARGE_INTEGER; この時点でいまいちよく分からなかったのですが、使う部分は64ビットなので「QuadPart」だろうと思いました。 LONGLONG型・・・。 理解せずにプログラムを以下のように組みました。 私の理解では、QueryPerformanceCounterはWindowsが起動してからの時間を返してくれるものだと思っています。 #include <stdio.h> #include <windows.h> int main() { LARGE_INTEGER i64; LONGLONG count; QueryPerformanceCounter(&i64); count = i64.QuadPart; printf("LONGLONG=%lld\n" , count); return 0; } 実行結果 LONGLONG=3670653187373 続行するには何かキーを押してください . . . 実際の経過時間 1025500秒 基本がわかってないとは思うのですが、QueryPerformanceCounterが実際に返してる値とWindowsが起動してからの時間を64ビットで取得する方法を教えていただけないでしょうか? よろしくお願いします

  • 値を変数に代入してprintfで表示しようとしてもうまくいきません

    値を変数に代入して、printfで表示しようとしてもうまくいきません。 ソースのどこが悪いか教えていただけないでしょうか? 変数の値を1個ずつ表示する場合は、正常になります。 問題は、1行で表示する場合に1個目の値は出るのですが正常な表示にはなりません。 //ソース開始 #include <stdio.h> #include <windows.h> int main() { LARGE_INTEGER i64,h64; LONGLONG work,h,m,s; QueryPerformanceCounter(&i64); QueryPerformanceFrequency(&h64); //起動時間をミリ秒取得 work = i64.QuadPart*1000 / h64.QuadPart; //時間をhに入れる h = work / 1000 / 3600; //分をmに入れる m = (work / 1000 - h * 3600) / 60; //秒をsに入れる s = work / 1000 - h * 3600 - m * 60; //表示 printf("%d時間%02d分%02d秒\n",h,m,s); printf("%d時間\n",h); printf("%d分\n",m); printf("%d秒\n",s); printf("%d秒\n",work); return 0; } //ソースここまで //実行結果 289時間00分55秒 289時間 55分 54秒 1043754590秒 続行するには何かキーを押してください . . . //理想の表示結果 1行目が以下のようになってほしい 289時間55分54秒

  • clock()関数の誤差

    プログラムの実行時間の計測について質問させていただきます。 現在,実行時間の計測でclock()関数を使っているのですが誤差が出ます。 timeコマンド(と実際に時計で測った時間)では95分、clock()関数で測った プログラム全体の実行時間は1376秒(約23分)と誤差が出る状態にあります。 プログラムでclock()関数を使っているのはmain()だけです。 printf()内がおかしいのでしょうか? 詳しい方、回答よろしくおねがいします。 ↓プログラム #include<time.h> (中略) clock_t t1,t2,t3,t4; (中略) main() { struct zahyo P,Q; int a,b,prime,Ord,sec; scanf("%d",&a); (中略) printf("Q.y = "); scanf("%d",&Q.y); t1=clock(); Ord=OrdCal(P,a,prime); t2=clock(); printf("Ord = %d\n",Ord); printf("OrdCal:%f(s)\n",(double)(t2-t1)/CLOCKS_PER_SEC); t3=clock(); PohlingBsgs(P,Q,a,prime,Ord); sec=secretkey(); t4=clock(); printf("secretkey=%d\n",sec); printf("Decipher:%f(s)\n",(double)(t4-t3)/CLOCKS_PER_SEC); printf("Total :%f(s)\n",(double)(t4-t1)/CLOCKS_PER_SEC); } 実行結果 Ordcal:74.170000(s) Decipher:1302.722704(s) Total :1376.902104(s) real 94m33.445s user 94m30.900s sys 0m0.980s

  • 秒数を数える(C言語)

    プログラム実行時に時間を数え始めて、100秒経過したらまた最初からプログラムを実行しようと考えています。(tcpdumpみたいなもの) まず、秒数を数えて出力するプログラムを作ってみたのですが、実行しても0.000000と出てしまい、数えることが出来ませんでした。 (例) #include<stdio.h> #include<time.h> void tekitou(); int main() { while(1) { /*無限ループ*/ tekitou(); } return 0; } void tekitou() { static time_t start; time_t last; start = clock(); last = clock(); printf("%f\n", (double)(last - start)/CLOCKS_PER_SEC); } OSはLinuxでコンパイラはgccです。よろしくおねがいします。

  • C言語のプログラムについてですが、

    #include <stdio.h> int main(void) { double r; printf("半径を入力してください:"); scanf("%lf", &r); printf("円周=%f\n", 2.0 * r * 3.14159); printf("円面積=%f\n", r * r * 3.14159); return 0; } 上のC言語のプログラムを参考にして 正三角形一辺を double値でキーボード入力し、次のように高さと 面積を計算するプログラムを教えてください。 よろしくお願いしますl_ω_l 3の平方根は1.73205とします。 [実行結果] ======= 一辺の値を入力してください: 20 高さ=17.320500 面積=173.205000

  • 複利の利率を求めるプログラムをC言語で作りたいんですが、、、

    計算を始める値、最終的な結果、計算回数(期間)、の3つから利率を求めたいんですが、、 単利の利率を求める公式から、利率を0.001ずつ減らすループを作れば、誤差0.001以内の値が出るのではないかと思ったんですが、、全く動いてくれなせん、何故でしょうか?? #include<stdio.h> double main() { double x; double s; double k; int counter1; double a; double b; double r; int counter2; printf("初期値=?\n"); scanf("%lf",&x); printf("結果=?\n"); scanf("%lf",&s); printf("期間=?\n"); scanf("%d",&counter1); k=((s-x)/(counter1*x));//*単利での利率の公式*// printf("単利での利率(%%)=%f\n",k*100); while(counter1==counter2){ b=x;//*bはxを上書きさせない為*// k=k-0.001; while(b<=s){ //*複利の計算*// a=b/100; r=a*k; b=b+r; counter1++; printf("複利での利率(誤差0.001)=%f\n",k);} }return 0;} whileの部分ををforで、 for(b=x;counter1==counter2;k-0.001){ for(counter1=1;b<=s;counter1++){ としても、、コンパイルはできるのですが、、ループ自体をしてくれません、、何故なのでしょうか??C言語以外でもいいですが、、複利の利率は公式とかはあるんでしょうか??

  • 複利の利率を求めるプログラムをC言語で作りたいんですが、、、

    計算を始める値、最終的な結果、計算回数(期間)、の3つから利率を求めたいんですが、、 単利の利率を求める公式から、利率を0.001ずつ減らすループを作れば、誤差0.001以内の値が出るのではないかと思ったんですが、、全く動いてくれなせん、何故でしょうか?? #include<stdio.h> double main() { double x; double s; double k; int counter1; double a; double b; double r; int counter2; printf("初期値=?\n"); scanf("%lf",&x); printf("結果=?\n"); scanf("%lf",&s); printf("期間=?\n"); scanf("%d",&counter1); k=((s-x)/(counter1*x));//*単利での利率の公式*// printf("単利での利率(%%)=%f\n",k*100); while(counter1==counter2){ b=x;//*bはxを上書きさせない為*// k=k-0.001; while(b<=s){ //*複利の計算*// a=b/100; r=a*k; b=b+r; counter1++; printf("複利での利率(誤差0.001)=%f\n",k);} }return 0;} whileの部分ををforで、 for(b=x;counter1==counter2;k-0.001){ for(counter1=1;b<=s;counter1++){ としても、、コンパイルはできるのですが、、ループ自体をしてくれません、、何故なのでしょうか??C言語以外でもいいですが、、複利の利率は公式とかはあるんでしょうか??

  • C++の打切り誤差についてお聞きしたいのですが・・

    ↓のプログラムが、なぜ実行結果のsumが1ではなく1.000054や9.99999999999906e-001、9.9999999999990619e-001といった答えにになってしまうのでしょうか? 10000のところを512でやった場合は普通に1と出力されたのに・・・ #include <stdio.h> int main(void){ int i; float f; double d; f = 0.0; for(i = 0; i<10000; i++) { f += 1.0/10000; } printf("float: n=%d sum=%f\n", 10000, f); d = 0.0; for(i = 0; i<10000; i++) { d += 1.0/10000; }// end for printf("double:16.14e: n=%d sum=%16.14e\n", 10000, d); printf("double:18.16e: n=%d sum=%18.16e\n", 10000, d); return 0; } 実行結果 float: n=10000 sum=1.000054 double:16.14e: n=10000 sum=9.99999999999906e-001 double:18.16e: n=10000 sum=9.9999999999990619e-001 続行するには何かキーを押してください . . .

  • 計算の誤差

    a = 0; for(z=0;z<100;z++){ a += 0.04; printf("%f\n",a); } という簡単なプログラムでaを0.04ずつ大きくしていきたいのですが、これを実行すると、 …… 1.680000 1.720000 1.760000 1.799999 1.839999 1.879999 1.919999 …… といった風にわずかな誤差が生じてしまいます。 この誤差をなくす方法は無いでしょうか。

  • C言語

    現在、ストップウォッチの一時停止およびラップをとる機能のプログラムを作成しているのですが、 「計測中にtを押すと一時停止.一時停止中にtで計測再開」の部分は計測していないときに計測が開始されてしまいます。(計測中の処理は上手く動作できました) 「計測中にlを押すとラップをとる」という使い方の部分も同様に計測していないときに計測が開始されてしまいます。それから、実際に計測中にlを押したとき、ラップタイムではなく、スピリットタイムで表示されてしまい、上手く動作しません。 至急、修正または追加のほうお願いします。 #include <windows.h> #include <mmsystem.h> #include <stdio.h> #include <conio.h> #pragma comment(lib, "winmm.lib") void disp(DWORD time_value) { printf("%02d:%02d:%02d:%03d\r",time_value/3600000,(time_value/60000)%60,(time_value/1000)%60,time_value%1000); } int main(void) { int add_flag = 0; DWORD counter=0,start_time,cur_time; printf("使い方:小文字の's'でカウントスタート.カウント中,小文字の's'で停止.次の's'でまた0からスタート\n"); printf("使い方:どんな状態でも小文字の'r'でカウントリセットして停止\n"); printf("使い方:qでプログラム終了\n\n"); printf("使い方:計測中にtを押すと一時停止.一時停止中にtで計測再開\n"); printf("使い方:計測中にlを押すとラップをとる\n"); disp(counter); for (;;){ start_time = timeGetTime(); while (start_time == (cur_time = timeGetTime())) { if (kbhit()) { switch (_getch()){ case 's': disp(counter); printf(add_flag ? "\n計測中止\n" : "\n計測開始\n"); add_flag = !add_flag; start_time = cur_time = timeGetTime(); counter = 0; break; case 't': disp(counter); printf(add_flag ? "\n一時停止\n" : "\n計測再開\n"); add_flag = !add_flag; start_time = cur_time = timeGetTime(); break; case 'r': disp(counter); printf("\nカウンタリセット,停止\n"); add_flag = 0; counter = 0; start_time = cur_time = timeGetTime(); break; case 'l': disp(counter); printf("\nラップ\n"); add_flag = 1; break; case 'q': printf("\n終了\n"); return 0; } disp(counter); } } if (add_flag != 0){ counter += cur_time - start_time; disp(counter); } } }

専門家に質問してみよう