Delphi嵌入式汇编版高性能Base64编码算法,欢迎拍砖&PK

僵哥 2010-09-28 07:48:37
加精
好久没发贴了,顺便散点分,不过散分是有条件的...

由于目前编写的代码最终都是在服务器上运行的,所以有时候也会有点变态地追求所谓的性能问题,因此希望各位能够贡献点资源上来,先谢过。先发一个比较变态的单元,其实也不是什么好东西,只是一个采用了16bit码表(有效位为12位)的Base64编码过程,之所以变态在于码表需要占用128KB的内存,当然在目前的系统当中这已经不是什么问题...

只要发出来的是公认的好东西,要分的话应该不是什么非常大的问题...
前提条件是:
1. 确实是好东西;
2. 具备实用价值;
3. 有一定的技术水准;
4. 原创.
unit uEncoder3To4_12BitsCoderTable;
{
2010-09-28 创建 by unsigned(僵哥)
}

interface



procedure Base64EncodeEx(InputCount: Integer; const Input: Pointer; Output: Pointer); assembler;
function GetSizeCoder3To4(InputCount: Integer): Integer; inline;

implementation
var
B64Table:array[0..65535] of Word;

function GetSizeCoder3To4(InputCount: Integer): Integer; inline;
begin
Result := (InputCount+2) div 3 * 4
end;

procedure Base64EncodeEx(InputCount: Integer; const Input: Pointer; Output: Pointer); assembler;
asm

TEST ECX, EDX // Input = Nil or Output = Nil ?
jz @end // if (Input = Nil) or (Output = Nil) then Exit;

Test EAX, EAX // InputCount = 0?
jz @end // if InputCount = 0 then Exit;

push esi //saving Registers
push edi
push ebx
push ebp

lea ebp,[B64Table] //load B64-CodeTable-16Bits

mov esi, edx //ESI := Input
mov edi, ecx //EDI := Output

mov edx, 0
mov ecx, 3
div ecx //EAX := InputCount div 3, EDX := InputCount mod 3
mov ecx, eax //ECX := (InputCount div 3), (Loop Count)

push edx //Saving (InputCount mod 3)
xor ebx,ebx //Clear EBX

dec esi
test ecx, ecx //InputCount div 3 = 0 ?
jz @next //if InputCount div 3 = 0 then Do Next with (InputCount mod 3) Bytes
mov eax, [esi+1] //EAX := 0x44332211 , Bits = 44444444 33333333 22222222 11111111
bswap eax //EAX := 0x11223344 , Bits = 11111111 22222222 33333333 44444444

shr eax, 8 //EAX := 0x00112233, valid-Bits: 00000000 11111111 22222222 33333333
mov bx, ax //BX := 0x2233, , Bits = 22222222 33333333, valid-Bits: 2222 33333333
mov dx,[ebp + ebx * 2] //DX := CodeTable[Bits:00000000 00000000 00002222 33333333]
mov [edi+2], dx //PWord(@Output[2])^ := DX

shr eax, 12 //First 12-bits, Bits = 00000000 0000000 00001111 11112222
mov bx, ax //BX := Bits:00001111 11112222
mov dx,[ebp + ebx * 2] //DX := CodeTable[Bits:00000000 00000000 00001111 11112222]
mov [edi], dx //PWord(@Output[0])^ := DX

add edi,4 //Inc(Output, 4)
add esi,3 //Inc(Input, 3)
Dec ECX
jz @next //Dec ECX, if ECX = 0 then Goto @next

@loop:
mov eax, [esi] //EAX := 0x66554433 , Bits = 66666666 55555555 44444444 33333333
bswap eax //EAX := 0x33445566 , Bits = 33333333 44444444 55555555 66666666
//valid-Bits: 00000000 44444444 55555555 66666666

mov bx, ax //BX := 0x5566', , Bits = 55555555 66666666, valid-Bits: 5555 66666666
mov dx,[ebp + ebx * 2] //DX := CodeTable[Bits:00000000 00000000 00005555 66666666]
mov [edi+2], dx //PWord(@Output[2])^ := DX

shr eax, 12 //First 12-bits, Bits = 00000000 0000000 00004444 444455555
mov bx, ax //BX := Bits:00004444 44445555
mov dx,[ebp + ebx * 2] //DX := CodeTable[Bits:00000000 00000000 00004444 44445555]
mov [edi], dx //PWord(@Output[0])^ := DX

