算术评估器中的垃圾收集过多

时间:2012-09-27 09:48:58

标签: android garbage-collection

我正在尝试创建一个Android应用程序,它可以绘制用户输入的简单数学函数(基本上是图形计算器)。 每次onDraw调用都需要每秒进行数百次算术运算(在屏幕上绘制以生成图形)。当我的代码评估表达式时,程序会大大减慢,当内置方法评估表达式时,应用程序运行时没有问题。

根据'LogCat',垃圾收集大约每秒发生12次,每次暂停应用程序大约15毫秒,每秒造成几百毫秒的冻结。我认为这是问题所在。

这是我的评估函数的简化版本。要评估的表达式名为“postfixEquation”,String ArrayList“list”在进程结束时保留最终答案。还有两个名为“digits”和“operators”的String数组,它们存储了能够使用的数字和符号:

String evaluate(String[] postfixEquation) {

    list.clear();

    for (int i = 0; i < postfixEquation.length; i++) {

        symbol = postfixEquation[i];

        // If the first character of our symbol is a digit, our symbol is a numeral
        if (Arrays.asList(digits).contains(Character.toString(symbol.charAt(0)))) {

            list.add(symbol);

            } else if (Arrays.asList(operators).contains(symbol))  {

                // There must be at least 2 numerals to operate on
                if (list.size() < 2) {
                    return "Error, Incorrect operator usage.";
                }

                // Operates on the top two numerals of the list, then removes them 
                // Adds the answer of the operation to the list
                firstItem = Double.parseDouble(list.get(list.size() - 1));
                secondItem = Double.parseDouble(list.get(list.size() - 2));
                list.remove(list.size() - 1);
                list.remove(list.size() - 1);

                if (symbol.equals(operators[0])){ 
                    list.add( Double.toString(secondItem - firstItem) );  
                } else if (symbol.equals(operators[1])) {
                    list.add( Double.toString(secondItem + firstItem) );
                } else if (symbol.equals(operators[2])) {
                    list.add( Double.toString(secondItem * firstItem) );
                } else if (symbol.equals(operators[3])) {
                    if (firstItem != 0) {
                        list.add( Double.toString(secondItem / firstItem) );
                    } else {
                        return "Error, Dividing by 0 is undefined.";
                    }
                } else {
                    return "Error, Unknown symbol '" + symbol + "'.";
                }
            }
        }

    // The list should contain a single item, the final answer 
    if (list.size() != 1) {
        return "Error, " + list has " + list.size() + " items left instead of 1.";
    }

    // All is fine, return the final answer
    return list.get(0);
}

操作中使用的数字都是字符串,因为我不确定是否可以在一个数组中保存多个类型(即字符串和双字符),因此猖獗的“Double.parseDouble”和“Double.toString”调用。

我如何减少此处发生的垃圾收集量?

如果有任何帮助,我一直在使用这些步骤来评估我的后缀表达式:http://scriptasylum.com/tutorials/infix_postfix/algorithms/postfix-evaluation/index.htm。 几周和几周我都无法解决这个问题。任何帮助,将不胜感激。谢谢。

4 个答案:

答案 0 :(得分:2)

Java中紧密循环的规则是不分配任何内容。你看到如此频繁的GC收集这一事实证明了这一点。

您似乎正在使用Double进行计算,然后转换为String。不要这样做,这对于性能来说太糟糕了,因为你创造了大量的字符串然后将它们抛出(加上你在字符串之间来回转换并且经常翻倍)。只需维护一个ArrayDeque<Double>并将其用作堆栈 - 这也可以使您免于执行可能也会破坏性能的阵列调整大小。

预编译输入方程式。将所有输入操作转换为enum个实例 - 它们比较快(只需要switch语句),甚至可以使用更少的内存。如果需要处理双精度数,请使用通用Object容器和instanceof,或者包含操作enumdouble的容器类。预编译使您无需在紧密循环中进行昂贵的测试。

如果你做这些事情,你的循环应该正确地飞行。

答案 1 :(得分:0)

可能你的列表操作是这个问题的根源。列表内部具有数组,根据列表中的数据量扩展/缩小数组。所以做大量的随机添加和删除将需要大量垃圾收集。

避免这种情况的解决方案是针对您的问题使用正确的List实现,在开头为列表分配足够的空间,以避免调整内部数组的大小并标记未使用的元素而不是删除它们

冻结症状是因为您在UIThread进行了计算。如果您不希望应用冻结,可能需要检查AsyncTask以在单独的线程上进行计算。

PS:看起来你在那里做了一些无用的操作......为什么parseDouble() secondItem

答案 2 :(得分:0)

您的UI线程中没有发生15ms的暂停,所以它们不应该影响性能。如果您的UI在执行方法时暂停,请考虑在另一个线程(使用AsyncTask)上运行它

要减少垃圾回收,您需要减少循环内分配的内存量。

我建议看看:

  1. 在循环外部执行Arrays.asList函数(理想情况下,只执行一次,例如构造函数或静态构造函数)
  2. 如果您的列表是LinkedList,请考虑将其更改为ArrayList
  3. 如果您的列表是ArrayList,请确保使用足够的容量对其进行初始化,以便不需要调整大小
  4. 考虑让你的List存储对象而不是字符串,然后你可以将你的符号和双打存储在其中,并且不需要从Double转换为String,而不是
  5. 考虑编写一个合适的解析器(但这是一个更多的工作)

答案 3 :(得分:0)

但是,你使用了很多字符串。虽然情况可能并非如此,但它始终是您可以查看的内容之一,因为Java使用String执行时髦的东西。如果你在输出时必须将字符串转换为double,那么就会有相当多的开销。

您需要将数据存储为String吗? (请注意,答案实际上可能是肯定的)大量使用临时字符串实际上会导致垃圾收集器经常被解雇。

小心过早优化。分析器和逐行运行功能可以提供帮助