php 7.1.0 版本中的 zend_mm_small_size_to_bit 函数中有如下的代码,用于求二进制最高位1的索引。
int n = 16;
if (size <= 0x00ff) {n -= 8; size = size << 8;}
if (size <= 0x0fff) {n -= 4; size = size << 4;}
if (size <= 0x3fff) {n -= 2; size = size << 2;}
if (size <= 0x7fff) {n -= 1;}
return n;
代码很巧妙,我们从性能角度比较一下我们直观写法和该写法的差距。
下面是一种直观的写法:
if (1 == size) return 1;
else if (size <= 2) return 2;
else if (size <= 4) return 3;
else if (size <= 8) return 4;
else if (size <= 16) return 5;
else if (size <= 32) return 6;
else if (size <= 64) return 7;
else if (size <= 128) return 8;
else if (size <= 256) return 9;
else if (size <= 512) return 10;
else if (size <= 1024) return 11;
else if (size <= 2048) return 12;
else if (size <= 4096) return 13;
else if (size <= 8192) return 14;
else if (size <= 16384) return 15;
else if (size <= 32768) return 16;
else return -1;
我们写一个 c 测试程序用于比较上面两种写法的性能:
#include <stdio.h>
#include <time.h>
int index_hign1_php(int size)
{
int n = 16;
if (size <= 0x00ff) {n -= 8; size = size << 8;}
if (size <= 0x0fff) {n -= 4; size = size << 4;}
if (size <= 0x3fff) {n -= 2; size = size << 2;}
if (size <= 0x7fff) {n -= 1;}
return n;
}
int index_hign1_normal(int size)
{
if (1 == size) return 1;
else if (size <= 2) return 2;
else if (size <= 4) return 3;
else if (size <= 8) return 4;
else if (size <= 16) return 5;
else if (size <= 32) return 6;
else if (size <= 64) return 7;
else if (size <= 128) return 8;
else if (size <= 256) return 9;
else if (size <= 512) return 10;
else if (size <= 1024) return 11;
else if (size <= 2048) return 12;
else if (size <= 4096) return 13;
else if (size <= 8192) return 14;
else if (size <= 16384) return 15;
else if (size <= 32768) return 16;
else return -1;
}
int main()
{
const int count = 60000;
clock_t begin = clock();
for (int i = 1; i < count; i++)
{
index_hign1_php(i);
}
clock_t end = clock();
double time_spent = (double)(end - begin) / CLOCKS_PER_SEC;
printf("index_hign1_php 耗时: %lf\n", time_spent);
begin = clock();
for (int i = 1; i < count; i++)
{
index_hign1_normal(i);
}
end = clock();
time_spent = (double)(end - begin) / CLOCKS_PER_SEC;
printf("index_hign1_normal 耗时: %lf\n", time_spent);
return 0;
}
使用 gcc -S *.c 生成汇编代码(只保留 index_hign1_php 和 index_hign1_normal 的函数部分),我们看到 index_hign1_php 汇编代码占 38 行,index_hign1_normal 汇编代码占 106 行, 我们根据代码行数比例初步判断 index_hign1_normal 耗时 等于 index_hign1_php 耗时 的接近三倍。
index_hign1_php:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl %edi, -20(%rbp)
movl $16, -4(%rbp)
cmpl $255, -20(%rbp)
jg .L2
subl $8, -4(%rbp)
sall $8, -20(%rbp)
.L2:
cmpl $4095, -20(%rbp)
jg .L3
subl $4, -4(%rbp)
sall $4, -20(%rbp)
.L3:
cmpl $16383, -20(%rbp)
jg .L4
subl $2, -4(%rbp)
sall $2, -20(%rbp)
.L4:
cmpl $32767, -20(%rbp)
jg .L5
subl $1, -4(%rbp)
.L5:
movl -4(%rbp), %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size index_hign1_php, .-index_hign1_php
.globl index_hign1_normal
.type index_hign1_normal, @function
index_hign1_normal:
.LFB1:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl %edi, -4(%rbp)
cmpl $1, -4(%rbp)
jne .L8
movl $1, %eax
jmp .L9
.L8:
cmpl $2, -4(%rbp)
jg .L10
movl $2, %eax
jmp .L9
.L10:
cmpl $4, -4(%rbp)
jg .L11
movl $3, %eax
jmp .L9
.L11:
cmpl $8, -4(%rbp)
jg .L12
movl $4, %eax
jmp .L9
.L12:
cmpl $16, -4(%rbp)
jg .L13
movl $5, %eax
jmp .L9
.L13:
cmpl $32, -4(%rbp)
jg .L14
movl $6, %eax
jmp .L9
.L14:
cmpl $64, -4(%rbp)
jg .L15
movl $7, %eax
jmp .L9
.L15:
cmpl $128, -4(%rbp)
jg .L16
movl $8, %eax
jmp .L9
.L16:
cmpl $256, -4(%rbp)
jg .L17
movl $9, %eax
jmp .L9
.L17:
cmpl $512, -4(%rbp)
jg .L18
movl $10, %eax
jmp .L9
.L18:
cmpl $1024, -4(%rbp)
jg .L19
movl $11, %eax
jmp .L9
.L19:
cmpl $2048, -4(%rbp)
jg .L20
movl $12, %eax
jmp .L9
.L20:
cmpl $4096, -4(%rbp)
jg .L21
movl $13, %eax
jmp .L9
.L21:
cmpl $8192, -4(%rbp)
jg .L22
movl $14, %eax
jmp .L9
.L22:
cmpl $16384, -4(%rbp)
jg .L23
movl $15, %eax
jmp .L9
.L23:
cmpl $32768, -4(%rbp)
jg .L24
movl $16, %eax
jmp .L9
.L24:
movl $-1, %eax
.L9:
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1:
.size index_hign1_normal, .-index_hign1_normal
.section .rodata
.LC1:
.string "index_hign1_php \350\200\227\346\227\266: %lf\n"
.align 8
.LC2:
.string "index_hign1_normal \350\200\227\346\227\266: %lf\n"
.text
.globl main
.type main, @function
运行 c 测试程序结果如下: