千分求最快的Base64编码函数

housisong 2007-07-20 11:37:50
为了便于查看和讨论,请移步到 “ http://community.csdn.net/Expert/topic/5665/5665480.xml?temp=.9847528 ” 查看或发帖

我有上万的可用分,可以开新贴给分

Base64编码是很常用的一种把二进制数据转换为字符串的算法;
(它把3个8bit数据分成4组6bit数据,然后拿6bit数映射到64个可显示字符集合中的一个)

我开贴征集最快的Base64编码函数,要求如下:
1.使用200MB随机数据来测试编码函数 (数据量最少的话也要20MB上)
2.测试单位时间(秒)编码出的字符数据量 (假设200MB数据用了2秒钟编码时间,那速度就是:200MB/3*4/2s=133.3MB/s)
3.必须说明测试使用的CPU型号(运行频率)、最好说明内存型号(运行频率) 、语言和编译器类型
4.可以测试已有的Base64编码函数(比如各种库中的、开源的等等),注明来源(可以不提供源代码),也可以是自己写的(最好能给出源代码)
5.可以补充测试一下Base64解码函数的速度
(如果是自己写的 看函数速度 给分时酌情加分)


==========================================================================
//testBase64.cpp
//一个简单的base64_encode编码函数例子,和测试框架
#include <time.h>
#include <iostream>
#include <vector>
#include <string>

// 3x8bit to 4x6bit
// |----------------------|----------------------|----------------------|
// | a[0..7] | b[0..7] | c[0..7] |
// |----------------------|----------------------|----------------------|
//
// |----------------------|----------------------|----------------------|----------------------|
// | a[2..7] | b[4..7] + a[0..1]<<4 | c[6..7] + b[0..3]<<2 | c[0..5] |
// |----------------------|----------------------|----------------------|----------------------|

const unsigned char BASE64_PADDING='='; //输入数据不足3的倍数时 输出字符后面填充'='号


inline unsigned char to_base64char(const unsigned char code6bit)
{
if (code6bit<26) //[ 0..25] => ['A'..'Z']
return code6bit+'A';
else if (code6bit<52) //[26..51] => ['a'..'z']
return code6bit+('a'-26);
else if (code6bit<62) //[52..61] => ['0'..'9']
return code6bit+('0'-52);
else if (code6bit==62) //62 => '+'
return '+';
else //if (code6bit==63) //63 => '/'
return '/';
}

void base64_encode(const void* pdata,const unsigned long data_size,void* out_pcode)
{
if (data_size<=0) return;
const unsigned char* input=(const unsigned char*)pdata;
const unsigned char* input_end=&input[data_size];
unsigned char* output=(unsigned char*)out_pcode;

const unsigned char* input_end_sub_2=input_end-2;
for(;input<input_end_sub_2;input+=3,output+=4)
{
output[0]=to_base64char(input[0]/4);
output[1]=to_base64char(input[0]%4*16 + input[1]/16);
output[2]=to_base64char(input[1]%16*4 + input[2]/64);
output[3]=to_base64char(input[2]%64);
}

unsigned long bord_width=input_end-input;
if (bord_width==1)
{
output[0]=to_base64char(input[0]/4);
output[1]=to_base64char(input[0]%4*16);
output[2]=BASE64_PADDING;
output[3]=BASE64_PADDING;
}
else if (bord_width==2)
{
output[0]=to_base64char(input[0]/4);
output[1]=to_base64char(input[0]%4*16 + input[1]/16);
output[2]=to_base64char(input[1]%16*4);
output[3]=BASE64_PADDING;
}
}


typedef void (*Tbase64_encode_proc)(const void* pdata,const unsigned long data_size,void* out_pcode);

inline unsigned long base64_code_size(const unsigned long data_size)
{
return (data_size+2)/3*4;
}


