我听说过使用过这个术语,但是我不确定它的含义,因此:
- 它是什么意思,不是什么意思?
- 什么是IS和IS N'T微基准测试的一些示例?
- 微基准测试有哪些危险,如何避免?
- (或者这是好事吗?)
我听说过使用过这个术语,但是我不确定它的含义,因此:
Answers:
它的意思恰恰是它在锡罐上所说的-它正在衡量“小”东西的性能,例如对操作系统内核的系统调用。
危险在于人们可能会使用从微基准测试中获得的任何结果来指示优化。众所周知:
我们应该忘掉效率低下的问题,比如说大约有97%的时间:过早的优化是万恶之源” –唐纳德·努斯(Donald Knuth)
可能有许多因素会扭曲微基准测试的结果。编译器优化就是其中之一。如果要测量的操作花费的时间很少,以至于您用来测量的时间要比实际操作本身花费的时间更长,那么您的微基准测试也会出现偏差。
例如,有人可能会对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);
}
显然,编译器可以看到该循环绝对不执行任何操作,并且根本不会为该循环生成任何代码。因此,价值elapsed
和elapsedPerIteration
是几乎无用。
即使循环执行了一些操作:
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);
}
编译器可能会看到该变量sum
将不会用于任何东西,并且对其进行了优化,并且也优化了for循环。可是等等!如果我们这样做:
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个应用程序,并在启动计算机时将应用程序代码预加载到内存中!
实际上,有无数变量在起作用:引用的局部性(例如数组与链表),缓存和内存带宽的影响,编译器内联,编译器实现,编译器切换,处理器核心数量,处理器级别的优化,操作系统调度程序,操作系统后台进程等。
因此,在许多情况下,微基准测试并不是完全有用的指标。它绝对不能用定义良好的测试用例(分析)代替整个程序的基准测试。首先编写可读代码,然后进行概要分析以查看需要完成的操作(如果有)。
我想强调一点,微基准本身并不是邪恶的,但人们必须谨慎使用它们(对于与计算机相关的许多其他事情,这是正确的)
Time elapsed for <whatever>
,我想这是我们正在测量的更准确的术语。但是使用微基准测试,您所测量的可能与实际代码本身无关!
没有微基准测试的定义,但是当我使用微基准测试时,它的意思是一个小的人造基准测试,旨在测试某些特定硬件1或语言功能的性能。相反,更好的基准是设计用于执行实际任务的真实程序。(在这两种情况之间划清界限是毫无意义的,IMO,我不会尝试。)
微观基准测试的危险在于,编写基准测试很容易得出完全误导的结果。Java微基准测试中的一些常见陷阱是:
但是,即使您已经解决了上述问题,仍然存在无法解决基准测试的系统性问题。基准测试的代码和行为通常与您真正关心的内容无关。即您的应用程序将如何执行。您有太多“隐藏变量”,无法从基准测试推广到典型程序,更不用说您的程序了。
由于这些原因,我们定期建议人们不要浪费时间使用微基准测试。相反,最好编写简单自然的代码,并使用分析器来识别需要手动优化的区域。有趣的是,通常会发现,实际应用中最重要的性能问题是由于数据结构和算法(包括网络,数据库和与线程相关的瓶颈)的不良设计所致,而不是典型的微基准所试图解决的问题。测试。
@BalusC在热点常见问题解答页面上提供了指向该主题资料的出色链接。这是Brian Goetz的IBM白皮书的链接。
1-专家甚至不会尝试使用Java进行硬件基准测试。字节码和硬件之间发生太多“复杂的事情”,无法从原始结果中得出关于硬件的有效/有用的结论。您最好使用更接近硬件的语言。例如C甚至汇编代码。
for()
vs.do{}while()
或if()
vs. x ? y : z
(以及哪个更好)构建循环的差异通常特定于周围的代码,而不是语言构造。
- 它是什么意思,不是什么意思?
我要说的是,微基准测试只是意味着测量一些微小的东西。Tiny可能与上下文有关,但通常在单个系统调用或类似级别上。基准测试是指以上所有内容。
- 什么是IS和IS N'T微基准测试的一些示例?
这篇(归档的)文章列出了测量getpid()系统调用的时间以及使用memcpy()作为微基准测试的示例来测量复制内存的时间的方法。
对算法实现等的任何衡量均不算作微基准测试。特别是结果报告列出了减少执行时间的任务,因此很少将其作为微基准测试。
- 微基准测试有哪些危险,如何避免?
明显的危险是它会诱使开发人员优化程序的错误部分。另一个危险是,精确地测量小物体非常困难。避免这种情况的最简单方法可能只是了解程序中花费最多时间的位置。
人们通常会说“不做微基准测试”,但他们可能的意思是“不要基于微基准测试做出优化决策”。
- (或者这是好事吗?)
它本身并不像这里的其他人那么坏,而且很多网页似乎都在暗示。它有地方。我从事程序重写和运行时方面编织等工作。我们通常会发布所添加指令的微基准,而不是为了指导任何优化,而是确保我们的额外代码几乎不会影响重写程序的执行。
但是,这是一门艺术,特别是在具有JIT,预热时间等的VM的上下文中。此处(归档)描述了一种针对Java的详细描述的方法。
“ Java Performance:The Definitive Guide”(Java性能:权威指南)一书中包含以下有关微基准的定义和示例:
微基准
微基准测试是一种旨在测量非常小的单元性能的测试:调用同步方法与非同步方法所需的时间;创建线程与使用线程池的开销;执行一种算术算法与另一种实现的时间;等等。
微基准测试似乎是一个好主意,但很难正确编写。考虑以下代码,这是尝试编写一个微基准测试,该微基准测试测试了计算第50个斐波那契数的方法的不同实现的性能:
public void doTest(){
double l;
long then = System.currentTimeMillis();
for(int i = 0; i < nLoops; i++){
l = fibImpl1(50);
}
long now = system.currentTimeMillis();
System.out.println("Elapsed time: " + (now - then))
}
...
private double fibImpl1(int n){
if(n < 0) throw new IllegalArgumentException("Must be > 0");
if(n == 0) return 0d;
if(n == 1) return 1d;
double d = fibImpl1(n - 2) + fibImpl(n - 1);
if(Double.isInfinited(d)) throw new ArithmeticException("Overflow");
return d;
}
微基准测试必须使用其结果。
此代码的最大问题是它实际上从未更改任何程序状态。由于从未使用过Fibonacci计算的结果,因此编译器可以自由地放弃该计算,因此智能编译器(包括当前的Java 7和8编译器)将最终执行以下代码:
long then = System.currentTimeMillis();
long now = System.currentTimeMillis();
System.out.println("Elapsed time: " + (now - then));
结果,无论斐波那契方法的实现方式是什么,或者应该执行循环的次数,经过的时间都只有几毫秒。
有一个解决该特定问题的方法:确保读取或不写入每个结果。实际上,将l的定义从局部变量更改为实例变量(使用volatile关键字声明)将允许测量方法的性能。
以下是Brian Goetz撰写的一些好文章,这些文章解释了为什么在Java中(微)基准测试特别困难:
微基准测试是基准测试,我认为这不值得。有效基准测试是基准测试,我认为值得。
一般而言,微基准测试(如计算机模拟所言)试图衡量某些非常细粒度的任务的性能,这很难做得好,而且在实际性能令人头痛的情况下通常毫无意义。