add edi,4 //Inc(Output, 4)
add esi,3 //Inc(Input, 3)
Dec ECX
jnz @loop //Dec ECX, if ECX <> 0 then Goto @Loop

@next:
inc esi
pop edx //Restore (InputCount mod 3)
test edx, edx // (InputCount mod 3) = 0?
jz @ret //if (InputCount mod 3) = 0 then Exit

//Last one or two Byte(s)
lea ebx, [edx - 4]
neg ebx
sub esi, ebx
mov ax, [esi + 2] //Bits: 11111111 00000000 ( 22222222 11111111 )
xchg ah, al //Bits: 00000000 11111111 ( 11111111 22222222 )

lea ecx, [edx*8]
mov esi, 1
shl esi, cl
dec esi
and eax,esi //Set valid-Bits: 00000000 00000000 00000000 11111111 ( 00000000 00000000 11111111 22222222 )


lea ecx,[edx * 4]
shl eax,cl //last Byte, valid-Bits: 00000000 00000000 00001111 11110000 ( 00000000 00000011 11111122 22222200 )
//last-Byte-valid-Bits: 00110000 ( 00222200 )


mov esi,edx
xor ebx,ebx

@loop2:
//1. valid-Bits: 00001111 11110000 ( 00001122 22222200 )
//2. valid-Bits: N/A ( 00000000 00111111 )
mov bx, ax
mov cx, [ebp + ebx * 2]
mov [edi+edx*2-2], cx
shr eax, 12
dec edx
jnz @loop2

add edi,esi
sub esi,3
neg esi
@loop3:
//Fill-Char(s)
mov Byte ptr [edi+1], 61
inc edi
dec esi
jnz @loop3

@ret:
pop ebp
pop ebx
pop edi
pop esi
@end:
ret
end;

var
i: Integer;
const
Base64_Chars: array[0..63] of AnsiChar = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
//0123456789012345678901234567890123456789012345678901234567890123
//0 1 2 3 4 5 6
initialization
for I := 0 to 65536 - 1 do begin
B64Table[I] := (Word(Byte(Base64_Chars[I and $3f])) shl 8 ) or (Word(Byte(Base64_Chars[(I shr 6) and $3f])));
if I = $e2ca then begin
B64Table[I] := (Word(Byte(Base64_Chars[I and $3f])) shl 8 ) or (Word(Byte(Base64_Chars[(I shr 6) and $3f])));
end;

end;
end.
...全文
2848 130 打赏 收藏 转发到动态 举报
写回复
用AI写文章
130 条回复
切换为时间正序
请发表友善的回复…
发表回复
僵哥 2010-10-09
  • 打赏
  • 举报
回复
[Quote=引用 129 楼 likeyrain 的回复:]
之前想做一个邮件系统的时候,找这个编码转换找了好久。。。,最是早点出来就好了
[/Quote]
编码程序到处都是。我只是在力所能及的范围内,让它稍稍变得快那么一点点而已。
likeyrain 2010-10-09
  • 打赏
  • 举报
回复
之前想做一个邮件系统的时候,找这个编码转换找了好久。。。,最是早点出来就好了
xianguang22 2010-10-08
  • 打赏
  • 举报
回复
牛人呀,崇拜以下
nmxmhyz 2010-10-08
  • 打赏
  • 举报
回复
,IE9无法显示
僵哥 2010-10-08
  • 打赏
  • 举报
回复
备注信息错了...

"//根据DelphiGuy的提醒,防止 EAX = Not EDX的情况"

应该是

"//根据DelphiGuy的提醒,防止 EAX and EDX = 0 的情况"
僵哥 2010-10-08
  • 打赏
  • 举报
回复
增加了一个支持含非BASE64有效字符编码信息的解码函数,对不含非BASE64有效字符编码信息的解码,性能降低了近30%。

这里放出完整代码,希望各位能够提供比较有效的优化建议或代码,谢谢!
unit uDecoder4To3_14BitsCoderTable;
{
2010-10-01 Create by Unsigned(僵哥)
2010-10-08 增加 Base64Decode 用于编码数据中存在非BASE64有效字符时的解码 by Unsigned(僵哥)
}
interface

function Base64DecodeEx( const Input: Pointer
; const Output: Pointer
; InputCount: Integer
): Integer; assembler;
function Base64Decode( const Input: Pointer
; const Output: Pointer
; InputCount: Integer
): Integer; assembler;
function GetSizeDecoder4To3(InputCount: Integer): Integer; inline;
implementation

