CLOCK_MONOTONICが使用できないようであるため、clock_gettimeが停止しています。
Mach_absolute_time()が正しい方法かもしれないと読んだところもありますが、それが「CPU依存値」であると読んだ後、すぐにrtdscを使用しているかどうか疑問に思いました。したがって、値が単調であっても、時間の経過とともにドリフトする可能性があります。また、スレッドアフィニティの問題により、関数の呼び出しとは意味のある異なる結果が生じる可能性があります(すべてのコアで単調ではなくなります)。
もちろん、それは単なる憶測です。 mach_absolute_timeが実際にどのように機能するか知っている人はいますか?私は実際にclock_gettime(CLOCK_MONOTONIC ...またはOSXのそれに似たものに代わるものを探しています。クロックソースが何であれ、少なくともミリ秒の精度とミリ秒の精度を期待しています。
使用可能なクロック、単調なクロック、特定のクロックがドリフトする場合、スレッドアフィニティの問題がある場合、すべてのMacハードウェアでサポートされていない場合、または実行に「超高」CPUサイクルが必要な場合を理解したいと思います。
このトピックについて私が見つけたリンクは次のとおりです(一部はすでにデッドリンクであり、archive.orgでは見つかりません):
https://developer.Apple.com/library/mac/#qa/qa1398/_index.html http://www.wand.net.nz/~smr26/wordpress/2009/01/ 19/monotonic-time-in-mac-os-x/ http://www.meandmark.com/timing.pdf
ありがとう!ブレット
Machカーネルは、システムクロックへのアクセスを提供します。そのうち、少なくとも1つ(SYSTEM_CLOCK
)は ドキュメントで宣伝されています 単調に増加します。
#include <mach/clock.h>
#include <mach/mach.h>
clock_serv_t cclock;
mach_timespec_t mts;
Host_get_clock_service(mach_Host_self(), SYSTEM_CLOCK, &cclock);
clock_get_time(cclock, &mts);
mach_port_deallocate(mach_task_self(), cclock);
mach_timespec_t
の精度はナノ秒です。ただし、精度についてはよくわかりません。
Mac OS Xは、次の3つのクロックをサポートしています。
SYSTEM_CLOCK
は起動時からの時間を返します。CALENDAR_CLOCK
は、1970-01-01以降のUTC時刻を返します。REALTIME_CLOCK
は非推奨であり、現在の実装ではSYSTEM_CLOCK
と同じです。clock_get_time
のドキュメント は、誰かがclock_set_time
を呼び出さない限り、時計は単調に増加していると述べています。 clock_set_time
の呼び出しは、クロックの単調なプロパティを壊す可能性があるため、 推奨されません です。実際、 現在の実装は何もせずにKERN_FAILURE
を返します。 。
これに対するいくつかの異なる答えを調べた後、私はマッハでclock_gettimeをエミュレートするヘッダーを定義することになりました。
#include <sys/types.h>
#include <sys/_types/_timespec.h>
#include <mach/mach.h>
#include <mach/clock.h>
#ifndef mach_time_h
#define mach_time_h
/* The opengroup spec isn't clear on the mapping from REALTIME to CALENDAR
being appropriate or not.
http://pubs.opengroup.org/onlinepubs/009695299/basedefs/time.h.html */
// XXX only supports a single timer
#define TIMER_ABSTIME -1
#define CLOCK_REALTIME CALENDAR_CLOCK
#define CLOCK_MONOTONIC SYSTEM_CLOCK
typedef int clockid_t;
/* the mach kernel uses struct mach_timespec, so struct timespec
is loaded from <sys/_types/_timespec.h> for compatability */
// struct timespec { time_t tv_sec; long tv_nsec; };
int clock_gettime(clockid_t clk_id, struct timespec *tp);
#endif
およびmach_gettime.c
#include "mach_gettime.h"
#include <mach/mach_time.h>
#define MT_NANO (+1.0E-9)
#define MT_GIGA UINT64_C(1000000000)
// TODO create a list of timers,
static double mt_timebase = 0.0;
static uint64_t mt_timestart = 0;
// TODO be more careful in a multithreaded environement
int clock_gettime(clockid_t clk_id, struct timespec *tp)
{
kern_return_t retval = KERN_SUCCESS;
if( clk_id == TIMER_ABSTIME)
{
if (!mt_timestart) { // only one timer, initilized on the first call to the TIMER
mach_timebase_info_data_t tb = { 0 };
mach_timebase_info(&tb);
mt_timebase = tb.numer;
mt_timebase /= tb.denom;
mt_timestart = mach_absolute_time();
}
double diff = (mach_absolute_time() - mt_timestart) * mt_timebase;
tp->tv_sec = diff * MT_NANO;
tp->tv_nsec = diff - (tp->tv_sec * MT_GIGA);
}
else // other clk_ids are mapped to the coresponding mach clock_service
{
clock_serv_t cclock;
mach_timespec_t mts;
Host_get_clock_service(mach_Host_self(), clk_id, &cclock);
retval = clock_get_time(cclock, &mts);
mach_port_deallocate(mach_task_self(), cclock);
tp->tv_sec = mts.tv_sec;
tp->tv_nsec = mts.tv_nsec;
}
return retval;
}
Mach Timeを使用するだけです。
これはパブリックAPIであり、macOS、iOS、およびtvOSで動作し、サンドボックス内から動作します。
Mach Timeは、私が通常「クロックティック」と呼ぶ抽象的な時間単位を返します。クロックティックの長さはシステム固有であり、CPUによって異なります。現在のIntelシステムでは、クロックティックは実際には正確に1ナノ秒ですが、それに依存することはできません(ARMの場合は異なる場合があり、PowerPC CPUの場合は確かに異なります)。クロックティックをナノ秒に変換し、ナノ秒をクロックティックに変換する変換係数(この係数は静的であり、実行時に変更されることはありません)。システムが起動すると、クロックは0
で始まり、毎回単調に増加します。その後、クロックティックが発生するため、Mach Timeを使用してシステムの稼働時間を取得することもできます(もちろん、稼働時間は単調です!)。
ここにいくつかのコードがあります:
#include <stdio.h>
#include <inttypes.h>
#include <mach/mach_time.h>
int main ( ) {
uint64_t clockTicksSinceSystemBoot = mach_absolute_time();
printf("Clock ticks since system boot: %"PRIu64"\n",
clockTicksSinceSystemBoot
);
static mach_timebase_info_data_t timebase;
mach_timebase_info(&timebase);
// Cast to double is required to make this a floating point devision,
// otherwise it would be an interger division and only the result would
// be converted to floating point!
double clockTicksToNanosecons = (double)timebase.numer / timebase.denom;
uint64_t systemUptimeNanoseconds = (uint64_t)(
clockTicksToNanosecons * clockTicksSinceSystemBoot
);
uint64_t systemUptimeSeconds = systemUptimeNanoseconds / (1000 * 1000 * 1000);
printf("System uptime: %"PRIu64" seconds\n", systemUptimeSeconds);
}
特定のマッハ時間に達するまでスレッドをスリープ状態にすることもできます。そのためのコードは次のとおりです。
// Sleep for 750 ns
uint64_t machTimeNow = mach_absolute_time();
uint64_t clockTicksToSleep = (uint64_t)(750 / clockTicksToNanosecons);
uint64_t machTimeIn750ns = machTimeNow + clockTicksToSleep;
mach_wait_until(machTimeIn750ns);
マッハタイムは実時間とは関係がないため、システムの日付と時刻の設定を好きなように試すことができます。マッハタイムには影響しません。
ただし、特定のユースケースにマッハタイムが不適切になる可能性がある特別な考慮事項が1つあります。Tシステムがスリープしている間はCPUクロックが実行されていません!スレッドを5分間待機させ、1分後にシステムがスリープ状態になり、30分間スリープ状態を維持した場合、30分のスリープ時間はカウントされないため、システムがウェイクアップした後もスレッドはさらに4分間待機します。その間、CPUクロックも停止していました。しかし、他の場合には、これはまさにあなたがしたいことです。
マッハタイムは、費やした時間を測定するための非常に正確な方法でもあります。そのタスクを示すコードは次のとおりです。
// Measure time
uint64_t machTimeBegin = mach_absolute_time();
sleep(1);
uint64_t machTimeEnd = mach_absolute_time();
uint64_t machTimePassed = machTimeEnd - machTimeBegin;
uint64_t timePassedNS = (uint64_t)(
machTimePassed * clockTicksToNanosecons
);
printf("Thread slept for: %"PRIu64" ns\n", timePassedNS);
スレッドが正確に1秒間スリープしないことがわかります。これは、スレッドをスリープ状態にし、再度ウェイクアップするのに時間がかかり、ウェイクアップしても、すべてのコアがあればすぐにCPU時間を取得できないためです。その時点ですでにスレッドの実行で忙しいです。
MacOS 10.12(Sierra)以降、mach_continuous_time
も存在します。 mach_continuous_time
とmach_absolute_time
の唯一の違いは、システムがスリープ状態のときも継続時間が進むことです。したがって、これがこれまでの問題であり、Mach Time 10.12以降を使用しない理由がこの問題の解決策を提供する場合は、使い方は上記と全く同じです。
また、macOS 10.9(Mavericks)以降、mach_approximate_time
があり、10.12にはmach_continuous_approximate_time
もあります。これら2つはmach_absolute_time
およびmach_continuous_time
と同じですが、唯一の違いは、高速でありながら精度が低いことです。標準関数では、カーネルがMach Timeを処理するため、カーネルを呼び出す必要があります。このような呼び出しは、特に Meltdown 修正が既に行われているシステムでは、いくらかコストがかかります。おおよそのバージョンは、常にカーネルを呼び出す必要はありません。カーネルクロックと時々同期するだけのユーザースペースのクロックを使用して、同期がずれすぎないようにしますが、常に小さな偏差が発生する可能性があるため、「おおよその」マッハ時間にすぎません。