web-dev-qa-db-ja.com

gettimeofdayと同等の高速

毎秒数百のメッセージを送信する必要があり、各メッセージに時間フィールドがある非常に遅延の影響を受けやすいアプリケーションを構築する際に、gettimeofdayの最適化を検討しました。最初に考えたのは、rdtscベースの最適化でした。何かご意見は ?他のポインタはありますか?返される時間値の必要な精度はミリ秒単位ですが、値が1〜2ミリ秒間レシーバーと同期しない場合があっても、大した問題ではありません。 gettimeofdayにかかる62ナノ秒よりも優れた処理を試みる

26
Humble Debugger

実際にベンチマークを行い、gettimeofdayが許容できないほど遅いと感じましたか?

1秒あたり100メッセージの割合で、メッセージごとに10msのCPU時間があります。複数のコアがある場合、完全に並列化できると仮定すると、簡単に4〜6倍に増やすことができます。つまり、メッセージあたり40〜60ミリ秒です。 gettimeofdayのコストが10ミリ秒近くになることはほとんどありません-1〜10マイクロ秒に近いと思います(私のシステムでは、マイクロベンチマークにより、1コールあたり約1マイクロ秒が得られます- 試してみてください )。あなたの最適化の努力は他の場所でよりよく使われるでしょう。

TSCの使用は合理的なアイデアですが、最新のLinuxにはすでに serspace TSC-based gettimeofday があります-可能な場合、vdsoはオフセットを適用するgettimeofdayの実装を取り込みます(共有カーネルから読み取ります)ユーザーメモリセグメント)をrdtscの値に変換することで、カーネルに入ることなく時刻を計算します。ただし、一部のCPUモデルでは、異なるコアまたは異なるパッケージ間でTSCが同期されていないため、これが無効になる可能性があります。高性能のタイミングが必要な場合は、最初に、同期されたTSCを備えたCPUモデルを見つけることを検討する必要があります。

つまり、かなりの解像度を犠牲にしても構わない場合(タイミングは最後のティックまでしか正確ではないため、数十ミリ秒ずれる可能性があります)、 CLOCK_MONOTONIC_COARSEまたはCLOCK_REALTIME_COARSE を使用できます。 = clock_gettime を使用します。これはvdsoでも実装されており、カーネルを呼び出さないことが保証されています(最近のカーネルお​​よびglibcの場合)。

45
bdonlan

POSIXクロック

POSIXクロックソースのベンチマークを作成しました。

  • 時間(s)=> 3サイクル
  • ftime(ms)=> 54サイクル
  • gettimeofday(us)=> 42サイクル
  • clock_gettime(ns)=> 9サイクル(CLOCK_MONOTONIC_COARSE)
  • clock_gettime(ns)=> 9サイクル(CLOCK_REALTIME_COARSE)
  • clock_gettime(ns)=> 42サイクル(CLOCK_MONOTONIC)
  • clock_gettime(ns)=> 42サイクル(CLOCK_REALTIME)
  • clock_gettime(ns)=> 173サイクル(CLOCK_MONOTONIC_RAW)
  • clock_gettime(ns)=> 179サイクル(CLOCK_BOOTTIME)
  • clock_gettime(ns)=> 349サイクル(CLOCK_THREAD_CPUTIME_ID)
  • clock_gettime(ns)=> 370サイクル(CLOCK_PROCESS_CPUTIME_ID)
  • rdtsc(サイクル)=> 24サイクル

これらの数値は、Linux 4.0上のIntel Core i7-4771 CPU @ 3.50GHzからのものです。これらの測定は、TSCレジスタを使用して、各クロックメソッドを数千回実行し、最小コスト値を使用して行われました。

これらの実装方法はハードウェアやカーネルのバージョンによって異なるため、実行する予定のマシンでテストする必要があります。コードは here にあります。これは、同じレポ( tsc.h )にあるサイクルカウントをTSCレジスタに依存しています。

TSC

