我在java中的长数组上执行了一个简短的基准测试,结果非常奇怪。看起来随机写入的顺序读取比使用顺序写入的随机读取更快 - 一半时间。有谁知道为什么??
以下两种方法在按顺序读取时随机写入一些长整数数组(使用-Xmx2G左右运行),并在随机写入时按顺序读取:
import java.util.Random;
public class Scratch {
static Random random = new Random();
static long[] arr = new long[100000000];
static void seqReadRandWrite() {
for(int i=0;i<arr.length;i++) {
int at = random.nextInt(arr.length);
arr[at] = arr[i];
}
}
static void seqWriteRandRead() {
for(int i=0;i<arr.length;i++) {
int at = random.nextInt(arr.length);
arr[i] = arr[at];
}
}
public static void main(String[] args) throws Exception {
seqWriteRandRead(); // warm up
long nanos = System.nanoTime();
seqReadRandWrite();
System.out.println("Time: " + (System.nanoTime()-nanos) + "ns");
nanos = System.nanoTime();
seqWriteRandRead();
System.out.println("Time: " + (System.nanoTime()-nanos) + "ns");
}
}
我的笔记本上的结果是
时间:2774662168ns
时间:6059499068ns
这意味着它的随机写入速度是读取的两倍...或者?我的笔记本坏了吗?
ps。:这并不是一个基准,虽然涵盖了关于基准测试的链接建议中的大多数要点。即使我多次运行已经200,000,000次的操作,这些结果仍然保持不变。似乎(似乎!)将内存从随机位置移动到顺序块比将内存从顺序位置移动到随机块更慢,至少使用这种大小的内存和上述方式。我想知道为什么?
答案 0 :(得分:3)
您的基准测试产生的数字不符合“它们有意义吗?”测试。在这种情况下,你应该总是加倍/三倍/四倍检查你的方法......之前把数字视为对现实的真实反映。
编写可靠的基准测试很难。在Java的情况下,它特别很难,因为Java平台的某些方面可能会在您的基准测量中引入系统性失真......除非您特别允许/补偿它们。
但是“检查你的方法论”规则适用于所有实验......尤其是那些产生看似不合理的结果的实验。 (就像中微子比光更快行进......)
另外需要注意的是,一旦你重写了基准来解释混淆因素,你可能仍然看到意外的数字。这里的问题是像这样的基准测试的性能很可能对L1和L2高速缓存的大小,高速缓存行的大小,不同级别的内存的相对速度以及它们与精确序列的相互作用等敏感。基准在紧密循环中产生的指令。
这些事情很复杂,难以分析,并且可能会产生违反直觉的行为。对我来说,不同的机器给出不同的测量性能并不奇怪。
因此,即使这些数据是真实的,从该基准测试中得出关于读取速度与写入速度的任何一般性结论仍然“不安全”。即使你将它们仅限于你的笔记本电脑也不行。
答案 1 :(得分:1)
总之,问题标题略有不正确。真相似乎是某些环境中的(例如我的和OP)随机数组写入比随机数组读取更快。但请注意,其他人并非如此。
基于@ JustinKSU的comment我将读写分开,发现随机写入比随机读取更快。结果如下。这似乎是原因,这里的集体意见似乎是缓存上的读取错误比写入错过更昂贵(如果写入中涉及任何缓存)。
在生产中,虽然有其他活动,但热点可能会发挥作用。
/cygdrive/c/Java/jdk1.7.0/bin/javac.exe Scratch.java && /cygdrive/c/Java/jdk1.7.0/bin/java Scratch
Starting
seqRead: 1273719725ns
seqRead: 1243055271ns
seqRead: 1245022497ns
seqRead: 1242868527ns
seqRead: 1241655611ns
randRead: 6900959912ns
randRead: 6965196004ns
randRead: 7379623094ns
randRead: 7020390995ns
randRead: 6938997617ns
seqWrite: 1266963940ns
seqWrite: 1250599487ns
seqWrite: 1246471685ns
seqWrite: 1230472648ns
seqWrite: 1246975416ns
randWrite: 3898382192ns
randWrite: 3897441137ns
randWrite: 3939947844ns
randWrite: 4207906037ns
randWrite: 4103594207ns
Compilation finished at Thu Jan 31 14:38:57
我的修改后的代码如下:
import java.util.Random;
public class Scratch {
static Random random = new Random();
static long[] arr = new long[100000000];
static void seqReadRandWrite() {
for(int i=0;i<arr.length;i++) {
int at = Math.abs(random.nextInt() % arr.length);
arr[at] = arr[i];
}
}
static void seqWriteRandRead() {
for(int i=0;i<arr.length;i++) {
int at = Math.abs(random.nextInt() % arr.length);
arr[i] = arr[at];
}
}
static void seqRead() {
int x = 0;
for(int i=0;i<arr.length;i++) {
int at = Math.abs(random.nextInt() % arr.length);
x += arr[i];
}
}
static void randRead() {
int x = 0;
for(int i=0;i<arr.length;i++) {
int at = Math.abs(random.nextInt() % arr.length);
x += arr[at];
}
}
static void seqWrite() {
for(int i=0;i<arr.length;i++) {
int at = Math.abs(random.nextInt() % arr.length);
arr[i] = at;
}
}
static void randWrite() {
for(int i=0;i<arr.length;i++) {
int at = Math.abs(random.nextInt() % arr.length);
arr[at] = at;
}
}
public static void main(String[] args) throws Exception {
// seqWriteRandRead(); // warm up
System.out.println("Starting");
long nanos = -1;
/*
for (int i = 0; i < 5; i++) {
nanos = System.nanoTime();
seqWriteRandRead();
System.out.println("WriteRandRead Time: " + (System.nanoTime()-nanos) + "ns");
nanos = System.nanoTime();
seqReadRandWrite();
System.out.println("ReadRandWrite Time: " + (System.nanoTime()-nanos) + "ns");
}
*/
for (int i = 0; i < 5; i++) {
nanos = System.nanoTime();
seqRead();
System.out.println("seqRead: " + (System.nanoTime()-nanos) + "ns");
}
for (int i = 0; i < 5; i++) {
nanos = System.nanoTime();
randRead();
System.out.println("randRead: " + (System.nanoTime()-nanos) + "ns");
}
for (int i = 0; i < 5; i++) {
nanos = System.nanoTime();
seqWrite();
System.out.println("seqWrite: " + (System.nanoTime()-nanos) + "ns");
}
for (int i = 0; i < 5; i++) {
nanos = System.nanoTime();
randWrite();
System.out.println("randWrite: " + (System.nanoTime()-nanos) + "ns");
}
}
}
<强>更新强>
@tomcarchrae在Linux上做了同样的测试,结果明显不同。下面,第一列是我测试的数字,第二列是Tom的:
seqRead: 1273719725ns 2810487542ns
seqRead: 1243055271ns 2780504580ns
seqRead: 1245022497ns 2746663894ns
seqRead: 1242868527ns 2746094469ns
seqRead: 1241655611ns 2763107970ns
randRead: 6900959912ns 23093543703ns
randRead: 6965196004ns 22458781637ns
randRead: 7379623094ns 24421031646ns
randRead: 7020390995ns 25880250599ns
randRead: 6938997617ns 26873823898ns
seqWrite: 1266963940ns 4226886722ns
seqWrite: 1250599487ns 4537680602ns
seqWrite: 1246471685ns 3880372295ns
seqWrite: 1230472648ns 4160499114ns
seqWrite: 1246975416ns 4008607447ns
randWrite: 3898382192ns 25985349107ns
randWrite: 3897441137ns 22259835568ns
randWrite: 3939947844ns 22556465742ns
randWrite: 4207906037ns 22143959163ns
randWrite: 4103594207ns 21737397817ns
答案 2 :(得分:1)
我相信这个基准对你来说完全没用。有许多测量参数需要考虑,你没有描述,以及你解决这个问题的方式是完全没有描述的。要对虚拟机,计算机,RAM速度,您同时处理的软件,对象类型或您复制的简单内容等实施速度做出任何结论,您必须了解有条不紊的方法。这个问题无法回答。您必须缩小您想要了解速度的具体情况。
特别是在使用随机数时,您无法得出任何结论。这大大增加了最佳,最差或平均情况复杂性的问题。
请查看算法的复杂性,然后继续搜索如何进行科学的运行时性能测量。我希望我能帮助你一点。
这第一个答案非常棒,可以帮助您理解。 How do I write a correct micro-benchmark in Java?
致以最诚挚的问候,
答案 3 :(得分:1)
答案在之前的评论中归结为内存访问模式效果。此博客post涵盖了随机读取的效果。写作不会受到类似的影响。
这不是Java问题(或者实际上是任何语言问题),而是您运行的硬件的现实(以及此处的常见现实)。这并不意味着你应该忽略它!虽然您的初始基准可能存在缺陷,但它仍然会对某些软件造成真正的问题,因此这是一个宝贵的教训。
结论并不是读取比写入更昂贵。这就是硬件不能很好地满足随机存储器访问的需要。这基本上是为什么LinkedList性能比ArrayList更糟糕的顺序访问,它们具有相同的计算复杂性,但是数组访问对链接列表没有的硬件强度起作用。
答案 4 :(得分:0)
您的实验已损坏,而不是您的笔记本电脑。请参阅此处以获取有关衡量效果的讨论和一些工具:Java performance timing library
以下是与您签约的一些结果。此外,我修改了你的代码,使其更加严谨和谨慎地进行测量。
我的环境是使用Sun JDK 1.6.0_38的Linux(Mint 14,它基于Ubuntu 12.10)
以1.5G的堆为例,即-Xmx1512
注意:有趣。可能是我的结果不同,因为下面的数组大小不同。将重新运行和更新。
不:结果相似,平均值差别不大。但有趣的是与短期的差异,即21092.5(/ 10 = 2109.2)与1645.2相比,由于内存分页,这可能会更慢。
static long[] arr = new long[100000000];
(原始数组大小)
写:DescriptiveStatistics:n:10 min:20893.0 max:22190.0 mean: 21092.5 std dev:390.90727800848117中位数:20953.5偏度:3.0092198852491543峰度:9.264808973899097
阅读:DescriptiveStatistics:n:10 min:21668.0 max:22736.0 mean: 21892.5 std dev:318.31509546359877中位数:21766.5偏度:2.5034216544466124峰度:6.560838306717343
我没有看到读取与写入之间的巨大差异。我将实验更改为在稍小的阵列上测量10次(结果是相同的读/写次数)。您可以使用更大尺寸的阵列或样本大小重新运行。
写:DescriptiveStatistics:n:10 min:1584.0 max:1799.0 mean: 1645.2 std dev:59.51619760853156中位数:1634.5偏度:2.137918517160786峰度:5.764166551997385
阅读:DescriptiveStatistics:n:10 min:1568.0 max:2202.0 mean: 1689.0 std dev:186.93908693000031中位数:1623.0偏度:2.770215113912315峰度:8.12245132320571
以下是代码的修改版本,可以执行更多示例:
import java.util.Random;
import org.apache.commons.lang.time.StopWatch;
import org.apache.commons.math.stat.descriptive.DescriptiveStatistics;
public class Test {
static Random random = new Random();
// static long[] arr = new long[100000000];
static long[] arr = new long[10000000];
static void seqReadRandWrite() {
for (int i = 0; i < arr.length; i++) {
int at = Math.abs(random.nextInt()) % arr.length;
arr[at] = arr[i];
}
}
static void seqWriteRandRead() {
for (int i = 0; i < arr.length; i++) {
int at = Math.abs(random.nextInt()) % arr.length;
arr[i] = arr[at];
}
}
public static void main(String[] args) throws Exception {
StopWatch timer = new StopWatch();
int count = 10;
// warm up
for (int i=0; i<3; i++){
seqReadRandWrite();
}
DescriptiveStatistics write = new DescriptiveStatistics();
for (int i=0; i<count; i++){
timer.reset();
timer.start();
seqReadRandWrite();
timer.stop();
write.addValue(timer.getTime());
}
System.out.println("Write: " + write);
// warm up
for (int i=0; i<3; i++){
seqWriteRandRead();
}
DescriptiveStatistics read = new DescriptiveStatistics();
for (int i=0; i<count; i++){
timer.reset();
timer.start();
seqWriteRandRead();
timer.stop();
read.addValue(timer.getTime());
}
System.out.println("Read: " + read);
}
}
答案 5 :(得分:0)
我的电脑上的结果:(ns / r / w)
seq read : 1.4
rnd read : 10x.x
seq write: 3.3
rnd write: 10x.x
和seqReadRandWrite和seqWriteRandRead在每个循环100ns时同样快。
所以这可能取决于硬件。还有VM设置。尝试java -server
并查看速度是否有所改善。