JavaScript Tips | 基本・共通ユーティリティ:汎用 – 実行時間計測

JavaScript JavaScript
スポンサーリンク

なぜ「実行時間計測」ユーティリティが業務で効いてくるのか

業務コードを書いていると、
「この処理、なんか重くない?」
「どこがボトルネックなのか分からない」
という場面が必ず出てきます。

ここで大事なのは、
「体感」ではなく「数字」で話せるようにすることです。

この関数は平均 3ms
この API 呼び出しは 250ms
このループは 10,000 件で 120ms

こうやって「実行時間を測る」ことで、
どこを最適化すべきか、どこは放っておいていいか、冷静に判断できるようになります。

そのための小さな道具が「実行時間計測ユーティリティ」です。


いちばん基本の計測:Date.now を使った前後差分

まずは「前後で時刻を取る」だけでいい

一番シンプルな計測は、こうです。

const start = Date.now();

// 計測したい処理
doSomething();

const end = Date.now();
const elapsedMs = end - start;

console.log(`doSomething の実行時間: ${elapsedMs}ms`);
JavaScript

やっていることはとても単純で、

開始前の時刻をミリ秒で取る
終了後の時刻をミリ秒で取る
差を取る

これだけです。

初心者のうちは、まずこの形を「手で書ける」ようになっておけば十分です。
ただし、毎回これを書くのは面倒なので、次のステップとしてユーティリティ化していきます。


同じパターンを何度も書かないための measure 関数

同期処理を計測するユーティリティ

まずは「普通の関数(同期処理)」を計測するユーティリティから。

function measure(fn) {
  const start = Date.now();
  const result = fn();
  const end = Date.now();

  const elapsedMs = end - start;

  return { result, elapsedMs };
}
JavaScript

使い方はこうなります。

function heavyTask() {
  let sum = 0;
  for (let i = 0; i < 1_000_000; i++) {
    sum += i;
  }
  return sum;
}

const { result, elapsedMs } = measure(heavyTask);

console.log("結果:", result);
console.log("実行時間:", elapsedMs, "ms");
JavaScript

ここでの重要ポイントは二つです。

処理の中身(heavyTask)は一切いじらなくていい
「結果」と「実行時間」をセットで返してくれる

つまり、「計測したい処理」をそのまま関数として渡すだけで、
実行時間付きの結果が手に入るようになります。


非同期処理(async / Promise)の実行時間を測る

async 関数用の measureAsync

API 呼び出しなど、業務では非同期処理の計測も必須です。
そのためのユーティリティを用意します。

async function measureAsync(fn) {
  const start = Date.now();
  const result = await fn();
  const end = Date.now();

  const elapsedMs = end - start;

  return { result, elapsedMs };
}
JavaScript

使い方の例です。

async function fetchUser() {
  const res = await fetch("/api/user");
  return res.json();
}

async function main() {
  const { result: user, elapsedMs } = await measureAsync(fetchUser);

  console.log("ユーザー:", user);
  console.log("API 実行時間:", elapsedMs, "ms");
}

main();
JavaScript

ここでの深掘りポイントは、

await fn() の前後で時刻を取っている
非同期処理が終わるまでちゃんと待ってから時間を計測している

ということです。

「Promise を返す処理」を計測したいときは、
必ずこの「measureAsync 版」を使う、という癖をつけておくと混乱しません。


より精度の高い計測:performance.now

Date.now との違いを押さえる

ブラウザ環境では、performance.now() という API があります。

const t1 = performance.now();
// 処理
const t2 = performance.now();

console.log(t2 - t1, "ms");
JavaScript

Date.now() との違いは、

Date.now() は「現在時刻」(1970年からのミリ秒)
performance.now() は「ページ読み込みからの経過時間」(小数点以下までの高精度)

という点です。

細かい差ですが、
「短い処理の計測」「パフォーマンスチューニング」では performance.now() の方が向いています。

同じ measure を performance.now で書き直すとこうなります。

function measureHighRes(fn) {
  const start = performance.now();
  const result = fn();
  const end = performance.now();

  const elapsedMs = end - start;

  return { result, elapsedMs };
}
JavaScript

「まずは Date.now で十分」「本気でチューニングするときに performance.now を使う」
くらいの感覚で覚えておけば OK です。


ログ出力まで含めた「計測+ログ」ユーティリティ

名前付きで計測すると後から見やすい

実務では、「どの処理を測ったのか」がログから分かることが大事です。
そこで、処理名を受け取るユーティリティにしておくと便利です。

function measureWithLog(label, fn) {
  const start = Date.now();
  const result = fn();
  const end = Date.now();

  const elapsedMs = end - start;

  console.log(`[measure] ${label}: ${elapsedMs}ms`);

  return result;
}
JavaScript

使い方はこうです。

function calc() {
  let x = 0;
  for (let i = 0; i < 500_000; i++) {
    x += i;
  }
  return x;
}

const value = measureWithLog("calc 処理", calc);
JavaScript

ログにはこう出ます。

[measure] calc 処理: 37ms

これだけで、「どの処理がどのくらい時間を食っているか」を
ざっくり把握するのがかなり楽になります。

非同期版も同じノリで書けます。

async function measureAsyncWithLog(label, fn) {
  const start = Date.now();
  const result = await fn();
  const end = Date.now();

  const elapsedMs = end - start;

  console.log(`[measure] ${label}: ${elapsedMs}ms`);

  return result;
}
JavaScript

実務での具体的な利用イメージ

API 呼び出しの「体感」と「実測」を揃える

「この画面、なんか遅いんだよね」というとき、
どの API がどのくらい遅いのかを測るのはとても有効です。

async function loadPage() {
  const user = await measureAsyncWithLog("ユーザー取得", () =>
    fetch("/api/user").then((r) => r.json())
  );

  const orders = await measureAsyncWithLog("注文一覧取得", () =>
    fetch("/api/orders").then((r) => r.json())
  );

  render({ user, orders });
}
JavaScript

ログを見れば、

ユーザー取得: 80ms
注文一覧取得: 450ms

のように、「どこが重いか」が一目で分かります。

重いループや処理のボトルネック探し

例えば、「この集計処理が重い」と感じたとき、
処理の中を細かく分けて計測することもできます。

function aggregate(data) {
  const step1 = measureWithLog("フィルタリング", () => {
    return data.filter((x) => x.active);
  });

  const step2 = measureWithLog("ソート", () => {
    return [...step1].sort((a, b) => a.value - b.value);
  });

  const result = measureWithLog("集計", () => {
    return step2.reduce((sum, item) => sum + item.value, 0);
  });

  return result;
}
JavaScript

これで、「フィルタリングが重いのか」「ソートが重いのか」「集計が重いのか」が数字で見えます。
最適化の優先順位がはっきりします。


小さな練習で感覚をつかむ

次のようなことを自分で試してみると、実行時間計測の感覚がつかみやすいです。

同じ処理を「1000 件」「1 万件」「10 万件」で実行して、
それぞれの実行時間を measure で測ってみる
配列の forforEachmap などで同じ処理を書いて、
どのくらい時間が違うかを比べてみる

ここで大事なのは、
「速い/遅いを感覚で語らない」
「必ず数字で確認する」

という習慣をつけることです。

実行時間計測ユーティリティは、その習慣を支えるための小さな道具です。
一度自分のプロジェクトに組み込んでしまえば、
「なんか遅いな」と感じたときに、すぐに数字を取りに行けるようになります。

タイトルとURLをコピーしました