さて、いきなりですが今回のテーマは 時間 です。



ところで 時間 ってなんでしょう?
いきなり哲学めいたことをのたまわりますが、
まずは 時間 というものの認識についてリサーチしてみましょう。っていうても、いつものように単に辞書を見るだけだけど・・・・

まずは大辞林から行ってみよう!
(1)時の長さ。時の流れのある一点からある一点まで
(2)時の流れのある一点。時刻
(3)時間の単位。三六〇〇秒
(4)〔哲〕空間とともに世界を成立させる基本形式
普通、出来事や意識の継起する流れとして認識され、過去・現在・未来の不可逆な方向をもつ。
理念・精神・神など超時間的な永遠の存在を認める立場では、生成変化する現象界(事物)の性質とみなされる。
また、先天的な直観形式だとする考え(カント)、物質の根本的な存在形式としての客観的実在だとする考え(唯物論)などがある
(5)〔物〕 自然現象の経過を記述するための変数
古典力学で用いられる時間(絶対時間)は、二つの事象の間の時間経過の長さが、座標系(観測者)に依らず一定である。
相対性理論では、時間は空間とともに四次元時空を形成し、観測者に対して運動する座標系での時間は、ゆっくり経過すると観測される。
また一般相対性理論によれば、時間経過の長さは、重力の大きさによっても影響される
・・・・・分かりましたか?
特に4つ目、カント(Kant, Immanuel 1724/4/22〜1804/2/12)ですか?
う〜ん、行き成りカントときましたか
特にこの説明は、『純粋理性批判』(Kritik der reine Vernunft 1781初版/1787第2版)の reine Formen sinnlicher Anschauung 的解釈ですね・・・果たして適した説明なのか、判断に困りますね ;^^
また、5項目めは
Galilei, Galileo や Descartes, Rene に始まり Einstein, Albert に至るまでの認識学上の説明になりますね・・・・これも時間の説明としては不適切のような気がしますね
だからって私がこれ以上の解説ができるわけではないので面目ないのですが、このような中途半端な説明でいいのかなぁ

さて、気を取り直して
つぎは新明解国語辞典を見てみましょう。
(一)「時(トキ)(一)」の字音語的表現。〔空間と共に、種種の現象が生起する舞台であるが、空間と異なり時間は一つの方向にわれわれの所を過ぎ去って行くように 意識される〕⇔ 空間
(二)ある時点から もう一つの時点までの間。〔何かを△している(するのに都合の良い)間として 考えることが多い。狭義では、学校における一回分の授業時間を指す〕
(三)「時間(二)」の長さ。継続時間。時間間隔。〔量の一種〕
(四)「時間(三)」を計る単位で、一日〔=厳密には、一平均太陽日〕の二十四分の一を表わす 〔記号h〕。 〔六十分に等しい。国際単位系では、三六〇〇秒と定義されている〕
(五)「時刻」の日常語的表現。
こりゃ、また一般的な説明ですね、とても辞典とは

ちなみに今回の用語説明にはこちらのサイトを用いました http://www.sanseido.net/

さて、最後に手持ちの洋物辞書の一説をご紹介しましょう。
time
The general concept, relation, or fact of continuous or successive existence, capable of division into measuable portions, and comprising the past, present, and future. In the theory of relativity, the coordinate which, with the three spatial dimensions, is essential to the complete definition of anobject, phenomenon, or event
ふにゃ?
今回は辞書を参考にしたのは失敗でしたね・・・


兎に角、時間とは
その定義を一定周期間隔で刻まれていく基準指標としておきましょう。

ですから、その主点において
時間を計るには定期的な律を刻む機構が必要となります。
そこで時間を測定するための代表的な周期測定技術を挙げて見ましょう。

まずは最も有名な手法は 調和振動子 (harmonic oscillator)を用いる方法です。
ちょいとこの項目を検索のキーワードにして調べると・・・いっぱい出てきたので紹介することはやめよう

取敢えず適当に調和振動子について列記してみましょう。
・振り子振動子
Galileoが深く研究したことで発展したもの
アンティークな壁掛けの仕掛け時計で多用されたいる仕組みです

・LC回路型振動子
高校の物理なんぞでよく出てくるもので、コイルとコンデンサーを用いて周期特性を実現させる
結構、精度がアバウトなので精々リモコン(家電製品の)の赤外線出力周期特性に用いられている

・原子核放射線
放射性同位体が周期的に放射する放射線を使用
Cs(セシウム)を用いた原子時計などがメジャーです
厳密な周期特性が得られるため、非常にシビアな制御を必要とするところで用いられているらしい


・水晶振動子
水晶の圧電効果を用いた振動子
時計やデジタル機器のシークエンス制御に使われています。

