当检查一个值x
是否为布尔值时,typeof x === 'boolean'
比x === true || x === false
更快,反之亦然?
我原以为直接比较会更快,但实际上 typeof 比较几乎快了两倍。
顺带一提:我知道这对于几乎任何实际目的都没有意义。
以下是基准测试代码(免责声明:我不知道如何进行基准测试):https://jsperf.com/check-if-boolean-123
当检查一个值x
是否为布尔值时,typeof x === 'boolean'
比x === true || x === false
更快,反之亦然?
我原以为直接比较会更快,但实际上 typeof 比较几乎快了两倍。
顺带一提:我知道这对于几乎任何实际目的都没有意义。
以下是基准测试代码(免责声明:我不知道如何进行基准测试):https://jsperf.com/check-if-boolean-123
这要看情况。
要给出绝对的答案,我们需要编译每一段代码,并在每个可能的浏览器/架构组合上观察解释器。然后我们才能给出一个绝对的答案,即哪种操作需要更少的处理器周期(processor cycles),否则一切都只是猜测。这正是我现在正在做的事情:
朴素的方法
我们假设引擎不执行任何优化,它们只按规范执行每一步操作。那么对于每个测试用例,将会发生以下情况:
typeof x === 'boolean'
(1) 查找x
的类型。因为引擎可能使用指向实际数据的指针和一个表示值类型的枚举来表示通用的 "JavaScript Value" 结构,所以获取描述类型的字符串可能是在类型 -> 类型字符串映射中查找。
(2) 现在我们有两个字符串值,需要比较 ('boolean' === 'boolean'
)。首先必须检查 类型 是否相等。这可能通过比较两个值的类型字段并执行位相等性操作(即:一个处理器操作)来完成。
(3) 最后需要比较值的相等性。对于字符串,这意味着迭代两个字符串并将它们的字符进行比较。
x === true || x === false
(1) 首先必须像上面描述的那样比较x
和true
/false
的类型。
(2) 其次,需要比较值,对于布尔值,这是位相等性(即:一个处理器操作)。
(3) 最后一步是or表达式。给定两个值,首先必须检查它们是否为真值(对于布尔值相当容易,但仍然需要再次检查这些值是否确实是布尔值),然后可以执行或操作(位或,即:一个处理器操作)。
那么哪个更快呢?如果我必须猜测,第二种方法更快,因为第一种方法需要进行字符串相等性比较,这可能需要更多的迭代。
最优方法
非常聪明的编译器可能会意识到 typeof x === 'boolean'
只有在 x 的类型为 boolean 时才为真。所以它可以被优化为以下代码(C++ 伪代码):
result = JSValue( JSType::Boolean, x->type == JSType::Boolean)
这只是一些处理器操作,因此非常快。与天真的方法相比,我们节省了字符串比较和多个类型检查。引擎会进行这样的优化吗?可能会,因为typeof检查是相当普遍的,并且很容易进行优化(因此这是一个简单的胜利)。
我们能够优化x === true || x === false
吗?好的,我们知道true
和false
的类型,因此可以将其简化为(C++伪代码):
var result = JSValue(JSType:Boolean, x->type == JSType:Boolean && !x->value || x->type == JSType:Boolean && x->value)
它不能再优化吗?编译器不能看到x->value
和!x->value
是互斥的,因此实际上与上面的代码完全相同。编译器会做这个优化吗?我不知道。毫无疑问,这并不容易,而优化x || !x
是开发人员可以采取的一种简单优化。
在一个完美的世界里,有一个非常周到的编译器,第一个版本将更快。
但是编译器会优化到这种程度吗?这要视情况而定。今天,引擎首先采用纯朴的方法(因为编译也需要时间),只有在函数变得热门时才进行优化。在您的测试用例中就是这种情况(这就是为什么第二个版本更快)。现实世界的代码是否变得热门取决于使用情况。
结论
在我的设备上,第一个版本的执行速度为451,701,256 ops / s,非常快!
第二个版本的执行速度为198,308,952 ops / s。它比第一个版本慢两倍吗?是的。它慢吗?不是的。可能还有其他代码消耗更多的处理器周期。
进一步思考
一个非常常见的优化是在推断出参数的数据类型后将函数编译下来。这意味着如果您执行以下操作
const check = it => typeof it === "boolean";
for(let i = 0; i < 100000; i++) check(true); // make function hot
这个函数可能会被编译成:
boolean check(/*deduced datatype*/ boolean it) {
return true; // <- even faster than anything above
}
如果你调用check("ouch")
,会发生什么?好吧,在函数的入口处,类型被断言,断言失败,引擎不得不回退到解释/重新编译函数。因此,使用布尔值调用函数100000次,然后使用字符串调用函数100000次可能比随机使用字符串/布尔值调用函数200000次更快。在编写性能测试时请牢记这一点。
x === !!x
进行了基准测试,结果几乎与typeof
一样快。但远远胜出的是x === Boolean(x)
。我猜测这个能够通过跳过已经是布尔值的操作来进行优化或者类似的操作。 - John Montgomery