如何解决JMH结果与算法复杂度解读
我试图通过使用基准数据来证明算法的复杂性。我要测试的算法是二分搜索算法(规定的复杂度为 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,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 次操作/毫秒
但是如果我绘制图形分数/数组大小,我得到的不是 log(n) 而是 1/x 图形。如果我使用 Mode.AverageTime
,则图表相当x^2。
这是我上面提供的数据图表,y[ms/ops],x[arraysize]:
如何从 JMH 获取操作单元或调整我的测试?
解决方法
你在绘制和比较错误的东西。
所以你得到了 每 1 毫秒的操作 ops_ms
,这或多或少是测量时间 t
(以 [ms] 为单位)除以 数量操作 m
。对于大小的二分搜索,n
是:
m = ~log2(n)
要获得复杂性和/或正确的绘图,您需要绘制测量时间 t
与大小 n
的关系,但是您绘制的是 ops_ms
与 n
...
所以首先我们需要得到测得的时间t
(top
是单次操作的时间):
t = m*top
m = log2(n)
ops_ms = 1/top
--------------
top=1/ops_ms
t = log2(n)*top
--------------
t = log2(n)/ops_ms
因此您需要将 t
绘制为 y 轴,将 n
绘制为 x 轴。但是,正如您所看到的,这种测量方式毫无价值,因为您需要知道要测量什么才能获得 m
,即使这只是一个近似值……更好/准确的方法是直接使用测量的时间作为你的 ops/ms
搞砸了。
当我在这样的数据上使用这个 measuring complexity 时:
const double l2=3.3219280948873623478703194294894;
double binsearch[]= // n[-],t[ms]
{
100000,l2*log( 100000.0)/31.602,200000,l2*log( 200000.0)/14.520,300000,l2*log( 300000.0)/ 9.004,400000,l2*log( 400000.0)/ 6.896,500000,l2*log( 500000.0)/ 5.333,1000000,l2*log( 1000000.0)/ 2.304,2000000,l2*log( 2000000.0)/ 0.790,3000000,l2*log( 3000000.0)/ 0.451,4000000,l2*log( 4000000.0)/ 0.330,5000000,l2*log( 5000000.0)/ 0.232,10000000,l2*log(10000000.0)/ 0.135,20000000,l2*log(20000000.0)/ 0.061,30000000,l2*log(30000000.0)/ 0.039,40000000,l2*log(40000000.0)/ 0.033,50000000,l2*log(50000000.0)/ 0.025,0.000
};
它导致了这个结果:
binsearch O(n.log^4(n)) error = 0.398668
这仍然与预期的 log2(n)
相差太远,但比其他选项更接近。这意味着额外的东西正在压榨你的 ops/ms
值,这是我所期望的......你知道你有 JRE 架构,还有主机架构混淆了诸如缓存、预取管道等东西的测量,最重要的是你的JMH 也可能会做一些事情(例如出于某种目的平均或“增强” ops/ms 值)...
如果 ops_ms
实际上是 binsearch/ms
,正如评论之一所建议的,那么时间由 1/ops_ms
计算,结果可能是真的稍微接近O(log(n))
,但仍然太远:
// time O(n) uncertainity
log2(n)/ops_ms O(n.log^4(n)) error = 0.398668 // m ops / ms
(n)/ops_ms O(n^2.log^3(n)) error = 0.398668 // n ops / ms
(1)/ops_ms O(n.log^3(n)) error = 0.398668 // binsearch / ms
所以我的建议是找到一种直接测量时间的方法,而不是使用 ops/ms
...
[edit1] 我测试过的 C++ 实现
int find(int *array,int size,int start,int end,int target)
{
if (end >= size) 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;
}
用法:
const int n=50000000;
double binsearch[]= // n[-],1.0,0.000
};
int *dat=new int[n],i,s;
Randomize();
for (s=0,i=0;i<n;i++)
{
s+=1+Random(10);
dat[i]=s;
}
for (i=0;binsearch[i];)
{
s=binsearch[i]; i++;
tbeg(); // star measuring of time
find(dat,s,s-1,dat[Random(s)]);
tend(); // end measuring of time
binsearch[i]=performance_tms; i++; // store measured time
}
delete[] dat;
这将生成 PRNG int 升序数组并在其上测试您的 find
我敢打赌,正如我在评论中所述,您的数据根本不是随机的。当我应用它时,结果如预期:
binsearch O(log(n)) error = 0.528393
所以要么你的数组和/或目标没有正确选择,或者甚至你的时间测量包括它的生成,这把事情搞砸了。
如果我没看错,你的数组生成要么是 O(n^2)
要么是 O(n.log(n))
反对我的 O(n)
所以如果它被包含在测量中,它将主导 O(log(n))
.. . 结果:
(1)/ops_ms O(n.log^3(n)) error = 0.398668 // binsearch / ms
建议这种情况,使用的代数约为 O(n.log(n))
,log
功率差异只是由于使用的计算架构和时间测量不精确...
我想我已经找到了这种行为的原因。这是我的修复:
- 将基准测试模式更改为
Mode.AverageTime
,因此现在基准测试输出以 ms/op 为单位的平均时间。 - 切换到纳秒
@OutputTimeUnit(TimeUnit.NANOSECONDS)
。 - 向基准添加了 1 个预热迭代。
- 将数组生成从测试移至
ExecutionPlan
类,并更改生成策略:现在生成随机整数值而不是连续整数数组(感谢@Spektre) - 已将
@Setup
级别更改为Level.Trial
,根据Level.Invocation
的 doc 用法有一些合理的警告。 - 添加了更多积分(现在为 30)。
这里是迭代二分查找得到的数据:
并用趋势线绘制图形:
有些点有很大的误差,但现在趋势趋向于O(log n)
。我认为可以使用更多的迭代、热身和分叉来调整基准以获得更高的精度。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。