mirror of
https://github.com/LingCoder/OnJava8.git
synced 2026-08-24 06:53:27 +08:00
[feat16](截止 微基准测试)
This commit is contained in:
@@ -1469,7 +1469,7 @@ SimpleDebugging.main(SimpleDebugging.java:20)
|
||||
|
||||
## 基准测试
|
||||
|
||||
> 我们应该忘掉微小的效率,说的就是这些 97% 的时间:过早的优化是万恶之源。
|
||||
> 我们应该忘掉微小的效率提升,说的就是这些 97% 的时间做的事:过早的优化是万恶之源。
|
||||
>
|
||||
> —— Donald Knuth
|
||||
|
||||
@@ -1477,6 +1477,100 @@ SimpleDebugging.main(SimpleDebugging.java:20)
|
||||
|
||||
基准测试意味着对代码或算法片段进行计时看哪个跑得更快,与下一节的分析和优化截然相反,分析优化是观察整个程序,找到程序中最耗时的部分。
|
||||
|
||||
可以简单地对一个代码片段的执行计时吗?在像 C 这样直接的编程语言中,这个方法的确可行。在像 Java 这样拥有复杂的运行时系统的编程语言中,基准测试变得更有挑战性。为了生成可靠的数据,环境设置必须控制诸如 CPU 频率,节能特性,其他运行在相同机器上的进程,优化器选项等等。
|
||||
|
||||
### 微基准测试
|
||||
|
||||
写一个计时工具类从而比较不同代码块的执行速度是具有吸引力的。看上去这会产生一些有用的数据。比如,这里有一个简单的 **Timer** 类,可以用以下两种方式使用它:
|
||||
|
||||
1. 创建一个 **Timer** 对象,执行一些操作然后调用 **Timer** 的 **duration()** 方法产生以毫秒为单位的运行时间。
|
||||
2. 向静态的 **duration()** 方法中传入 **Runnable**。任何符合 **Runnable** 接口的类都有一个函数式方法 **run()**,该方法没有入参,且没有返回。
|
||||
|
||||
```java
|
||||
// onjava/Timer.java
|
||||
package onjava;
|
||||
import static java.util.concurrent.TimeUnit.*;
|
||||
|
||||
public class Timer {
|
||||
private long start = System.nanoTime();
|
||||
|
||||
public long duration() {
|
||||
return NANOSECONDS.toMillis(System.nanoTime() - start);
|
||||
}
|
||||
|
||||
public static long duration(Runnable test) {
|
||||
Timer timer = new Timer();
|
||||
test.run();
|
||||
return timer.duration();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这是一个很直接的计时方式。难道我们不能只运行一些代码然后看它的运行时长吗?
|
||||
|
||||
有许多因素会影响你的结果,即使是生成提示符也会造成计时的混乱。这里举一个看上去天真的例子,它使用了 标准的 Java **Arrays** 库(后面会详细介绍):
|
||||
|
||||
```java
|
||||
// validating/BadMicroBenchmark.java
|
||||
// {ExcludeFromTravisCI}
|
||||
import java.util.*;
|
||||
import onjava.Timer;
|
||||
|
||||
public class BadMicroBenchmark {
|
||||
static final int SIZE = 250_000_000;
|
||||
|
||||
public static void main(String[] args) {
|
||||
try { // For machines with insufficient memory
|
||||
long[] la = new long[SIZE];
|
||||
System.out.println("setAll: " + Timer.duration(() -> Arrays.setAll(la, n -> n)));
|
||||
System.out.println("parallelSetAll: " + Timer.duration(() -> Arrays.parallelSetAll(la, n -> n)));
|
||||
} catch (OutOfMemoryError e) {
|
||||
System.out.println("Insufficient memory");
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
/* Output
|
||||
setAll: 272
|
||||
parallelSetAll: 301
|
||||
```
|
||||
|
||||
**main()** 方法的主体包含在 **try** 语句块中,因为一台机器用光内存后会导致构建停止。
|
||||
|
||||
对于一个长度为 250,000,000 的 **long** 型(仅仅差一点就会让大部分机器内存溢出)数组,我们比较了 **Arrays.setAll()** 和 **Arrays.parallelSetAll()** 的性能。这个并行的版本会尝试使用多个处理器加快完成任务(尽管我在这一节谈到了一些并行的概念,但是在 [并发编程](./24-Concurrent-Programming.md) 章节我们才会详细讨论这些 )。然而非并行的版本似乎运行得更快,尽管在不同的机器上结果可能不同。
|
||||
|
||||
**BadMicroBenchmark.java** 中的每一步操作都是独立的,但是如果你的操作依赖于同一资源,那么并行版本运行的速度会骤降,因为不同的进程会竞争相同的那个资源。
|
||||
|
||||
```java
|
||||
// validating/BadMicroBenchmark2.java
|
||||
// Relying on a common resource
|
||||
|
||||
import java.util.*;
|
||||
import onjava.Timer;
|
||||
|
||||
public class BadMicroBenchmark2 {
|
||||
static final int SIZE = 5_000_000;
|
||||
|
||||
public static void main(String[] args) {
|
||||
long[] la = new long[SIZE];
|
||||
Random r = new Random();
|
||||
System.out.println("parallelSetAll: " + Timer.duration(() -> Arrays.parallelSetAll(la, n -> r.nextLong())));
|
||||
System.out.println("setAll: " + Timer.duration(() -> Arrays.setAll(la, n -> r.nextLong())));
|
||||
SplittableRandom sr = new SplittableRandom();
|
||||
System.out.println("parallelSetAll: " + Timer.duration(() -> Arrays.parallelSetAll(la, n -> sr.nextLong())));
|
||||
System.out.println("setAll: " + Timer.duration(() -> Arrays.setAll(la, n -> sr.nextLong())));
|
||||
}
|
||||
}
|
||||
/* Output
|
||||
parallelSetAll: 1147
|
||||
setAll: 174
|
||||
parallelSetAll: 86
|
||||
setAll: 39
|
||||
```
|
||||
|
||||
|
||||
|
||||
<!-- Profiling and Optimizing -->
|
||||
|
||||
## 分析和优化
|
||||
|
||||
Reference in New Issue
Block a user