什么是微模板标记?

我听说过这个术语,但我不完全确定它是什么意思,所以:

  • 这是什么意思,不意味着什么?
  • 什么是IS和IS不是微模板标记的一些例子?
  • 微板凳标记有哪些危险,如何避免它?
    • (还是一件好事?

答案 1

它的意思是它在锡罐上所说的 - 它正在测量“小”东西的性能,比如对操作系统内核的系统调用。

危险在于,人们可能会使用他们从微基准标记中获得的任何结果来指示优化。众所周知:

我们应该忘记效率低下,比如说大约97%的时间:过早的优化是万恶之源“ - 唐纳德·高德纳

可能有许多因素会扭曲微基准标记的结果。编译器优化就是其中之一。如果被测操作花费的时间太短,以至于无论您用来测量什么,它都需要比实际操作本身更长的时间,那么您的微基准也会被扭曲。

例如,有人可能会对循环的开销进行微基准标记:for

void TestForLoop()
{
    time start = GetTime();

    for(int i = 0; i < 1000000000; ++i)
    {
    }

    time elapsed = GetTime() - start;
    time elapsedPerIteration = elapsed / 1000000000;
    printf("Time elapsed for each iteration: %d\n", elapsedPerIteration);
}

显然,编译器可以看到循环绝对不执行任何操作,并且根本不为循环生成任何代码。因此,和 的价值几乎是无用的。elapsedelapsedPerIteration

即使循环执行某些操作:

void TestForLoop()
{
    int sum = 0;
    time start = GetTime();

    for(int i = 0; i < 1000000000; ++i)
    {
        ++sum;
    }

    time elapsed = GetTime() - start;
    time elapsedPerIteration = elapsed / 1000000000;
    printf("Time elapsed for each iteration: %d\n", elapsedPerIteration);
}

编译器可能会看到变量不会用于任何事情,并优化它,并优化for循环。但是等等!如果我们这样做会怎样:sum

void TestForLoop()
{
    int sum = 0;
    time start = GetTime();

    for(int i = 0; i < 1000000000; ++i)
    {
        ++sum;
    }

    time elapsed = GetTime() - start;
    time elapsedPerIteration = elapsed / 1000000000;
    printf("Time elapsed for each iteration: %d\n", elapsedPerIteration);
    printf("Sum: %d\n", sum); // Added
}

编译器可能足够聪明,意识到这将始终是一个常量值,并优化所有这些值。如今,许多人会对编译器的优化功能感到惊讶。sum

但是编译器无法优化的东西呢?

void TestFileOpenPerformance()
{
    FILE* file = NULL;
    time start = GetTime();

    for(int i = 0; i < 1000000000; ++i)
    {
        file = fopen("testfile.dat");
        fclose(file);
    }

    time elapsed = GetTime() - start;
    time elapsedPerIteration = elapsed / 1000000000;
    printf("Time elapsed for each file open: %d\n", elapsedPerIteration);
}

即使这不是一个有用的测试!操作系统可能会看到文件被非常频繁地打开,因此它可能会将其预加载到内存中以提高性能。几乎所有的操作系统都这样做。当您打开应用程序时,也会发生同样的事情 - 操作系统可能会找出您打开最多的前5个应用程序,并在启动计算机时将应用程序代码预加载到内存中!

事实上,有无数的变量在起作用:引用的局部性(例如数组与链接列表),缓存和内存带宽的影响,编译器内联,编译器实现,编译器开关,处理器内核数,处理器级别的优化,操作系统调度程序,操作系统后台进程等。

因此,在很多情况下,微基准标记并不是一个有用的指标。它绝对不会用定义良好的测试用例(分析)取代整个程序基准测试。首先编写可读的代码,然后分析以查看需要执行的操作(如果有)。

我想强调的是,微弯道本身并不是邪恶的,但人们必须谨慎使用它们(对于与计算机有关的许多其他事情都是如此)。


答案 2

没有微观基准测试的定义,但是当我使用它时,我的意思是一个小型的人为基准测试,旨在测试某些特定硬件1或语言功能的性能。相比之下,更好的基准测试是旨在执行实际任务的真实程序。(IMO,在这两种情况之间划一条强硬的界线是没有意义的,我不会尝试。

微基准测试的危险在于,很容易编写一个给出完全误导性结果的基准测试。Java微基准测试中的一些常见陷阱是:

  • 编写编译器可以推断出的代码没有用处,因此完全优化,
  • 没有考虑到Java内存管理的“块状”性质,以及
  • 不考虑JVM启动效应;例如,加载和JIT编译类所需的时间,以及(相反)在对方法进行JIT编译后发生的执行加速。

但是,即使您已经解决了上述问题,也存在无法解决的基准测试系统性问题。基准测试的代码和行为通常与您真正关心的内容关系不大。即您的应用程序将如何执行。有太多的“隐藏变量”,你无法从基准推广到典型程序,更不用说推广到你的程序了。

出于这些原因,我们经常建议人们不要浪费时间在微观基准测试上。相反,最好编写简单而自然的代码,并使用探查器来确定需要手动优化的区域。有趣的是,通常事实证明,实际应用程序中最重要的性能问题是由于数据结构和算法(包括网络,数据库和线程相关的瓶颈)的不良设计,而不是典型的微基准测试试图测试的那种东西。

@BalusC在热点常见问题解答页面中提供了指向此主题材料的绝佳链接。这是Brian Goetz的IBM白皮书的链接。


1 - 专家甚至不会尝试在Java中进行硬件基准测试。字节码和硬件之间发生了太多的“复杂事情”,无法从原始结果中得出关于硬件的有效/有用的结论。您最好使用更接近硬件的语言;例如,C甚至汇编代码。


推荐