より詳細な事柄を探求したい人は次のURLをお奨めします
http://homepage1.nifty.com/sokano/sub1.htm


蛇足ですが、最後に
・ぜんまい機械式周期振動子
昔の懐中時計や腕時計、置時計に採用されていた周期確定のメカニズムです。
詳細はこちらのサイトなどお奨め
http://www.fh-tokyo.com/Mechanism/menuJ.htm




相変わらず前置きが長くなりましたが、
いよいよ こんぴーた と戯れてみましょう。
って、云うか、よく話題にされている RDTSC (read-time stamp counter) や Win32API の時刻関連の関数をちょいと使ってみるだけですが

改めて云うまでもなく、全てのコンピュータには水晶振動子が搭載されており、この振動子を用いて内部チップ間の制御タイミングの同期を行っています。
当然のことながら MPU のクロック制御にも水晶振動子を用いています。

前置きは程々にして、
まずは前述の RDTSC について説明しましょう
この RDTSC とは、ReaD-Time Stamp Counter の略でして、intel系のx86系(AI-32系)の MPU、それも Pentium 以降の MPU だけに限定された話になりますが、 MPU にサポートされた機能として、マシンにパワーが入ったときから、マシン内の MPU のクロック時間単位毎にインクリメントされ続ける高分解能クロックカウンターになります。

クロックサイクル毎にインクリメントされるカウンターの実態は 64-bit MSR であり、ユーザーはこのカウンターにアクセスすることで正確に時間を測定することが可能となります。
もっとも精度の程は、マザーボードに搭載されている水晶振動子の質とマザーの設計によります。ここだけの話ですが、結構あてにならない精度を叩き出すくそボードが、巷に多く存在します(特に安物に多いです)

さて、いざこのカウンターにアクセスしようとしますと
カウンターが格納されているレジスターサイズが 64bit で在ったりしまして、なかなか厄介な仕様になっています。具体的なカウンター値へのアクセスの方法は
RDTSC コマンド発行後、32bit分の上位ビットが EDX 汎用レジスターに、残りの32bit分の下位ビットが EAX レジスター上に収められています。
そこで単純な使い方を紹介すると、下記の如くインラインアセンブラで記述すれば OK!

RDTSC Access Sample

 DWORD  HbitC, LbitC; 
  __asm __emit 0fh 
  __asm __emit 031h 
  __asm mov HbitC ,edx 
  __asm mov LbitC ,eax 
まずは何も考えず、RDTSCコマンドを打叩き
その後、無条件にデータレジスターとアキュムレーターレジスターの値をメモリーフィールドポインターの指し示す先に転移させます。
以上です!
これだけで高分解クロックカウンターを取得することができます(Intel系のみですが)

ついでですが、この RDTSC に関しましては、次に挙げますURLを参考にしてください。
http://cedar.intel.com/cgi-bin/ids.dll/content/content.jsp?cntKey=Legacy::irtp_RDTSCPM1_12033&cntType=IDS_EDITORIAL

内容は英語ですが、RDTSCに関しては詳しく書いております、って云うかIntelが公式に出しているドキュメントだから下手なサイトの薀蓄よりは信憑性が高い!






さて、実際に使えるサンプルコードを書いてみましょう。
仕様は至極単純に、起動してから何クロック経ったか覗くだけです。
具体的なコードは以下の通りです。
(いくつか姑息な技を使っておりますが、取敢えず自分の手持ちの資産上では、下記コードは問題なく動いています)


