Java 2D数组填充-无害优化导致了严重的减速
我试图通过计算两个元素(相对于主对角线相反)的每一个和来优化每个元素的索引和填充正方形二维Java数组。但是我没有加速,或者至少没有可比的性能,而是让代码慢了23倍 我的代码:Java 2D数组填充-无害优化导致了严重的减速,java,arrays,performance,multidimensional-array,benchmarking,Java,Arrays,Performance,Multidimensional Array,Benchmarking,我试图通过计算两个元素(相对于主对角线相反)的每一个和来优化每个元素的索引和填充正方形二维Java数组。但是我没有加速,或者至少没有可比的性能,而是让代码慢了23倍 我的代码: @State(Scope.Benchmark) @BenchmarkMode(Mode.AverageTime) @OperationsPerInvocation(ArrayFill.N * ArrayFill.N) @OutputTimeUnit(TimeUnit.NANOSECONDS) public class A
@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OperationsPerInvocation(ArrayFill.N * ArrayFill.N)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class ArrayFill {
public static final int N = 8189;
public int[][] g;
@Setup
public void setup() { g = new int[N][N]; }
@GenerateMicroBenchmark
public int simple(ArrayFill state) {
int[][] g = state.g;
for(int i = 0; i < g.length; i++) {
for(int j = 0; j < g[i].length; j++) {
g[i][j] = i + j;
}
}
return g[g.length - 1][g[g.length - 1].length - 1];
}
@GenerateMicroBenchmark
public int optimized(ArrayFill state) {
int[][] g = state.g;
for(int i = 0; i < g.length; i++) {
for(int j = 0; j <= i; j++) {
g[j][i] = g[i][j] = i + j;
}
}
return g[g.length - 1][g[g.length - 1].length - 1];
}
}
问题是:
如何解释如此巨大的性能下降 另一方面,Java版本是1.8.0-ea-b124,64位3.2 GHz AMD处理器,基准测试在一个线程中执行。旁注:您的“优化”版本可能不会更快,即使我们将所有可能的问题都放在一边。现代CPU中有多种资源,其中一种资源饱和可能会阻止您进行任何改进。我的意思是:速度可能是内存限制的,在一次迭代中尝试写两倍的速度可能根本不会改变任何东西 我可以看到三个可能的原因:
- 您的访问模式可能强制执行绑定检查。在“简单”循环中,它们可以被明显消除,而在“优化”循环中,只有当阵列是正方形时,它们才能被消除。是的,但是这些信息只在方法之外可用(此外,不同的代码可能会改变它!)
- “优化”循环中的内存局部性不好。它访问基本上是随机的内存位置,因为Java中没有什么像2D数组(只有一个数组数组,
是其快捷方式)。按列迭代时,您只使用每个加载缓存线中的一个newint[N][N]
,即64个字节中的4个字节int
- 内存预取器可能与您的访问模式有问题。包含8189*8189*4字节的数组太大,无法放入任何缓存。现代CPU有一个预取器,当它发现一个常规的访问模式时,可以预先加载缓存线。预取器的功能差别很大。这在这里可能无关紧要,因为您只是在写,但我不确定是否有可能写入尚未获取的缓存线
g[j][i] = i + j;
而不是
g[i][j] = i + j;
这种“无害”的变化是一种性能下降:
Benchmark Mode Samples Mean Mean error Units
o.o.j.s.ArrayFillBenchmark.optimized avgt 20 10.484 0.048 ns/op
o.o.j.s.ArrayFillBenchmark.reversed avgt 20 20.989 0.294 ns/op
o.o.j.s.ArrayFillBenchmark.simple avgt 20 0.693 0.003 ns/op
我写了一个比“简单”更快的版本。但是,我不知道为什么它会更快。下面是代码:
class A {
public static void main(String[] args) {
int n = 8009;
long st, en;
// one
int gg[][] = new int[n][n];
st = System.nanoTime();
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++) {
gg[i][j] = i + j;
}
}
en = System.nanoTime();
System.out.println("\nOne time " + (en - st)/1000000.d + " msc");
// two
int g[][] = new int[n][n];
st = System.nanoTime();
int odd = (n%2), l=n-odd;
for(int i = 0; i < l; ++i) {
int t0, t1;
int a0[] = g[t0 = i];
int a1[] = g[t1 = ++i];
for(int j = 0; j < n; ++j) {
a0[j] = t0 + j;
a1[j] = t1 + j;
}
}
if(odd != 0)
{
int i = n-1;
int a[] = g[i];
for(int j = 0; j < n; ++j) {
a[j] = i + j;
}
}
en = System.nanoTime();
System.out.println("\nOptimized time " + (en - st)/1000000.d + " msc");
int r = g[0][0]
// + gg[0][0]
;
System.out.println("\nZZZZ = " + r);
}
}
有人能解释一下为什么它更快吗?
图片:
int[][]==值数组的数组
int[]==值数组
A类{
公共静态void main(字符串[]args){
int n=5000;
int g[][]=新的int[n][n];
朗街,恩;
//一个
st=系统.nanoTime();
对于(int i=0;i
两次71.998012 msc
两次551.664166 msc
3次63.74851 msc
4次57.215167 msc
另外,我不是java规范=)
我明白了,您为第二次运行分配了一个新阵列,但您是否尝试更改“未优化”和“优化”运行的顺序菲克托 我改变了它们的顺序,并对其进行了一些优化:
class A {
public static void main(String[] args) {
int n = 8009;
double q1, q2;
long st, en;
// two
int g[][] = new int[n][n];
st = System.nanoTime();
int odd = (n%2), l=n-odd;
for(int i = 0; i < l; ++i) {
int t0, t1;
int a0[] = g[t0 = i];
int a1[] = g[t1 = ++i];
for(int j = 0; j < n; ++j, ++t0, ++t1) {
a0[j] = t0;
a1[j] = t1;
}
}
if(odd != 0)
{
int i = n-1;
int a[] = g[i];
for(int j = 0; j < n; ++j, ++i) {
a[j] = i;
}
}
en = System.nanoTime();
System.out.println("Optimized time " + (q1=(en - st)/1000000.d) + " msc");
// one
int gg[][] = new int[n][n];
st = System.nanoTime();
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++) {
gg[i][j] = i + j;
}
}
en = System.nanoTime();
System.out.println("One time " + (q2=(en - st)/1000000.d) + " msc");
System.out.println("1 - T1/T2 = " + (1 - q1/q2));
}
}
您可能想读一下:@Mysticial我不相信缓存争用会导致x23速度下降如果将
8189
推离8192
,性能会发生什么变化?在您的“优化”版本中,您减少了比较和迭代的时间,但增加了(大量)阵列访问,包括,这三种中最贵的一种。请参阅:@Mysticial我希望内部数组正好占用8页,但显然我忘了应用-XX:+UseCompressedOops
,它们占用了8页+4字节:(注意,我没有从“大”中读取任何内容)数组内存处于紧循环中,因此你的第2点和第3点是不相关的。我说得不对?@leventov:你说得不对,但CPU可能必须这样做。好吧,缓存和内存之间的所有通信都使用缓存线作为最小的单元。CPU可以请求特定的地址,并首先获取缓存线的相应部分,但总是这样获取一整行。我想,编写并不是更灵活。我可能没有抓住你评论的重点。你确实可以访问g[j][I]
这在理想情况下意味着像4*(8189*I+j)
这样的地址。这已经够糟糕了,但正如我所写的,Java中没有2D数组,所以你基本上可以访问一个随机位置。maaartinus是正确的,a在(几乎)所有情况下,“存储”到内存本质上都是一种增强的读取操作—您获取行,并保证所有权以保持缓存一致性。我知道,您为第二次运行分配了一个新数组,但您是否尝试更改“未优化”和“优化”的顺序“运行?是的,它有点不同,但第二个版本更好。它很好。”
One time 165.177848 msc
Optimized time 99.536178 msc
ZZZZ = 0
class A {
public static void main(String[] args) {
int n = 8009;
double q1, q2;
long st, en;
// two
int g[][] = new int[n][n];
st = System.nanoTime();
int odd = (n%2), l=n-odd;
for(int i = 0; i < l; ++i) {
int t0, t1;
int a0[] = g[t0 = i];
int a1[] = g[t1 = ++i];
for(int j = 0; j < n; ++j, ++t0, ++t1) {
a0[j] = t0;
a1[j] = t1;
}
}
if(odd != 0)
{
int i = n-1;
int a[] = g[i];
for(int j = 0; j < n; ++j, ++i) {
a[j] = i;
}
}
en = System.nanoTime();
System.out.println("Optimized time " + (q1=(en - st)/1000000.d) + " msc");
// one
int gg[][] = new int[n][n];
st = System.nanoTime();
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++) {
gg[i][j] = i + j;
}
}
en = System.nanoTime();
System.out.println("One time " + (q2=(en - st)/1000000.d) + " msc");
System.out.println("1 - T1/T2 = " + (1 - q1/q2));
}
}
Optimized time 99.360293 msc
One time 162.23607 msc
1 - T1/T2 = 0.3875573231033026