//根据DelphiGuy的提醒,将码表增大到足够安全
{
const B_0 = -11051;
const B_1 = 9253;
const B_2 = 29557;
const B_3 = 60912;
var
CoderTable: array[0..60911 + 256] of Byte;
}
const B_0 = 0;
const B_1 = 65536;
const B_2 = 131072;
const B_3 = 196608;
var
CoderTable: array[0..196607 + 256] of Byte;


function GetSizeDecoder4To3(InputCount: Integer): Integer; inline;
begin
Result := (InputCount) div 4 * 3;
end;

function Base64DecodeEx( const Input: Pointer
; const Output: Pointer
; InputCount: Integer
): Integer; assembler;
asm
//根据DelphiGuy的提醒,防止 EAX = Not EDX的情况
{
TEST EAX, EDX
JZ @RET
}
TEST EAX, EAX
JZ @RET

TEST EDX, EDX
JZ @RET

SHR ECX, 2
TEST ECX, ECX
JZ @RET

PUSH ESI
PUSH EDI
PUSH EBP
PUSH EBX

PUSH EDX

LEA EBP, [CoderTable]

MOV ESI, EAX
MOV EDI, EDX

XOR EDX, EDX
XOR EAX, EAX

@LOOP:

MOV AX, [ESI] //Bits: 00222211 00111111
MOV DX, [ESI + 2] //Bits: 00333333 00332222
MOV BL, [EBP + EAX + B_0] //CoderTable[Bits: 00222211 00111111]

MOV AL, DL //EAX := Bits: 0000000 0000000 00222211 00332222
MOV BH, [EBP + EAX + B_1] //CoderTable[Bits: 00222211 00332222]

MOV [EDI], BX

MOV BL, [EBP + EDX + B_2] //CoderTable[Bits: 00333333 00332222]
MOV [EDI + 2], BL

ADD ESI, 4
ADD EDI, 3

DEC ECX
JNZ @LOOP

POP EAX
NEG EAX
ADD EAX, EDI

CMP DH, $3D
JNZ @END
DEC EAX

CMP DL, $3D
JNZ @END
DEC EAX

@END:
POP EBX
POP EBP
POP EDI
POP ESI
@RET:
end;

function Base64Decode( const Input: Pointer
; const Output: Pointer
; InputCount: Integer
): Integer; assembler;
asm
//根据DelphiGuy的提醒,防止 EAX = Not EDX的情况
{
TEST EAX, EDX
JZ @RET
}
TEST EAX, EAX
JZ @RET

TEST EDX, EDX
JZ @RET

SHR ECX, 2
TEST ECX, ECX
JZ @RET

PUSH ESI
PUSH EDI
PUSH EBP
PUSH EBX

PUSH EDX

LEA EBP, [CoderTable]

MOV ESI, EAX
MOV EDI, EDX

XOR EDX, EDX
XOR EAX, EAX

XOR EBX, EBX
@@0:
MOV BL, [ESI + 0] //Bits: 00111111
CMP Byte ptr [EBP + EBX + B_3], 1
JE @AL
INC ESI
DEC ECX
JZ @ENDLOOP
JMP @@0
@AL:
MOV AL, BL
@@1:
MOV BL, [ESI + 1] //Bits: 00222211
CMP Byte ptr [EBP + EBX + B_3], 1
JE @AH
INC ESI
DEC ECX
JNZ @@1
JMP @ENDLOOP
@AH:
MOV AH, BL
@@2:
MOV BL, [ESI + 2] //Bits: 00332222
CMP Byte ptr [EBP + EBX + B_3], 1
JE @DL
INC ESI
DEC ECX
JNZ @@2
JMP @ENDLOOP
@DL:
MOV DL, BL
@@3:
MOV BL, [ESI + 3] //Bits: 00333333
CMP Byte ptr [EBP + EBX + B_3], 1
JE @DH
INC ESI
DEC ECX
JNZ @@3
JMP @ENDLOOP
@DH:
MOV DH, BL

MOV BL, [EBP + EAX + B_0] //CoderTable[Bits: 00222211 00111111]
MOV [EDI + 0], BL

MOV AL, DL //EAX := Bits: 0000000 0000000 00222211 00332222
MOV BL, [EBP + EAX + B_1] //CoderTable[Bits: 00222211 00332222]

MOV [EDI + 1], BL

MOV BL, [EBP + EDX + B_2] //CoderTable[Bits: 00333333 00332222]
MOV [EDI + 2], BL

ADD ESI, 4
ADD EDI, 3

SUB ECX, 4
JNZ @LOOP

