JMH结果和算法复杂性的java解释
我试图通过使用基准数据来证明算法的复杂性。我要测试的算法是二进制搜索算法(声明的复杂度为O(log n)
),我想使用JMH库进行基准测试
以下是测试示例:
public class BinarySearchTest {
private static SearchAlgorithm binaryIterative = new BinarySearchIterative();
private static SearchAlgorithm binaryRecursive = new BinarySearchRecursive();
@Test
public void runBenchmarks() throws Exception {
Options options = new OptionsBuilder()
.include(this.getClass().getName() + ".*")
.mode(Mode.Throughput)
.forks(1)
.threads(1)
.warmupIterations(0)
.measurementIterations(1)
.shouldFailOnError(true)
.shouldDoGC(true)
.build();
new Runner(options).run();
}
@Benchmark
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public void binarySearchIterativeBenchmark(ExecutionPlan plan) {
//given
int size = randomPositiveIntLessThan(plan.arraySize);
int[] array = generateUninterrupted(0, size);
int target = randomPositiveIntLessThan(size);
//when
var result = binaryIterative.find(array, 0, array.length, target);
//then
assertTrue(result != -1);
}
这是一个具有算法实现的类:
public class BinarySearchIterative implements SearchAlgorithm {
@Override
public int find(int[] array, int start, int end, int target) {
if (end > array.length) {
return -1;
}
int left = start;
int right = end;
while (left <= right) {
int median = left + (right - left) / 2;
if (array[median] == target) {
return median;
}
if (array[median] > target) {
right = median - 1;
}
if (array[median] < target) {
left = median + 1;
}
}
return -1;
}
我使用带有@State
注释的类来获取数组的大小:
@State(Scope.Benchmark)
public class ExecutionPlan {
@Param({"100000", "200000", "300000", "400000", "500000",
"1000000", "2000000", "3000000", "4000000", "5000000",
"10000000", "20000000", "30000000", "40000000", "50000000"})
public int arraySize;
所以我有下一个结果:
BinarySearchTest.binarySearchIterativeBenchmark 100000 thrpt
31.602 ops/ms BinarySearchTest.binarySearchIterativeBenchmark 200000 thrpt 14.520 ops/ms BinarySearchTest.binarySearchIterativeBenchmark 300000 thrpt
9.004 ops/ms BinarySearchTest.binarySearchIterativeBenchmark 400000 thrpt 6.896 ops/ms BinarySearchTest.binarySearchIterativeBenchmark 500000 thrpt
5.333 ops/ms BinarySearchTest.binarySearchIterativeBenchmark 1000000 thrpt 2.304 ops/ms BinarySearchTest.binarySearchIterativeBenchmark 2000000 thrpt
0.790 ops/ms BinarySearchTest.binarySearchIterativeBenchmark 3000000 thrpt 0.451 ops/ms BinarySearchTest.binarySearchIterativeBenchmark 4000000 thrpt
0.330 ops/ms BinarySearchTest.binarySearchIterativeBenchmark 5000000 thrpt 0.232 ops/ms BinarySearchTest.binarySearchIterativeBenchmark 10000000 thrpt
0.135 ops/ms BinarySearchTest.binarySearchIterativeBenchmark 20000000 thrpt 0.061 ops/ms BinarySearchTest.binarySearchIterativeBenchmark 30000000 thrpt
0.039 ops/ms BinarySearchTest.binarySearchIterativeBenchmark 40000000 thrpt 0.033 ops/ms BinarySearchTest.binarySearchIterativeBenchmark 50000000 thrpt
0.025 ops/ms
但是如果我绘制图表得分/排列,我得到的不是log(n),而是1/x图表。如果我使用Mode.AverageTime
,则图形相当x^2
这是我上面提供的数据图表,y[ms/ops],x[arraysize]:
如何从JMH获取操作单元或调整测试
# 1 楼答案
你在策划和比较错误的东西
因此每1ms有个操作
ops_ms
,这或多或少是测量时间t
除以个操作m
。对于大小为n
的二进制搜索:为了获得复杂性和/或正确的绘图,您需要绘制测量的时间
t
与大小n
的关系,但是您正在绘制ops_ms
与n
因此首先我们需要获得测量的时间
t
(top
是单个操作的时间):因此,您需要将
t
绘制为y轴,将n
绘制为x轴。然而,正如你所看到的,这种测量方法是毫无价值的,因为你需要知道你测量的是什么才能得到m
,甚至这只是一个近似值。。。更好/更准确的方法是直接使用测量的时间,因为你的ops/ms
把一切都搞糟了当我对这样的数据使用measuring complexity时:
它导致了这样的结果:
这仍然是太远的预期
log2(n)
但比其他选项更接近。这意味着额外的东西是平衡你的ops/ms
价值观,这是我所期望的。。。你知道你有JRE体系结构,还有主机体系结构,它把测量与缓存、预取管道等混为一谈,而且最重要的是你的JMH可能也会做一些事情(比如为了某种目的平均或“增强”ops/ms值)如果
ops_ms
实际上是binsearch/ms
,正如其中一条评论所建议的那么时间是由1/ops_ms
计算的,这可能是真的,因为结果稍微接近O(log(n))
,但仍然太远了:所以我的建议是找到一种直接测量时间的方法,而不是使用
ops/ms
<强> [Eddi1]我在C++中实现的
用法:
这将生成PRNG int升序数组并测试你的
find
。我打赌你的数据并不像我在评论中描述的那样是随机的。当我应用此方法时,结果与预期一致:因此,要么你的数组和/或目标选择不正确,要么你的时间度量包括它的生成,这会把事情搞砸
如果我看对了,你的数组生成要么{}要么{}与我的{}相反,因此如果它被包括在测量中,它将主导{}。。。结果是:
表明是这样的并且使用的生成大约是
O(n.log(n))
功率差异仅仅是由于使用的计算架构和时间测量不精确# 2 楼答案
我想我已经找到了这种行为的原因。以下是我的修复方法:
Mode.AverageTime
,因此现在基准测试输出平均时间,单位为ms/op@OutputTimeUnit(TimeUnit.NANOSECONDS)
李>ExecutionPlan
类,并更改了生成策略:现在生成随机整数值,而不是连续整数数组(这要感谢@Spektre)@Setup
级别更改为Level.Trial
。使用Level.Invocation
有一些合理的警告李>以下是用于迭代二进制搜索的数据:
并用趋势线绘制图形:
有些点有很大的误差,但现在的趋势是
O(log n)
。我认为可以使用更多的迭代、预热和分叉来调整基准以获得更高的精度