void testSpeed(const char* proc_name_str,Tbase64_encode_proc base64_encode,const long DATA_SIZE)
{
std::cout<<">> 编码函数: "<<proc_name_str<<std::endl;

const long DATA_SIZE_MAX=DATA_SIZE+12;

std::vector<unsigned char> data_buf(DATA_SIZE_MAX); //data_buf保存需要编码的数据
for (long r=0;r<DATA_SIZE_MAX;++r)
data_buf[r]=rand(); //data_buf填充随机数据 用以测试

const long code_size_MAX=base64_code_size(DATA_SIZE_MAX);
std::string code_str;//code_str用以储存编码后的字符串数据
code_str.resize(code_size_MAX,' ');

long RunCount=0;
double SumSpeed=0;
for (long data_size=DATA_SIZE;data_size<DATA_SIZE_MAX;++data_size)
{

const long code_size=base64_code_size(data_size);
double start_time=(double)clock();

base64_encode(&data_buf[0],data_size,&code_str[0]);//编码测试

double run_time=((double)clock()-start_time)*(1.0/CLOCKS_PER_SEC);

double encode_speed=code_size*(1.0/1024/1024)/run_time;//编码速度(MB/秒)
++RunCount;
SumSpeed+=encode_speed;

std::cout<<" 编码前数据大小(MB): "<<data_size*(1.0/1024/1024)<<" 编码速度(MB/秒): "<<encode_speed<<std::endl;
//if (data_size<=1000) std::cout<<code_str<<std::endl; //
}
std::cout<<std::endl<<" 平均编码速度(MB/秒): "<<SumSpeed/RunCount<<std::endl;
std::cout<<std::endl;

}

int main()
{
std::cout<<" 请输入任意字符开始测试(可以把进程优先级设置为“实时”)> ";
getchar();
std::cout<<std::endl;

const long DATA_SIZE=80*1024*1024;

testSpeed("base64_encode" ,base64_encode ,DATA_SIZE);
return 0;
}
===========================================================================


例子的结果说明:
1.测试数据集80MB
2.base64_encode编码函数速度(进程为"实时"优先级): 90.8MB/s
3.CPU: AMD64x2 3600+(2.01G); 内存: DDR2 667(334.9MHz); 语言:C++; 编译器:VC2005


为了便于查看和讨论,请移步到 “ http://community.csdn.net/Expert/topic/5665/5665480.xml?temp=.9847528 ” 查看或发帖
...全文
468 15 打赏 收藏 转发到动态 举报
写回复
用AI写文章
15 条回复
切换为时间正序
请发表友善的回复…
发表回复
housisong 2007-07-31
  • 打赏
  • 举报
回复
"千分求最快的Base64编码函数"的帖子已经好久没有新帖,开贴目的也已经基本达到,所以结贴
《千分求最快的Base64编码函数》帖子参见:
http://community.csdn.net/Expert/topic/5665/5665480.xml?temp=.3156092
http://community.csdn.net/Expert/topic/5665/5665503.xml?temp=.4164087
http://community.csdn.net/Expert/topic/5665/5665498.xml?temp=.3687403

谢谢大家的参与,1千分的分配:

maozefa(阿发伯) --150
cczlp(不惑) --100
ERR0RC0DE() --150
DelphiGuy() --150
zyl910(编程的乐趣在于编程控制硬件,与用图形学实现绚丽效果) --150
constantine(飘遥的安吉儿) -- 40
SafeF8(A++.NET) -- 75
beelzebub918(ww) -- 10
unsigned(僵哥(发站内消息,请附上链接或问题说明,否则不予回复)) -- 15
cheer2008() -- 75
mLee79() -- 10
quark(夸克) -- 75



cczlp(不惑) 领分请到:
http://community.csdn.net/Expert/topic/5686/5686121.xml?temp=.2338526
maozefa(阿发伯) 领分请到:
http://community.csdn.net/Expert/topic/5686/5686126.xml?temp=.8005335
http://community.csdn.net/Expert/topic/5686/5686128.xml?temp=5.535525E-02
ERR0RC0DE() 领分请到:
http://community.csdn.net/Expert/topic/5686/5686136.xml?temp=.3937189
http://community.csdn.net/Expert/topic/5686/5686137.xml?temp=.6492426
DelphiGuy() 领分请到:
http://community.csdn.net/Expert/topic/5686/5686138.xml?temp=.3677179
http://community.csdn.net/Expert/topic/5686/5686139.xml?temp=.565838
zyl910 领分请到:
http://community.csdn.net/Expert/topic/5686/5686140.xml?temp=.2164118
http://community.csdn.net/Expert/topic/5686/5686142.xml?temp=.8785059


