qint64 QElapsedTimer::restart()
QElapsedTimer
は、Qtが提供する高精度な経過時間を計測するためのクラスです。その中のrestart()
メソッドは、以下の2つの重要な機能を持っています。
-
タイマーのリセットと再開:
restart()
を呼び出すと、QElapsedTimer
の内部カウンタが現在の時刻にリセットされ、同時にタイマーが再開されます。これは、ある時点から再度時間を計測し始めたい場合に便利です。 -
前回の経過時間の返却:
restart()
は、qint64
型の値を返します。この値は、restart()
が呼び出される直前までの、タイマーが最後に開始されてからの経過時間(ミリ秒単位) を示します。つまり、前の計測期間がどれくらいだったかを知ることができます。
使用例
#include <QElapsedTimer>
#include <QDebug>
#include <QThread> // QThread::msleepのために必要
int main() {
QElapsedTimer timer;
// タイマーを開始
timer.start();
qDebug() << "タイマー開始";
// 何らかの処理(例: 100ミリ秒待機)
QThread::msleep(100);
// タイマーをリスタートし、経過時間を取得
qint64 elapsed1 = timer.restart();
qDebug() << "最初の処理にかかった時間 (ms):" << elapsed1; // おおよそ100ミリ秒が出力されるはず
// 別の処理(例: 50ミリ秒待機)
QThread::msleep(50);
// 再びリスタートし、経過時間を取得
qint64 elapsed2 = timer.restart();
qDebug() << "二番目の処理にかかった時間 (ms):" << elapsed2; // おおよそ50ミリ秒が出力されるはず
// タイマーがリスタートされたので、現在の計測開始点は直前のrestart()呼び出し時点
qDebug() << "現在のタイマーからの経過時間 (ms):" << timer.elapsed();
return 0;
}
ポイント
start()
との違い:start()
はタイマーを開始するだけですが、restart()
はタイマーをリセットしつつ、リセット前の経過時間を返すという点で異なります。- 返り値の利用:
restart()
の返り値を利用することで、一連の処理の中で各ステップにかかる時間を連続的に計測し、分析することができます。 - 高精度:
QElapsedTimer
は、システムが提供する最も高精度なタイマーを利用するため、ミリ秒以下の精度が必要な場合にも適しています。
QElapsedTimer
は時間の計測に非常に便利ですが、誤った使い方をすると予期せぬ結果を招くことがあります。restart()
メソッドに関連する一般的なエラーとそのトラブルシューティングを以下に示します。
isValid() でタイマーの状態を確認しない
エラー
QElapsedTimer
オブジェクトが有効でない(例えば、start()
が一度も呼び出されていないか、invalidate()
で無効化された)状態でelapsed()
やrestart()
を呼び出すと、未定義の動作 (undefined behavior) を引き起こします。これにより、異常な値が返されたり、クラッシュしたりする可能性があります。
トラブルシューティング
restart()
やelapsed()
を呼び出す前に、必ずisValid()
を使ってタイマーが有効であるかを確認する習慣をつけましょう。
QElapsedTimer timer;
// タイマーはまだ有効ではない
qDebug() << "Is valid before start():" << timer.isValid(); // false
// タイマーを開始
timer.start();
qDebug() << "Is valid after start():" << timer.isValid(); // true
// now it's safe to call restart()
qint64 elapsed = timer.restart();
qDebug() << "Elapsed time:" << elapsed;
timer.invalidate();
qDebug() << "Is valid after invalidate():" << timer.isValid(); // false
// この呼び出しは未定義の動作を引き起こす可能性がある
// qint64 badElapsed = timer.restart();
32ビットシステムでのオーバーフロー(古いQtバージョンや特定の環境)
エラー
非常に長い時間を計測する場合、特に古いQtバージョンや一部の32ビットシステムでは、qint64
が64ビット整数であるにもかかわらず、基盤となるシステムクロックの制限(例: 32ビットカウンタ)により、QElapsedTimer
がオーバーフローして予期せぬ小さな値や負の値を返すことがあります。これは稀なケースですが、特にミリ秒単位で数日以上の非常に長い期間を計測する場合に発生する可能性があります。
トラブルシューティング
- QElapsedTimer::clockType()の確認
使用しているシステムクロックの種類を確認し、その特性を理解しておくことが重要です。QElapsedTimer
はデフォルトで単調増加する(monotonic)クロックを使用しますが、システムによっては非単調なクロックが使用されることもあります。 - 短い間隔でのrestart()
非常に長い期間を一度に計測するのではなく、定期的にrestart()
を呼び出して経過時間を累積する方法を検討します。 - 最新のQtバージョンを使用する
最新のQtバージョンでは、この問題が解決されていることが多いです。
別のAPIとの時間の比較における不一致
エラー
QElapsedTimer
で計測した時間と、他のシステムAPI(例: std::chrono::high_resolution_clock
やOS固有の関数)で計測した時間を直接比較すると、異なる基点や解像度のため、不一致が生じることがあります。
トラブルシューティング
- QElapsedTimer::clockType()を確認
異なるAPI間の比較が必要な場合は、QElapsedTimer::clockType()
を使用して、Qtがどの種類のクロックを使用しているかを把握し、他のAPIが使用しているクロックと比較可能なものかを確認します。 - 同じクロックを使用する
可能な限り、同じ計測API(この場合はQElapsedTimer
)を使用してすべての時間を計測するようにします。
スレッド間の共有
エラー
QElapsedTimer
オブジェクトを複数のスレッドで共有し、同時にstart()
やrestart()
、elapsed()
を呼び出すと、スレッドセーフではないため、競合状態 (race condition) が発生し、予期せぬ結果やクラッシュを引き起こす可能性があります。
トラブルシューティング
- ミューテックスなどでの保護
どうしてもQElapsedTimer
オブジェクトを共有する必要がある場合は、QMutex
などを使用して、start()
やrestart()
、elapsed()
の呼び出しを保護し、一度に一つのスレッドからしかアクセスされないようにします。 - スレッドごとに独立したQElapsedTimerを使用する
各スレッドで独自のQElapsedTimer
インスタンスを作成し、それぞれのスレッド内で管理するようにします。
restart()の返り値を誤解する
エラー
restart()
の返り値が、restart()
呼び出し後の経過時間であると誤解してしまうケースがあります。
トラブルシューティング
- ドキュメントの再確認
restart()
が返すのは、restart()
が呼び出される直前までの、タイマーが最後に開始されてからの経過時間であることを常に覚えておきましょう。新しい計測はrestart()
が呼び出された時点から開始されます。
<!-- end list -->
QElapsedTimer timer;
timer.start();
QThread::msleep(100);
qint64 firstElapsed = timer.restart(); // ここでタイマーはリセットされ、100msが返る
// この時点から新しい計測が始まる
QThread::msleep(50);
qint64 secondElapsed = timer.elapsed(); // restart()後に50ms経過している
qDebug() << "First elapsed:" << firstElapsed; // 約100
qDebug() << "Second elapsed:" << secondElapsed; // 約50
QElapsedTimer::restart()
は、連続する処理の各ステップの時間を計測したり、特定のイベント間の時間を効率的に計測したりするのに非常に役立ちます。いくつかの具体的な使用例を見ていきましょう。
例1: 連続する処理の各ステップの時間を計測する
この例では、複数の異なる処理ブロックがあり、それぞれの処理にどれくらいの時間がかかったかを計測したい場合を想定します。restart()
を使用することで、各ステップの開始時にタイマーをリセットし、同時に前のステップの時間を取得できます。
#include <QCoreApplication>
#include <QElapsedTimer>
#include <QDebug>
#include <QThread> // QThread::msleepのために必要
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QElapsedTimer timer;
// --- ステップ1: 初期化処理 ---
qDebug() << "--- ステップ1: 初期化処理 ---";
timer.start(); // タイマーを開始
QThread::msleep(150); // 150ミリ秒の処理をシミュレート
qDebug() << "初期化処理完了。";
// ここでrestart()を呼び出すことで、
// 1. タイマーが現在の時刻にリセット(次のステップの計測開始)
// 2. 前のステップ(初期化処理)の経過時間(150ms)が返される
qint64 elapsedStep1 = timer.restart();
qDebug() << "ステップ1(初期化処理)にかかった時間:" << elapsedStep1 << "ms";
// --- ステップ2: データ読み込み処理 ---
qDebug() << "\n--- ステップ2: データ読み込み処理 ---";
QThread::msleep(300); // 300ミリ秒の処理をシミュレート
qDebug() << "データ読み込み完了。";
// 再びrestart()を呼び出し、ステップ2の時間を取得し、次のステップの準備をする
qint64 elapsedStep2 = timer.restart();
qDebug() << "ステップ2(データ読み込み処理)にかかった時間:" << elapsedStep2 << "ms";
// --- ステップ3: 計算処理 ---
qDebug() << "\n--- ステップ3: 計算処理 ---";
QThread::msleep(200); // 200ミリ秒の処理をシミュレート
qDebug() << "計算処理完了。";
// 最後のステップなので、restart()の代わりにelapsed()を使用することもできる
// または、次の何らかの処理のためにrestart()を使っても良い
qint64 elapsedStep3 = timer.elapsed(); // restart()ではないが、参考のため
qDebug() << "ステップ3(計算処理)にかかった時間:" << elapsedStep3 << "ms";
qDebug() << "\n全ての処理が完了しました。";
return 0;
}
出力例
--- ステップ1: 初期化処理 ---
初期化処理完了。
ステップ1(初期化処理)にかかった時間: 150 ms
--- ステップ2: データ読み込み処理 ---
データ読み込み完了。
ステップ2(データ読み込み処理)にかかった時間: 300 ms
--- ステップ3: 計算処理 ---
計算処理完了。
ステップ3(計算処理)にかかった時間: 200 ms
全ての処理が完了しました。
この例では、restart()
を各処理ブロックの直後に配置することで、前のブロックの処理時間を正確に取得し、同時に次のブロックの計測を開始しています。
例2: 一定間隔でイベントが発生しているかをチェックするループ
特定のイベントが所定の時間内に発生したかどうかを監視するようなループ処理で、restart()
を利用する例です。
#include <QCoreApplication>
#include <QElapsedTimer>
#include <QDebug>
#include <QThread>
// イベントのシミュレーション関数
bool simulateEvent() {
// 3回に1回の確率でイベントが発生すると仮定
return (qrand() % 3 == 0);
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QElapsedTimer timer;
timer.start(); // タイマー開始
int eventCount = 0;
const int checkIntervalMs = 500; // 500ミリ秒ごとにイベントをチェック
qDebug() << "イベント監視ループ開始...";
for (int i = 0; i < 10; ++i) { // 10回のループをシミュレート
// 処理の合間に少し待機
QThread::msleep(100 + (qrand() % 50)); // ランダムな時間待機
// restart()の返り値が指定したチェック間隔を超えていないか確認
// 超えている場合は、前回のチェックから時間がかかりすぎていることを示す
qint64 elapsedSinceLastCheck = timer.restart(); // ここでタイマーはリセットされる
qDebug() << "前回チェックからの経過時間:" << elapsedSinceLastCheck << "ms";
if (elapsedSinceLastCheck > checkIntervalMs) {
qWarning() << "警告: チェック間隔 (" << checkIntervalMs << "ms) を"
<< (elapsedSinceLastCheck - checkIntervalMs) << "ms超過しました!";
}
// イベントが発生したかチェック
if (simulateEvent()) {
qDebug() << "イベント発生!";
eventCount++;
}
}
qDebug() << "イベント監視ループ終了。発生したイベント数:" << eventCount;
return 0;
}
出力例 (実行ごとに異なる場合があります)
イベント監視ループ開始...
前回チェックからの経過時間: 104 ms
前回チェックからの経過時間: 133 ms
警告: チェック間隔 (500ms) を 1ms超過しました!
前回チェックからの経過時間: 501 ms
イベント発生!
前回チェックからの経過時間: 112 ms
前回チェックからの経過時間: 121 ms
イベント発生!
前回チェックからの経過時間: 125 ms
前回チェックからの経過時間: 109 ms
前回チェックからの経過時間: 104 ms
イベント発生!
前回チェックからの経過時間: 110 ms
イベント監視ループ終了。発生したイベント数: 3
この例では、ループの各イテレーションの冒頭でrestart()
を呼び出すことで、前回のrestart()
呼び出しからの正確な経過時間を取得し、それが期待されるcheckIntervalMs
を超えていないかを監視しています。
例3: 関数呼び出しのベンチマーク
ある関数の実行時間を複数回計測し、その平均を算出するようなベンチマーク処理です。
#include <QCoreApplication>
#include <QElapsedTimer>
#include <QDebug>
#include <QVector>
// ベンチマーク対象のダミー関数
void heavyComputation() {
// 複雑な計算をシミュレート
long long sum = 0;
for (int i = 0; i < 1000000; ++i) {
sum += i;
}
// 計算結果が最適化で消されないようにダミーで使用
if (sum < 0) {
qDebug() << "This should not happen.";
}
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QElapsedTimer timer;
const int numIterations = 10;
QVector<qint64> durations;
qDebug() << "heavyComputation関数のベンチマークを開始します。";
for (int i = 0; i < numIterations; ++i) {
timer.start(); // 各イテレーションの開始時にタイマーをスタート
heavyComputation(); // ベンチマーク対象の関数呼び出し
// 関数実行後、restart()を呼び出し、そのイテレーションの時間を取得し、タイマーを次のためにリセット
// この場合は次のループのstart()でリセットされるので、elapsed()でも良いが、
// 連続した処理で次の処理がすぐに続く場合はrestart()が有効
qint64 duration = timer.elapsed(); // この場合はelapsed()でもOK
durations.append(duration);
qDebug() << QString("Iteration %1: %2 ms").arg(i + 1).arg(duration);
// 次のイテレーションの準備のため、厳密にはここでrestart()を呼ぶのが適切
// timer.restart(); // 今回はループのstart()でカバーされるので省略
}
qint64 totalDuration = 0;
for (qint64 d : durations) {
totalDuration += d;
}
if (numIterations > 0) {
qDebug() << QString("平均実行時間: %1 ms").arg(static_cast<double>(totalDuration) / numIterations, 0, 'f', 2);
}
return 0;
}
出力例
heavyComputation関数のベンチマークを開始します。
Iteration 1: 1 ms
Iteration 2: 1 ms
Iteration 3: 1 ms
Iteration 4: 1 ms
Iteration 5: 1 ms
Iteration 6: 1 ms
Iteration 7: 1 ms
Iteration 8: 1 ms
Iteration 9: 1 ms
Iteration 10: 1 ms
平均実行時間: 1.00 ms
この例では、ループ内で毎回timer.start()
を呼び出していますが、もしheavyComputation()
の後にすぐに別の処理が続き、その処理時間も計測したい場合は、heavyComputation()
の直後にtimer.restart()
を使うことで、よりスムーズに次の計測を開始できます。今回の例のように独立した関数の場合はelapsed()
でも十分です。
QElapsedTimer::restart()
は非常に便利ですが、特定のシナリオや要件によっては、他の時間計測方法がより適切であったり、あるいは代替として機能したりすることがあります。主な代替方法をいくつかご紹介します。
QElapsedTimer::start() と QElapsedTimer::elapsed() の組み合わせ
timer.elapsed()
: タイマーが最後に開始されてからの経過時間(ミリ秒)を返します。タイマーはリセットされません。timer.start()
: タイマーを開始またはリセットします。返り値はありません。
restart()の動作を模倣するコード
QElapsedTimer timer;
timer.start(); // 最初の計測開始
// ... 最初の処理 ...
QThread::msleep(100);
// restart()の代替
qint64 elapsedBeforeRestart = timer.elapsed(); // 経過時間を取得
timer.start(); // タイマーをリセットして再開
qDebug() << "経過時間 (start/elapsed):" << elapsedBeforeRestart << "ms";
// ... 次の処理 ...
QThread::msleep(50);
利点
restart()
の動作が直感的でないと感じる場合、より明示的に「経過時間を取得し、その後タイマーを再開する」という意図をコードで表現できます。
欠点
elapsed()
とstart()
の間に非常に短いながらも時間差が生じる可能性がありますが、ほとんどの用途では無視できるレベルです。- 2つの関数呼び出しが必要になるため、
restart()
よりも少しコード量が増えます。
QDateTime::currentMSecsSinceEpoch()
このメソッドは、エポック(1970年1月1日00:00:00 UTC)からの経過ミリ秒数を返します。これを使って、開始時と終了時のタイムスタンプを取得し、その差を計算することで経過時間を計測できます。
#include <QDateTime>
#include <QDebug>
#include <QThread>
qint64 startTime;
// 計測開始
startTime = QDateTime::currentMSecsSinceEpoch();
// ... 処理 ...
QThread::msleep(200);
// 計測終了と経過時間の計算
qint64 endTime = QDateTime::currentMSecsSinceEpoch();
qint64 elapsed = endTime - startTime;
qDebug() << "経過時間 (QDateTime):" << elapsed << "ms";
// 次の計測のために、再度startTimeを更新
startTime = QDateTime::currentMSecsSinceEpoch(); // restart()に相当
利点
- タイマーオブジェクトの管理が不要で、純粋なタイムスタンプの差分計算なので、コードがシンプルになる場合があります。
- Qtの他の日付/時刻関連機能と連携しやすい。
欠点
- ミリ秒単位より細かい精度は得られません。
- 単調性がない
システム時刻が逆行すると、計算結果が負になることがあります。 - 精度が低い可能性
システムの時計に依存するため、QElapsedTimer
が利用する高精度な単調増加クロック(monotonic clock)とは異なり、システム時刻の変更(NTP同期など)に影響される可能性があります。そのため、厳密なパフォーマンス計測には向いていません。
C++標準ライブラリ std::chrono
C++11以降で導入されたstd::chrono
は、プラットフォームに依存しない高精度な時間計測を提供します。QElapsedTimer
と同等かそれ以上の精度を必要とする場合、またはQtに依存しないクロスプラットフォームなコードを書きたい場合に非常に強力な選択肢です。
通常、std::chrono::high_resolution_clock
またはstd::chrono::steady_clock
を使用します。steady_clock
は単調増加が保証されており、システム時刻の変更に影響されません。
#include <chrono>
#include <iostream> // qDebugの代わりにcoutを使用
#include <thread> // std::this_thread::sleep_forのために必要
// 型エイリアスでコードを読みやすくする
using Clock = std::chrono::steady_clock;
using TimePoint = Clock::time_point;
using Milliseconds = std::chrono::milliseconds;
TimePoint startTimeChrono;
// 計測開始
startTimeChrono = Clock::now();
// ... 処理 ...
std::this_thread::sleep_for(Milliseconds(150));
// 計測終了と経過時間の計算
TimePoint endTimeChrono = Clock::now();
Milliseconds elapsedChrono = std::chrono::duration_cast<Milliseconds>(endTimeChrono - startTimeChrono);
std::cout << "経過時間 (std::chrono): " << elapsedChrono.count() << "ms" << std::endl;
// restart()に相当する操作: 現在の時刻でstartTimeChronoを更新
startTimeChrono = Clock::now();
利点
- ナノ秒単位などの非常に細かい精度で計測できます。
- 標準C++
Qtに依存しないため、Qtプロジェクト以外でも再利用可能です。 - 高精度かつ単調性
QElapsedTimer
と同様に、システム時刻の変更に影響されない高精度な単調増加クロックを使用できます。
duration_cast
による単位変換を明示的に行う必要があります。- Qtの
qDebug()
のような便利なデバッグ出力機能とは直接連携しません(std::cout
などを使用する必要があります)。
どの代替方法を選ぶべきか?
std::chrono
:- Qtに依存しないコードベースを目指す場合。
- 非常に高い精度と単調性が絶対に必要で、C++標準ライブラリの利用に抵抗がない場合。
- ナノ秒レベルの計測が必要な場合。
QDateTime::currentMSecsSinceEpoch()
: 厳密なパフォーマンス計測が不要で、大まかな時間計測で十分な場合。システム時刻の変更に敏感な処理には向きません。QElapsedTimer::start()
+elapsed()
:restart()
の動作を明示的に表現したい場合や、コードの可読性を重視する場合。Qtアプリケーション内で最も自然な選択肢の一つです。