二进制最高位的 1

August 12, 2020
php c

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 测试程序结果如下:
Test Image

参考:
Execution time of C program

1209. 删除字符串中的所有相邻重复项 II

leetcode php

链表中环的检测

链表 c

移除链表倒数第N个节点

链表 c