我想了解Java对连续循环做了什么样的优化.更确切地说,我正在尝试检查是否执行了循环融合.从理论上讲,我期待这种优化不会自动完成,并且期望确认融合版本比具有两个循环的版本更快.
但是,在运行基准测试之后,结果显示两个独立(和连续)循环比完成所有工作的单个循环更快.
我已经尝试使用JMH创建基准测试并获得相同的结果.
我使用了该javap命令,它显示生成的具有两个循环的源文件的字节码实际上对应于正在执行的两个循环(没有循环展开或执行其他优化).
正在测量的代码BenchmarkMultipleLoops.java:
private void work() {
List<Capsule> intermediate = new ArrayList<>();
List<String> res = new ArrayList<>();
int totalLength = 0;
for (Capsule c : caps) {
if(c.getNumber() > 100000000){
intermediate.add(c);
}
}
for (Capsule c : intermediate) {
String s = "new_word" + c.getNumber();
res.add(s);
}
//Loop to assure the end result (res) is used for something
for(String s : res){
totalLength += s.length();
}
System.out.println(totalLength);
}
Run Code Online (Sandbox Code Playgroud)
正在测量的代码BenchmarkSingleLoop.java:
private void work(){ …Run Code Online (Sandbox Code Playgroud) 考虑这个例子:
public static void main(final String[] args) {
final List<String> myList = Arrays.asList("A", "B", "C", "D");
final long start = System.currentTimeMillis();
for (int i = 1000000; i > myList.size(); i--) {
System.out.println("Hello");
}
final long stop = System.currentTimeMillis();
System.out.println("Finish: " + (stop - start));
}
Run Code Online (Sandbox Code Playgroud)
VS
public static void main(final String[] args) {
final List<String> myList = Arrays.asList("A", "B", "C", "D");
final long start = System.currentTimeMillis();
final int size = myList.size();
for (int i = 1000000; i > size; i--) …Run Code Online (Sandbox Code Playgroud) java compiler-construction performance for-loop microbenchmark
我写了一个简单的基准测试,以便找出当通过按位和数组计算数组时是否可以消除边界检查.这几乎就是所有哈希表的作用:它们计算
h & (table.length - 1)
Run Code Online (Sandbox Code Playgroud)
作为索引table,其中h是hashCode或派生值.该结果表明,边界检查不被淘汰.
我的基准的想法很简单:计算两个值i和j,其中既保证是有效的数组索引.
i是循环计数器.当它被用作数组索引时,边界检查被消除.j计算为x & (table.length - 1),x每次迭代时某些值都在变化.当它被用作数组索引时,边界检查不会被消除.相关部分如下:
for (int i=0; i<=table.length-1; ++i) {
x += result;
final int j = x & (table.length-1);
result ^= i + table[j];
}
Run Code Online (Sandbox Code Playgroud)
另一个实验使用
result ^= table[i] + j;
Run Code Online (Sandbox Code Playgroud)
代替.时间上的差异可能是15%(在我尝试的不同变体中非常一致).我的问题:
j?MarkoTopolnik的回答表明它更复杂,并且不能保证取消边界检查是一种胜利,特别是在他的计算机上,"正常"代码比"蒙面"慢.我想这是因为它允许一些额外的优化,在这种情况下显示实际上是有害的(鉴于当前CPU的复杂性,编译器甚至几乎不知道).
leventov的答案清楚地表明,数组边界检查是在"屏蔽"中完成的,并且它的消除使得代码与"正常"一样快.
Donal Fellows指出这样一个事实,即屏蔽不适用于零长度表,x & (0-1)等于x.因此,编译器可以做的最好的事情是用零长度检查替换绑定的检查.但这是恕我直言仍然值得,因为零长度检查可以轻松地移出循环.
我在Java列表迭代代码上运行一些微基准测试.我使用了-XX:+ PrintCompilation和-verbose:gc标志来确保在运行定时时后台没有发生任何事情.但是,我在输出中看到了一些我无法理解的东西.
这是代码,我正在运行基准测试:
import java.util.ArrayList;
import java.util.List;
public class PerformantIteration {
private static int theSum = 0;
public static void main(String[] args) {
System.out.println("Starting microbenchmark on iterating over collections with a call to size() in each iteration");
List<Integer> nums = new ArrayList<Integer>();
for(int i=0; i<50000; i++) {
nums.add(i);
}
System.out.println("Warming up ...");
//warmup... make sure all JIT comliling is done before the actual benchmarking starts
for(int i=0; i<10; i++) {
iterateWithConstantSize(nums);
iterateWithDynamicSize(nums);
}
//actual
System.out.println("Starting the actual test");
long constantSizeBenchmark …Run Code Online (Sandbox Code Playgroud) 在分析最近一个问题的结果时,我遇到了一个非常奇怪的现象:显然,HotSpot的额外一层JIT优化实际上会降低我机器上的执行速度.
这是我用于测量的代码:
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
@OperationsPerInvocation(Measure.ARRAY_SIZE)
@Warmup(iterations = 2, time = 1)
@Measurement(iterations = 5, time = 1)
@State(Scope.Thread)
@Threads(1)
@Fork(2)
public class Measure
{
public static final int ARRAY_SIZE = 1024;
private final int[] array = new int[ARRAY_SIZE];
@Setup public void setup() {
final Random random = new Random();
for (int i = 0; i < ARRAY_SIZE; ++i) {
final int x = random.nextInt();
array[i] = x == 0? 1 : x;
}
}
@GenerateMicroBenchmark public int normalIndex() …Run Code Online (Sandbox Code Playgroud) 我有一个基准:
@BenchmarkMode(Mode.Throughput)
@Fork(1)
@State(Scope.Thread)
@Warmup(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS, batchSize = 1000)
@Measurement(iterations = 40, time = 1, timeUnit = TimeUnit.SECONDS, batchSize = 1000)
public class StringConcatTest {
private int aInt;
@Setup
public void prepare() {
aInt = 100;
}
@Benchmark
public String emptyStringInt() {
return "" + aInt;
}
@Benchmark
public String valueOfInt() {
return String.valueOf(aInt);
}
}
Run Code Online (Sandbox Code Playgroud)
这是结果:
Benchmark Mode Cnt Score Error Units
StringConcatTest.emptyStringInt thrpt 40 66045.741 ± 1306.280 ops/s
StringConcatTest.valueOfInt thrpt 40 …Run Code Online (Sandbox Code Playgroud) 在Chandler Carruth的CppCon 2015演讲中,他介绍了两种神奇的功能,可以在没有任何额外性能损失的情况下击败优化器.
作为参考,这里是函数(使用GNU样式的内联汇编):
void escape(void* p)
{
asm volatile("" : : "g"(p) : "memory");
}
void clobber()
{
asm volatile("" : : : "memory");
}
Run Code Online (Sandbox Code Playgroud)
它适用于任何支持GNU样式内联汇编的编译器(GCC,Clang,Intel编译器,可能还有其他编译器).但是,他提到它在MSVC中不起作用.
检查Google Benchmark的实现,似乎他们使用重新解释转换为a volatile const char&并将其传递给隐藏在非gcc/clang编译器上的不同翻译单元中的函数.
template <class Tp>
inline BENCHMARK_ALWAYS_INLINE void DoNotOptimize(Tp const& value) {
internal::UseCharPointer(&reinterpret_cast<char const volatile&>(value));
}
// some other translation unit
void UseCharPointer(char const volatile*) {}
Run Code Online (Sandbox Code Playgroud)
但是,我对此有两个顾虑:
MSVC中是否有与GNU样式的汇编函数相同的低级别?或者这是MSVC上最好的?
根据文档,microbenchmark:::autoplot"使用ggplot2生成更清晰的微基准时序图."
凉!我们来试试示例代码:
library("ggplot2")
tm <- microbenchmark(rchisq(100, 0),
rchisq(100, 1),
rchisq(100, 2),
rchisq(100, 3),
rchisq(100, 5), times=1000L)
autoplot(tm)
Run Code Online (Sandbox Code Playgroud)

我没有看到任何关于...文档中的软弱起伏,但是我从功能创建者的这个答案中得到的最好的猜测是,这就像一个平滑的系列时间表的箱形图,上下都是四分相连接在形状的主体上.也许?这些情节看起来太有趣了,不知道这里发生了什么.
这是一个情节是什么?
如何为perf调用图启用C++ demangling?当我进入注释模式时,似乎是解码符号,而不是在主调用图中.
示例代码(使用Google Benchmark):
#include <benchmark/benchmark.h>
#include <vector>
static __attribute__ ((noinline)) int my_really_big_function()
{
for(size_t i = 0; i < 1000; ++i)
{
benchmark::DoNotOptimize(i % 5);
}
return 0;
}
static __attribute__ ((noinline)) void caller1()
{
for(size_t i = 0; i < 1000; ++i)
{
benchmark::DoNotOptimize(my_really_big_function());
benchmark::DoNotOptimize(i % 5);
}
}
static __attribute__ ((noinline)) void myfun(benchmark::State& state)
{
while(state.KeepRunning())
{
caller1();
}
}
BENCHMARK(myfun);
BENCHMARK_MAIN();
Run Code Online (Sandbox Code Playgroud)
构建命令:
clang++ main.cpp -o main -fno-omit-frame-pointer -O0 -lpthread -lbenchmark
Run Code Online (Sandbox Code Playgroud)
perf命令:
perf record -g …Run Code Online (Sandbox Code Playgroud) 如果我在Rust中运行这些基准测试:
#[bench]
fn bench_rnd(b: &mut Bencher) {
let mut rng = rand::weak_rng();
b.iter(|| rng.gen_range::<f64>(2.0, 100.0));
}
#[bench]
fn bench_ln(b: &mut Bencher) {
let mut rng = rand::weak_rng();
b.iter(|| rng.gen_range::<f64>(2.0, 100.0).ln());
}
Run Code Online (Sandbox Code Playgroud)
结果是:
test tests::bench_ln ... bench: 121 ns/iter (+/- 2)
test tests::bench_rnd ... bench: 6 ns/iter (+/- 0)
Run Code Online (Sandbox Code Playgroud)
每次ln通话121-6 = 115 ns .
但是Java中的相同基准:
@State(Scope.Benchmark)
public static class Rnd {
final double x = ThreadLocalRandom.current().nextDouble(2, 100);
}
@Benchmark
public double testLog(Rnd rnd) {
return Math.log(rnd.x);
}
Run Code Online (Sandbox Code Playgroud)
给我:
Benchmark …Run Code Online (Sandbox Code Playgroud) microbenchmark ×10
java ×7
performance ×3
benchmarking ×2
jmh ×2
optimization ×2
c++ ×1
cpu ×1
for-loop ×1
ggplot2 ×1
intel ×1
linux ×1
perf ×1
plot ×1
r ×1
rust ×1
ubuntu ×1
visual-c++ ×1