1

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

根据“LogCat”,垃圾收集大约每秒发生 12 次,每次暂停应用程序大约 15 毫秒,导致每秒冻结数百毫秒。我认为这是问题所在。

这是我的评估器功能的提炼版本。要评估的表达式名为“postfixEquation”,String ArrayList“list”在过程结束时保存最终答案。还有两个名为“digits”和“operators”的字符串数组,它们存储可以使用的数字和符号:

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

4 回答 4

3

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

您似乎正在使用 进行计算Double,然后转换为String。不要那样做,这对性能很糟糕,因为你创建了大量的字符串然后把它们扔掉(而且你在字符串之间来回转换并且翻倍很多)。只需维护一个ArrayDeque<Double>并将其用作堆栈——这也使您免于进行可能也会影响性能的数组调整大小。

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

如果你做这些事情,你的循环应该会飞起来。

于 2012-09-27T10:27:48.520 回答
0

可能您的列表操作是此问题的根源。列表内部有数组,这些数组根据列表中的数据量进行扩展/缩小。因此,随机进行大量添加和删除将非常需要垃圾收集。

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

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

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

于 2012-09-27T10:23:22.620 回答
0

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

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

我建议看:

  1. 在循环外执行 Arrays.asList 函数(理想情况下,在某个地方只执行一次,例如您的构造函数或静态构造函数)
  2. 如果您的列表是 LinkedList,请考虑将其更改为 ArrayList
  3. 如果您的 List 是一个 ArrayList,请确保使用足够的容量对其进行初始化,这样就不需要调整其大小
  4. 考虑让您的列表存储对象而不是字符串,然后您可以在其中存储符号和双精度数,并且不需要从双精度数到字符串的来回转换
  5. 考虑编写一个适当的解析器(但这需要更多的工作)
于 2012-09-27T11:11:12.213 回答
0

但是,您使用了很多字符串。虽然情况可能并非如此,但它始终是您可以检查的事情之一,因为 Java 使用 String 做了一些时髦的事情。如果您在输出时必须将字符串转换为双精度,那么就会产生相当多的开销。

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

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

于 2012-09-27T13:35:29.493 回答