@ENDLOOP:

POP EAX
NEG EAX
ADD EAX, EDI

CMP DH, $3D
JNZ @END
DEC EAX

CMP DL, $3D
JNZ @END
DEC EAX

@END:
POP EBX
POP EBP
POP EDI
POP ESI
@RET:
end;

{$REGION 'InitTable'}
procedure InitTable; inline;
var
I,J: Integer;
Index_I, Bits_I_0, Bits_I_1, Bits_I_2: Integer;
begin

FillChar(CoderTable, SizeOf(CoderTable), 0);

for I := 0 to 64 do begin

Bits_I_0 := (I shr 4) and $3;
Bits_I_1 := (I shl 4) and $F0;
Bits_I_2 := I;

case I of
0..25: begin
Index_I := (I + 65) shl 8;
CoderTable[B_3 + I + 65] := 1;
end;
26..51: begin
Index_I := (I - 26 + 97) shl 8;
CoderTable[B_3 + I - 26 + 97] := 1;
end;
52..61: begin
Index_I := (I - 52 + 48) shl 8;
CoderTable[B_3 + I - 52 + 48] := 1;
end;
62: begin
Index_I := 43 shl 8;
CoderTable[B_3 + 43] := 1;
end;
63: begin
Index_I := 47 shl 8;
CoderTable[B_3 + 47] := 1;
end
else begin
Index_I := 61 shl 8;
CoderTable[B_3 + 61] := 1;

Bits_I_0 := 0;
Bits_I_1 := 0;
Bits_I_2 := 0;
end;
end;

for J := 0 to 9 do begin
CoderTable[(Index_I or (J + 48)) + B_0] := Bits_I_0 or ((J + 52) shl 2);
CoderTable[(Index_I or (J + 65)) + B_0] := Bits_I_0 or ((J + 0) shl 2);
CoderTable[(Index_I or (J + 97)) + B_0] := Bits_I_0 or ((J + 26) shl 2 );

CoderTable[(Index_I or (J + 48)) + B_1] := Bits_I_1 or ((J + 52) shr 2);
CoderTable[(Index_I or (J + 65)) + B_1] := Bits_I_1 or ((J + 0) shr 2);
CoderTable[(Index_I or (J + 97)) + B_1] := Bits_I_1 or ((J + 26) shr 2);

CoderTable[(Index_I or (J + 48)) + B_2] := Bits_I_2 or (((J + 52) shl 6) and $C0);
CoderTable[(Index_I or (J + 65)) + B_2] := Bits_I_2 or (((J + 0) shl 6) and $C0);
CoderTable[(Index_I or (J + 97)) + B_2] := Bits_I_2 or (((J + 26) shl 6) and $C0);
end;

for J := 10 to 25 do begin
CoderTable[(Index_I or (J + 65)) + B_0] := Bits_I_0 or ((J + 0) shl 2);
CoderTable[(Index_I or (J + 97)) + B_0] := Bits_I_0 or ((J + 26) shl 2);

CoderTable[(Index_I or (J + 65)) + B_1] := Bits_I_1 or ((J + 0) shr 2);
CoderTable[(Index_I or (J + 97)) + B_1] := Bits_I_1 or ((J + 26) shr 2);

CoderTable[(Index_I or (J + 65)) + B_2] := Bits_I_2 or (((J + 0) shl 6) and $C0);
CoderTable[(Index_I or (J + 97)) + B_2] := Bits_I_2 or (((J + 26) shl 6) and $C0);
end;

CoderTable[(Index_I or 43) + B_0] := Bits_I_0 or 248;
CoderTable[(Index_I or 47) + B_0] := Bits_I_0 or 252;
CoderTable[(Index_I or 61) + B_0] := Bits_I_0 or 0;

CoderTable[(Index_I or 43) + B_1] := Bits_I_1 or 15;
CoderTable[(Index_I or 47) + B_1] := Bits_I_1 or 15;
CoderTable[(Index_I or 61) + B_1] := Bits_I_1 or 0;

CoderTable[(Index_I or 43) + B_2] := Bits_I_2 or 128;
CoderTable[(Index_I or 47) + B_2] := Bits_I_2 or 192;
CoderTable[(Index_I or 61) + B_2] := Bits_I_2 or 0;
end;

end;
{$ENDREGION}
initialization
InitTable;
end.
僵哥 2010-10-08
  • 打赏
  • 举报
回复
[Quote=引用 124 楼 delphiguy 的回复:]
1. TEST EAX, EDX在eax、edx均不为0的情况下,也可能得到0的结果。