TSC(プロセッサタイムスタンプカウンター)へのアクセスは、最も正確で最も安価な方法です。一般に、これはカーネルが使用しているものです。 TSCはコア間で同期され、周波​​数スケーリングの影響を受けないため、最新のIntelチップでも非常に簡単です。したがって、シンプルでグローバルなタイムソースを提供します。使用例 here とアセンブリコードのウォークスルー here をご覧ください。

これの主な問題(移植性以外)は、サイクルからナノ秒に移行するための適切な方法がないようです。私が知る限り、IntelのドキュメントではTSCは固定周波数で動作しますが、この周波数はプロセッサの規定周波数とは異なる場合があります。 IntelはTSC周波数を把握する信頼できる方法を提供していないようです。 Linuxカーネルは、2つのハードウェアタイマー間でTSCサイクルがいくつ発生するかをテストすることでこれを解決しているようです( ここ を参照)。

Memcached

Memcachedは、キャッシュメソッドを行うのに面倒です。それは単に、プラットフォーム間でパフォーマンスを予測可能にすること、または複数のコアでより適切にスケーリングすることです。また、価値のある最適化ではないかもしれません。

52
David Terei

Bdonianの言うように、1秒あたり数百通のメッセージを送信するだけであれば、gettimeofdayで十分高速になります。

ただし、1秒あたり数百万のメッセージを送信している場合は、異なる可能性があります(ただし、ボトルネックであることをmeasureにする必要があります)。その場合は、次のようなものを検討することをお勧めします。

  • グローバル変数を持ち、現在のタイムスタンプを希望する精度で与える
  • タイムスタンプの更新以外は何も行わない専用のバックグラウンドスレッドがある(タイムスタンプをT単位の時間ごとに更新する必要がある場合は、スレッドにTの一部をスリープさせてからタイムスタンプを更新する。必要に応じてリアルタイム機能を使用する)
  • 他のすべてのスレッド(または、スレッドを使用しない場合はメインプロセス)は、グローバル変数を読み取るだけです。

C言語は、タイムスタンプ値がsig_atomic_tより大きい場合にタイムスタンプ値を読み取れることを保証していません。これを処理するためにロックを使用することもできますが、ロックは重いです。代わりに、volatile sig_atomic_t型付き変数を使用して、タイムスタンプの配列にインデックスを付けることができます。バックグラウンドスレッドは、配列の次の要素を更新してから、インデックスを更新します。他のスレッドはインデックスを読み取ってから配列を読み取ります。これらのスレッドは非常に古いタイムスタンプを取得する可能性があります(ただし、次回は正しいタイムスタンプを取得します)が、タイムスタンプを読み取るときに問題が発生することはありません。それが更新されると同時に、古い値のいくつかのバイトと新しい値のいくつかを取得します。

しかし、これはすべて、毎秒数百のメッセージに対してはやり過ぎです。

4
user25148

以下はベンチマークです。約30ns見えます。 rashadからのprintTime() C++で現在の日時を取得する方法

#include <string>
#include <iostream>
#include <sys/time.h>
using namespace std;

void printTime(time_t now)
{
    struct tm  tstruct;
    char       buf[80];
    tstruct = *localtime(&now);
    strftime(buf, sizeof(buf), "%Y-%m-%d.%X", &tstruct);
    cout << buf << endl;
}

int main()
{
   timeval tv;
   time_t tm;

   gettimeofday(&tv,NULL);
   printTime((time_t)tv.tv_sec);
   for(int i=0; i<100000000; i++)
        gettimeofday(&tv,NULL);
   gettimeofday(&tv,NULL);
   printTime((time_t)tv.tv_sec);

   printTime(time(NULL));
   for(int i=0; i<100000000; i++)
        tm=time(NULL);
   printTime(time(NULL));

   return 0;
}

100,000,000コールまたは30nsの場合は3秒。

2014-03-20.09:23:35
2014-03-20.09:23:38
2014-03-20.09:23:38
2014-03-20.09:23:41
1
edW

ミリ秒の精度が必要ですか?そうでない場合は、単純にtime()を使用して、UNIXタイムスタンプを処理できます。

0