如何测量函数执行所需的时间

1722

我需要以毫秒为单位获得执行时间。

我最初在2008年提出了这个问题。当时被接受的答案是使用 new Date().getTime()。然而,现在我们都同意使用标准的performance.now() API更加合适。因此,我将接受的答案更改为这个答案。


5
通常,说明您在执行时间方面想要实现的目标可能比仅回答问题更有用。现在,使用Firebug或Chrome Dev工具中的Profiling通常是查找占用CPU资源的代码的更好方法。 - oligofren
16
performance.now() 在 Node 中无法使用。你可以使用 new Date().getTime() 来代替,在 Node 中它能正常工作。 - Ryan Walker
2
@RyanWalker或者更简单的Date.now(),在node中也可以工作。 - f278f1b2
2
@oligofren - 有时候你可能想要捕获这些数据。我有一个情况,我正在将它写入indexedDB。 - ThomasRones
1
我想知道是否有最新的文档(例如在2020/201)表明在Node中performance.now()比Date.now()更好? - Qiulang
对于一个可以用于函数的一行代码,使它们在每次运行时记录其执行时间,请查看下面的答案 https://dev59.com/3XRC5IYBdhLWcg3wYf8p#52286301。它将计时逻辑封装到一个装饰器中,以便根据需要将其“包装”到函数中。 - aljgom
30个回答

3

在函数前使用console.time('这里加标签名'),在函数后使用console.timeEnd('这里加标签名'),这将会给你提供函数的运行时间。


我点了赞,但这只对非常长/慢的函数有用 :) - KECG

2
最好的方法是使用“性能挂钩”模块。虽然不稳定,但可以标记代码的特定区域并测量标记区域之间的持续时间。
const { performance, PerformanceObserver } = require('perf_hooks');

const measures = []

const obs = new PerformanceObserver(list => measures.push(...list.getEntries()));
obs.observe({ entryTypes: ['measure'] });
const getEntriesByType = cb => cb(measures);

const doSomething = val => {
  performance.mark('beginning of the process');

  val *= 2;

  performance.mark('after multiplication');

  performance.measure('time taken', 'beginning of the process', 'after multiplication');

  getEntriesByType(entries => {
    entries.forEach(entry => console.log(entry));
  })

  return val;
}

doSomething(4);

在这里尝试此处

最初的回答

2

带有累积周期的秒表

与服务器和客户端(Node或DOM)一起工作,使用Performance API。 当您有许多小周期时很好用,例如在被调用1000次的函数中处理1000个数据对象,但您想要查看此函数中每个操作如何相加到总和。

因此,这个使用一个模块全局(单例)定时器。与类单例模式相同,只是使用起来稍微简单一些,但您需要将其放入单独的文件中,例如stopwatch.js

最初的回答

const perf = typeof performance !== "undefined" ? performance : require('perf_hooks').performance;
const DIGITS = 2;

let _timers = {};

const _log = (label, delta?) => {
    if (_timers[label]) {
        console.log(`${label}: ` + (delta ? `${delta.toFixed(DIGITS)} ms last, ` : '') +
            `${_timers[label].total.toFixed(DIGITS)} ms total, ${_timers[label].cycles} cycles`);
    }
};

export const Stopwatch = {
    start(label) {
        const now = perf.now();
        if (_timers[label]) {
            if (!_timers[label].started) {
                _timers[label].started = now;
            }
        } else {
            _timers[label] = {
                started: now,
                total: 0,
                cycles: 0
            };
        }
    },
    /** Returns total elapsed milliseconds, or null if stopwatch doesn't exist. */
    stop(label, log = false) {
        const now = perf.now();
        if (_timers[label]) {
            let delta;
            if(_timers[label].started) {
                delta = now - _timers[label].started;
                _timers[label].started = null;
                _timers[label].total += delta;
                _timers[label].cycles++;
            }
            log && _log(label, delta);
            return _timers[label].total;
        } else {
            return null;
        }
    },
    /** Logs total time */
    log: _log,
    delete(label) {
        delete _timers[label];
    }
};

1
在我的情况下,我更喜欢使用@语法糖,并与babel一起编译。
这种方法的问题在于函数必须在对象内部。

示例JS代码

function timer() {
    return (target, propertyKey, descriptor) => {
        const start = Date.now();
        let oldFunc = descriptor.value;

        descriptor.value = async function (){
            var result = await oldFunc.apply(this, arguments);
            console.log(Date.now() - start);
            return result;
        }
    }
}

// Util function 
function delay(timeout) {
    return new Promise((resolve) => setTimeout(() => {
        resolve();
    }, timeout));
}

class Test {
    @timer()
    async test(timout) {
        await delay(timout)
        console.log("delay 1");
        await delay(timout)
        console.log("delay 2");
    }
}

const t = new Test();
t.test(1000)
t.test(100)

.babelrc (适用于 Babel 6)

 {
    "plugins": [
        "transform-decorators-legacy"
    ]
 }

1
export default class Singleton {

  static myInstance: Singleton = null;

  _timers: any = {};

