JavaScript字符串相等性能比较

3

我有一个初学者的JavaScript问题。假设我们有两个非常大的字符串(约百万个字符或更多),它们是相等的-它们具有相同的长度和相同的内容。 假设我们有这两个函数,它们都执行相同的操作(比较字符串):

function equals1(a, b) {
    return a === b;
}

function equals2(a, b) {
    if (a.length !== b.length) {
           return false;
    }
    for (var i = 0; i < a.length; ++i) {
        if (a[i] !== b[i]) {
            return false;
         }
    }
    return true;
}

第一个函数(equals1())为什么几乎比第二个函数快两倍?如何改进第二个函数,使其与第一个函数一样快?


8
因为它是用本地代码实现的?你为什么想重新实现字符串比较? - John Dvorak
2
@joel 除了不太准确之外,你认为它为什么会更快呢? - John Dvorak
3
我发誓,只要标题中有“性能”这个词,人们就会点赞任何问题。 - user1106925
1
我不想重新实现字符串比较 :)。这只是一个假设性的问题... - Tihomir Meščić
1
如果您将字符串值分配在引号中,并且不是逐步构建它们,那么它们将被interned。正如JsPerf所示(请参见我的答案),时间是恒定的,因为我一直坚持这一点http://jsperf.com/eqaulity-is-constant-time。我很想看看您的基准测试结果。谢谢。 - Michail Michailidis
显示剩余5条评论
4个回答

7
最有可能的是JavaScript正在进行字符串内联(常见的JavaScript实现是否使用字符串内联?),这是根据ECMAScript委员会的一位成员所说。我认为这时 === 应该是 O(1) 的,但根据原帖作者的性能测试结果,当字符串加倍时,相等操作的时间复杂度是 O(n),这真的很令人沮丧,因为字符串内联没有被正确地使用。

更新:JSPerf

原帖作者声称应该支持O(N)复杂度,从http://jsperf.com/eqaulity-is-constant-time看来,即使我有一个16倍更大的字符串,时间也不会增加超过1-2%。

所以请重新考虑我已经删除的东西和你们的反对票。

换句话说:

当你这样做时:

var str1 = "stringwithmillionchars"; //stored in address 51242
var str2 = "stringwithmillionchars"; //stored in address 12313

"stringwithmillionchars"将被存储在地址201012的内存中,str1和str2都将“指向”此地址201012。可以使用某种哈希方法来确定此地址并映射到内存中的特定位置。

因此,在执行

"stringwithmillionchars" === "stringwithmillionchars"

时,会看起来像

getContentOfAddress(51242) === getContentOfAddress(12313)

201012 === 201012

这需要O(1)/常数时间

您示例中的for循环(equals2())需要O(N)时间,其中N是两个字符串的长度。这是因为它必须在每对字符和i与str.length之间进行N次比较。

注意:地址数字是为说明目的随机选择的。

重要提示:根据我在问题(为什么JavaScript的===/==字符串相等有时具有恒定时间复杂度,有时具有线性时间复杂度)中的性能比较,只有在直接使用引号分配字符串时才会发生内部化,否则将进行字符对字符比较,而不是恒定时间复杂度,将会变成线性时间复杂度。


1
如果您熟悉字符串驻留,则需要比较两个字符串的内存地址-如果相同,则两个字符串相同。@Bergi https://dev59.com/Gm435IYBdhLWcg3wrSLN - Michail Michailidis
2
谢谢您的回答,但我认为情况并非如此。我尝试将字符串长度增加2倍,比较(a === b)需要两倍的时间(因此,它也是O(n))。 - Tihomir Meščić
2
我建议你放弃它,不要再浪费时间了。你的努力是值得赞赏的,但我认为你已经表达了你的观点。你将从我这里获得100赏金作为你的努力奖励。 - lexicore
1
有趣。看起来当你使用字符串字面量时,JavaScript会进行字符串内部化。然而,在我的测试案例中,我通过简单地在循环中连接它们来生成字符串(打一百万个字符将会很困难:)。http://jsperf.com/equality-no-interning - Tihomir Meščić
1
@TihomirMeščić "...常量字符串会自动进行内部化处理,而字符串拼接的结果等则不会"(请参见olliej在已接受答案下的评论 - Rafael Emshoff
显示剩余8条评论

2
第一个函数更快,因为它不必一百万次检查i < a.length,也不必对i进行一百万次递增操作。

(除了一百万次比较之外) - Ian

1

你可以像下面这样做,让你的equals2函数运行速度提高一倍:

function equals2(a, b) {
    if (a.length !== b.length) {
           return false;
    }
    for (var i = 0; i < a.length/2; ++i) {
        if (a[i] !== b[i] || a[a.length-i-1] !== b[b.length-i-1]) {
            return false;
         }
    }
    return true;
}

这似乎非常合乎逻辑。 - Nagibaba

0

第一个函数执行相同的操作。如果字符串长度不同,它将返回false。然后它检查字符在相同索引处是否相同。但是,它是在JS实现级别上实现的,因此它运行得像C、C++、Java或JavaScript实现所编写的任何语言一样快。


是的,这就是我在这里说的。 - Brennan
他编写了代码,所以他知道这个。他只对两个长度和内容相同的字符串的示例感兴趣。性能问题在于一个要进行2*1000000次比较,而另一个只需要一次。 - Michail Michailidis
它需要进行多个比较,你认为它在幕后是如何进行比较的? - Brennan
它只是比较两个整数(哈希值),这比逐个字符比较要快得多。当然,在您的CPU的ALU中,int比较不是单个指令,但这是离题的,我们认为它是O(1)或常数时间。 - Michail Michailidis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接