我也写了一篇blog文章《代码优化-之-Base64编码函数的极限优化挑战》,
地址: http://blog.csdn.net/housisong/archive/2007/07/27/1711153.aspx
(文章中有源代码下载,欢迎大家提供其测试成绩,我会把结果合并到文章中)
housisong 2007-07-22
  • 打赏
  • 举报
回复
我装好了Delphi2007,建立好了Delphi版的速度测试环境和验证函数正确性的单元测试
当前所有版本在我的电脑上的速度测试成绩参见: http://community.csdn.net/Expert/topic/5665/5665480.xml?temp=.7324182
阿发伯 2007-07-22
  • 打赏
  • 举报
回复
呵呵,看样子,我前面的测试代码还是起了抛砖引玉的作用,出现这么多快速的Base64函数,没法和这些高手比啊!不过,我觉得如果非要比较速度的话,同样的算法,C/C++绝对赶不上优化了的汇编代码(当然是汇编高手进行的优化),如果模仿base64_from_delphi_VCL的流程,我想应该不会比它慢吧,不过我认为没多大必要:
1、因为几十上百兆字节的编码毕竟不多,几兆几十兆以下字节的编码悬殊不大;
2、base64_from_delphi_VCL基本属于顺序执行代码,如果按它的流程写汇编码,代码会很长,一句C/C++代码,汇编可能要7-8句甚至上10句,而且代码不那么美观。


我昨晚把我的函数稍稍优化了一下,没改变算法,在我的机器上,由以前的87/s提高到164/s,提高了近一倍的速度。在优化过程中,发现我自己对汇编还有很大误区,十几年来,我一直认为lodsb要比mov al,[esi] inc esi快;stosb比mov [edi],al inc edi快;2句inc语句不会比add慢等,但是昨晚测试结果搞好相反,后者比前者速度快多了,我仅仅改了这3个语句,速度就提高50%左右,真是吓人!

下面把优化前后代码贴上,请各位多多指教(希望在不改变基本算法的前提下,提出优化意见):

//以前的代码
function Base64_Encode(const Source; var Buf; SourceSize: Integer): Integer;
asm
push esi
push edi
push ebx
mov esi, eax // esi = Source
mov edi, edx // edi = Buf
push ecx
add ecx, esi // ecx = esi + SourceSize
xor eax, eax
cld
@Loop1:
cmp esi, ecx // while (esi != ecx)
je @@11 // {
xor bl, bl // bl = 0
mov bh, 2 // bh = 2
mov dl, 4 // for (dl = 4; dl > 0; dl--)
@Loop2: // {
cmp dl, 1 // if (dl > 1)
jle @@1 // {
cmp esi, ecx
je @@0
lodsb // if (esi < ecx) al = *esi++
jmp @encode
@@0:
xor al, al // else al = 0
@encode:
mov dh, al // dh = al
push ecx
mov cl, bh // al = (al >> bh) | bl
shr al, cl
or al, bl
inc bh // bh += 2
inc bh
mov cl, 8 // bl = (dh << (8 - bh)) & 0x3f
sub cl, bh
shl dh, cl
and dh, 03fh
mov bl, dh
pop ecx // }
jmp @@2
@@1:
mov al, bl // else al = bl
@@2: // // al --> eax
push esi // al = *(Base64_Chars + eax)
mov esi, offset Base64_Chars
mov al, [esi + eax]
pop esi
stosb // *Buf++ = al
dec dl
jnz @Loop2 // }
jmp @Loop1 // }
@@11:
pop eax
cdq
mov ecx, 3 // eax = SourceSize / 3
div ecx // edx = SourceSize % 3
test edx, edx // if (edx != 0)
jz @end // {
inc eax // eax ++
push eax
mov al, 61 // for (ecx = 3 - edx, edi -= ecx; ecx > 0; ecx --)
sub ecx, edx
sub edi, ecx
rep stosb // *edi ++ = '='
pop eax // }
@end:
shl eax, 2 // eax *= 4 // return value
pop ebx
pop edi
pop esi
end;

