字符串转化为数值

工作时遇到这样的一个情况:服务器A向B发送请求并接受结果,耗时300ms,服务器B从接受请求到发送完毕,耗时100ms。由于是内网,不可能有200ms的网络延迟。后经检测,是因为传输时用的是字符串格式,解析时用了sscanf造成的。


由于sscanf要考虑到变参问题,并且接受的种类更加丰富(相对于strto*函数族),所以会很慢,之前我以为scanf函数族慢是因为IO,现在看来当时的看法很幼稚。经过测试对比,发现还是手工编写的有针对性的代码速度更快。测试代码如下(其中测时类Timer是我自己定义的,用的rdtsc取cpu的cycle)


#include <stdio.h>
#include <stdlib.h>

#include "utility.d/timing.d/timing.h"

#define N 30000

int table[256];
int* ptable = table + 128;

void
init_table()
{
	for( char x = -128; x < '0'; ++x ) ptable[x] = -1;
	for( char x = '0'; x <= '9'; ++x ) ptable[x] = x - '0';
	for( char x = '9' + 1; x > 0; ++x ) ptable[x] = -1;
}

#define str2i_table( str, rst ) \
	do{ \
		rst = 0; \
		char* __s = str; \
		int  __x = (*__s) == '-' ? ( ++__s, 1 ) : \
				   (*__s) == '+' ? ( ++__s, 0 ) : 0; \
		while( ptable[*__s] >= 0 ) rst = rst * 10 + ptable[*__s++]; \
		if( __x ) rst = -rst; \
	}while(0)

inline int
str2l( char* str )
{
	int sign = (*str) == '-' ? ( ++str, 1 ) :
			   (*str) == '+' ? ( ++str, 0 ) : 0;
	int rst = 0;
	//while( '0' <= *str && *str <= '9' ) rst = (rst<<3) + (rst<<1) + ( *str++ - '0' );
	while( '0' <= *str && *str <= '9' ) rst = rst * 10 + ( *str++ - '0' );
	if( sign ) rst = -rst;
	return rst;
}

int main()
{
	init_table();
	_UtilitY_::Timer  timer;
	char str[] = "\"count\":\"-123456\"";
	int  arr[N];
	timer.start();
	for( size_t i = 0; i < N; ++i ) sscanf( str + 9, "%d", arr + i );
	timer.stop();
	printf( "sscanf cost : %lld %d\n", timer.get_ticks(), arr[0] );

	timer.start();
	for( size_t i = 0; i < N; ++i ) arr[i] = strtol( str + 9, NULL, 10 );
	timer.stop();
	printf( "strtol cost : %lld %d\n", timer.get_ticks(), arr[0] );

	timer.start();
	for( size_t i = 0; i < N; ++i ) arr[i] = atoi( str + 9 );
	timer.stop();
	printf( "atoi cost : %lld %d\n", timer.get_ticks(), arr[0] );

	timer.start();
	for( size_t i = 0; i < N; ++i ) arr[i] = str2l( str + 9 );
	timer.stop();
	printf( "str2l cost : %lld %d\n", timer.get_ticks(), arr[0] );

	timer.start();
	for( size_t i = 0; i < N; ++i ) str2i_table( str + 9, arr[i] );
	timer.stop();
	printf( "str2i_table cost : %lld %d\n", timer.get_ticks(), arr[0] );

	return 0;
}

开优化 -O2时,输出如下:

sscanf cost : 13705800 -123456
strtol cost : 2819204 -123456
atoi cost : 2692044 -123456
str2l cost : 735160 -123456
str2i_table cost : 828984 -123456

可以看到,只转化整数,还是手工会快很多。但是查表法str2i_table还是稍慢于直接转化str2l的。

另外我在想能否将rst*10优化,如上面我注释掉的代码,将一个乘法转化为两个移位与一个加法,结果出乎意料,用这个方法反而更慢了。看来编译器能做的优化远远超出我的想象啊。于是我将其编译成汇编代码,看了一下区别。

rst = rst * 10 + ( *str++ - '0' );
 16     leal    (%rcx,%rcx,4), %eax                   
            # %rcx = rst, 所以这一句是 %eax = rst + rst * 4 = rst * 5
 17     movsbl  %dl,%edx
            # %dl = *str, 将其按有符号扩展到%edx
 18     leal    -48(%rdx,%rax,2), %ecx
            # %ecx (存放rst) = %rdx ( 存放*str ) + %rax ( 存放rst的5倍 ) * 2 - 48 ( 这是'0' )

rst = (rst<<3) + (rst<<1) + ( *str++ - '0' );
 16     leal    (%rcx,%rcx), %eax                     
            # %rcx = rst, 这一句是 %eax = %rcx + %rcx = rst * 2
 17     movsbl  %dl,%edx                              
            # %dl = *str, 将其按有符号扩展到%edx
 18     leal    -48(%rax,%rcx,8), %eax                
            # %eax = %rax + %rcx * 8 - 48 = rst * 2 + rst * 8 - '0' = rst * 10 - '0'
 19     leal    (%rax,%rdx), %ecx                     
            # %ecx (存放rst) = %rax ( rst * 10 - '0') + %rdx ( 存放符号扩展的*str )

多出了一条lea指令,所以会变慢。

lea和mov的opcode都是一个字节,区别就是

leal (ebx), eax   <==>  movl ebx, eax

但是由于lea指令是加载有效地址,里面按照Base+Index*Scale+Displacement,一条指令可以直接计算两个加法和一个乘法,可以用这个特点做一些算术运算,当然,要求乘法因子Scale必须是2的幂。例如:

leal    -48(%rdx,%rax,2), %ecx

就是一个很好的例子,Base = %rdx, Index = %rax, Scale = 2,Displacement = -48,这些运算都直接集中在一条指令里了。


如果对汇编指令有疑问,可以参看以下这篇文章,讲述了Intel和AT&T格式的一些区别。

http://personales.mundivia.es/jap/djasm.htm



你可能感兴趣的:(timer,优化,汇编,服务器,table,扩展)