【问题标题】:String#indexOf: Why comparing 15 million strings with JDK code is faster than my code?String#indexOf:为什么用 JDK 代码比较 1500 万个字符串比我的代码要快?
【发布时间】:2021-05-20 13:44:33
【问题描述】:

答案可能存在于某个地方,但我找不到。我从我正在创建的算法中提出了这个问题。本质上是一个.contains(String s1, String s2),如果 s1 包含 s2,则返回 true,忽略希腊/英语字符差异。例如,字符串 'nai, course' 包含字符串 'ναι'。但是,这与我的问题无关。

Stringcontains() 方法使用naive approach,我在我的算法中使用相同的方法。 contains() 本质上所做的就是用正确的参数调用存在于java.lang.String.class 中的static indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex)

当我对我的算法进行不同类型的基准测试和测试时,我删除了所有希腊语 - 英语逻辑,以查看仅使用英语字符串时它的运行速度有多快。而且速度较慢。比来自 JDK 的 s1.contains(s2) 慢大约 2 倍。

所以,我花时间将这个 indexOf 方法复制并粘贴到我的类中,并以与 JDK 调用字符串相同的方式调用它 1500 万次。

类如下:

public class TestIndex {
    public static int fromJdk;
    public static int fromMe;

    public static void main(String[] args) {
        String s1 = "papapatita";
        String s2 = "patita";
        final char[] s1Arr = s1.toCharArray();
        int s1Length = s1Arr.length;

        final char[] s2Arr = s2.toCharArray();
        int s2Length = s2Arr.length;

        long be4 = System.nanoTime();
        for (int i = 0; i < 15_000_000; i++) {
            fromJdk = s1.indexOf(s2);
            //          fromMe = indexOf(s1Arr, 0, s1Length, s2Arr, 0, s2Length, 0);
        }
        long after = System.nanoTime();
        System.out.println((after - be4) / 1000000.0);
    }

    static int indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset,
            int targetCount, int fromIndex) {
        if (fromIndex >= sourceCount) {
            return (targetCount == 0 ? sourceCount : -1);
        }
        if (fromIndex < 0) {
            fromIndex = 0;
        }
        if (targetCount == 0) {
            return fromIndex;
        }

        char first = target[targetOffset];
        int max = sourceOffset + (sourceCount - targetCount);

        for (int i = sourceOffset + fromIndex; i <= max; i++) {
            /* Look for first character. */
            if (source[i] != first) {
                while (++i <= max && source[i] != first)
                    ;
            }

            /* Found first character, now look at the rest of v2 */
            if (i <= max) {
                int j = i + 1;
                int end = j + targetCount - 1;
                for (int k = targetOffset + 1; j < end && source[j] == target[k]; j++, k++)
                    ;

                if (j == end) {
                    /* Found whole string. */
                    return i - sourceOffset;
                }
            }
        }
        return -1;
    }
}

虽然fromJdk = s1.indexOf(s2); 没有评论,但结果是(在我的机器上)大约 150 毫秒。如果我评论这一行并留下 fromMe = indexOf(s1Arr, 0, s1Length, s2Arr, 0, s2Length, 0); 没有评论,我应该得到相同的结果。结果几乎是两倍。大约 300 毫秒。

那么,问题是为什么? indexOf 方法与 JDK 中存在的完全一样。我知道在 Java 中测量性能是一件很难的事情(可能是 JMH)?但是差别很大。来自 JDK 的 indexOf 是否在后台得到特殊处理?

如果答案在某处存在,请指出它。

【问题讨论】:

  • 您使用的是哪个 Java 版本?
  • 这能回答你的问题吗? How do I write a correct micro-benchmark in Java?
  • @GeorgeZ。 Kayaman 回答了什么,如果您查看 Java 11(最新 LTS 版本)的源代码,我会补充一下,您可以看到一个名为 @HotSpotIntrinsicCandidate 的注释,表明 JVM 可以用更高效的机器代码替换该方法。我想这种行为存在于 Java 8 中,而且很可能在此之前也存在。
  • @MAnouti 也很高兴听到这个消息。感谢您的信息。我会记住的。
  • here一样的问题

标签: java jvm


【解决方案1】:

因为String.indexOf()intrinsic method,所以JDK 调用本地实现而您调用Java 实现。

JVM 并不实际执行您看到的 Java 代码,它知道可以用更高效的版本替换它。当您复制代码时,它会通过常规的 JIT 编译,从而降低效率。 JVM 为提高性能所做的dozens of tricks 中的一个,开发人员通常甚至没有意识到。

【讨论】:

  • (1+)。这就说得通了。谢谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-11-02
  • 1970-01-01
  • 2011-06-21
  • 2012-02-09
  • 1970-01-01
  • 2011-05-28
  • 2013-08-12
相关资源
最近更新 更多