这里有一个程序的例子,协程确实有助于简化算法-否则很难实现。我还试着为演示选择一个有用的任务-这个实用程序将一个二进制文件转换为一系列A-Z符号(来回),没有任何重要的冗余,并且它能够处理任何指定的字母表(参见M.Init行)。基本上,它类似于广义的base64。该代码已在MSC,IntelC和gcc/mingw上进行了测试和工作。
更新:该算法基于精确的算术编码,因此它不是默认的一行程序。可以通过使用putc/getc文件i/o将其一分为二(因此只保留修改后的rangecoder类和do_process() ),但这将是非常有限的(例如,将不适用于解码内存块或网络流)。实际上协程在这里被用作速度优化,这也是这篇文章的重点。不幸的是,我没有任何更简单的应用程序来正确地演示这一点-我可以编写一个上下文建模压缩器,但这可能会多出100行代码。
问题:
1)如何用正确的C++代码替换INCLUDE_PROCESS_TEMPLATE宏?
2)有没有办法在没有协程的情况下实现这个功能?
(但仍使用内存编码和缓冲文件i/o)
3)是否有任何修复/改进?
#include <io.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <setjmp.h>
#define NOINLINE __declspec(noinline)
typedef unsigned int uint;
typedef unsigned char byte;
typedef unsigned long long int qword;
enum{ STKPAD=1<<16 };
struct coroutine {
volatile int state;
volatile byte* inpptr;
volatile byte* inpbeg;
volatile byte* inpend;
volatile byte* outptr;
volatile byte* outbeg;
volatile byte* outend;
jmp_buf PointA, PointB;
void yield( int value ) { if( setjmp(PointB)==0 ) { state=value; longjmp(PointA,value); } }
void chkinp( void ) { if( inpptr>=inpend ) yield(1), inpptr=*&inpptr; }
void chkout( void ) { if( outptr>=outend ) yield(2); }
template <int f> byte get( void ) { if( f ) chkinp(); return *inpptr++; }
template <int f> void put( uint c ) { *outptr++ = c; if( f ) chkout(); }
void coro_init( void ) { inpptr=inpbeg=inpend=0; outptr=outbeg=outend=0; state=0; }
void addinp( byte* inp,uint inplen ) { inpbeg=inpptr=inp; inpend=&inp[inplen]; }
void addout( byte* out,uint outlen ) { outbeg=outptr=out; outend=&out[outlen]; }
};
#define INCLUDE_PROCESS_TEMPLATE \
NOINLINE void call_do_process() { byte stktmp[STKPAD]; state=ptrdiff_t(stktmp); do_process(); } \
int coro_process( void ) { if( setjmp(PointA)==0 ) if( state ) longjmp(PointB,3); else call_do_process(); return state; }
struct Rangecoder_SH1x : coroutine {
enum { SCALElog=15, SCALE=1<<SCALElog };
enum { NUM=4, sTOP=0x01000000U, Thres=0xFF000000U };
uint f_decode; // 0=encode, 1=decode;
uint range, Cache, FFNum;
union {
struct { uint low; uint Carry; };
qword lowc;
uint code;
};
uint rc_BProcess( uint freq, uint bit ) {
uint rnew = (range>>SCALElog)*freq;
if( f_decode ) bit = (code>=rnew);
range = ((range-rnew-rnew)&(-bit)) + rnew;
rnew &= -bit;
if( f_decode ) code-=rnew; else lowc+=rnew;
if( f_decode ) while( range<sTOP ) range<<=8, (code<<=8)+=get<1>();
else while( range<sTOP ) range<<=8, ShiftLow();
return bit;
}
void ShiftLow( void ) {
if( low<Thres || Carry ) {
put<1>( Cache+Carry );
for(; FFNum!=0; FFNum-- ) put<1>( Carry-1 );
Cache=low>>24; Carry=0;
} else FFNum++;
low<<=8;
}
void rc_Init( int DECODE ) {
f_decode=DECODE; range=-1; lowc=FFNum=Cache=0;
if( f_decode ) for(int _=0; _<NUM+0; _++) (code<<=8)+=get<1>();
}
};
struct Model : Rangecoder_SH1x {
uint DECODE, f_quit;
enum{ lCNUM=8, CNUM=1<<lCNUM, ROWSIZE=80 };
uint count[2*CNUM];
enum{ inpbufsize=1<<16, outbufsize=1<<16 };
byte inpbuf[inpbufsize], outbuf[outbufsize];
void Init( const char* s ) {
uint i,j;
uint (&p)[CNUM] = (uint(&)[CNUM])count[CNUM];
for( i=0; i<2*CNUM; i++) count[i]=0;
for( i=0; s[i]; i++ ) p[byte(s[i])]++;
for( j=0; j<lCNUM; j++ ) for( i=(CNUM>>j); i<((CNUM+CNUM)>>j); i++ ) count[i>>1] += count[i];
}
INCLUDE_PROCESS_TEMPLATE
void do_process( void ) {
uint i,j;
rc_Init(1-DECODE);
for( i=0; !f_quit; i++ ) {
uint c=0, ctx=1;
if( DECODE ) do c=get<1>(); while( c==10 );
for( j=lCNUM-1; j!=-1; j-- ) {
uint freq = count[ctx*2+0]*SCALE/(count[ctx*2+0]+count[ctx*2+1]);
ctx += ctx + ((freq==0) ? 1 : (freq==SCALE) ? 0 : rc_BProcess(freq,(c>>j)&1));
}
if( !DECODE ) put<1>(ctx), (((i+1)%ROWSIZE==0)?put<1>(10),0:0);
}
yield(0);
}
void ProcessFile( uint Mode, FILE* f, FILE* g ) {
volatile uint r; volatile qword g_len=0; uint f_len=0;
DECODE=Mode; f_quit=0;
if( DECODE ) addout( (byte*)&g_len, sizeof(f_len)+1 ), r=1;
else f_len=filelength(fileno(f)), addinp( (byte*)&f_len, sizeof(f_len) ),addout(0,0), r=2;
do {
if( r==1 ) {
uint l = fread( inpbuf, 1, inpbufsize, f );
if( l>0 ) {
addinp( inpbuf, l );
} else {
if( inpbeg==inpbuf+1 ) f_quit=1;
memset( inpbuf, 0x80, inpbufsize );
addinp( inpbuf+1, 5 );
}
} else if( r==2 ) {
if( outbeg==outbuf ) fwrite( outbuf, 1, outptr-outbeg, g ); else g_len>>=8;
addout( outbuf, outbufsize );
}
r = coro_process();
} while(r);
fwrite( outbuf, 1,outptr-outbuf, g ); // flush
if( DECODE==0 ) fputc( 10, g ); else fflush(g), chsize( fileno(g), g_len );
}
} M;
int main( int argc, char** argv ) {
if( argc<4 ) return 1;
int DECODE = argv[1][0]=='d';
FILE* f = fopen( argv[2], "rb" ); if( f==0 ) return 2;
FILE* g = fopen( argv[3], "wb" ); if( g==0 ) return 3;
M.Init( "ABCDEFGHIJKLMNOPQRSTUVWXYZ" );
M.ProcessFile( DECODE, f, g );
}发布于 2010-07-26 06:07:30
只是为了笑一笑,这里有一个粗略的想法,我将如何处理从任意字母表进行编码/解码的部分。正如承诺的那样,实际的编码/解码大约需要十几行代码。整体大小更大,很大程度上是因为我从头到尾都使用了模板,所以数字可以是任意整数类型,字符可以是任意字符类型,它使用迭代器来实现这两种类型,因此它可以读取/写入任意集合(流、字符串流、向量等)。
编辑:修改代码,从文件读取输入并将输出写入文件(并修复了一两个小错误):
#include <iterator>
#include <iostream>
#include <string>
#include <limits>
#include <vector>
#include <fstream>
#include <time.h>
#include <math.h>
#include <stdlib.h>
template <class intT>
intT log2(intT input) {
return intT(log10((double)input) / log10(2.0));
}
template <class intT>
class coder {
std::string alphabet;
size_t range;
unsigned ratio;
public:
coder(std::string const &alpha) : alphabet(alpha), range(alpha.size()) {
ratio = ceil(double(log2(std::numeric_limits<intT>::max())/log2(range)));
}
template <class inIt, class outIt>
void encode(inIt begin, inIt end, outIt out) {
while (begin != end) {
intT val = *begin++;
for (int i=0; i<ratio; i++) {
*out++ = alphabet[val % range];
val /= range;
}
}
}
template <class inIt, class outIt>
void decode(inIt begin, inIt end, outIt out) {
while (begin != end) {
int temp = 0;
for (int i=0; i<ratio; i++)
temp += alphabet.find(*begin++) * pow((double)range, i);
*out++ = temp;
}
}
};
int main(int argc, char **argv) {
if (argc != 3) {
std::cerr << "Usage: encode <infile> <outfile>\n";
return EXIT_FAILURE;
}
coder<unsigned> enc("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
std::ifstream in(argv[1], std::ios::binary);
std::ofstream out(argv[2]);
clock_t start = clock();
enc.encode(std::istream_iterator<char>(in),
std::istream_iterator<char>(),
std::ostream_iterator<char>(out, ""));
clock_t stop = clock();
std::cerr << "Processing time: " << double(stop-start)/CLOCKS_PER_SEC << "\n";
return 0;
}至少目前,我忽略了算术编码部分,但它应该(至少是IMO)遵循类似的结构,这样您就可以很容易地将它们或多或少地任意组合在一起。
就比较速度和大小而言,请记住,这根本不是进行任何压缩,而只是进行baseX编码--在这种情况下,尝试与进行压缩的东西进行比较没有实际意义(除了,例如,为了了解压缩有多有效--但如果它确实有效,它显然会产生较小的输出)。
就可执行文件的大小而言,我所能说的就是,gcc生成大型可执行文件从来不会让我感到惊讶。使用MS VC++,我为上面的代码获得了一个9,728字节的可执行文件。
发布于 2010-07-26 05:24:08
实现可移植的协程是一项艰巨的任务。请考虑使用Boost.coroutine候选。Here是对库的更新。
我在OS和Linux上多次使用它和boost::asio,它们已经被证明是非常健壮的实现,并且是一个非常有用的线程抽象,具有顺序程序的确定性行为
我不知道为什么它还没有被添加到主boost发行版中。我的猜测是,这一事实背后隐藏着一些伪装成技术上的政治论点,尽管我鼓励你对我的偏执持保留态度。
编辑: boost vault中有一个新的boost候选者,名为Boost.Context,它是一个名为Boost.Fiber的更大库的一部分。它还没有网页,所以我不会在这里链接它。它似乎有更好的支持
https://stackoverflow.com/questions/3330838
复制相似问题