2. 如果我没理解错的话,你的解码函数并没有过滤掉非base64字符,不是“没有对它进行处理”,而是将它当成base64字符来解码了,这会导致程序运行异常。
[/Quote]
1. 机会主义...写这行代码的时候,还真没有多想...
2. 确实如此,最初设置的码表是65536,后来将它精减掉了。从程序的健状性来讲,这是完全不允许的。

非常感谢指正。
  • 打赏
  • 举报
回复
1. TEST EAX, EDX在eax、edx均不为0的情况下,也可能得到0的结果。

2. 如果我没理解错的话,你的解码函数并没有过滤掉非base64字符,不是“没有对它进行处理”,而是将它当成base64字符来解码了,这会导致程序运行异常。
僵哥 2010-10-08
  • 打赏
  • 举报
回复
[Quote=引用 122 楼 delphiguy 的回复:]
僵哥在91楼的base64解码程序写得不错。不过存在一些问题:

一是函数的返回值在某些情况下可能是无效的,见这两行:
TEST EAX, EDX
JZ @RET

二是此函数只能解码仅包含base64字符的源数据(就我初步看是这样的),如果源数据中包含其他字符,象空格、换行回车、制表符等等,程序可能出现不确定的举止,访问违例、异常终止等等。
当然,如果限定应用场合可以保证源数据确实……
[/Quote]
TEST EAX, EDX
JZ @RET
这段代码,其实并没有意义,入参都错了,那结果自然不理会了。毕竟是一个过程,这里面也无须多余处理。

至于多余字符,这个本来就没有对它进行处理,否则性能会降不少。目前很多应用当中都为了争取性能,而忽略掉对这些异常的处理,完全由人为进行保证。

有一句话叫作:程序永远不要试图去替代测试员的工作。
  • 打赏
  • 举报
回复
僵哥在91楼的base64解码程序写得不错。不过存在一些问题:

一是函数的返回值在某些情况下可能是无效的,见这两行:
TEST EAX, EDX
JZ @RET

二是此函数只能解码仅包含base64字符的源数据(就我初步看是这样的),如果源数据中包含其他字符,象空格、换行回车、制表符等等,程序可能出现不确定的举止,访问违例、异常终止等等。
当然,如果限定应用场合可以保证源数据确实仅包含base64字符,那也是可以的。不过还是存在潜在的风险,而且这样解码不符合RFC 1521的规范(69楼引用的housisong的帖子里也讨论过这个问题)。


仅供参考。
  • 打赏
  • 举报
回复
啊,各位放假还没休息呀,辛苦了。:)
ccrun.com 2010-10-06
  • 打赏
  • 举报
回复
友情围观僵哥的牛帖。
lihuasoft 2010-10-05
  • 打赏
  • 举报
回复
[Quote=引用 110 楼 unsigned 的回复:]
104#的代码可以这样子写

......

在我的机器上性能大约提升12%
[/Quote]

僵哥 2010-10-05
  • 打赏
  • 举报
回复
104#的代码可以这样子写
{ 返回一个整型数的某二进位值 }
function TestBit(Value, Index : integer) : Byte;
asm
BT EAX, Index //检测Index位是否为1,如为1则置PSW寄存器的CF位为1
SETB AL // CF -> AL
end;

在我的机器上性能大约提升12%
linghengmao 2010-10-05
  • 打赏
  • 举报
回复
膜拜牛人!
僵哥 2010-10-05
  • 打赏
  • 举报
回复
[Quote=引用 114 楼 lihuasoft 的回复:]
肯定会有时间差的,多一行指令就多一个时钟周期
既然是在细微之处追求高效,那就要精益求精
向僵哥学习
[/Quote]
不同的指令,时钟周期是不同的,所以并不能以指令的多少来判断其时间消耗。
ninetowns2008 2010-10-05
  • 打赏
  • 举报
回复
学习了
lihuasoft 2010-10-05
  • 打赏
  • 举报
回复
肯定会有时间差的,多一行指令就多一个时钟周期
既然是在细微之处追求高效,那就要精益求精
向僵哥学习
僵哥 2010-10-05
  • 打赏
  • 举报
回复
我的意思是两种写法之间几乎没有时间差
僵哥 2010-10-05
  • 打赏
  • 举报
回复
[Quote=引用 111 楼 lihuasoft 的回复:]

[/Quote]
绝大部分机器测试的结果都差不多。
加载更多回复(94)

16,748

社区成员

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

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