【问题标题】:Extremely slow even with TLongObjectHashMap即使使用 TLongObjectHashMap 也非常慢
【发布时间】:2015-01-06 10:43:46
【问题描述】:

我需要将大约 2000 万个条目放入 HashMap。我选择了 TLongObjectHashMap 为:Why is Java HashMap slowing down?

代码如下:

StringBuilder sb = new StringBuilder("");
StringBuilder value = new StringBuilder("");
TLongObjectHashMap<String> map = new TLongObjectHashMap<String>();

in = new FileInputStream(new File(inputFile));
br = new BufferedReader(new InputStreamReader(in), 102400);
for (String inLine; (inLine = br.readLine()) != null;) {
    sb.setLength(0);
    for (i = 0; i < 2; i++) {
                for (j = 1; j < 12; j++) {
                    sb.append(record.charAt(j));
                }
            }

            for (k = 2; k < 4; k++) {
                value.append(record.charAt(k));
            }
            for (k = 7; k < 11; k++) {
                value.append(record.charAt(k));
            }
    map.put(Long.parseLong(sb.toString()), value.toString());
    value.delete(0, value.length());
}

我使用了 GNU Trove。尽管如此,它还是变得非常缓慢,几乎停止在大约 1500 万个条目上。目前还没有 OutOfMemoryError。有什么问题?

我没有选择为此使用 DB。

注意:像 1、12、2,4 等的值在此循环之前计算并存储在一个变量中,该变量将在此处使用。我现在只是用一些值替换了它们

【问题讨论】:

  • 运行它的 JVM 的最大堆大小是多少?
  • 您是否尝试过开启 GC 跟踪以查看在 GC 中花费了多少时间? value 变量(StringBuilder)的用途是什么?
  • 2000 万个条目可能正在突破极限,您是否考虑过外部存储,例如数据库?
  • 那不是 GC 跟踪。这只是将 VM 的最大堆设置为 512M,我怀疑这还不够。使用-Xloggc:gc.txt 登录到文件。
  • 当然可以,但是你可以猜猜,如果你是推2000万次,初始容量可以设置在2000000左右。这会在每次插入数据之前预先分配。在我看来,这将减少性能问题。

标签: java dictionary


【解决方案1】:

我使用了 GNU Trove。尽管如此,它还是变得非常缓慢,几乎停止在大约 1500 万个条目上。目前还没有 OutOfMemoryError。有什么问题?

问题在于您是在做出假设而不是验证它们。

而且你没有分析你的代码。您的真实代码,而不是您在此处发布的半编辑内容(提示:当变量名称不匹配时,很明显它不是真实代码)。

是的,您正在编写低效的代码。例如,用于复制字符的那些循环重复 String.substring()。你已经被告知了。但它被埋在大量的 cmets 中,你可能错过了它。另一个好的评论是使用这些子字符串的简单连接,而不是使用StringBuilder

但真正的问题是假设您的地图效率低下,基于您在互联网上阅读的内容,并且没有采取任何措施来挑战该假设。我可以保证从磁盘读取记录所花费的时间远远大于为每条记录在映射中插入一个值的时间。

您需要做的是向自己证明这一点。分析您的代码是执行此操作的最佳方法,但您也可以分离出程序的各个部分。使用如下所示的简单循环来了解您的地图的实际速度(我使用了HashMap,因为我没有安装 Trove 库;用 100,000,000 个条目填充地图大约需要 2 分钟)。我会留给你写一个类似的测试来从你的文件中读取数据。

private static Map<Long,String> fillMap(int items)
{
    Map<Long,String> map = new HashMap<Long,String>(items);
    Random rnd = new Random();

    long start = System.currentTimeMillis();

    for (int ii = 0 ; ii < items ; ii++)
    {
        map.put(new Long(rnd.nextLong()), new String("123456789012345678901234567890"));
    }

    long finish = System.currentTimeMillis();
    double elapsed = ((finish - start) / 1000.0);
    System.out.format("time to produce %d items: %8.3f seconds (map size = %d)\n", items, elapsed, map.size());
    return map;
}

【讨论】:

    【解决方案2】:

    我不相信JDK内置的HashMap不能处理这个。我看到了 2 个问题

    • 随着地图的增长不断地重新散列
    • 非必需的字符串生成器对象

    当底层存储阵列负载率达到 75% 时会进行重新散列

    DEFAULT_INITIAL_CAPACITY = 16;  
    DEFAULT_LOAD_FACTOR = 0.75;  
    THRESHOLD = DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR;
    

    我假设跟随的工作量成倍减少,并且做同样的事情

    double expected_maximal_number_of_data = 30000000d;
    int capacity = (int) ((expected_maximal_number_of_data)/0.75+1);
    HashMap<Long, String> map = new HashMap<Long, String>(capacity);
    for (String inLine; (inLine = br.readLine()) != null;) {
        Long key = Long.parseLong(record.substring(1, 12));
        String value = record.substring(2, 4) + record.substring(7, 11);
        map.put(key, value);
    }
    

    如果您的计算机有 2gb 内存,您应该没有问题,估计完成时间是

    【讨论】:

    • 其实是更多的工作。 put() 已经检查该项目是否存在。
    • @EJP 这不是重点,你应该使用 map.key() 来代替。我只是试图给出微妙的暗示,他应该提到如何处理重复项或为什么首先使用 hashmap。
    • @Margus 输入中肯定没有重复项。它们已被删除。
    • @Margus 为每个循环创建一个 long 和 String?
    • @Harbinger 是的,我可以做到。您也可以事先声明它们,但实际上并没有摊销时间差。另请注意,expected_maximal_number_of_data 应该大于您的假设。为了避免甚至 1 rehash 将其设置为 ex: 30m。
    猜你喜欢
    • 2018-02-02
    • 2013-03-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-21
    • 1970-01-01
    • 2012-07-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多