【问题标题】:Java - What can make this code run faster?Java - 什么可以让这段代码运行得更快?
【发布时间】:2011-10-15 23:39:15
【问题描述】:

我在 Objective-c 中实现了几乎相同的代码,它的运行速度比在 Java 中快两到三倍。我试图找出哪些指令可能是最耗费资源的指令,并看看是否有更好的方法来做同样的事情,在 Java 中更有效。

这是从数据库读取大型结果集的例程的一部分,对于返回的每个单词,它会检查该单词是否可以从玩家拥有的字母拼贴中组成。它包括对空白图块的支持,可以用作任何字母。空白图块将由下划线字符表示。

基本上,对于从数据库返回的每个单词,我都会遍历单词的每个字母,并查看可用字母的 player 数组。如果我找到那封信,我会将它从玩家数组中删除并继续前进。如果我没有找到该字母,则丢弃该单词并读取下一个单词。除非我在玩家的数组中找到下划线字符,否则我会将它用于字母,并将其从数组中删除。如果我到达数据库单词的字母数组的末尾并“找到”每个字母,那么该单词将保存在一个列表中。

我已经对整个函数的各个部分进行了计时,并且数据库查询发生得非常快。只是这个游标的处理速度很慢。任何建议将不胜感激!

if (c.moveToFirst()) {

    do { 
        boolean found = false;
        int aValue = 0;
        int letterValue = 0;

        // Word and Word's length from the database
        String sWord = c.getString(0);
        int wordLength = c.getInt(1);

        // Refresh the Tile array, underscores sorted to the front
        // sortedTiles sorted the players tiles {_,_,a,b,c}
        char[] aTiles = sortedTiles.clone();

        // Calculate the value of the word
        for (int i = 0; i < wordLength; i++) {

            // For each character in the database word
            switch (sWord.charAt(i)) {
            case 97:
                letterValue = 1;
                break;
            case 98:
                letterValue = 4;
                break;
            case 99:
                letterValue = 4;
                break;
            case 100:
                letterValue = 2;
                break;
            case 101:
                letterValue = 1;
                break;
            case 102:
                letterValue = 4;
                break;
            case 103:
                letterValue = 3;
                break;
            case 104:
                letterValue = 3;
                break;
            case 105:
                letterValue = 1;
                break;
            case 106:
                letterValue = 10;
                break;
            case 107:
                letterValue = 5;
                break;
            case 108:
                letterValue = 2;
                break;
            case 109:
                letterValue = 4;
                break;
            case 110:
                letterValue = 2;
                break;
            case 111:
                letterValue = 1;
                break;
            case 112:
                letterValue = 4;
                break;
            case 113:
                letterValue = 10;
                break;
            case 114:
                letterValue = 1;
                break;
            case 115:
                letterValue = 1;
                break;
            case 116:
                letterValue = 1;
                break;
            case 117:
                letterValue = 2;
                break;
            case 118:
                letterValue = 5;
                break;
            case 119:
                letterValue = 4;
                break;
            case 120:
                letterValue = 8;
                break;
            case 121:
                letterValue = 3;
                break;
            case 122:
                letterValue = 10;
                break;
            default:
                letterValue = 0;
                break;
            } // switch

            found = false;

            // Underscores will be sorted to the front of the array, 
            // so start from the back so that we give
            // real letters the first chance to be removed.
            for (int j = aTiles.length - 1; j > -1; j--) {
                if (aTiles[j] == sWord.charAt(i)) {
                    found = true;
                    // Increment the value of the word
                    aValue += letterValue;

                    // Blank out the player's tile so it is not reused
                    aTiles[j] = " ".charAt(0);

                    // I was removing the element from the array
                    // but I thought that might add overhead, so
                    // I switched to just blanking that letter out
                    // so that it wont be used again
                    //aTiles = removeItem(aTiles, j);

                    break;
                }

                if (aTiles[j] == cUnderscore) {
                    found = true;

                    // Blank out the player's tile so it is not reused
                    aTiles[j] = " ".charAt(0);

                    // I was removing the element from the array
                    // but I thought that might add overhead, so
                    // I switched to just blanking that letter out
                    // so that it wont be used again
                    //aTiles = removeItem(aTiles, j);
                    break;
                }

            } // for j

            // If a letter in the word could not be fill by a tile 
            // or underscore, the word doesn't qualify
            if (found == false) {
                break;
            }

        } // for i

        // If all the words letters were found, save the value and add to the list.
        if (found == true) {

            // if all the tiles were used it's worth extra points
            String temp = aTiles.toString().trim();

            if (temp.length() < 1) {
                aValue += 35;
            }

            Word word = new Word();
            word.word = sWord;
            word.length = wordLength;
            word.value = aValue;
            listOfWords.add(word);
        }

    } while (c.moveToNext());
}

【问题讨论】:

  • " ".charAt(0)可以简单写成' '
  • 你是在iphone上运行objective-c版本,在android上运行java版本吗?如果有,硬件是否有类似的速度?
  • 在开始遍历每个字符之前,我会将单词字符串转换为字符数组。数组访问可能比 charAt() 更快,即使 charAt 正在做同样的事情,从堆栈中删除额外的方法调用。还为那个小花絮 +1 @Banthar,对于来自 C 代码的东西,这段代码令人惊讶地对象繁重。
  • 另一个提示我总是告诉任何使用 switch 或 else if 语句的人。始终将常见情况放在首位,这些值在您的代码中似乎是按顺序排列的。如果你把共同的放在第一位,那么他们会在进行所有其他比较之前绕过路径。至少把元音放在第一位。

