在不到4秒的时间内执行100k次迭代

时间:2016-12-21 08:06:55

标签: java performance time hashmap

我需要迭代HashMap包含10^5转的整数。我随机生成整数。然后,我对该整数执行所需的算术运算。之后,我正在检查HashMap是否包含此Integer =>如果它包含这个整数,我增加整数并重新检查HashMap是否包含新的整数,直到HashMap中不存在整数。如果它不包含整数,我将整数添加到HashMap。

我在下面添加了我的代码。 “开始”和“结束”注释之间的代码需要太长时间。 如果我在{start'和'end'之间comment这段代码,它会在不到一秒的时间内执行。 因此,时间不会在Random.nextInt()HashMap.containsKey()

中消耗

MyProgram.java

import java.util.HashMap;
import java.util.Random;

public class MyProgram {

    public static void main(String[] args) {

        long total = 0;
        int randomInt;
        int count = 100000;
        int divider = 3;
        Random random = new Random();   

        HashMap<Integer, Integer> map = new HashMap<>();

        for(int i=0; i < count; i++){

            randomInt = random.nextInt(count);

            // start
            int value1 = randomInt / divider;
            int value2 = (randomInt % divider != 0) ? 1 : 0;
            randomInt = value1 + value2;
            // end

            while(map.containsKey(randomInt)){
                randomInt++;
            }

            map.put(randomInt, 0);  // don't care about value
            total += randomInt;
        }

        System.out.println("Total : " + total);
    }
}

此实施需要30秒以上。

如果您认为它们很快,我可以使用List,Arrays,ArrayLists等。请举例说明。

5 个答案:

答案 0 :(得分:4)

由于您不关心存储在地图中的值,而只关心整数键,只需使用JsonConvert.DeSerializeObject<MyClass1>(target)

  • 用适当大小的BitSet替换地图:

    BitSet
  • 使用BitSet bits = new BitSet(count); 方法查找下一个未设置位:

    nextClearBit
  • 然后设置该位:

    randomInt = bits.nextClearBit(randomInt);
    

这对我很快完成(0.16s,在Ideone中):Ideone demo

我没有测量过,但我猜测OP代码缓慢的主要原因是行中的隐式对象创建:

bits.set(randomInt);

由于地图包含引用,因此必须将while(map.containsKey(randomInt)){ 加到randomInt;只保证在-128到127范围内的整数被缓存,因此这将导致创建大量对象。 Integer避免创建对象,因为它在原始BitSet上运行。

这将在 Effective Java 2nd Ed 第5项:“避免创建不必要的对象”中进行讨论(查找“Hideously slow program!”的位置。)

答案 1 :(得分:2)

问题不在于除法代码,问题是由于在尝试插入randomInt之前为map生成较小的范围,因此会产生更多的碰撞

如果您查看代码的以下变体

        for(int i=0; i < count; i++){

            randomInt = random.nextInt(33333);

            // start
//            int value1 = randomInt / divider;
//            int value2 = (randomInt % divider != 0) ? 1 : 0;
//            randomInt = value1 + value2;
            // end

            while(map.containsKey(randomInt)){
                randomInt++;
            }

            map.put(randomInt, 0);  // don't care about value
            total += randomInt;
        }

您将看到它与执行分割的代码大致相同。所以你应该考虑你的插入策略,看看你是否可以改进它。 (我无法帮助你,因为我还没有完全理解你用你的代码想要实现的目标)。

答案 2 :(得分:1)

问题在于内部while(map.containsKey(randomInt))循环运行至少30亿次(查看下面粘贴的输出中的Entered变量的值),因为与最终的数字发生了巨大的冲突计算后生成。

int value1 = randomInt / divider;
int value2 = (randomInt % divider != 0) ? 1 : 0;
randomInt = value1 + value2;

此代码在循环100000次时生成许多常用值,并且与自动装箱一起使用可能会导致性能问题。

您可以检查发生碰撞时执行while循环的时间。

int randomInt;
int count = 100000;
int divider = 3;
long entered = 0;
Random random = new Random();   

HashMap<Integer, Integer> map = new HashMap<>();

for(int i=0; i < count; i++){

    randomInt = random.nextInt(count);

    // start
    int value1 = randomInt / divider;
    int value2 = (randomInt % divider != 0) ? 1 : 0;
    randomInt = value1 + value2;
    // end

    while(map.containsKey(randomInt)){
        entered++;
        randomInt++;
    }

    map.put(randomInt, 0);  // don't care about value
    total += randomInt;
}

System.out.println("Total : " + total);
System.out.println("Entered : " + entered);

输出

Total : 4999950000
Entered : 3335662228

因此,您应该重新审视value1+value2的逻辑,而只使用random.nextInt(count)并计算您想要生成的范围。

randomInt = random.nextInt(count);
while(map.containsKey(randomInt)){
    randomInt = random.nextInt(count);
}

答案 3 :(得分:1)

一些微不足道的事情:

  • 使用HashSet&lt;&gt;如果你不关心价值,而不是地图。
  • 将评论之间的部分替换为:randomInt = (randomInt + divider - 1) / divider;

这些都是次要的事情,但评论之间的部分可能不是您的绩效问题的主要原因。

因为你在0&lt; = n&lt; 0的范围内生成100K数字了。 100K,你的值会非常密集,所以我希望你会在相当长的时间内进行迭代。你可能会更好地维护一个Intervals数组,如下所示:

import java.util.HashSet;
import java.util.Random;

class Interval {
  int min, max;
  public Interval(int min, int max) {
    this.min = min;
    this.max = max;
  }

  public String toString() {
    return "[" + min + "," + max + "]";
  }
}

public class MyProgram {
  private static void checkConsistency(Interval[] intv) {
    for(int i=0; i<intv.length; i++) {
      Interval v = intv[i];
      if (v != null && (i < v.min || i > v.max)) {
        throw new Error(i + " -> " + v);
      }
    }
  }

  public static void main(String[] args) {
    long total = 0;
    int randomInt;
    int count = 100000;
    int divider = 3;
    Random random = new Random();
    HashSet<Integer> hs = new HashSet<>();
    Interval[] data = new Interval[count];

    for(int i=0; i < count; i++){
        randomInt = random.nextInt(count);
        // start
        randomInt = (randomInt + divider -1) / divider;
        // end
        Interval intv = data[randomInt];
        if (intv != null) {
          randomInt = intv.max + 1;
        }
        int idx = randomInt < count ? randomInt : count - 1;
        hs.add(randomInt);
        Interval pre = randomInt > 0 ? data[randomInt-1] : null;
        Interval post = randomInt < count-1 ? data[randomInt+1] : null;
        if (pre == null && post == null) {
          data[idx] = new Interval(randomInt, randomInt);
        } else if (pre != null && post != null) {
          if (pre.max-pre.min < post.max-post.min) {
            for (int j=pre.min; j <= pre.max; j++) {
              data[j] = post;
            }
            data[idx] = post;
          } else {
            for (int j=post.min; j <= post.max; j++) {
              data[j] = pre;
            }
            data[idx] = pre;
          }
          data[idx].min = pre.min;
          data[idx].max = post.max;
        } else if (pre != null) {
          data[idx] = pre;
          data[idx].max = randomInt;
        } else {
          data[idx] = post;
          data[idx].min = randomInt;
        }
        // just for verifying consistency
        checkConsistency(data);
        total += randomInt;
    }
      System.out.println("Total : " + total);
  }
}

答案 4 :(得分:0)

如果您不关心这些值(当然,如果您的程序中不需要它),请使用HashSet代替HashMap。它具有与HashMap键列表相同的行为。没有重复的值。

这意味着如果您的集合已经包含值102并且生成的下一个随机整数也是102,则将其添加到集合中将不执行任何操作。 (对于Hashmap,它只会替换键/值对,但如果您的值始终为0,则不会引人注意)。

因此,您不需要检查您的集合是否包含新的随机int并且此部分变得无用:

while(map.containsKey(randomInt)){
            randomInt++;
}

另外,这部分时间在地图上以containsKey循环为时间,因此当地图中有许多键时,你可以循环一段时间(如果增量值也在列表中,会发生什么?和下一个递增的值?依此类推?)。 这应该会让你的编程速度更快。

现在您仍然可以通过删除value1value2的声明来优化您的代码,但时间消耗低于您的while循环,这样可能不会很大差。

最后,您的代码应如下所示:

int randomInt;
int count = 100000;
int divider = 3;
Random random = new Random();   

HashSet<Integer> set = new HashSet<>();

while(set.size()<count){

    randomInt = random.nextInt(count);

    // start
    map.add((randomInt/divider) + ((randomInt % divider != 0) ? 1 : 0));
    // end
}