MPUCycle.cpp

  //コンパイラのバージョン定義
  //Microsoft Visual C++R 6.0 を使用しているので 1200 を定義
  #if _MSC_VER > 1200
  #pragma once
  #endif 

  // Windows ヘッダーから殆ど使用されないスタッフを除外
  #define WIN32_LEAN_AND_MEAN

  #include <windows.h>
  #include <stdio.h>
  #include <stdarg.h>

  #define rdtsc __asm __emit 0fh __asm __emit 031h

  void PopUpMsgBox(char* szMsg,...)
  {
      //本来は入力ストリングストリームの
      //サイズチェックを行い、
      //打っ飛ぶ危険性を
      //回避する必要がある
      //悲しいがな、現状では256Byte以上の
      //ストリングストリームが流れてきたら
      //あっさりと領域破壊を起こします・・・
	  
      char	szBuf[256];
      va_list         vaList;
      //可変個引数初期化
      va_start(vaList, szMsg) ;
      vsprintf(szBuf, szMsg, vaList) ;
      va_end(vaList) ;	
      MessageBox(NULL, szBuf, "Cycle counter", MB_OK) ;
  }

  int APIENTRY WinMain(HINSTANCE hInstance,
                       HINSTANCE hPrevInstance,
                       LPSTR     lpCmdLine,
                       int       nCmdShow )
  {
      HANDLE    hCurThread = GetCurrentThread() ;
      int        nPriority = GetThreadPriority(hCurThread) ;
      HANDLE   hCurProcess =  GetCurrentProcess();
      DWORD     dwPriority = GetPriorityClass(hCurProcess);
	
      DWORD      TCH, TCL ;

      //プロセスの優先順位クラスを設定
      SetPriorityClass(hCurProcess, REALTIME_PRIORITY_CLASS);
      //スレッドの相対優先順位値設定
      SetThreadPriority(hCurThread, THREAD_PRIORITY_TIME_CRITICAL) ;
      __asm{
            push  eax         ; eaxのデータを退避させておく
            push  edx         ; edxのデータを退避させておく
            rdtsc
            mov   TCH ,edx
            mov   TCL ,eax
            pop   edx         ; edxを元に戻す
            pop   eax         ; eaxを元に戻す
      }
      //相対優先順位値を元に戻す
      SetThreadPriority(hCurThread, nPriority) ;
      //優先順位クラスを元に戻す
      SetPriorityClass(hCurProcess, dwPriority);

      unsigned __int64 nCyclesCount = ((unsigned __int64) TCH << 32 ) | TCL ;

      PopUpMsgBox("Cycle count is %I64u\n"
                  "0x%016I64x", nCyclesCount, nCyclesCount) ;
      return (0) ;
  }


上記のコードについて補足しておきましょう。
まずは WinMain 関数からモジュールが起動します。
Windows 系における GUI 環境のモジュールに対してロードモジュールは WinMain 関数をエントリーポインターとして認識しております。 詳細は こちら を参照してください。

モジュール起動と同時に、ひとまず GetCurrentThread() 関数を用いて現在のスレッドの擬似ハンドルを取得します。
で、もって、GetThreadPriority 関数を用いて自身のスレッド相対優先順位値を取得します。
取得される値については以下の何れかが返ります。
THREAD_PRIORITY_ERROR_RETURN関数呼出に失敗
ちなみにエラー情報を取得するには、GetLastError 関数を用いるべし
THREAD_PRIORITY_HIGHESTTHREAD_PRIORITY_NORMALに対し 2 ポイント高い相対優先順位値
THREAD_PRIORITY_ABOVE_NORMALTHREAD_PRIORITY_NORMALに対し 1 ポイント高い相対優先順位値
THREAD_PRIORITY_NORMAL-- 標準の相対優先順位値 --
THREAD_PRIORITY_BELOW_NORMALTHREAD_PRIORITY_NORMALに対し 1 ポイント低い相対優先順位値
THREAD_PRIORITY_LOWESTTHREAD_PRIORITY_NORMALに対し 2 ポイント低い相対優先順位値
THREAD_PRIORITY_IDLEプロセス優先順位クラスが
  IDLE_PRIORITY_CLASS
  BELOW_NORMAL_PRIORITY_CLASS
  NORMAL_PRIORITY_CLASS
  ABOVE_NORMAL_PRIORITY_CLASS
  HIGH_PRIORITY_CLASS
のいずれかである場合は、基本優先順位レベルが 1
プロセスの優先順位クラスが
  REALTIME_PRIORITY_CLASS
である場合は、基本優先順位レベルが 16
THREAD_PRIORITY_TIME_CRITICALプロセスの優先順位クラスが
  IDLE_PRIORITY_CLASS
  BELOW_NORMAL_PRIORITY_CLASS
  NORMAL_PRIORITY_CLASS
  ABOVE_NORMAL_PRIORITY_CLASS
  HIGH_PRIORITY_CLASS
のいずれかである場合は、基本優先順位レベルが 15
プロセスの優先順位クラスが
  REALTIME_PRIORITY_CLASS
である場合は、基本優先順位レベルが 31
例によって例の如く今回もMSDNの焼き回しになります。ついでに参考文献として日経BP社の「WIndows 2000 Server 完全技術解説」を使っています
続いて、GetCurrentProcess 関数でプロセスの疑似ハンドルを取得後、GetPriorityClass 関数でプロセスの優先順位を得ます。
どうでもいいことですが、手順としては、先にプロセス、次にスレッドの順位を取得するほうがコードの書き方としては読解性が高いのですが・・・まぁ、これぐらいだったら読み難くもないので OK としましょう。

