如何评估哈希表实现? (使用HashMap作为参考)

时间:2015-07-23 19:35:32

标签: java performance memory-management hashmap hashtable

问题:

  • 我需要比较2个哈希表实现(基本上是HashMap与另一个)并得出一个合理的结论。

  • 我对100%的准确度并不感兴趣,只是在我的估计中朝着正确的方向发展。

  • 我不仅对每个操作的区别感兴趣,而且主要对哈希表作为"整体"感兴趣。

  • 我对速度没有严格要求,所以如果其他实施方案合理慢,我可以接受它,但我期望/要求内存使用情况更好(因为其中一个哈希表由原始表支持)。

到目前为止我做了什么:

最初我创建了自己的定制"基准"有循环和许多调用提示gc以获得差异的感觉,但我在网上阅读使用标准工具更可靠/适当。
我的方法示例(MapInterface只是一个包装器,所以我可以在实现之间切换。):

int[] keys = new int[10000000];
String[] values = new String[10000000];  
for(int i = 0; i < keys.length; ++i) {  
   keys[i] = i;  
   values[i] = "" + i;
}

if(operation.equals("put", keys, values)) {  
   runPutOperation(map);  
}  

public static long[] runOperation(MapInterface map, Integer[] keys, String[] values) {  
    long min = Long.MAX_VALUE;  
    long max = Long.MIN_VALUE;  
    long run = 0;  
    for(int i = 0; i < 10; ++i) {  
       long start = System.currentTimeMillis();  
       for(int i = 0; i < keys.length; ++i) {          
            map.put(keys[i], values[i]);  
        }
        long total = System.currentTimeMillis() - start;  
        System.out.println(total/1000d + " seconds");    
        if(total < min) {
            min = time;
        }
        if(total > max) {
            max = time;
         }
         run += time;  
         map = null;  
         map = createNewHashMap();
         hintsToGC();    
   }  
  return new long[] {min, max, run};
 }     


public void hintsToGC() {  
    for(int i = 0; i < 20; ++i) {
            System.out.print(". ");
            System.gc();            
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {              
                e.printStackTrace();
          }           
       } 
}


private HashMapInterface<String> createNewHashMap() {  
    if(jdk) {  
        return new JDKHashMapWrapper<String>();  
    }  
    else {
        return new AlternativeHashMapWrapper<String>();   
    }  
 }  



public class JDKHashMapWrapper implements HashMapInterface<String>  {
    HashMap<Integer, String> hashMap;         
    JDKHashMapWrapper() {   
       hashMap = new HashMap<Integer, String>();  
    }  
    public String put(Integer key, String value)  {
       return hashMap.put(key, value);  
    }  
 //etc  
}

(我想测试putgetcontains和内存利用率)
我可以通过使用我的方法确定合理的测量结果吗? 如果不是最适合使用的工具以及如何使用?

更新
- 我还使用SecureRandom测试随机数(也是~10M随机数) - 当哈希表调整大小时,我打印实际表的哈希表/大小的逻辑大小以获得加载因子

更新
对于我的具体案例,我对整数感兴趣的是我的方法有哪些陷阱?

@ dimo414评论后更新

  

至少哈希表作为&#34;整体&#34;没有意义

我的意思是哈希表在运行时和内存消耗中的各种负载下的行为。

  

每种数据结构都是不同方法的权衡

我同意。 我的权衡是对内存改进的可接受的访问惩罚

  

您需要确定您对验证

感兴趣的功能

1)put(key,value);
2)得到(关键,价值);
3)containsKey(键);
4)当哈希表中有许多条目时,以上所有内容

3 个答案:

答案 0 :(得分:1)

使用哈希表的一些关键考虑因素是“桶”分配的大小,冲突解决策略以及数据的形状。实质上,哈希表获取应用程序提供的密钥,然后将其哈希值小于或等于分配的桶数。当两个键值散列到同一个存储桶时,实现必须解决冲突并返回正确的值。例如,可以为每个存储桶分配一个已排序的链接列表,并搜索该列表。

如果您的数据碰巧有很多冲突,那么您的性能将受到影响,因为哈希表实现将花费太多时间来解决冲突。另一方面,如果你有很多桶,你可以用内存为代价来解决碰撞问题。此外,如果条目数量大于一定数量,Java的内置HashMap实现将“重新散列” - 我认为这是一个值得避免的昂贵操作。

由于您的关键数据是1到10M的正整数,因此您的测试数据看起来不错。我还要确保针对给定测试将不同的哈希表实现初始化为相同的桶大小,否则它不是公平的比较。最后,我会在相当大的范围内改变桶大小,并重新运行测试以查看实现如何改变其行为。

答案 1 :(得分:1)

据我了解,您对测试中的地图的操作执行时间和内存消耗感兴趣。

我将从内存消耗开始,因为这种接缝根本无法回答。我建议使用一个名为Classmexer的小型库。当我需要获得任何对象的100%正确内存消耗时,我个人使用它。它具有java代理方法(因为它使用Instrumentation API),这意味着您需要将它作为参数添加到执行测试的JVM中:

-javaagent: [PATH_TO]/classmexer.jar

Classmexer的使用非常简单。在任何时候,您都可以通过执行以下操作来获取内存消耗:

MemoryUtil.deepMemoryUsageOf(mapIamInterestedIn, VisibilityFilter.ALL)

请注意,使用可见性过滤器,您可以指定是否应对对象(我们的地图)以及所有其他可通过引用进行内存计算。那是 VisibilityFilter.ALL 的用途。但是,这意味着您获得的大小包括您用于键和值的所有对象。因此,如果您有100个整数/字符串条目,则报告的大小也将包含这些条目。

对于时间方面,我会提出JMH工具,因为这个工具是为微工作台标记而制作的。网上有很多例子,例如this article有地图测试示例可以指导你很好。

请注意,当您拨打Classmexer的 Memory Util 时,我应该小心,因为如果您在测量时间内调用时间结果,它会干扰时间结果。此外,我确信还有许多类似于Classmexer的其他工具,但我喜欢它,因为它小而简单。

答案 2 :(得分:0)

我刚刚做了类似的事情,最后我在Netbeans IDE中使用了内置的分析器。您可以获得有关CPU和内存使用情况的详细信息。我最初在Eclipse中编写了我的所有代码,但是Netbeans有一个导入Eclipse项目的导入功能,如果你的情况可能也是如此,它就没有问题。

对于计时,您可能还会查看Apache Commons中的StopWatch类。这是一种更直观的跟踪目标操作时间的方法,例如:

StopWatch myMapTimer = new StopWatch();
HashMap<Integer, Integer> hashMap = new HashMap<>();

myMapTimer.start();
for (int i = 0; i < numElements; i++)
    hashMap.put(i, i);
myMapTimer.stop();

System.out.println(myMapTimer.getTime()); // time will be in milliseconds