为什么Groovy中的Map比Array更具伸缩性?

10

今天我遇到了这个问题,我无法弄清为什么当Groovy数组变大时,它不能比Map更好地扩展。

在我的示例中,我创建了一个Map(LinkedHashMap)和一个字符串数组(String[])。然后,我从0迭代到10 ^ 7将i插入到Map或Array中。我执行了10次以确保异常值不会破坏结果。

int max = 10**7
int numTests = 10

long totalTimeMap = 0
long totalTimeArray = 0

numTests.times{
    long start = System.currentTimeMillis()

    Map m = [:]
    max.times {
        m[it] = "${it}"
    }

    long end = System.currentTimeMillis()
    totalTimeMap += (end-start)
}

numTests.times {
    long start = System.currentTimeMillis()

    String[] s = new String[max]
    max.times{
        s[it] = "${it}"
    }

    long end = System.currentTimeMillis()
    totalTimeArray += (end-start)
}

println "Map: ${totalTimeMap}"
println "Array: ${totalTimeArray}"

输出结果出乎意料,因为Map的性能优于Array:

Map: 49361
Array: 101123

我在Java中做了同样的实验:

public static void main(String[] args) {

        int max = 10000000;
        int numTests = 10;

        long totalTimeMap = 0;
        long totalTimeArray = 0;

        for(int i=0; i<numTests; i++){
            long start = System.currentTimeMillis();

            Map m = new LinkedHashMap();
            for(int j=0; j<max; j++){
                m.put(j, "" + j);
            }

            long end = System.currentTimeMillis();
            totalTimeMap += (end-start);
        }

        for(int i=0; i<numTests; i++){
            long start = System.currentTimeMillis();

            String[] s = new String[max];
            for(int j=0; j<max; j++){
                s[j] = "" + j;
            }

            long end = System.currentTimeMillis();
            totalTimeArray += (end-start);
        }

        System.out.println("Map: " + totalTimeMap);
        System.out.println("Array: " + totalTimeArray);
    }

并且预期的输出结果为(数组比Map更快):

Map: 34564
Array: 12822

我的问题是:为什么在使用Groovy时,Map比Array更快?


1
这在多次执行中都是一致的吗?只是有点追求严谨。 - christopher
是的,你可以将最大值设置为10^6或10^5,这样示例运行会更快。 - lfrodrigues
2
还要确保它实际上是一个数组;Groovy [] 通常创建一个列表,即 ArrayList,这将具有调整大小的惩罚。 - Dave Newton
2
你的性能分析有缺陷。第二个测试可能受益于第一个测试预热缓存。每个测试需要在自己的JVM实例中运行。 - Steve Kuo
还应该测试总时间,而不是对增量时间求和。 - Steve Kuo
显示剩余2条评论
1个回答

20

当您在Groovy中将字符串添加到数组中时,您正在创建一个模板化字符串,然后在完成模板化之后将其转换回Java字符串,因为它必须适合String[]

对于Map版本,您只需存储一个模板化字符串,因此无需进行评估...

以下是基准测试代码:

@Grab('org.gperfutils:gbench:0.4.3-groovy-2.4')

int max = 10000

new groovyx.gbench.BenchmarkBuilder().run {
    'Array' {
        String[] s = new String[max]
        max.times { int idx ->
            s[idx] = Integer.toString(idx)
        }  
    }
    'List' {
        def s = []
        max.times{
            s << "${it}"
        }  
    }
    'Map' {
        Map m = [:]
        max.times {
            m[it] = "${it}"
        }
    }
}.prettyPrint()

当我们在Array方法中不使用GroovyStrings时,会得到以下结果:

* Groovy: 2.4.3
* JVM: Java HotSpot(TM) 64-Bit Server VM (25.45-b02, Oracle Corporation)
    * JRE: 1.8.0_45
    * Total Memory: 800.5 MB
    * Maximum Memory: 1820.5 MB
* OS: Mac OS X (10.10.3, x86_64)

Options
=======
* Warm Up: Auto (- 60 sec)
* CPU Time Measurement: On

          user  system      cpu     real

Array  1819502    6491  1825993  1833209
List   1697948    6533  1704481  1724448
Map    2040521    8932  2049453  2116760

1
太棒了!映射:38225 数组:34171。谢谢! - lfrodrigues
哈哈,它们两个都很棒,一个用于分析性能,另一个用于早餐;-) - tim_yates

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接