问题标签 [jmh]
For questions regarding programming in ECMAScript (JavaScript/JS) and its various dialects/implementations (excluding ActionScript). Note JavaScript is NOT the same as Java! Please include all relevant tags on your question; e.g., [node.js], [jquery], [json], [reactjs], [angular], [ember.js], [vue.js], [typescript], [svelte], etc.
java - 我应该使用“-server”选项运行 JMH 基准测试吗
我正在使用JMH,一个 OpenJDK 微基准测试工具。构建过程创建microbenchmarks.jar
我调用java -jar
并传递 jar 名称和 JMH 参数。
我想知道我们是否应该使用-server
选项运行基准测试,为什么?
换句话说,我应该运行我的基准测试:
java - JMH 没有上课以进行基准测试
有一个错误,JMH 没有选择我的课程进行基准测试。
其中 EventRunner 包含:
我得到这个错误:
排除:org.sample.MyBenchmark.testMethod,不匹配 com.stecurra.benchmark.strategy.EventRunner 没有匹配的基准。拼写错误的正则表达式?使用 -v 进行详细输出。
如何将我的正则表达式更改为有效?
谢谢
java - JMH 谜题:StringBuilder 与 StringBand
我很难理解这个基准发生了什么。我想测量我的示例类StringBand
与StringBuilder
. with 的想法StringBand
是在 上连接字符串toString()
,而不是在append()
.
来源
这是StringBand
源代码-为基准而精简:
此代码使用:UnsafeUtil.getChars()
要实际获取String
char[] 而无需复制,请参见此处的代码。我们也可以使用getChars()
它仍然是一样的。
这是 JMH 测试:
分析
这是我对添加两个 20 个字符的字符串时发生的情况的理解。
字符串生成器
new char[20+16]
已创建(36 个字符)arraycopy
被调用复制 20 个string1
字符到StringBuilder
- 在第二次附加之前,
StringBuilder
扩展容量,因为 40 > 36 - 因此,
new char[36*2+2]
被创建 arraycopy
新缓冲区的 20 个字符arraycopy
追加 20 个字符string2
- 最后,
toString()
返回new String(buffer, 0, 40)
弦带
new String[2]
被建造- 两者都追加只是将字符串保留在内部缓冲区中,直到
toString()
被调用 length
增加了两次new char[40]
已创建(结果字符串的总长度)arraycopy
20个第一个字符串字符(UnsafeUtil
提供字符串的真实char[]
缓冲区)arraycopy
20 秒的字符串字符- 最后,返回
new String(buffer, 0, 40)
期望
StringBand
我们有:
- 少一个
arraycopy
- 这样做的全部目的是什么 - 更少的分配大小:
new String[]
和new char[]
与两个new char[]
- 另外,我们没有像
StringBuilder
方法中那样进行很多检查(对于大小等)
所以我希望它的StringBand
工作原理至少与 相同StringBuilder
,如果不是更快的话。
基准测试结果
我在 2013 年中期的 MacBookPro 上运行基准测试。使用 JMH v0.2 和 Java 1.7b45
命令:
预热迭代次数(2)很好,因为我可以看到第二次迭代达到了相同的性能。
结果是说StringBuilder
快两倍。当我将线程数增加到 16 或BlackHole
在代码中显式使用 s 时,也会发生同样的情况。
为什么?
java - 在构造函数中设置 Java 集合的大小是否更好?
如果我知道当时的大小,将大小传递Collection
给构造函数会更好吗?在扩展和分配/重新分配Collection
方面的节省效果是否显着?Collection
如果我知道的最小尺寸Collection
但不知道上限怎么办。至少以最小的尺寸创建它仍然值得吗?
java - 在 Eclipse 中从 main 运行 JMH 时出现“没有匹配的基准”
我想通过在 Eclipse 中将其作为 Java 应用程序运行来尝试 JMH 的新功能。我导入并构建了 jmh-samples 项目。编译的类以 /jmh-samples/target/generated-sources/annotations 结尾,/target/ 中有几个 JAR,从命令行运行 microbenchmarks.jar 照常工作。
但是,当我执行 main 我总是得到
有任何想法吗?我使用的是 0.3 版
java - 分支预测不起作用吗?
关于这个问题,答案指定未排序的数组需要更多时间,因为它未能通过分支预测测试。但是如果我们在程序中做一个小的改动:
在这里我已经替换(来自原始问题)
和
未排序的数组给出了大约。同样的结果,我想问为什么分支预测在这种情况下不起作用?
java - 什么可以解释编写对堆位置的引用的巨大性能损失?
在研究分代垃圾收集器对应用程序性能的微妙影响时,我发现在一个非常基本的操作(简单写入堆位置)的性能方面存在相当惊人的差异,即写入的值是原始值还是引用。
微基准
结果
由于整个循环几乎慢了 8 倍,因此写入本身可能慢了 10 倍以上。什么可以解释这种放缓?
写出原始数组的速度超过每纳秒 10 次写入。也许我应该问我问题的另一面:是什么让原始写作如此之快?(顺便说一句,我已经检查过了,时间与数组大小成线性关系。)
请注意,这都是单线程的;指定@Threads(2)
将增加两个测量值,但比率将相似。
一点背景知识:卡表和相关的写屏障
年轻代中的对象可能碰巧只能从老年代中的对象访问。为了避免收集活动对象,YG 收集器必须知道自上次 YG 收集以来写入老年代区域的任何引用。这是通过一种称为卡表的“脏标志表”来实现的,它为每个 512 字节的堆块有一个标志。
当我们意识到每次写入引用都必须伴随着卡表不变的代码时,该方案的“丑陋”部分就出现了:必须标记卡表中保护被写入地址的位置一样脏。这段代码被称为写屏障。
在特定的机器代码中,如下所示:
当写入的值是原始值时,这就是相同的高级操作所需要的全部内容:
写入屏障似乎“仅”贡献了一次写入,但我的测量表明它会导致数量级的减速。我无法解释这一点。
UseCondCardMark
只会让事情变得更糟
如果条目已被标记为脏,则有一个非常模糊的 JVM 标志应该避免卡表写入。这主要在一些退化的情况下很重要,因为大量的卡表写入导致线程之间通过 CPU 缓存进行错误共享。无论如何,我尝试使用该标志:
java - 当 JMH 什么都不做时,它会做什么?
这是我的第一个 JMH 基准测试。我可能做错了一切,但是......
我的基准看起来像这样
我开始了它……然后等了又等,然后杀死了它。我怀疑 中的问题@Setup
,所以我简化了它,但没有任何改变。跑步开始时非常乐观......
然后什么也没有发生。很长一段时间后,它继续写20行,如
和5行像
然后它输出一些结果
并更正其估计的 eta:
我是否@Setup
比我更频繁地被调用,或者还有什么可能是导致缓慢的原因?
java - 循环习语的奇怪 JIT 悲观化
在分析这里最近一个问题的结果时,我遇到了一个非常奇怪的现象:显然,HotSpot 的 JIT 优化的额外层实际上会减慢我机器上的执行速度。
这是我用于测量的代码:
代码非常微妙,所以让我指出重要的部分:
- “正常索引”变体使用直接变量
i
作为数组索引。HotSpot 可以轻松确定i
整个循环的范围并消除数组边界检查; - “屏蔽索引”变体索引为
j
,实际上等于i
,但这一事实通过 AND 屏蔽操作从 HotSpot 中“隐藏”了; - “带出口点”变体引入了显式循环出口点。下面将解释这一点的重要性。
循环展开和重新排序
观察边界以两种重要方式检查数字:
- 它具有与之相关的运行时开销(比较后跟条件分支);
- 它构成了一个循环退出点,可以在任何步骤中中断循环。事实证明,这对适用的 JIT 优化产生了重要影响。
通过检查上述四种方法发出的机器代码,我注意到以下几点:
- 在所有情况下,循环都是展开的;
- 在 的情况下
normalIndex
,它被区分为唯一没有过早循环退出点的情况,所有展开步骤的操作都被重新排序,以便首先执行所有数组获取,然后将所有值异或到累加器中。
预期和实际测量结果
现在我们可以根据讨论的特征对这四种方法进行分类:
normalIndex
没有边界检查,也没有循环退出点;normalWithExitPoint
没有边界检查和 1 个退出点;maskedIndex
有 1 个边界检查和 1 个出口点;maskedWithExitPoint
有 1 个边界检查和 2 个出口点。
显而易见的期望是,上面的列表应该按性能降序排列方法;但是,这些是我的实际结果:
normalWithExitPoint
和maskedIndex
是相同的模测量误差,即使只有后者有边界检查;- 观察到的最大异常
normalIndex
应该是最快的,但明显慢于,除了多了一行代码,即引入退出点的代码之外normalWithExitPoint
,在各方面都与它相同。
由于normalIndex
是唯一对其应用了额外的重新排序“优化”的方法,因此得出的结论是,这是导致速度下降的原因。
我正在测试:
Java HotSpot(TM) 64-Bit Server VM (build 24.0-b56, mixed mode)
(Java 7 更新 40)- OS X 版本 10.9.1
- 2.66 GHz 英特尔酷睿 i7
我也成功地在 Java 8 EA b118 上重现了结果。
我的问题:
上述现象是否可以在其他类似机器上重现?从一开始提到的问题中,我已经暗示至少有些机器不会重现它,所以同一个 CPU 的另一个结果会很有趣。
更新 1:更多测量灵感来自maaartinus的发现
我收集了下表,它将执行时间与-XX:LoopUnrollLimit
命令行参数相关联。在这里,我只关注两个变体,有和没有if (entry == 0) break;
线:
可以观察到以下突然变化:
在从 14 到 15 的过渡中,该
withoutExitPoint
变体接受了有益的 LCM 1转换并显着加快了速度。由于循环展开限制,所有加载的值都适合寄存器;在 18->19 上,
withExitPoint
变体获得了加速,小于上述值;在 22->23 上,
withoutExitPoint
变体变慢了。在这一点上,我看到溢出到堆栈位置,如maaartinus的回答中所述,开始发生。
我的设置的默认loopUnrollLimit
值为 60,因此我在最后一列中展示了它的结果。
1 LCM = 本地代码运动。正是这种转换导致所有数组访问都发生在顶部,然后处理加载的值。
更新 2:这实际上是一个已知的,报告的问题
https://bugs.openjdk.java.net/browse/JDK-7101232