代碼優化-之-Base64編碼函數的極限優化挑戰
[email protected] 2007.07.27
tag:速度優化,Base64,CPU緩存優化,代碼優化,查找表,彙編,SSE、SSE2優化,并行
摘要: Base64編碼是很常用的一種把二進制資料轉換為字元串的算法;
本文章對Base64的編碼函數進行了各種優化嘗試,目标是極限編碼速度!
并對優化過程中使用的方法進行了詳細說明(主要使用了查表優化);
(2008.01.07 在文章中添加了Base64解碼函數base64_decode,其實文章末尾提供的下載下傳源代碼中已經有該函數了,這次隻是把其代碼貼在文章中;)
(2007.11.25 重新實作base64_encode3_sse2_prefetch的記憶體預讀優化;由于加了一條記憶體組成雙通道,并行測試成績都提高了5-10%)
(2007.09.07 添加base64_encode3_sse2_prefetch的實作,測試記憶體預讀的效果,但效果不太好。)
(2007.07.27 覺得隻使用64位元組表的彙編優化也是有其意義的,是以添加了base64_encode1_asm實作)
正文:
代碼使用C++;涉及到彙編優化的時候假定為x86平台;
測試平台:(CPU:AMD64x2 4200+(2.37G);記憶體:DDR2 677(雙通道);C/C++編譯器:VC2005)
作業系統:WindowsXP
( 警告:代碼移植到其他體系的CPU時需要重新考慮位元組順序的大端小端問題! 實際項目中如果想要用這裡的代碼,建議選用64位元組表的那個優化版本; )
A:本文章的來源和起因:
cpper程式設計論壇( www.cpper.com/c )以前進行過一次Base64編碼器速度競賽
( http://www.cpper.com/c/t665.html 和 http://www.cpper.com/c/t694.html );
當時我沒有參加,後來應管理者要求看看能不能在結果上繼續優化,那時就簡單寫
了點代碼嘗試,但沒能得到更好的結果;
最近因為工作中需要用到Base64,就拿了其競賽結果(Base64EncodeSXMNew版本)
來改寫;由于拿人手短,而且以前有答應看看是否還有改進的餘地;是以空餘時間
就在這個基礎之上進行了一些速度優化改進嘗試,本文章就是本次嘗試的結果;
(聲明:本文章引用這次競賽和其代碼得到了cpper管理者的授權)
嘗試了一些代碼後,我在CSDN程式員網站( www.csdn.net )開貼征集最快的Base64
編碼函數:
http://community.csdn.net/Expert/topic/5665/5665480.xml?temp=.4219171 ,
以求獲得更多的思路和使已有的優化政策獲得更多的驗證機會;發帖的朋友對本文章
的形成也起到了重要的作用,某些版本的函數速度得到了提高,某些在我的AMDx2
CPU上運作很快的版本、在網友的奔騰4 CPU上運作減慢(放棄了一簇版本)、由讨論
而産生的新的函數版本等等;本文章也是對這次讨論的一些簡要總結(某些網友的實作
版本或政策沒有整合到本文章中,請到csdn論壇直接檢視);
B:Base64編碼原理簡要說明
有時為了更好的傳遞或儲存二進制資料,需要先把二進制資料轉換成純文字的編碼方式。
最容易想到的方案就是直接轉換成16進制的文本方式;即把一個位元組(8bit)先分成兩
個4bit(值域[0..15]),然後映射到'0'--'9''A'--'F'這16個字元編碼中; 那麼一個位元組
的資料轉換為了兩個字元,也就是說資料轉換後變為原資料大小的兩倍!
Base64的也能完成這個任務,但更節省空間,轉換後的文本資料隻是原資料大小的4/3倍;
這是怎麼做到的呢? 将原資料3個位元組一組(3x8bit),按一定方式分組成4個6bit(值
域[0..63]); 然後将[0..25]的值映射到'A'--'Z',将[26..51]的值映射到'a'--'z',
将[52..61]的值映射到'0'--'9',将62的值映射為'+',将63的值映射為'/'; 由于原資料不
一定正好是3的倍數,是以分組後的4個值中有可能沒有被配置設定bit位的情況,沒有配置設定bit位
的值輸出資料填充'='。
bit位分組方式示意:
// 3x8bit
// |------------------|------------------|------------------|
// | a[0..7] | b[0..7] | c[0..7] |
// |------------------|------------------|------------------|
//
// to 4x6bit
// |------------------|------------------|------------------|------------------|
// | a[2..7] |b[4..7]+a[0..1]<<4|c[6..7]+b[0..3]<<2| c[0..5] |
// |------------------|------------------|------------------|------------------|
C:一個基本實作和速度測試架構
使用了較大的資料量多次測試取平均值;
//編碼函數每秒編碼出的資料量:
// base64_encode0 109.0 MB/s
#include <time.h>
#include <iostream>
#include <vector>
#include <string>
#define asm __asm
const unsigned char BASE64_PADDING='='; //輸入資料不足3的倍數時 輸出字元後面填充'='号
//将6bit資料按規則映射成字元(6bit資料)
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_encode0(const void* pdata,const unsigned long data_size,void* out_pcode)
{
const unsigned char* input=(const unsigned char*)pdata;
const unsigned char* input_end=&input[data_size];
unsigned char* output=(unsigned char*)out_pcode;
for(;input+2<input_end;input+=3,output+=4)
{
output[0]=to_base64char( input[0] >> 2 );
output[1]=to_base64char( ((input[0] << 4) | (input[1] >> 4)) & 0x3F );
output[2]=to_base64char( ((input[1] << 2) | (input[2] >> 6)) & 0x3F );
output[3]=to_base64char( input[2] & 0x3F);
}
unsigned long bord_width=input_end-input;
if (bord_width==1)
{
output[0]=to_base64char( input[0] >> 2 );
output[1]=to_base64char( (input[0] << 4) & 0x3F );
output[2]=BASE64_PADDING;
output[3]=BASE64_PADDING;
}
else if (bord_width==2)
{
output[0]=to_base64char( input[0] >> 2 );
output[1]=to_base64char( ((input[0] << 4) | (input[1] >> 4)) & 0x3F );
output[2]=to_base64char( (input[1] << 2) & 0x3F );
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_encode0" ,base64_encode0 ,DATA_SIZE);
return 0;
}
D.優化to_base64char函數
to_base64char有很多的條件分支,在目前的CPU上會嚴重的降低性能;
分析該函數有這樣的特征: 函數的輸入資料允許的取值個數很小(隻能取64個值中的一個);
單個輸入值對應的傳回值固定;
那麼我們可以建立一個數組的表格,該數組有64個元素,每個元素的值等于該元素的序
号(假定數組序号從0開始)作為to_base64char參數時的傳回值;
即: unsigned char BASE64_CODE[64];
其中BASE64_CODE[i]=to_base64char(i); // i屬于[0..63]
那麼對于這樣的代碼: output[0]=to_base64char(input[0]>>2);
可以化簡為: output[0]=BASE64_CODE[input[0]>>2];
這就是利用查表來替代計算的優化方法!
(提示:在不同的需求下,表需要靈活的構造)
base64_encode0使用查詢表改進後的新代碼:
//編碼函數每秒編碼出的資料量:
// base64_encode0_table 294.6 MB/s
const unsigned char BASE64_CODE[]=
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
//使用64位元組的表
void base64_encode0_table(const void* pdata,const unsigned long data_size,void* out_pcode)
{
const unsigned char* input=(const unsigned char*)pdata;
const unsigned char* input_end=&input[data_size];
unsigned char* output=(unsigned char*)out_pcode;
for(;input+2<input_end;input+=3,output+=4)
{
output[0]=BASE64_CODE[ input[0] >> 2 ];
output[1]=BASE64_CODE[ ((input[0] << 4) | (input[1] >> 4)) & 0x3F ];
output[2]=BASE64_CODE[ ((input[1] << 2) | (input[2] >> 6)) & 0x3F ];
output[3]=BASE64_CODE[ input[2] & 0x3F];
}
unsigned long bord_width=input_end-input;
if (bord_width==1)
{
output[0]=BASE64_CODE[ input[0] >> 2 ];
output[1]=BASE64_CODE[ (input[0] << 4) & 0x3F ];
output[2]=BASE64_PADDING;
output[3]=BASE64_PADDING;
}
else if (bord_width==2)
{
output[0]=BASE64_CODE[ input[0] >> 2 ];
output[1]=BASE64_CODE[ ((input[0] << 4) | (input[1] >> 4)) & 0x3F ];
output[2]=BASE64_CODE[ (input[1] << 2) & 0x3F ];
output[3]=BASE64_PADDING;
}
}
E:在目前的32比特CPU上一次寫入4位元組将獲得更好的性能;而輸入資料的地方,可以先轉化
成32比特整形資料再做各種複雜的位運算有利于編譯器的優化(各個PC平台的差異可能比較大);
//編碼函數每秒編碼出的資料量:
// base64_encode1 533.3 MB/s
inline void base64_addpaing(const unsigned char* input,const unsigned int bord_width,unsigned char* output)
{
if (bord_width==1)
{
unsigned int input0=input[0];
unsigned int output0=BASE64_CODE[ input0 >> 2 ];
unsigned int output1=BASE64_CODE[ (input0 << 4) & 0x3F ];
*(unsigned long*)(&output[0])=output0 | (output1<<8) | ((BASE64_PADDING<<16) | (BASE64_PADDING<<24));
}
else if (bord_width==2)
{
unsigned int input0=input[0];
unsigned int input1=input[1];
unsigned int output0=BASE64_CODE[ input0 >> 2 ];
unsigned int output1=BASE64_CODE[ ((input0 << 4) | (input1 >> 4)) & 0x3F ];
unsigned int output2=BASE64_CODE[ (input1 << 2) & 0x3F ];
*(unsigned long*)(&output[0])=output0 | (output1<<8) | (output2<<16) | (BASE64_PADDING<<24);
}
}
//使用64位元組的表
void base64_encode1(const void* pdata,const unsigned long data_size,void* out_pcode)
{
const unsigned char* input=(const unsigned char*)pdata;
const unsigned char* input_end=&input[data_size];
unsigned char* output=(unsigned char*)out_pcode;
for(;input+2<input_end;input+=3,output+=4)
{
//unsigned int output0=BASE64_CODE[ input[0] >> 2 ];
//unsigned int output1=BASE64_CODE[ ((input[0] << 4) | (input[1] >> 4)) & 0x3F ];
//unsigned int output2=BASE64_CODE[ ((input[1] << 2) | (input[2] >> 6)) & 0x3F ];
//unsigned int output3=BASE64_CODE[ input[2] & 0x3F ];
//*(unsigned long*)(&output[0])=output0 | (output1<<8) | (output2<<16) | (output3<<24);
// 可以測試一下這種記憶體讀取方式的速度
unsigned int input0=input[0];
unsigned int input1=input[1];
unsigned int input2=input[2];
unsigned int output0=BASE64_CODE[ input0 >> 2 ];
unsigned int output1=BASE64_CODE[ ((input0 << 4) | (input1 >> 4)) & 0x3F ];
unsigned int output2=BASE64_CODE[ ((input1 << 2) | (input2 >> 6)) & 0x3F ];
unsigned int output3=BASE64_CODE[ input2 & 0x3F ];
*(unsigned long*)(&output[0])=output0 | (output1<<8) | (output2<<16) | (output3<<24);
}
base64_addpaing(input,input_end-input,output);
}
F:減少位操作複雜度的一個方案:預先交換輸入資料的位元組順序
//編碼函數每秒編碼出的資料量:
// base64_encode1_bswap 579.2 MB/s
//使用64位元組的表
void base64_encode1_bswap(const void* pdata,const unsigned long data_size,void* out_pcode)
{
const unsigned char* input=(const unsigned char*)pdata;
const unsigned char* input_end=&input[data_size];
unsigned char* output=(unsigned char*)out_pcode;
for(;input+2<input_end;input+=3,output+=4)
{
unsigned long input_all=(input[0]<<16) | (input[1]<<8) | (input[2]);
//彙編指令中有一條BSWAP指令交換位元組順序,可能能加快該代碼的速度
unsigned int output0=BASE64_CODE[ input_all >> 18 ];
unsigned int output1=BASE64_CODE[ (input_all >> 12) & 0x3F ];
unsigned int output2=BASE64_CODE[ (input_all >> 6) & 0x3F ];
unsigned int output3=BASE64_CODE[ input_all & 0x3F ];
*(unsigned long*)(&output[0])=output0 | (output1<<8) | (output2<<16) | (output3<<24);
}
base64_addpaing(input,input_end-input,output);
}
G:使用64位元組表的彙編優化版本base64_encode1_asm
//編碼函數每秒編碼出的資料量:
// base64_encode1_asm 674.6 MB/s
void __declspec(naked) __stdcall _base64_encode1_line_asm(const unsigned char* input,unsigned char* output,const long line_count)
{
asm
{
push esi
push edi
push ebx
push ebp
mov esi,[esp+4+16]//input
mov edi,[esp+8+16]//output
mov ebp,[esp+12+16]//line_count
lea edi,[edi+ebp*4]
neg ebp
align 4
loop_begin:
mov ebx,dword ptr [esi]
bswap ebx
mov ecx,ebx
mov edx,ebx
mov eax,ebx
shr ecx,14
shr edx,8
shr eax,26
and ecx,0x3F
shr ebx,20
and edx,0x3F
and eax,0x3F
movzx ecx, BYTE PTR [BASE64_CODE+ecx]
and ebx,0x3F
mov ch , BYTE PTR [BASE64_CODE+edx]
movzx eax, BYTE PTR [BASE64_CODE+eax]
shl ecx,16
mov ah , BYTE PTR [BASE64_CODE+ebx]
add esi,3
or ecx,eax
mov [edi+ebp*4],ecx
add ebp,1
jnz loop_begin
pop ebp
pop ebx
pop edi
pop esi
ret 12
}
}
//使用64位元組的表
void base64_encode1_asm(const void* pdata,const unsigned long data_size,void* out_pcode)
{
const unsigned char* input=(const unsigned char*)pdata;
unsigned char* output=(unsigned char*)out_pcode;
unsigned long fast_cell=data_size/3;
if (fast_cell>0)
{
unsigned long fast_cell_safe=fast_cell-1;
if (fast_cell_safe>0)
{
_base64_encode1_line_asm(input,output,fast_cell_safe);
input+=(fast_cell_safe*3);
output+=(fast_cell_safe*4);
}
unsigned long input_all=(input[0]<<16) | (input[1]<<8) | (input[2]);
unsigned int output0=BASE64_CODE[ input_all >> 18 ];
unsigned int output1=BASE64_CODE[ (input_all >> 12) & 0x3F ];
unsigned int output2=BASE64_CODE[ (input_all >> 6) & 0x3F ];
unsigned int output3=BASE64_CODE[ input_all & 0x3F ];
*(unsigned long*)(&output[0])=output0 | (output1<<8) | (output2<<16) | (output3<<24);
input+=3;
output+=4;
}
base64_addpaing(input,data_size-(fast_cell*3),output);
}
H:為了減少計算量,我們可以嘗試适量增大表的大小(空間換時間)
對于這樣的代碼: output0=BASE64_CODE[input0/4];
我想改寫成這樣: output0=BASE64_CODE_SHIFT2[input0];
那麼BASE64_CODE_SHIFT2應該怎樣定義呢?由于output0=BASE64_CODE_SHIFT2[input0]=BASE64_CODE[input0/4];
有BASE64_CODE_SHIFT2[i]=BASE64_CODE[i/4]; //i屬于[0.255]
對于這樣的代碼: output3=BASE64_CODE[input2 & 0x3F];
我想改寫成這樣: output3=BASE64_CODE_EX[input2];
那麼BASE64_CODE_EX[i]=BASE64_CODE[i & 0x3F]; //i屬于[0.255]
為了能夠簡化output1和output2的計算,擴大BASE64_CODE_EX到4k,參見源代碼:
//編碼函數每秒編碼出的資料量:
// base64_encode2 701.6 MB/s
// |------------------|------------------|------------------|
// | a[0..7] | b[0..7] | c[0..7] |
// |------------------|------------------|------------------|
//
// |------------------|------------------|------------------|------------------|
// | a[0..7] |b[4..7]+a[0..7]<<4|c[6..7]+b[0..7]<<2| c[0..7] |
// |------------------|------------------|------------------|------------------|
//使用64+256+4096位元組的表(4K+)
void base64_encode2(const void* pdata,const unsigned long data_size,void* out_pcode)
{
static unsigned char BASE64_CODE_SHIFT2[256];
static unsigned char BASE64_CODE_EX[256];
static bool initialized=false;
if(!initialized)
{
unsigned int i;
for(i=0;i<256;++i)
BASE64_CODE_SHIFT2[i]=BASE64_CODE[i/4];
for(i=0;i<(1<<12);++i)
BASE64_CODE_EX[i]=BASE64_CODE[i%64];
initialized=true;
}
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;
for(;input+2<input_end;input+=3,output+=4)
{
//output[0]=BASE64_CODE_SHIFT2[input[0]];
//output[1]=BASE64_CODE_EX[(input[0]<<4) + (input[1]>>4)];
//output[2]=BASE64_CODE_EX[(input[1]<<2) + (input[2]>>6)];
//output[3]=BASE64_CODE_EX[input[2]];
// 可以測試一下這種記憶體讀取和寫入方式的速度
unsigned int input0=input[0];
unsigned int input1=input[1];
unsigned int input2=input[2];
unsigned int output0=BASE64_CODE_SHIFT2[input0];
unsigned int output1=BASE64_CODE_EX[(input0<<4) + (input1>>4)];
unsigned int output2=BASE64_CODE_EX[(input1<<2) + (input2>>6)];
unsigned int output3=BASE64_CODE_EX[input2];
*(unsigned long*)(&output[0])=output0 | (output1<<8) | (output2<<16) | (output3<<24);
}
base64_addpaing(input,input_end-input,output);
}
I:使用更大的表來換取更快的速度!
前面的代碼中6bit的資料查表可以得到8bit的輸出資料,那麼我可以構造一個更大的表,
一次使用12bit的資料查表得到16bit的輸出資料!
原代碼: unsigned int output0=BASE64_CODE[input0 >> 2];
unsigned int output1=BASE64_CODE[((input0 << 4) | (input1 >> 4)) & 0x3F];
想要的新代碼: output_0_1=BASE64_WCODE[(input0<<4) | (input1>>4)]
有 BASE64_WCODE[(input0<<4)|(input1>>4)]= BASE64_CODE[input0 >> 2]
| (BASE64_CODE[((input0 << 4) | (input1 >> 4)) & 0x3F] << 8);
令i==(input0<<4) | (input1>>4); 則有: input0==i>>4; (input>>4)==i&0F;
即:BASE64_WCODE[i]=BASE64_CODE[(i>>4)>>2]
| (BASE64_CODE[ (((i>>4)<< 4) | i&0F) & 0x3F ] << 8);
BASE64_WCODE[i]=BASE64_CODE[i>>6] | (BASE64_CODE[i & 0x3F]<<8);//i屬于[0..4095]
(當你熟悉用建資料表來表達計算的時候,這些推導反而顯得累贅;
對于這裡的表的建立,隻需要注意表的序号就是需要替換的函數(也可以是假想的函數)的參數,
資料表中的值就是該輸入參數時函數的傳回值,這樣就可以直接寫出表的建立表達式。)
為了不增加新的表,我們在的BASE64_WCODE表的基礎上來得到計算output_2_3的表達式;
output_2_3=BASE64_WCODE[((input1<<8) | input2) & 0x0FFF];
//編碼函數每秒編碼出的資料量:
// base64_encode3 791.3 MB/s
// |------------------|------------------|------------------|
// | a[0..7] | b[0..7] | c[0..7] |
// |------------------|------------------|------------------|
//
// |-------------------------------------|-----------------------------------|
// | a[0..7]<<4 + b[4..7] | b[0..3]<<8 + c[0..7] |
// |-------------------------------------|-----------------------------------|
//使用4096x2位元組的表(8K)
void base64_encode3(const void* pdata,const unsigned long data_size,void* out_pcode)
{
static unsigned short BASE64_WCODE[1<<12];
static bool initialized=false;
if(!initialized)
{
for(unsigned int i=0;i<(1<<12);++i)
{
BASE64_WCODE[i]=BASE64_CODE[i>>6] | (BASE64_CODE[i & 0x3F] << 8);
}
initialized=true;
}
const unsigned char* input=(const unsigned char*)pdata;
unsigned char* output=(unsigned char*)out_pcode;
long i=data_size/3;
output+=(i*4);
for(i=-i;i;input+=3,++i)
{
unsigned int input0=input[0];
unsigned int input1=input[1];
unsigned int input2=input[2];
unsigned int output_0_1=BASE64_WCODE[ (input0<<4) | (input1>>4)];
unsigned int output_2_3=BASE64_WCODE[((input1 & 0x0F)<<8) | input2];
*(unsigned long*)(&output[i*4])=output_0_1 | (output_2_3<<16);
}
base64_addpaing(input,data_size%3,output);
}
(聲明:在實際項目中Base64編碼函數很少會成為瓶頸;項目中應該使用性能剖分工具來
定位程式熱點,集中精力優化這些瓶頸;不過本文章的目的正好是要看看對它的極緻優
化所能使用的方案:)
如果使用更大的表(256K),一次使用16bit做查詢,可以更好的化簡計算
(但由于表太大可能不能裝入CPU的一級緩存,是以函數運作可能會很慢)
//編碼函數每秒編碼出的資料量:
// base64_encode_256K 409.8 MB/s
// |------------------|------------------|------------------|
// | a[0..7] | b[0..7] | c[0..7] |
// |------------------|------------------|------------------|
//
// |-------------------------------------|-------------------------------------|
// | a[0..7] + b[0..7]<<8 | b[0..7] + c[0..7]<<8 |
// |-------------------------------------|-------------------------------------|
//使用(1<<16)x2x2位元組的表(256K)
void base64_encode_256K(const void* pdata,const unsigned long data_size,void* out_pcode)
{
static unsigned short BASE64_WCODE_L[1<<16];
static unsigned short BASE64_WCODE_H[1<<16];
static bool initialized=false;
if(!initialized)
{
for(unsigned long i=0;i<(1<<16);++i)
{
unsigned int input0=i&0xFF;
unsigned int input1=(i>>8)&0xFF;
unsigned int output0=BASE64_CODE[input0>>2];
unsigned int output1=BASE64_CODE[((input0 & 3)<<4) + (input1>>4)];
BASE64_WCODE_L[i]=(unsigned short)( output0 | (output1<<8) );
input1=i&0xFF;
unsigned int input2=(i>>8)&0xFF;
unsigned int output2=BASE64_CODE[((input1 & 0x0F)<<2) + (input2>>6)];
unsigned int output3=BASE64_CODE[input2 & 0x3F];
BASE64_WCODE_H[i]=(unsigned short)( output2 | (output3<<8) );
}
initialized=true;
}
const unsigned char* input=(const unsigned char*)pdata;
const unsigned char* input_end=&input[data_size];
unsigned char* output=(unsigned char*)out_pcode;
for(;input+2<input_end;input+=3,output+=4)
{
//unsigned int input0=input[0];
//unsigned int input1=input[1];
//unsigned int input2=input[2];
//unsigned int output_0_1=BASE64_WCODE_L[input0+(input1<<8)];
//unsigned int output_2_3=BASE64_WCODE_H[input1+(input2<<8)];
//*(unsigned long*)(&output[0])=output_0_1+(output_2_3<<16);
// 可以測試一下這種記憶體讀取方式的速度
unsigned int output_0_1=BASE64_WCODE_L[*(unsigned short*)(&input[0])];
unsigned int output_2_3=BASE64_WCODE_H[*(unsigned short*)(&input[1])];
*(unsigned long*)(&output[0])=output_0_1+(output_2_3<<16);
}
base64_addpaing(input,input_end-input,output);
}
嘗試一下24bit的查詢表,(1<<24)*4位元組(64M),恐怖的表大小!
(警告:base64_encode_256K和base64_encode_64M函數隻是作為例子給出,并不建議真的使用)
//編碼函數每秒編碼出的資料量:
// base64_encode_64M 111.3 MB/s
// |----------------------|----------------------|----------------------|
// | a[0..7] | b[0..7] | c[0..7] |
// |----------------------|----------------------|----------------------|
//
// |--------------------------------------------------------------------|
// | a[0..7] + b[0..7]<<8 + c[0..7]<<16 |
// |--------------------------------------------------------------------|
void base64_encode_64M(const void* pdata,const unsigned long data_size,void* out_pcode)
{
static unsigned long BASE64_DWCODE[1<<24];
static bool initialized=false;
if(!initialized)
{
for(unsigned long i=0;i<(1<<24);++i)
{
unsigned int input0=i & 0xFF;
unsigned int input1=(i>>8) & 0xFF;
unsigned int input2=(i>>16) & 0xFF;
unsigned int output0=BASE64_CODE[input0>>2];
unsigned int output1=BASE64_CODE[((input0 & 3)<<4) + (input1>>4)];
unsigned int output2=BASE64_CODE[((input1 & 0x0F)<<2) + (input2>>6)];
unsigned int output3=BASE64_CODE[input2 & 0x3F];
BASE64_DWCODE[i]=output0 | (output1<<8) | (output2<<16) | (output3<<24);
}
initialized=true;
}
const unsigned char* input=(const unsigned char*)pdata;
const unsigned char* input_end=&input[data_size];
unsigned char* output=(unsigned char*)out_pcode;
for(;input+2<input_end;input+=3,output+=4)
{
*(unsigned long*)(&output[0])=BASE64_DWCODE[input[0]+(input[1]<<8)+(input[2]<<16)];
}
base64_addpaing(input,input_end-input,output);
}
J:現在在base64_encode3的基礎上使用彙編來進行優化;
想法是盡量壓縮寄存器的使用,然後多個寄存器就能同時執行多路,增加了指令的并發能力;
(
說明: 剛開始寫過雙8K表的C++實作和其彙編優化實作,在AMDx2上速度很快,但覺得16k的表太
浪費,而且在奔騰4等CPU上速度降低嚴重,經曆過幾個版本(也有比較平衡的);
但它們比起base64_encode3_asm來就遜色了,是以就略去了。
這裡的版本(8K表)base64_encode3_asm在各種CPU上的适應性應該不錯(但我還沒有機會測試過)
)
//編碼函數每秒編碼出的資料量:
// base64_encode3_asm 1061.3 MB/s
static unsigned short BASE64_WCODE_asm[1<<12];
void __declspec(naked) __stdcall _base64_encode3_line_asm(const unsigned char* input,unsigned char* output,const long line_count_AL2)
{
asm
{
push esi
push edi
push ebx
push ebp
mov esi,[esp+4+16]//input
mov edi,[esp+8+16]//output
mov ebp,[esp+12+16]//line_count_AL2
lea edi,[edi+ebp*4]
neg ebp
align 4
loop_begin:
mov edx,dword ptr [esi]
mov ebx,dword ptr [esi+3]
bswap edx
bswap ebx
mov eax,edx
mov ecx,ebx
shr edx,8
shr ebx,8
shr eax,20
shr ecx,20
and edx,0x0FFF
and ebx,0x0FFF
movzx eax,word ptr [BASE64_WCODE_asm+eax*2]
movzx edx,word ptr [BASE64_WCODE_asm+edx*2]
movzx ebx,word ptr [BASE64_WCODE_asm+ebx*2]
movzx ecx,word ptr [BASE64_WCODE_asm+ecx*2]
shl edx,16
shl ebx,16
or edx,eax
or ecx,ebx
mov [edi+ebp*4],edx
mov [edi+ebp*4+4],ecx
add esi,3*2
add ebp,2
jnz loop_begin
pop ebp
pop ebx
pop edi
pop esi
ret 12
}
}
void base64_encode3_asm(const void* pdata,const unsigned long data_size,void* out_pcode)
{
static bool initialized=false;
if(!initialized)
{
for(unsigned int i=0;i<(1<<12);++i)
{
BASE64_WCODE_asm[i]=BASE64_CODE[i>>6] | (BASE64_CODE[i & 0x3F] << 8);
}
initialized=true;
}
const unsigned char* input=(const unsigned char*)pdata;
unsigned char* output=(unsigned char*)out_pcode;
unsigned long fast_cell=data_size/3;
if (fast_cell>0)
{
unsigned long fast_cell_AL2=((fast_cell-1)>>1)<<1;
//預留一個cell保證_base64_encode3_line_asm記憶體通路安全,并且使cell大小2個對齊
if (fast_cell_AL2>0)
{
_base64_encode3_line_asm(input,output,fast_cell_AL2);
input+=(fast_cell_AL2*3);
output+=(fast_cell_AL2*4);
}
for(unsigned long i=fast_cell_AL2;i<fast_cell;++i,input+=3,output+=4)
{
unsigned int input0=input[0];
unsigned int input1=input[1];
unsigned int input2=input[2];
unsigned int output_0_1=BASE64_WCODE_asm[(input0<<4) | (input1>>4)];
unsigned int output_2_3=BASE64_WCODE_asm[((input1&0x0F)<<8) | input2];
*(unsigned long*)(&output[0])=output_0_1 | (output_2_3<<16);
}
}
base64_addpaing(input,data_size-(fast_cell*3),output);
}
K:使用sse和sse2的寫緩存優化
警告: 優化寫緩沖需要滿足的條件是寫入的資料量比較大或者需要寫入的資料不需要很快就通路,
進而避免了寫入的資料被CPU自動緩沖而"污染"緩存;(如果條件不滿足,“優化”反而會變成劣化)
提示: 在不支援SSE和 SSE2指令的CPU上函數将不能執行
使用sse中的movntq指令來改寫_base64_encode3_line_asm為_base64_encode3_line_sse;
代碼如下:
//編碼函數每秒編碼出的資料量:
// base64_encode3_sse 1205.6 MB/s
//(在奔騰4上使用該優化版本可能反而比base64_encode3_asm版本慢,需要測試一下)
void __declspec(naked) __stdcall _base64_encode3_line_sse(const unsigned char* input,unsigned char* output,const long line_count_AL2)
{
asm
{
push esi
push edi
push ebx
push ebp
mov esi,[esp+4+16]//input
mov edi,[esp+8+16]//output
mov ebp,[esp+12+16]//line_count_AL2
lea edi,[edi+ebp*4]
neg ebp
align 4
loop_begin:
mov edx,dword ptr [esi]
mov ebx,dword ptr [esi+3]
bswap edx
bswap ebx
mov eax,edx
mov ecx,ebx
shr edx,8
shr ebx,8
shr eax,20
shr ecx,20
and edx,0x0FFF
and ebx,0x0FFF
movzx eax,word ptr [BASE64_WCODE_asm+eax*2]
movzx edx,word ptr [BASE64_WCODE_asm+edx*2]
movzx ebx,word ptr [BASE64_WCODE_asm+ebx*2]
movzx ecx,word ptr [BASE64_WCODE_asm+ecx*2]
shl edx,16
shl ebx,16
or edx,eax
or ecx,ebx
//普通寫入指令
//mov [edi+ebp*4],edx
//mov [edi+ebp*4+4],ecx
//sse支援的寫入指令
MOVD MM0,edx
MOVD MM1,ecx
PUNPCKlDQ MM0,MM1
MOVNTQ [edi+ebp*4],MM0
add esi,3*2
add ebp,2
jnz loop_begin
SFENCE //寫入重新整理
EMMS //mmx寄存器使用結束
pop ebp
pop ebx
pop edi
pop esi
ret 12
}
}
void base64_encode3_sse(const void* pdata,const unsigned long data_size,void* out_pcode)
{
static bool initialized=false;
if(!initialized)
{
for(unsigned int i=0;i<(1<<12);++i)
{
BASE64_WCODE_asm[i]=BASE64_CODE[i>>6] | (BASE64_CODE[i & 0x3F] << 8);
}
initialized=true;
}
const unsigned char* input=(const unsigned char*)pdata;
unsigned char* output=(unsigned char*)out_pcode;
unsigned long fast_cell=data_size/3;
if (fast_cell>0)
{
unsigned long fast_cell_AL2=((fast_cell-1)>>1)<<1;
//預留一個cell保證_base64_encode3_line_sse記憶體通路安全,并且使cell大小2個對齊
if (fast_cell_AL2>0)
{
_base64_encode3_line_sse(input,output,fast_cell_AL2);
input+=(fast_cell_AL2*3);
output+=(fast_cell_AL2*4);
}
for(unsigned long i=fast_cell_AL2;i<fast_cell;++i,input+=3,output+=4)
{
unsigned int input0=input[0];
unsigned int input1=input[1];
unsigned int input2=input[2];
unsigned int output_0_1=BASE64_WCODE_asm[(input0<<4) | (input1>>4)];
unsigned int output_2_3=BASE64_WCODE_asm[((input1&0x0F)<<8) | input2];
*(unsigned long*)(&output[0])=output_0_1 | (output_2_3<<16);
}
}
base64_addpaing(input,data_size-(fast_cell*3),output);
}
使用sse2中的movnti指令來改寫_base64_encode3_line_asm為_base64_encode3_line_sse2;
代碼如下:
//編碼函數每秒編碼出的資料量:
// base64_encode3_sse2 1340.6 MB/s
void __declspec(naked) __stdcall _base64_encode3_line_sse2(const unsigned char* input,unsigned char* output,const long line_count_AL2)
{
asm
{
push esi
push edi
push ebx
push ebp
mov esi,[esp+4+16]//input
mov edi,[esp+8+16]//output
mov ebp,[esp+12+16]//line_count_AL2
lea edi,[edi+ebp*4]
neg ebp
align 4
loop_begin:
mov edx,dword ptr [esi]
mov ebx,dword ptr [esi+3]
bswap edx
bswap ebx
mov eax,edx
mov ecx,ebx
shr edx,8
shr ebx,8
shr eax,20
shr ecx,20
and edx,0x0FFF
and ebx,0x0FFF
movzx eax,word ptr [BASE64_WCODE_asm+eax*2]
movzx edx,word ptr [BASE64_WCODE_asm+edx*2]
movzx ebx,word ptr [BASE64_WCODE_asm+ebx*2]
movzx ecx,word ptr [BASE64_WCODE_asm+ecx*2]
shl edx,16
shl ebx,16
or edx,eax
or ecx,ebx
//普通寫入指令
//mov [edi+ebp*4],edx
//mov [edi+ebp*4+4],ecx
//sse2支援的寫入指令
MOVNTI [edi+ebp*4],edx
MOVNTI [edi+ebp*4+4],ecx
add esi,3*2
add ebp,2
jnz loop_begin
SFENCE //寫入重新整理
pop ebp
pop ebx
pop edi
pop esi
ret 12
}
}
void base64_encode3_sse2(const void* pdata,const unsigned long data_size,void* out_pcode)
{
static bool initialized=false;
if(!initialized)
{
for(unsigned int i=0;i<(1<<12);++i)
{
BASE64_WCODE_asm[i]=BASE64_CODE[i>>6] | (BASE64_CODE[i & 0x3F] << 8);
}
initialized=true;
}
const unsigned char* input=(const unsigned char*)pdata;
unsigned char* output=(unsigned char*)out_pcode;
unsigned long fast_cell=data_size/3;
if (fast_cell>0)
{
unsigned long fast_cell_AL2=((fast_cell-1)>>1)<<1;
//預留一個cell保證_base64_encode3_line_sse2記憶體通路安全,并且使cell大小2個對齊
if (fast_cell_AL2>0)
{
_base64_encode3_line_sse2(input,output,fast_cell_AL2);
input+=(fast_cell_AL2*3);
output+=(fast_cell_AL2*4);
}
for(unsigned long i=fast_cell_AL2;i<fast_cell;++i,input+=3,output+=4)
{
unsigned int input0=input[0];
unsigned int input1=input[1];
unsigned int input2=input[2];
unsigned int output_0_1=BASE64_WCODE_asm[(input0<<4) | (input1>>4)];
unsigned int output_2_3=BASE64_WCODE_asm[((input1&0x0F)<<8) | input2];
*(unsigned long*)(&output[0])=output_0_1 | (output_2_3<<16);
}
}
base64_addpaing(input,data_size-(fast_cell*3),output);
}
L:使用軟體預讀指令prefetchnta來進一步優化base64_encode3_sse2:
//編碼函數每秒編碼出的資料量:
// base64_encode3_sse2_prefetch 1404.73 MB/s
void __declspec(naked) __stdcall _base64_encode3_line_sse2_prefetch(const unsigned char* input,unsigned char* output,const long line_count)
{
asm
{
push esi
push edi
push ebx
push ebp
mov esi,[esp+4+16]//input
mov edi,[esp+8+16]//output
mov ebp,[esp+12+16]//line_count
lea edi,[edi+ebp*4]
neg ebp
align 4
loop_begin:
mov edx,dword ptr [esi]
mov ebx,dword ptr [esi+3]
bswap edx
bswap ebx
prefetchnta [esi+64*4]
mov eax,edx
mov ecx,ebx
shr edx,8
shr ebx,8
shr eax,20
shr ecx,20
and edx,0x0FFF
and ebx,0x0FFF
movzx eax,word ptr [BASE64_WCODE_asm+eax*2]
movzx edx,word ptr [BASE64_WCODE_asm+edx*2]
movzx ebx,word ptr [BASE64_WCODE_asm+ebx*2]
movzx ecx,word ptr [BASE64_WCODE_asm+ecx*2]
shl edx,16
shl ebx,16
or edx,eax
or ecx,ebx
//sse2支援的寫入指令
MOVNTI [edi+ebp*4],edx
MOVNTI [edi+ebp*4+4],ecx
add esi,3*2
add ebp,2
jnz loop_begin
pop ebp
pop ebx
pop edi
pop esi
ret 12
}
}
//使用CPU顯式prefetchnta預讀指令
void base64_encode3_sse2_prefetch(const void* pdata,const unsigned long data_size,void* out_pcode)
{
static bool initialized=false;
if(!initialized)
{
for(unsigned int i=0;i<(1<<12);++i)
{
BASE64_WCODE_asm[i]=BASE64_CODE[i>>6] | (BASE64_CODE[i & 0x3F] << 8);
}
initialized=true;
}
const unsigned char* input=(const unsigned char*)pdata;
unsigned char* output=(unsigned char*)out_pcode;
unsigned long fast_cell=data_size/3;
if (fast_cell>0)
{
unsigned long fast_cell_AL2=((fast_cell-1)>>1)<<1;
//預留一個cell保證_base64_encode3_line_sse2記憶體通路安全,并且使cell大小2個對齊
if (fast_cell_AL2>0)
{
_base64_encode3_line_sse2_prefetch(input,output,fast_cell_AL2);
input+=(fast_cell_AL2*3);
output+=(fast_cell_AL2*4);
}
for(unsigned long i=fast_cell_AL2;i<fast_cell;++i,input+=3,output+=4)
{
unsigned int input0=input[0];
unsigned int input1=input[1];
unsigned int input2=input[2];
unsigned int output_0_1=BASE64_WCODE_asm[(input0<<4) | (input1>>4)];
unsigned int output_2_3=BASE64_WCODE_asm[((input1&0x0F)<<8) | input2];
*(unsigned long*)(&output[0])=output_0_1 | (output_2_3<<16);
}
}
base64_addpaing(input,data_size-(fast_cell*3),output);
asm SFENCE //寫入重新整理
}
M:Base64編碼函數的并行化
Base64編碼函數其實很容易實作并行算法,把資料切割成幾段讓多個CPU執行就可以了;
(既然是追求最快的速度,而且現在多核的普及已成必然,是以不增加Base64編碼函數的
并行版本測試有點說不過去:) 這裡利用CWorkThreadPool類來并行執行任務; 參見我
的Blog文章《并行計算簡介和多核CPU程式設計Demo》,裡面有CWorkThreadPool類的完整源代碼)
将Base64編碼函數并行執行的代碼:
#include "WorkThreadPool.h"
struct TBase64WorkData
{
Tbase64_encode_proc base64_encode_proc;
const unsigned char* input;
unsigned long input_size;
unsigned char* output;
};
void base64_encode_part_callback(void* wd)
{
TBase64WorkData* WorkData=(TBase64WorkData*)wd;
WorkData->base64_encode_proc(WorkData->input,WorkData->input_size,WorkData->output);
}
void parallel_base64_encode(Tbase64_encode_proc base64_encode,const void* pdata,const unsigned long data_size,void* out_pcode)
{
const unsigned char* input=(const unsigned char*)pdata;
unsigned char* output=(unsigned char*)out_pcode;
long work_count=CWorkThreadPool::best_work_count();
std::vector<TBase64WorkData> work_list(work_count);
std::vector<TBase64WorkData*> pwork_list(work_count);
unsigned long part_cell_count=data_size/3/work_count;
for (long i=0;i<work_count;++i)
{
work_list[i].base64_encode_proc=base64_encode;
work_list[i].input=input;
work_list[i].output=output;
work_list[i].input_size=part_cell_count*3;
pwork_list[i]=&work_list[i];
input+=part_cell_count*3;
output+=part_cell_count*4;
}
work_list[work_count-1].input_size=data_size-part_cell_count*3*(work_count-1);
CWorkThreadPool::work_execute(base64_encode_part_callback,(void**)&pwork_list[0],work_count);
}
假設需要測試base64_encode3_sse2在并行情況下的速度:
以前的調用代碼: base64_encode3_sse2(pdata,data_size,out_pcode);
并行改成這樣調用就可以了:
parallel_base64_encode(base64_encode3_sse2,pdata,data_size,out_pcode);
( 附:Base64解碼函數
enum B64ReultType{
b64Result_OK=0,
b64Result_CODE_SIZE_ERROR,
b64Result_DATA_SIZE_SMALLNESS,
b64Result_CODE_ERROR
};
// 445MB/s
B64ReultType base64_decode(const void* pcode,const unsigned long code_size,void* out_pdata,const unsigned long data_size,unsigned long* out_pwrited_data_size)
{
const unsigned char DECODE_DATA_MAX = 64-1;
const unsigned char DECODE_PADDING = DECODE_DATA_MAX+2;
const unsigned char DECODE_ERROR = DECODE_DATA_MAX+3;
static unsigned char BASE64_DECODE[256];
static bool initialized=false;
if(!initialized)
{
unsigned long i;
for(i=0;i<256;++i) BASE64_DECODE[i]=DECODE_ERROR;
for(i='A';i<='Z';++i) BASE64_DECODE[i]=(unsigned char)(i-'A');
for(i='a';i<='z';++i) BASE64_DECODE[i]=(unsigned char)(i-'a'+26);
for(i='0';i<='9';++i) BASE64_DECODE[i]=(unsigned char)(i-'0'+26*2);
BASE64_DECODE['+']=26*2+10;
BASE64_DECODE['/']=26*2+10+1;
BASE64_DECODE['=']=DECODE_PADDING;
initialized=true;
}
*out_pwrited_data_size=0;
unsigned long code_node=code_size/4;
if ((code_node*4)!=code_size)
return b64Result_CODE_SIZE_ERROR;
else if (code_node==0)
return b64Result_OK;
//code_node>0
const unsigned char* input=(const unsigned char*)pcode;
unsigned char* output=(unsigned char*)out_pdata;
unsigned long output_size=code_node*3;
if (input[code_size-2]==BASE64_PADDING)
{
if (input[code_size-1]!=BASE64_PADDING)
return b64Result_CODE_ERROR;
output_size-=2;
}
else if (input[code_size-1]==BASE64_PADDING)
--output_size;
if (output_size>data_size) return b64Result_DATA_SIZE_SMALLNESS;
const unsigned char* input_last_fast_node=&input[output_size/3*4];
for(;input<input_last_fast_node;input+=4,output+=3)
{
unsigned int code0=BASE64_DECODE[input[0]];
unsigned int code1=BASE64_DECODE[input[1]];
unsigned int code2=BASE64_DECODE[input[2]];
unsigned int code3=BASE64_DECODE[input[3]];
if ( ((code0|code1)|(code2|code3)) <= DECODE_DATA_MAX )
{
output[0]=(unsigned char)((code0<<2) + (code1>>4));
output[1]=(unsigned char)((code1<<4) + (code2>>2));
output[2]=(unsigned char)((code2<<6) + code3);
}
else
return b64Result_CODE_ERROR;
}
unsigned long bord_width=output_size%3;
if (bord_width==1)
{
unsigned int code0=BASE64_DECODE[input[0]];
unsigned int code1=BASE64_DECODE[input[1]];
if ((code0|code1) <= DECODE_DATA_MAX)
{
output[0]=(unsigned char)((code0<<2) + (code1>>4));
}
else
return b64Result_CODE_ERROR;
}
else if (bord_width==2)
{
unsigned int code0=BASE64_DECODE[input[0]];
unsigned int code1=BASE64_DECODE[input[1]];
unsigned int code2=BASE64_DECODE[input[2]];
if ((code0|code1|code2) <= DECODE_DATA_MAX)
{
output[0]=(unsigned char)((code0<<2) + (code1>>4));
output[1]=(unsigned char)((code1<<4) + (code2>>2));
}
else
return b64Result_CODE_ERROR;
}
*out_pwrited_data_size=output_size;
return b64Result_OK;
}
)
N:測試的結果放到一起:
//todo:代碼在不同CPU上的速度差異的簡單分析 等待更多平台的測試結果
//測試平台I7:(CPU:Intel i7 920(2.66G); 記憶體:DDR3 1333(三通道); 編譯器:VC2005)
//測試平台A2:(CPU:AMD64x2 4200+(2.37G); 記憶體:DDR2 677(雙通道); 編譯器:VC2005)
//測試平台C2:(CPU:Intel Core2 4400(2.00G);記憶體:DDR2 667(雙通道); 編譯器:VC2005)
//測試平台AS:(CPU:AMD Sempron2800+(1.61G);記憶體:DDR 400; 編譯器:VC2005)
//測試平台IX:(CPU:Intel Xeon(x4)(2.33G); 記憶體: ? ; 編譯器:VC2005)
//測試平台Q6:(CPU:Intel Q6600(x4)(2.4G); 記憶體:DDR2 800(單通道); 編譯器:VC2005)
//測試平台IC:(CPU:Intel Celeron(2.1G); 記憶體:DDR 333 ; 編譯器:VC2005)
//每秒編碼出的資料量測試(MB/s)
//========================================================================
// A2 C2 IX Q6 AS IC I7
//========================================================================
// base64_encode0 109 103 120 124 74 69 125
// base64_encode0_table 295 679 818 829 204 386 1014
// base64_encode1 533 618 733 755 391 409 884
// base64_encode1_bswap 579 496 586 604 403 397 727
// base64_encode_256K 410 567 661 685 237 26 964
// base64_encode_64M 111 135 162 164 74 21 279
//
// base64_encode1_asm 675 532 641 660 793
// base64_encode2 702 771 911 938 489 413 1033
// base64_encode3 791 1098 1359 1379 553 424 1667
// base64_encode3_asm 1061 1116 1343 1391 703 417 2162
// base64_encode3_sse 1206 1110 1297 1339 841 660 1888
// base64_encode3_sse2 1341 1160 1367 1409 943 685 2021
// base64_encode3_sse2_prefetch
// 1405 1146 1382 1876
//
// 多路并行執行測試:
// base64_encode1_asm 1321 1058 1693 1486 3186
// base64_encode2 1366 1284 1668 1481 3823
// base64_encode3 1483 1315 1681 1478 6373
// base64_encode3_asm 1796 1317 1668 1480 7318
// base64_encode3_sse 2228 1978 2812 2408 8448
// base64_encode3_sse2 2520 2019 2845 2425 8685
// base64_encode3_sse2_prefetch
// 2643 2040 2316 6682
//
//A2/C2/Q6和I7由我提供,IX由網友cat提供,IC和AS由網友constantine提供
(歡迎送出這些代碼在你的電腦上的測試成績(說明測試用的CPU和記憶體資訊);歡迎提出改進意見)
( 本文章涉及到的測試源代碼: https://github.com/sisong/demoForHssBlog )