2016-07-07 9 views
2

一定の間隔でイベントを起動するためのビジー待機ループのパフォーマンスを評価しています。私は、次のコードを使用して、いくつかの奇妙な行動に気づいた:ビジー待機ループの可変パフォーマンスですか?

試験機(デュアル14コアE5-2683 V3する@ 2.00Ghz、256ギガバイトDDR4)、forループの20万回の繰り返しで
#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 
#include <time.h> 

int timespec_subtract(struct timespec *, struct timespec, struct timespec); 

int main(int argc, char *argv[]) { 
    int iterations = atoi(argv[1])+1; 

    struct timespec t[2], diff; 

    for (int i = 0; i < iterations; i++) { 
     clock_gettime(CLOCK_MONOTONIC, &t[0]); 

     static volatile int i; 
     for (i = 0; i < 200000; i++) 
      ; 

     clock_gettime(CLOCK_MONOTONIC, &t[1]); 

     timespec_subtract(&diff, t[1], t[0]); 
     printf("%ld\n", diff.tv_sec * 1000000000 + diff.tv_nsec); 
    } 
} 

は約1ミリ秒です。またはそうでないかもしれない時間は三時間をシフトダウンするとき

1030854 
1060237 
1012797 
1011479 
1025307 
1017299 
1011001 
1038725 
1017361 
... (about 700 lines later) 
638466 
638546 
638446 
640422 
638468 
638457 
638468 
638398 
638493 
640242 
... (about 200 lines later) 
606460 
607013 
606449 
608813 
606542 
606484 
606990 
606436 
606491 
606466 
... (about 3000 lines later) 
404367 
404307 
404309 
404306 
404270 
404370 
404280 
404395 
404342 
406005 

、彼らは時折、数百回の反復約450usにジャンプアップを除いて、(約2〜3マイクロ秒以内に)ほとんど一貫とどまります。この動作は、同様のマシン上で繰り返し実行されます。

私は、ビジーなループはコンパイラによって最適化できることを理解していますが、ここではそれが問題ではないと思います。私はキャッシュがそれに影響を与えているとは思わない。なぜなら、無効化が行われてはならないし、突然の最適化を説明しないからだ。また、ループカウンタにレジスタintを使用してみましたが、顕著な効果はありませんでした。

何が起こっているのか、これを(もっと)一貫性のあるものにするための考えはありますか?

EDIT:このプログラムをusleep、nanosleep、または10k回の繰り返しで表示すると、すべてのプログラムはtime -vで約20000個の非自発的コンテキストスイッチを表示します。

+0

ジャンプを呼び出す/スリープ遅延から差し引くことができる時間間隔を計算するためにはgettimeofday使用することができ、おそらくコンテキストスイッチです。 – Mysticial

+2

申し訳ありませんが、あなたのアプローチは完全に間違っています。そういう意味では、aPCのようなシステムでは、本当に信頼できるタイミングを得ることはできません。これは確かにXY問題です。 **実際に**達成したいことと関連するすべての詳細を述べてください。 – Olaf

+0

私が実際に達成したいのは、質問タイトルに述べたように、なぜビジーループのパフォーマンスが変化するのかを理解することです。私は自分のプログラムのタイミングを決める別の方法を知っています。 – Rakurai

答えて

1

CPUリソースを使い切っただけでなく、待つ時間もCPUブロックの速度に大きく左右されます。したがって、同じループを別々のマシンで別々の時間に実行することができます。

スリーピングの方法の問題は、OSのスケジューリングにより、意図したよりも長い時間スリープ状態になることがあることです。 nanosleepのマニュアルページには、信号を受信した場合の残りの時間を教えてくれるのに、rem引数を使用すると書かれていますが、あまりにも長く待つことについては何も言いません。

usleepを呼び出すたびにタイムスタンプを取得する必要がありますので、実際にどれぐらい長く眠っているかを知ることができます。あまりにも眠れない場合は、赤字を追加します。あまりにも長い時間寝ると、過剰を差し引いてしまいます。ここで

は私が UFTP、マルチキャストファイル転送アプリケーションでこれをやった方法の例ですが、一貫性の速度でパケットを送信するために:

int64_t diff_usec(struct timeval t2, struct timeval t1) 
{ 
    return (t2.tv_usec - t1.tv_usec) + 
      (int64_t)1000000 * (t2.tv_sec - t1.tv_sec); 
} 

... 

     int32_t packet_wait = 10000; 
     int64_t overage = 0, tdiff; 
     struct timeval current_sent, last_sent; 

     gettimeofday(&last_sent, NULL); 

     while(...) { 
      ... 

      if (packet_wait > overage) { 
       usleep(packet_wait - (int32_t)overage); 
      } 
      gettimeofday(&current_sent, NULL); 
      tdiff = diff_usec(current_sent, last_sent); 
      overage += tdiff - packet_wait; 

      last_sent = current_sent; 
      ... 
     } 
+0

あなたのコメントをありがとう、それは睡眠が長すぎる場合に役立つ情報とタイミングを制御する正しい方法です。しかし、それは私が求めているものではありません。私は、プログラムが進行するにつれて、ビジーループのパフォーマンスが段階的に変化する理由を理解しようとしています。 – Rakurai

+0

@Rakuraiこれは、OSに全面的に依存しているためです。このため、ビジー待機ループは非常に予測不可能です。 OSスケジューラのソースコードを掘り下げたいのでない限り(Linuxや他のオープンソースOSを使用していると仮定して)、これはあなたがダウンしたいパスではありません。 – dbush

1

は私が作ると思います2ポイント - によりコンテキストswtichingへ睡眠/休憩時間が予想以上に長くなる可能性があります。 - さらに、割り込みのような優先度の高いタスクがあると、スリープがまったく実行されない場合があります。あなたのアプリケーションの正確な遅延をしたい場合は

したがって、あなたははusleep

関連する問題