Consider the following binary search tree node:
pub struct Node {
key: i64,
left_ptr: Option<Box<Node>>,
right_ptr: Option<Box<Node>>,
}
Here are two implementations of "contains" method with identical behavior:
pub fn contains_match(root: &Option<Box<Node>>, key: i64) -> bool {
let mut node_ptr = root;
while let Some(node) = node_ptr {
match node.key.cmp(&key) {
Ordering::Equal => return true,
Ordering::Less => node_ptr = &node.right_ptr,
Ordering::Greater => node_ptr = &node.left_ptr,
}
}
false
}
pub fn contains_if_else(root: &Option<Box<Node>>, key: i64) -> bool {
let mut node_ptr = root;
while let Some(node) = node_ptr {
if node.key == key {
return true;
}
if node.key < key {
node_ptr = &node.right_ptr;
} else {
node_ptr = &node.left_ptr;
}
}
false
}
However, the ASM code they translate into differs significantly:
playground::contains_match:
movq (%rdi), %rax
testq %rax, %rax
je .LBB0_7
movq $-1, %rcx
.LBB0_2:
xorl %edx, %edx
cmpq %rsi, (%rax)
setne %dl
cmovlq %rcx, %rdx
cmpq $-1, %rdx
je .LBB0_5
movb $1, %al
testq %rdx, %rdx
je .LBB0_8
movq (%rdi), %rdi
addq $8, %rdi
movq (%rdi), %rax
testq %rax, %rax
jne .LBB0_2
jmp .LBB0_7
.LBB0_5:
movq (%rdi), %rdi
addq $16, %rdi
movq (%rdi), %rax
testq %rax, %rax
jne .LBB0_2
.LBB0_7:
xorl %eax, %eax
.LBB0_8:
retq
playground::contains_if_else:
cmpq $0, (%rdi)
je .LBB1_5
.LBB1_2:
movq (%rdi), %rdi
movq (%rdi), %rax
cmpq %rsi, %rax
je .LBB1_3
leaq 16(%rdi), %rcx
addq $8, %rdi
cmpq %rsi, %rax
cmovlq %rcx, %rdi
cmpq $0, (%rdi)
jne .LBB1_2
.LBB1_5:
xorl %eax, %eax
retq
.LBB1_3:
movb $1, %al
retq
According to my benchmarks, the "match" version is 15 to 25% slower then then the "if/else" version:
100k_random_lookup_hits/contains_if_else
time: [14.096 ms 14.146 ms 14.212 ms]
100k_random_lookup_hits/contains_match
time: [17.938 ms 17.961 ms 17.991 ms]
100k_random_lookup_misses/contains_if_else
time: [20.451 ms 20.475 ms 20.502 ms]
100k_random_lookup_misses/contains_match
time: [23.900 ms 23.920 ms 23.943 ms]
Is there a reason why the "match" version is compiled into a slower asm?