几个小时前,我回答了另一个 Stack Overflow 问题,得到了一个非常令人惊讶的结果。答案可以在 here 中找到。答案是/部分错误,但我觉得重点是字节加法。
严格来说,其实是byte-to-long相加。
这是我一直在使用的基准代码:
public class ByteAdditionBenchmark {
private void start() {
int[] sizes = {
700_000,
1_000,
10_000,
25_000,
50_000,
100_000,
200_000,
300_000,
400_000,
500_000,
600_000,
700_000,
};
for (int size : sizes) {
List<byte[]> arrays = createByteArrays(size);
//Warmup
arrays.forEach(this::byteArrayCheck);
benchmark(arrays, this::byteArrayCheck, "byteArrayCheck");
}
}
private void benchmark(final List<byte[]> arrays, final Consumer<byte[]> method, final String name) {
long start = System.nanoTime();
arrays.forEach(method);
long end = System.nanoTime();
double nanosecondsPerIteration = (end - start) * 1d / arrays.size();
System.out.println("Benchmark: " + name + " / iterations: " + arrays.size() + " / time per iteration: " + nanosecondsPerIteration + " ns");
}
private List<byte[]> createByteArrays(final int amount) {
Random random = new Random();
List<byte[]> resultList = new ArrayList<>();
for (int i = 0; i < amount; i++) {
byte[] byteArray = new byte[4096];
byteArray[random.nextInt(4096)] = 1;
resultList.add(byteArray);
}
return resultList;
}
private boolean byteArrayCheck(final byte[] array) {
long sum = 0L;
for (byte b : array) {
sum += b;
}
return (sum == 0);
}
public static void main(String[] args) {
new ByteAdditionBenchmark().start();
}
}
这是我得到的结果:
Benchmark: byteArrayCheck / iterations: 700000 / time per iteration: 50.26538857142857 ns
Benchmark: byteArrayCheck / iterations: 1000 / time per iteration: 20.12 ns
Benchmark: byteArrayCheck / iterations: 10000 / time per iteration: 9.1289 ns
Benchmark: byteArrayCheck / iterations: 25000 / time per iteration: 10.02972 ns
Benchmark: byteArrayCheck / iterations: 50000 / time per iteration: 9.04478 ns
Benchmark: byteArrayCheck / iterations: 100000 / time per iteration: 18.44992 ns
Benchmark: byteArrayCheck / iterations: 200000 / time per iteration: 15.48304 ns
Benchmark: byteArrayCheck / iterations: 300000 / time per iteration: 15.806353333333334 ns
Benchmark: byteArrayCheck / iterations: 400000 / time per iteration: 16.923685 ns
Benchmark: byteArrayCheck / iterations: 500000 / time per iteration: 16.131066 ns
Benchmark: byteArrayCheck / iterations: 600000 / time per iteration: 16.435461666666665 ns
Benchmark: byteArrayCheck / iterations: 700000 / time per iteration: 17.107615714285714 ns
据我所知,在开始吐出基准测试数据之前,JVM 在前 700000 次迭代后已经完全预热。
那怎么可能热身了,成绩还是出人意表呢?几乎就在预热字节添加变得非常快之后,但在那之后它似乎再次收敛到标称的每次添加 16 纳秒。
测试是在配备 Intel i7 3770 标准配置和 16 GB RAM 的 PC 上运行的,因此我不能超过 700000 次迭代。如果重要的话,它在 Windows 8.1 64 位上运行。
事实证明,根据 raphw's suggestion,JIT 正在优化所有内容。
因此我用以下方法替换了基准方法:
private void benchmark(final List<byte[]> arrays, final Predicate<byte[]> method, final String name) {
long start = System.nanoTime();
boolean someUnrelatedResult = false;
for (byte[] array : arrays) {
someUnrelatedResult |= method.test(array);
}
long end = System.nanoTime();
double nanosecondsPerIteration = (end - start) * 1d / arrays.size();
System.out.println("Result: " + someUnrelatedResult);
System.out.println("Benchmark: " + name + " / iterations: " + arrays.size() + " / time per iteration: " + nanosecondsPerIteration + "ns");
}
这将确保它不会被优化掉并且测试结果也会显示它(为清楚起见省略了结果打印):
Benchmark: byteArrayCheck / iterations: 700000 / time per iteration: 1658.2627914285715 ns
Benchmark: byteArrayCheck / iterations: 1000 / time per iteration: 1241.706 ns
Benchmark: byteArrayCheck / iterations: 10000 / time per iteration: 1215.941 ns
Benchmark: byteArrayCheck / iterations: 25000 / time per iteration: 1332.94656 ns
Benchmark: byteArrayCheck / iterations: 50000 / time per iteration: 1456.0361 ns
Benchmark: byteArrayCheck / iterations: 100000 / time per iteration: 1753.26777 ns
Benchmark: byteArrayCheck / iterations: 200000 / time per iteration: 1756.93283 ns
Benchmark: byteArrayCheck / iterations: 300000 / time per iteration: 1762.9992266666666 ns
Benchmark: byteArrayCheck / iterations: 400000 / time per iteration: 1806.854815 ns
Benchmark: byteArrayCheck / iterations: 500000 / time per iteration: 1784.09091 ns
Benchmark: byteArrayCheck / iterations: 600000 / time per iteration: 1804.6096366666666 ns
Benchmark: byteArrayCheck / iterations: 700000 / time per iteration: 1811.0597585714286 ns
我想说这些结果在计算时间方面看起来更有说服力。但是,我的问题仍然存在。通过在随机时间重复测试,相同的模式仍然存在,即迭代次数少的基准测试比迭代次数多的基准测试更快,尽管它们似乎确实稳定在 100,000 次迭代或更低的地方。
这是什么解释?
最佳答案
你的结果的原因是你实际上并不知道你在测量什么。 Java 的即时编译器肯定会查看您的代码,而您可能什么也没测量。
编译器足够聪明,可以找出你的 List<byte[]>实际上并没有用于任何事情。因此,它最终将从您正在运行的应用程序中删除所有相关代码。因此,您的基准测试很可能是在衡量越来越空的应用程序。
所有此类问题的答案始终是:在我们实际查看有效基准之前不值得讨论。基准线束,例如 JMH (我可以推荐)知道一个叫做黑洞的概念。黑洞旨在混淆即时编译器,以便认为计算值实际上用于某些东西,即使它不是。有了这样的黑洞,否则被删除为空操作的代码将保留。
本土基准测试的另一个典型问题是优化循环。同样,即时编译器会注意到循环对任何迭代产生相同的计算,因此将完全删除循环。使用(质量)基准测试工具,您只会建议运行一些循环,而不是对它们进行硬编码。这样,线束就可以欺骗编译器。
用JMH写一个benchmark,你会发现你测出来的时间会有很大的不同。
关于您的更新:我只能重复一遍。永远不要相信未经利用的基准!运行 JITwatch 是了解 JVM 对您的代码执行的操作的一种简单方法。基准测试的主要问题是它忽略了 JVM 的分析。配置文件是 JVM 尝试记住代码的属性,然后将其作为优化的基础。对于基准测试,您将不同运行的配置文件混合在一起。然后,JVM 必须更新其当前配置文件并即时重新编译字节代码,这会花费时间。
为了避免这个问题,像 JMH 这样的工具可以让你为每个基准测试创建一个 JVM 新进程。以下是我使用已利用的基准测量的内容:
Benchmark Mode Samples Mean Mean error Units
o.s.MyBenchmark.test100k avgt 20 1922.671 29.155 ns/op
o.s.MyBenchmark.test10k avgt 20 1911.152 13.217 ns/op
o.s.MyBenchmark.test1k avgt 20 1857.205 3.086 ns/op
o.s.MyBenchmark.test200k avgt 20 1905.360 18.102 ns/op
o.s.MyBenchmark.test25k avgt 20 1832.663 102.562 ns/op
o.s.MyBenchmark.test50k avgt 20 1907.488 18.043 ns/op
下面是基于上述 JMH 的基准测试的源代码:
@State(Scope.Benchmark)
public class MyBenchmark {
private List<byte[]> input1k, input10k, input25k, input50k, input100k, input200k;
@Setup
public void setUp() {
input1k = createByteArray(1_000);
input10k = createByteArray(10_000);
input25k = createByteArray(25_000);
input50k = createByteArray(50_000);
input100k = createByteArray(100_000);
input200k = createByteArray(200_000);
}
private static List<byte[]> createByteArray(int length) {
Random random = new Random();
List<byte[]> resultList = new ArrayList<>();
for (int i = 0; i < length; i++) {
byte[] byteArray = new byte[4096];
byteArray[random.nextInt(4096)] = 1;
resultList.add(byteArray);
}
return resultList;
}
@GenerateMicroBenchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@OperationsPerInvocation(1_000)
public boolean test1k() {
return runBenchmark(input1k, this::byteArrayCheck);
}
@GenerateMicroBenchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@OperationsPerInvocation(10_000)
public boolean test10k() {
return runBenchmark(input10k, this::byteArrayCheck);
}
@GenerateMicroBenchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@OperationsPerInvocation(25_000)
public boolean test25k() {
return runBenchmark(input25k, this::byteArrayCheck);
}
@GenerateMicroBenchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@OperationsPerInvocation(50_000)
public boolean test50k() {
return runBenchmark(input50k, this::byteArrayCheck);
}
@GenerateMicroBenchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@OperationsPerInvocation(100_000)
public boolean test100k() {
return runBenchmark(input100k, this::byteArrayCheck);
}
@GenerateMicroBenchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@OperationsPerInvocation(200_000)
public boolean test200k() {
return runBenchmark(input200k, this::byteArrayCheck);
}
private static boolean runBenchmark(List<byte[]> arrays, Predicate<byte[]> method) {
boolean someUnrelatedResult = false;
for (byte[] array : arrays) {
someUnrelatedResult |= method.test(array);
}
return someUnrelatedResult;
}
private boolean byteArrayCheck(final byte[] array) {
long sum = 0L;
for (byte b : array) {
sum += b;
}
return (sum == 0);
}
public static void main(String[] args) throws RunnerException {
new Runner(new OptionsBuilder()
.include(".*" + MyBenchmark.class.getSimpleName() + ".*")
.forks(1)
.build()).run();
}
}
关于java - 为什么字节加法性能如此不可预测?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23828392/
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co
我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返
它不等于主线程的binding,这个toplevel作用域是什么?此作用域与主线程中的binding有何不同?>ruby-e'putsTOPLEVEL_BINDING===binding'false 最佳答案 事实是,TOPLEVEL_BINDING始终引用Binding的预定义全局实例,而Kernel#binding创建的新实例>Binding每次封装当前执行上下文。在顶层,它们都包含相同的绑定(bind),但它们不是同一个对象,您无法使用==或===测试它们的绑定(bind)相等性。putsTOPLEVEL_BINDINGput
我可以得到Infinity和NaNn=9.0/0#=>Infinityn.class#=>Floatm=0/0.0#=>NaNm.class#=>Float但是当我想直接访问Infinity或NaN时:Infinity#=>uninitializedconstantInfinity(NameError)NaN#=>uninitializedconstantNaN(NameError)什么是Infinity和NaN?它们是对象、关键字还是其他东西? 最佳答案 您看到打印为Infinity和NaN的只是Float类的两个特殊实例的字符串
如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象
关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion为什么SecureRandom.uuid创建一个唯一的字符串?SecureRandom.uuid#=>"35cb4e30-54e1-49f9-b5ce-4134799eb2c0"SecureRandom.uuid方法创建的字符串从不重复?
我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/