标签: java android performance sqlite


【解决方案1】:

我不知道您的代码大部分时间都花在了哪里。您应该为此进行概要分析。但我会用表查找替换你的长 switch 语句:

// In the class:
private static final int[] letterValues = {
    1, 4, 4, 2, 1, // etc.
}

// In the code:

// Calculate the value of the word
for (int i = 0; i < wordLength; i++) {

    // For each character in the database word
    char ch = sWord.charAt(i);
    if (ch >= 97 && ch <= 122) {
        letterValue = letterValues[ch - 97];
    } else {
        letterValue = 0;
    }

    // the rest of your code

这可能比 switch 语句快得多。

编辑:我注意到在您的 j 循环中,您正在为每个 j 值调用 sWord.charAt(i)。您可以通过将该函数调用排除在循环之外来加快速度。如果您使用我的代码,您可以使用ch 代替sWord.charAt(i)

附:就风格而言,编码if (found) { ... 而不是if (found == true) { ... 更好。同样使用if (!found) { 而不是if (found == false) {

【讨论】:

  • 将数据库单词、sWord 转换为 char 数组听起来不错,和/或在 j 循环之外获取 charAt 肯定会有所帮助。这也是获取 letterValue 的好方法。
  • 我不知道转换为 char 数组会有多大帮助,特别是如果您只为每个字母位置调用一次 charAt 时。但是,唯一知道的方法是尝试两种方式并对其进行分析(或至少计时)。
  • 我刚刚尝试将 letterValues 表化,对于 50,000 字的结果集,与使用 switch 语句相比,它缩短了 2 秒的时间。而且,它在代码中看起来也更干净了,谢谢!
【解决方案2】:

我认为switch语句可能会被编译器变成一个跳转表,所以我看不出有什么问题。

另一方面,您可能可以为您的牌手使用更好的数据结构。现在,您基本上使用的是三重嵌套循环:

  1. 遍历数据库中的每个单词
  2. 遍历单词中的每个字符
  3. 遍历玩家瓷砖数组中的每个角色

前两个是无法避免的。另一方面,您可以为第三个使用哈希表或某种 O(N) 查找数据结构。

我可能会将手表示为 27 个整数的数组。每个元素代表一个字母或“_”,其值为手中的牌数。当你找到一个匹配的瓷砖时,你可以减少它的价值。如果该值已经为零,那么您就知道玩家没有该图块。

但正如 Ted 指出的那样,最好的办法是使用分析器来查找最昂贵的调用。然后弄清楚如何尽可能少地拨打这些电话。

【讨论】:

  • 我认为switch语句可能是Android编译器的问题。
【解决方案3】:

你往往会得到猜测的答案。

要做的事情是,在每个平台上,只需 squeeze the code until it's optimal。 那么如果有任何速度差异,至少你会知道每个代码都尽可能快。

分析是经常被推荐的,但是here's an example of how I do it

【讨论】:

  • 感谢您的示例,我将通读它以了解如何正确配置文件。我确实在整个代码中都放置了系统计时器,以试图找出最大的延迟在哪里......更多细节如下!
  • @Michael:是的,如果你擅长这一点,你实际上会拥有一项罕见的技能。一般的智慧是“测量测量”或“使用分析器”,但如果你听说过(你没有听说过)实现了多少加速,它可能是 10%-40%。相当贫血。不像第一个链接中的 43
【解决方案4】:

谢谢大家。我期待收到电子邮件提醒,所以,我没有意识到人们已经回复了。

我最终将系统时间的日志打印输出放置在游标循环中的不同位置,以尝试确定花费最多的时间。最初的 moveToFirst() 花费的时间最多,但这是意料之中的,我真的无能为力。我注意到大多数单词在两到三毫秒内处理,这应该足够快,但是,没有明显的原因,一个单词需要 20 或 30 毫秒。我推断在后台一定有一些垃圾收集,所以,我试图尽可能减少分配/释放。在所有循环中,而不是将变量声明为 for (int i = 0... 到 for (i = 0,将变量声明移动到方法的顶部。这有点帮助,但我还是离开了一条线没有动过。当我改变它时,世界就大不相同了。

    // Refresh the Tile array, underscores sorted to the front

    for (i = 0; i < sortedTiles.length; i++) {
        aTiles[i] = sortedTiles[i];
    }

    // Instead of doing it this way                 
    //aTiles = sortedTiles.clone();

我在光标循环上方分配aTiles,在这里,我只是用玩家手中的角色重新初始化它。这一次 clone() 导致频繁的垃圾收集成为必要,并扼杀了我的表现。

你们都提供了很好的建议,我会根据你们的建议进一步调整代码以获得更好的性能。非常感谢!

【讨论】:

  • 你可以用这个调用替换那个循环:System.arraycopy(sortedTiles, 0, aTiles, 0, sortedTiles.length);。它不应该运行得更快,但它更干净一些。
  • int 不会被垃圾回收。只有引用可以被垃圾收集,而不是原语。
  • 好的,但是通过在代码块内声明任何变量似乎仍然会导致该变量进出范围,我认为在幕后,这会导致为该变量分配资源的额外时间。即使它不会影响垃圾收集。我认为在大多数情况下重用变量会更好。
猜你喜欢
  • 2014-09-14
  • 2020-04-26
  • 2020-03-05
  • 1970-01-01
  • 2021-10-13
  • 2021-11-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多