【问题标题】:Exponentially increasing amounts of time to repeat a function重复功能的时间呈指数增长
【发布时间】:2013-06-29 13:01:41
【问题描述】:

我已经编写了自己的数学解析器,但由于某种原因,当我尝试分析解析器时,它需要越来越多的时间来解析。

为了测试,我使用了这个输入:Cmd.NUM_9,Cmd.NUM_0,Cmd.NUM_0,Cmd.DIV,Cmd.NUM_2,Cmd.ADD,Cmd.NUM_6,Cmd.MULT,Cmd.NUM_3

单次执行~1.7ms
3000 次重复 ~ 1,360 毫秒
6000 次重复 ~ 5,290 毫秒
9000 次重复 ~11,800 毫秒

分析器说 64% 的时间都花在了这个函数上: 这是我允许隐式乘法的函数。

private void enableImplicitMultiplication(ArrayList<Cmd> input) {
    int input_size = input.size();
    if (input_size<2) return;
    for (int i=0; i<input_size; i++) {
        Cmd cmd = input.get(i);
        if (i>0) {
            Cmd last = input.get(i-1);
            // [EXPR1, EXPR2] => [EXPR1, MULT, EXPR2]
            boolean criteria1 = Cmd.exprnCmds.contains(cmd) && Cmd.exprnCmds.contains(last);
            // [CBRAC, OBRAC] => [CBRAC, MULT, OBRAC]
            // [NUM_X, OBRAC] => [NUM_X, MULT, OBRAC]
            boolean criteria2 = cmd==Cmd.OBRAC && (last==Cmd.CBRAC || Cmd.constantCmds.contains(last));
            // [CBRAC, NUM_X] => [CBRAC, MULT, NUM_X]
            boolean criteria3 = last==Cmd.CBRAC && Cmd.constantCmds.contains(cmd);
            if (criteria1 || criteria2 || criteria3) {
                input.add(i++, Cmd.MULT);
            }
        }
    }
}

这是怎么回事??

我这样重复:

public static void main(String[] args) {
    Cmd[] iArray = {
        Cmd.NUM_9,Cmd.NUM_0,Cmd.NUM_0,Cmd.DIV,Cmd.NUM_2,Cmd.ADD,Cmd.NUM_6,Cmd.MULT,Cmd.NUM_3
    };
    ArrayList<Cmd> inputArray = new ArrayList<Cmd>(Arrays.asList(iArray));
    DirtyExpressionParser parser = null;
    int repeat=9000;
    double starttime = System.nanoTime();
    for (int i=0; i<repeat; i++) {
         parser = new DirtyExpressionParser(inputArray);
    }
    double endtime = System.nanoTime();
    System.out.printf("Duration: %.2f ms%n",(endtime-starttime)/1000000);
    System.out.println(parser.getResult());
}

构造函数如下所示:

public DirtyExpressionParser(ArrayList<Cmd> inputArray) {
    enableImplicitMultiplication(inputArray); //executed once for each repeat
    splitOnBrackets(inputArray); //resolves inputArray into Expr objects for each bracket-group
    for (Expr expr:exprArray) {
        mergeAndSolve(expr);
    }
}

【问题讨论】:

  • 分析器是否指出哪一行代码花费的时间最多?
  • 我认为这里的问题是 contains 的调用
  • 否 - 我使用 NetBeans IDE 进行分析,它只是给了我一个函数列表及其累积执行时间
  • 你能把 if (i>0) 放在 input.add() 附近吗?这可能使所有情况下的执行时间几乎相等。
  • 在 20000 次迭代后,它将开始检查包含 10s 的数千个事物的列表。经过一百万次迭代后,它将检查 1000000 个元素的输入列表。所以这是 O(N*N) (最坏条件)算法,使得执行时间呈指数增长。也许您可以将它们全部添加并在最后只检查一次,如果可能的话删除不必要的。

标签: java performance for-loop


【解决方案1】:

您的微基准代码完全错误:JVM 上的微基准测试本身就是一门手艺,最好留给专用工具,例如 jmh 或 Google Caliper。你不预热代码,不控制 GC 暂停,等等。

通过分析您的代码得出的一个细节是:

  1. 您对函数调用的所有重复重复使用相同的ArrayList
  2. 每个函数调用都可以向列表中插入一个元素;
  3. insertion 是对ArrayList 的重量级操作:必须复制插入元素之后的列表的全部内容。

您至少应该为每次调用创建一个新的ArrayList,但这不会使您的整个方法正确。

根据我们在 cmets 中的讨论,我诊断出您在理解代码时可能遇到的以下问题:

在 Java 中,不存在值是对象的变量。变量的值是对象的引用。因此,当您说new DirtyExpressionParser(inputArray) 时,构造函数不会收到它自己的列表的私有副本,而是对您在main 方法中实例化的唯一ArrayList引用。下一个构造函数调用得到这个相同的列表,但现在被前面的调用修改了。这就是为什么您的列表一直在增长的原因。

【讨论】:

  • +1 虽然列表似乎随着迭代次数的增加而增长,使得算法 O(大于 N,可能是 N^2)。
  • 虽然 NetBeans 内存分析器确实说 GC 花费了 0.2% 的时间?实际上在峰值时大约是 5%
  • @assylias 是的,我已经添加了一些涵盖add 操作的文本。它实际上是插入,这比仅仅增加输入的大小要糟糕得多。
  • 感谢您的更新,但我只是从网上自学的,所以我不知道这有什么问题:1.you reuse the same ArrayList for all repetitions of the function call;,如果您可以分享一些链接或详细说明的话有帮助吗?
  • 您在main 方法中创建一个ArrayList,并在每次重复时将对它的引用传递给DirtyExpressionParser 构造函数。因此,这个单一的ArrayList 正在被调用的代码修改,然后在下一次重复中传递修改后的版本。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多