さて、プロセスの優先順位クラスについて以下に纏めます。
関数呼出に失敗した際には0が返ります。
エラー情報の取得には GetLastError 関数
ABOVE_NORMAL_PRIORITY_CLASSNORMAL_PRIORITY_CLASS より高く、HIGH_PRIORITY_CLASS より低い優先順位
BELOW_NORMAL_PRIORITY_CLASSIDLE_PRIORITY_CLASS より高く、NORMAL_PRIORITY_CLASS より低い優先順位
HIGH_PRIORITY_CLASS タイムクリティカルなタスクを実行することを示す。
NORMAL_PRIORITY_CLASS や IDLE_PRIORITY_CLASS を割り当てられたプロセスのスレッドより先に実行
IDLE_PRIORITY_CLASSシステムがアイドル状態のときにのみ、このプロセスを実行
NORMAL_PRIORITY_CLASS特別なスケジューリングを必要としない、一般的なプロセス
REALTIME_PRIORITY_CLASSもっとも高い優先順位クラスを持つプロセス
とりあえず起動前に取得する情報はこんなもんです。

いよいよプログラムの核心に迫ります。
まず、気のまわせ過ぎかもしれませんが、OS のタスクスケジュールコントロールによって RDTSC 呼出の邪魔をされないようにしておきます。
手順は SetPriorityClass 関数に対して REALTIME_PRIORITY_CLASS をセットしてプロセスの優先順位クラスを設定します。次に SetThreadPriority 関数に THREAD_PRIORITY_TIME_CRITICAL をセットしてスレッドの相対優先順位値設定を設定しておきます。
両パラメーターの値の意味は上記の表を参照してください。
ここからが核心です!
インラインアセンブラを使って MPU の命令セットを呼び出します。
ASM に関しては、こちらのサイトなんぞが、お奨めでしょうか
http://webster.cs.ucr.edu/
さらにX86系の汎用オペラントに関しては同URLのこちらなんぞにまとまっています
http://webster.cs.ucr.edu/Page_asm/Doc386/C17.HTM


さて、低レベルコードをガリガリ書くわけですが、まずは作法に則り、使用するレジスターの現値を退避させておきましょう。
RDTSC コマンドは汎用レジスターであるアキュムレーターとデーターレジスターを使いますので、両者を PUSH しておきます。
PUSH 後、RDTSC を呼び出して、予め確保しておいた領域に MOV コマンドで両値を渡します。最後に POP で使用したレジスターの値を元の値に戻します。
後始末はまだ続きます。
再び、 SetPriorityClass 関数と SetThreadPriority 関数を使って、変更前の状態に戻します。
ひとまずこれで終わりです、あとは

unsigned __int64 nCyclesCount = ((unsigned __int64) TCH << 32 ) | TCL ;

__int64 の変数領域に入れ込む、この↑ステートメントで値を参照すれば終わりです。

蛇足になりますが、上記の如くキャストを用いますと、VC++の場合、SHL、SHLD を多用するコードルーチンに跳びます。
最適化が云々と論じされる方は、以下のようなコードの方が直感的にも理解しやすいかと
RDTSC Access Sample

  unsigned __int64 nCyclesCount;
  void* pbuf = &nCyclesCount;
  __asm{
    push eax
    push ebx
    push edx
    rdtsc
    mov ebx, pbuf
    mov [ebx],eax
    mov [ebx+4],edx
    pop edx
    pop ebx
    pop eax
  }
ただ、わたくし個人的には
この様に低レベルコードを駆使したところで、
実質的にはコスト的に見合ったものではないかと思っています。
だって、きょうびのこんぴーたーの性能を考慮すれば、
一サイクル辺りのコスト高は無視できるのでは?
みなさんは如何思われますか?

ちなみに上記のコードは以下のサイトにも同種のコードがまんま載っておりますが、 http://msdn.microsoft.com/msdnmag/issues/1000/VTrace/VTrace.asp
決して真似ではないので・・・
上記のマイクロソフトのURLを知る前から、キャストを用いないこのコードを書いていましたから


実行結果の表示には PopUpMsgBox 関数を作成して、この関数内部でメッセージボックスを表示させています。
この PopUpMsgBox 関数には可変個引数受け渡しを用いています。可変個引数の参照には va_list を始めとする va_ 系のマクロを用いています。
ついでに PopUpMsgBox 関数の注意点としては、完全に汎用化されていないので、受け渡されるストリームサイズが内部バッファサイズを越えますと、運がよければ問題なく動き、次に運がついていれば例外でぶっ飛び、最悪な運気の人は OS が固まります。特に95系のカーネルを使っていると
もっとも大抵の場合は例外でアプリだけがぶち落ちるので安心してクラッシュさせてください;^^
・・・・以上!

いや〜、長くなってしまいましたが、以上が RDTSC の呼出方法になります。
続いてはWin32APIを用いていろいろと戯れてみようかと・・・
続かせていただきます・・・では、また




目次に戻る
全文メニュー
サイトの玄関


ご意見、ご感想、ご要望等々はこちらのメールアドレス takamegu@lycos.ne.jp までご一報ください。