String#indexOf:为什么用 JDK 代码比较 1500 万个字符串比我的代码快?
String#indexOf: Why comparing 15 million strings with JDK code is faster than my code?
答案可能存在于某处,但我找不到。我从我正在创建的算法中得出这个问题。本质上是 .contains(String s1, String s2)
如果 s1 包含 s2 则 returns 为真,忽略 Greek/English 字符差异。例如,字符串“nai,当然”包含字符串“ναι”。但是,这与我的问题无关。
String
的 contains()
方法使用 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
方法复制并粘贴到我的 class,并以相同的方式调用它 1500 万次 JDK 为字符串调用它.
class如下:
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
是否在幕后得到特殊对待?
如果某处有答案,请指出。
因为 String.indexOf()
是一个 intrinsic method,使 JDK 调用本机实现而您调用 Java 实现。
JVM 实际上并不执行您看到的 Java 代码,它知道它可以用更高效的版本替换它。当您复制代码时,它会经过常规的 JIT 编译,从而降低效率。这只是 JVM 为提高性能所做的 dozens of tricks 之一,而开发人员通常甚至没有意识到。
答案可能存在于某处,但我找不到。我从我正在创建的算法中得出这个问题。本质上是 .contains(String s1, String s2)
如果 s1 包含 s2 则 returns 为真,忽略 Greek/English 字符差异。例如,字符串“nai,当然”包含字符串“ναι”。但是,这与我的问题无关。
String
的 contains()
方法使用 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
方法复制并粘贴到我的 class,并以相同的方式调用它 1500 万次 JDK 为字符串调用它.
class如下:
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
是否在幕后得到特殊对待?
如果某处有答案,请指出。
因为 String.indexOf()
是一个 intrinsic method,使 JDK 调用本机实现而您调用 Java 实现。
JVM 实际上并不执行您看到的 Java 代码,它知道它可以用更高效的版本替换它。当您复制代码时,它会经过常规的 JIT 编译,从而降低效率。这只是 JVM 为提高性能所做的 dozens of tricks 之一,而开发人员通常甚至没有意识到。