  /**
   * @returns {Singleton}
   */
  static getInstance() {
    if (Singleton.myInstance == null) {
      Singleton.myInstance = new Singleton();
    }

    return this.myInstance;
  }

  initTime(label: string) {
    this._timers[label] = Date.now();
    return this._timers[label];
  }

  endTime(label: string) {
    const endTime = Date.now();
    if (this._timers[label]) {
      const delta = endTime - this._timers[label];
      const finalTime = `${label}: ${delta}ms`;
      delete this._timers[label];
      return finalTime;
    } else {
      return null;
    }
  }
}

InitTimestring相关。

return Singleton.getInstance().initTime(label); // 返回初始化时间

return Singleton.getInstance().endTime(label); // 返回初始化和结束之间的总时间


1

注意:这是最简单的纯函数式ES6实现方法,不需要额外的变量,只需三行代码。处理同步和异步代码,因此不需要外部库,在JavaScript和Node JS中都可以使用,甚至可以用来测试API的延迟

// Create one-liner timer function
let [timer, timingMonitor] = [0, () => timer = !timer ? Date.now() : `${Date.now() - timer}ms`]
        
// Initiate timer
timingMonitor();
        
// Your code here
doSomething();
        
// End timer 
console.log(timingMonitor());

// Console output: "102ms", for example 

1
  1. 要启动计时器,请使用console.time("myTimer");
  2. 可选:要打印经过的时间,请使用console.timeLog("myTimer");
  3. 最后,要停止计时器并打印最终时间,请使用console.timeEnd("myTimer");

您可以在MDNNode.js documentation中了解更多信息。

适用于Chrome、Firefox、Opera和NodeJS。(不适用于Edge或Internet Explorer)。


0

基本的TypeScript示例,支持标记。调用start('something')将开始计时,并且stop('something')将结束计时并返回包含经过时间的格式化字符串。

查看Flems示例

/**
 * Mark entries
 */
export const marks: { [id: string]: number } = {};

/**
 * Start timing 
 */
export const start = (id: string) => {
  return Object.assign(marks, {[id]: Date.now() })[id]
}

/**
 * Clear all 
 */
export const clear = () => {
  for (const id in marks) delete marks[id]; 
};

/**
 * Stop timing and return formatted elapsed time
 */
export const stop = (id: string) => {
  const ms = Date.now() - marks[id];
  delete marks[id];
  return ms > 1000 
    ? `${(ms / 1000).toFixed(0)}s ${+ms.toFixed(0).slice(1)}ms` 
    : `${ms.toFixed(0)}ms`;
};


示例代码正在导出每个函数。您可以将其放入项目中,并从默认的as导入中相应地调用方法,例如:
import * as time from './timer.js'

time.start('foo')

// do something

console.log('elapsed time: ' + time.stop('bar'))

-3

如先前所述,请检查并使用内置计时器。但如果您想要或需要编写自己的计时器,这是我的建议:

//=-=|Source|=-=//
/**
 * JavaScript Timer Object
 *
 *      var now=timer['elapsed'](); 
 *      timer['stop']();
 *      timer['start']();
 *      timer['reset']();
 * 
 * @expose
 * @method timer
 * @return {number}
 */
timer=function(){
    var a=Date.now();
    b=0;
    return{
        /** @expose */
        elapsed:function(){return b=Date.now()-a},
        start:function(){return a=Date.now()},
        stop:function(){return Date.now()},
        reset:function(){return a=0}
    }
}();

//=-=|Google Advanced Optimized|=-=//
timer=function(){var a=Date.now();b=0;return{a:function(){return b=Date.now()-a},start:function(){return a=Date.now()},stop:function(){return Date.now()},reset:function(){return a=0}}}();

编译成功!

  • 原始大小:219字节(压缩后405字节)
  • 编译后大小:109字节(压缩后187字节)
  • 压缩后大小减少了50.23%(未使用gzip时为53.83%)

-7

被接受的答案是错误的

由于JavaScript是异步的,所以被接受的答案中变量end的值将会是错误的。

var start = new Date().getTime();

for (i = 0; i < 50000; ++i) {
// JavaScript is not waiting until the for is finished !!
}

var end = new Date().getTime();
var time = end - start;
alert('Execution time: ' + time); 

for循环的执行可能非常快,因此您可能无法看到结果是错误的。您可以使用一些请求代码进行测试:

var start = new Date().getTime();

for (i = 0; i < 50000; ++i) {
  $.ajax({
    url: 'www.oneOfYourWebsites.com',
    success: function(){
       console.log("success");
    }
  });
}

var end = new Date().getTime();
var time = end - start;
alert('Execution time: ' + time); 

因此,警报会非常快速地提示,但在控制台中,您将看到Ajax请求正在继续。

以下是您应该如何做: https://developer.mozilla.org/en-US/docs/Web/API/Performance.now


10
不是因为for循环。for循环将等待最后一个循环才会继续执行您的源代码。AJAX调用是异步的。还有其他运行异步的函数。但for循环不是异步执行的。 - Scriptlabs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接