// 优化后的代码
function Base64_Encode(const Source; var Buf; SourceSize: Integer): Integer;
asm
push ebp
push esi
push edi
push ebx
mov esi, eax // esi = Source
mov edi, edx // edi = Buf
mov edx, ecx
push edx
add edx, esi // edx = esi + SourceSize
lea ebx, Base64_Chars
cld
@Loop1:
cmp esi, edx // while (esi != ecx)
je @@11 // {
mov cx, 2 // cl = 2; ch = 0
mov ebp, 4 // for (ebp = 4; ebp > 0; ebp--)
@Loop2: // {
cmp ebp, 1 // if (ebp > 1)
jle @@1 // {
cmp esi, edx // if (esi == edx)
je @@3 // goto @@3
movzx ax, [esi] // al = *esi++
inc esi
ror ax, cl
shr ah, 2 // ah = (al << (8 - cl)) & 0x3f
or al, ch // al = (al >> cl) | ch
mov ch, ah // ch = ah
add cl, 2 // cl += 2
jmp @@2 // }
@@1:
mov al, ch // else al = ch
@@2:
xlat // al = *(Base64_Chars + al)
mov [edi], al // *Buf++ = al
inc edi
dec ebp
jnz @Loop2 // }
jmp @Loop1 // }
@@3: // @@3:
mov al, ch // al = ch
xlat // al = *(Base64_Chars + al)
stosb // *Buf++ = al
dec ebp
mov al, 61 // al = '='
mov ecx, ebp // for (ecx = 3 - (SourceSize % 3); ecx > 0; ecx --)
rep stosb // *Buf++ = al;
@@11:
pop eax
cdq
mov ecx, 3 // eax = SourceSize / 3
div ecx // edx = SourceSize % 3
test edx, edx
jz @end
inc eax // if (edx != 0) eax ++
@end:
shl eax, 2 // eax *= 4 // return value
pop ebx
pop edi
pop esi
pop ebp
end;
housisong 2007-07-21
  • 打赏
  • 举报
回复
to: constantine(飘遥的安吉儿)
谢谢你的测试
constantine 2007-07-21
  • 打赏
  • 举报
回复
SafeF8(A++.NET) 的代码速度不错,
lz的代码我pc上只有29.7M/s
maozefa(阿发伯) 的有47M/s
我的pc配置太差了
  • 打赏
  • 举报
回复
在我的机器上,
faststring的Base64Encode只有253MB/S
而base64_from_delphi_VCL有519MB/S
constantine 2007-07-21
  • 打赏
  • 举报
回复
base64_from_delphi_VCL 是不错,有117M/s,第二
constantine 2007-07-21
  • 打赏
  • 举报
回复
不惑的速度我这里只有58.8M/s
还是SafeF8(A++.NET)的最快
我早上能到143M/s
  • 打赏
  • 举报
回复
目前最快的还是C版本的base64_from_delphi_VCL,
大概要比这个改进版的Base64Encode快将近一倍。
cczlp 2007-07-21
  • 打赏
  • 举报
回复
程序是Release版的
cczlp 2007-07-21
  • 打赏
  • 举报
回复
//1.测试数据1600MB
//2.Base64Encode编码函数速度: 285.6MB/s
//3.CPU: P4 2.4G; 内存: DDR 333(PC2700), 512M; 语言:C++; 编译器:C++Builder 6.0
#pragma pack(1)
typedef struct _BASE64{
union
{
struct
{
UINT En4 : 6;
UINT En3 : 6;
UINT En2 : 6;
UINT En1 : 6;
UINT Reserved: 8;
};
BYTE De[4];
};
}BASE64, *PBASE64;
#pragma pack()

void Base64Encode(const void* pData,const unsigned long DataSize,void* pOutData)
{
const char Table[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
DWORD i;
char *p = (char *)pData;
char *pOut = (char *)pOutData;
PBASE64 pBase;

for (i = 0; i < DataSize; i += 3, p += 3)
{
pBase = (PBASE64)p;
*pOut++ = Table[pBase->En1];
*pOut++ = Table[pBase->En2];
*pOut++ = Table[pBase->En3];
*pOut++ = Table[pBase->En4];
}
}
//---------------------------------------------------------------------------
void Test()
{
const long DATA_SIZE=80*1024*1024;
long OutSize = (DATA_SIZE + 2) / 3 * 4;
long i, j;

char *pData = new char[DATA_SIZE + 2];
char *pOut = new char[OutSize];

for (i = 0; i < DATA_SIZE; i++)
{
pData[i] = i;
}
DWORD t2, t1 = GetTickCount();

for (i = 0; i < 20; i++)
{
Base64Encode(pData, DATA_SIZE, pOut);
}

t2 = GetTickCount();

delete []pData;
delete []pOut;

float Speed = OutSize * 1000.0 / 1024 / 1024 / (t2 - t1) * 20;
ShowMessage("平均速度MB/s:" + FloatToStr(Speed));
}
brightyang 2007-07-20
  • 打赏
  • 举报
回复
关注
housisong 2007-07-20
  • 打赏
  • 举报
回复
为了便于查看和讨论,请移步到 “ http://community.csdn.net/Expert/topic/5665/5665480.xml?temp=.9847528 ” 查看或发帖
SafeF8 2007-07-20
  • 打赏
  • 举报
回复
试试看下面这个,也是用汇编写的,取自faststrings,效率没有测试过,不过faststings里面的部分字符串处理函数我测试过,效率确实高。相信这个也会不错。
unit Base64;

interface

const
Base64_Table: shortstring = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';

function Base64Encode(const Source: AnsiString): AnsiString;
function Base64Decode(const Source: string): string;

implementation

//Encode to Base64
function Base64Encode(const Source: AnsiString): AnsiString;
var
NewLength: Integer;
begin
NewLength := ((2 + Length(Source)) div 3) * 4;
SetLength(Result, NewLength);

asm
Push ESI
Push EDI
Push EBX
Lea EBX, Base64_Table
Inc EBX // Move past String Size (ShortString)
Mov EDI, Result
Mov EDI, [EDI]
Mov ESI, Source
Mov EDX, [ESI-4] //Length of Input String
@WriteFirst2:
CMP EDX, 0
JLE @Done
MOV AL, [ESI]
SHR AL, 2
{$IFDEF VER140} // Changes to BASM in D6
XLATB
{$ELSE}
XLAT
{$ENDIF}
MOV [EDI], AL
INC EDI
MOV AL, [ESI + 1]
MOV AH, [ESI]
SHR AX, 4
AND AL, 63
{$IFDEF VER140} // Changes to BASM in D6
XLATB
{$ELSE}
XLAT
{$ENDIF}
MOV [EDI], AL
INC EDI
CMP EDX, 1
JNE @Write3
MOV AL, 61 // Add ==
MOV [EDI], AL
INC EDI
MOV [EDI], AL
INC EDI
JMP @Done
@Write3:
MOV AL, [ESI + 2]
MOV AH, [ESI + 1]
SHR AX, 6
AND AL, 63
{$IFDEF VER140} // Changes to BASM in D6
XLATB
{$ELSE}
XLAT
{$ENDIF}
MOV [EDI], AL
INC EDI
CMP EDX, 2
JNE @Write4
MOV AL, 61 // Add =
MOV [EDI], AL
INC EDI
JMP @Done
@Write4:
MOV AL, [ESI + 2]
AND AL, 63
{$IFDEF VER140} // Changes to BASM in D6
XLATB
{$ELSE}
XLAT
{$ENDIF}
MOV [EDI], AL
INC EDI
ADD ESI, 3
SUB EDX, 3
JMP @WriteFirst2
@done:
Pop EBX
Pop EDI
Pop ESI
end;
end;

//Decode Base64
function Base64Decode(const Source: string): string;
var
NewLength: Integer;
begin
{
NB: On invalid input this routine will simply skip the bad data, a
better solution would probably report the error

ESI -> Source String
EDI -> Result String

ECX -> length of Source (number of DWords)
EAX -> 32 Bits from Source
EDX -> 24 Bits Decoded

BL -> Current number of bytes decoded
}

SetLength(Result, (Length(Source) div 4) * 3);
NewLength := 0;
asm
Push ESI
Push EDI
Push EBX

Mov ESI, Source

Mov EDI, Result //Result address
Mov EDI, [EDI]

Or ESI,ESI // Nil Strings
Jz @Done

Mov ECX, [ESI-4]
Shr ECX,2 // DWord Count

JeCxZ @Error // Empty String

Cld

jmp @Read4

@Next:
Dec ECX
Jz @Done

@Read4:
lodsd

Xor BL, BL
Xor EDX, EDX

Call @DecodeTo6Bits
Shl EDX, 6
Shr EAX,8
Call @DecodeTo6Bits
Shl EDX, 6
Shr EAX,8
Call @DecodeTo6Bits
Shl EDX, 6
Shr EAX,8
Call @DecodeTo6Bits


// Write Word

Or BL, BL
JZ @Next // No Data

Dec BL
Or BL, BL
JZ @Next // Minimum of 2 decode values to translate to 1 byte

Mov EAX, EDX

Cmp BL, 2
JL @WriteByte

Rol EAX, 8

BSWAP EAX

StoSW

Add NewLength, 2

@WriteByte:
Cmp BL, 2
JE @Next
SHR EAX, 16
StoSB

Inc NewLength
jmp @Next

@Error:
jmp @Done

@DecodeTo6Bits:

@TestLower:
Cmp AL, 'a'
Jl @TestCaps
Cmp AL, 'z'
Jg @Skip
Sub AL, 71
Jmp @Finish

@TestCaps:
Cmp AL, 'A'
Jl @TestEqual
Cmp AL, 'Z'
Jg @Skip
Sub AL, 65
Jmp @Finish

@TestEqual:
Cmp AL, '='
Jne @TestNum
// Skip byte
ret

@TestNum:
Cmp AL, '9'
Jg @Skip
Cmp AL, '0'
JL @TestSlash
Add AL, 4
Jmp @Finish

@TestSlash:
Cmp AL, '/'
Jne @TestPlus
Mov AL, 63
Jmp @Finish

@TestPlus:
Cmp AL, '+'
Jne @Skip
Mov AL, 62

@Finish:
Or DL, AL
Inc BL

@Skip:
Ret

@Done:
Pop EBX
Pop EDI
Pop ESI

end;

SetLength(Result, NewLength); // Trim off the excess
end;

end.
阿发伯 2007-07-20
  • 打赏
  • 举报
回复
1.测试数据集80MB
2.Base64Encode编码函数速度: 87.3MB/s
3.CPU: P4 2.8G; 内存: DDR2 667(334.9MHz); 语言:Pascal; 编译器:Delphi7.0
4.Base64源代码地址:http://blog.csdn.net/maozefa/archive/2007/06/14/1653020.aspx

procedure TForm1.Button1Click(Sender: TObject);
const
DATA_SIZE = 80 * 1024 * 1024;
DATA_SIZE_MAX = DATA_SIZE + 12;
var
StartTime, RunTime: Double;
EncodeSpeed, SumSpeed: Double;
CodeSize, DataSize, RunCount, I: Integer;
DataBuf: array of Byte;
CodeStr: String;
begin
SetLength(DataBuf, DATA_SIZE_MAX * Sizeof(Byte));
Randomize;
for I := 0 to DATA_SIZE_MAX - 1 do
DataBuf[I] := Random(255);
RunCount := 0;
SumSpeed := 0;
for DataSize := DATA_SIZE to DATA_SIZE_MAX - 1 do
begin
StartTime := GetTickCount;
CodeStr := Base64Encode(DataBuf[0], DataSize);
RunTime := GetTickCount;
RunTime := (RunTime - StartTime) / 1000;
CodeSize := Length(CodeStr);
EncodeSpeed := CodeSize * (1.0 / 1024 / 1024) / RunTime;
Inc(RunCount);
SumSpeed := SumSpeed + EncodeSpeed;
end;
ShowMessage('平均速度MB/s:' + FloatToStr(SumSpeed / RunCount));
end;

16,748

社区成员

发帖
与我相关
我的任务
社区描述
Delphi 语言基础/算法/系统设计
社区管理员
  • 语言基础/算法/系统设计社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