数据库系统实现两阶段多路归并排序算法的C实现.docx
《数据库系统实现两阶段多路归并排序算法的C实现.docx》由会员分享,可在线阅读,更多相关《数据库系统实现两阶段多路归并排序算法的C实现.docx(24页珍藏版)》请在冰豆网上搜索。
![数据库系统实现两阶段多路归并排序算法的C实现.docx](https://file1.bdocx.com/fileroot1/2023-2/5/f8071308-abe4-4a2c-b6da-054c542b77d9/f8071308-abe4-4a2c-b6da-054c542b77d91.gif)
数据库系统实现两阶段多路归并排序算法的C实现
两阶段多路归并排序
Two-PhaseMultiwayMerge-Sort
实验报告
目录
1实验目的4
2实验内容4
3实验环境4
4实验的设计和实现4
4.1算法描述4
4.2设计思路5
4.3数据结构6
4.4具体实现7
5实验结果10
5.150MB内存TPMMS实验结果10
5.210MB内存TPMMS实验结果10
5.3100MB内存TPMMS实验结果11
5.4三者的比较12
6实验遇到的问题和解决方法12
6.1Phase2阶段遇到的问题和解决方法12
6.2生成子记录文件名的方法14
7代码附录14
1实验目的
通过merge-sort算法的实现,掌握外存算法所基于的I/O模型与内存算法基于的RAM模型的区别;理解不同的磁盘访问优化方法是如何提高数据访问性能的。
2实验内容
生成一个具有10,000,000个记录的文本文件,其中每个记录由100个字节组成。
实验只考虑记录的一个属性A,假定A为整数类型。
记录在block上封装时,采用non-spanned方式,即块上小于一个记录的空间不使用。
Block的大小可在自己的操作系统上查看,xp一般为4096bytes。
在内存分配50M字节的空间用于外部merge-sort。
要求设计和实现程序完成下列功能:
1)生成文本文件,其中属性A的值随机产生。
2)按照ppt中的方法对文本文件中的记录,按照属性A进行排序,其中在第二阶段的排序中每个子列表使用一个block大小的缓冲区缓冲数据。
3)按照教材cylinder-basedbuffers(1Mbytes)的方法,修改第二阶段的算法。
4)比较两种方法的时间性能,如果有更大的内存空间,算法性能还能提高多少?
3实验环境
1)VisualC++6.0
2)Windows7操作系统
4实验的设计和实现
1
2
3
4
4.1算法描述
Two-PhaseMultiwayMerge-Sort算法的具体描述分为2个阶段,如下所示:
●Phase1
1)Fillmainmemorywithrecords.
2)Sortwithfavoritemainmemorysortingalgorithms.
3)Writesortedlisttodisk.
4)Repeatuntilallrecordshavebeenputintooneofthesortedlists.
●Phase2
1)Initiallyloadinputbufferswiththefirstblockoftheirrespectivesortedlists.
2)Repeatedrunacompetitionamongthefirstunchosenrecordsofeachofthebufferedblocks.
3)Ifaninputblockisexhausted,getthenextblockfromthesamefile.
4)Iftheoutputblockisfull,writeittodisk.
4.2设计思路
从上述的算法描述中,我们知道,系统主要由2大模块组成:
Phase1和Phase2。
Phase1阶段主要将生成的记录文件按内存块大小(本实验中是50MB)分成多个(本实验中是20个)相应的子记录文件,把这些文件中的记录读进内存进行排序,再写回磁盘上。
Phase2阶段利用多路归并排序算法,将Phase1阶段已经排好序的子记录文件重新归并为1个有序的记录文件,写回到磁盘上。
由于我们在Phase1和Phase2阶段之前必须先生成1个含有10000000个100B记录的文件,所以系统必须再加上1个生成记录文件的GenerateRecordFile模块。
终上所述,系统由3大模块组成,分别为:
GenerateRecordFile、Phase1、Phase2。
Phase1模块可以细分为内存块排序模块MainMemorySort和写回磁盘模块WriteToDisk。
Phase2模块可以细分为多路归并排序模块Merge-Sort和写回磁盘模块WriteToDisk。
详细的系统逻辑结构图如图3-1所示:
图3-1TPMMS系统逻辑结构图
4.3数据结构
我们讨论单个记录的数据结构。
由于1个记录有100个字节,其中4字节是由随机整数组成的主键属性PrimaryKey,另外96个字节是随意填充的数据content,而且本系统又是由C语言进行实现的,所以我们可以采取结构体来作为记录的数据结构。
其中整形字段key记录4字节的主键属性,以便进行排序工作。
数组字段contents用来填充剩余的96个字节,内容可以随意(本实验中均为0)。
具体的数据结构如图4-1所示:
图4-1单个记录的数据结构
4.4具体实现
1
2
3
4
4.1
4.2
4.3
4.4
4.4.1GenerateRecordFile阶段
GenerateRecordFile阶段比较简单,首先打开一个文件,然后生成随机数key并将其写入文件中,再填充96个任意内容的字节(本实验中均为0),即能生成1条完整的记录。
重复10000000次,生成我们所需的记录文件。
核心代码实现如图4-2所示,其中MAX_RECORD_NUMBER大小为10000000,ROW_NUMBER大小为95。
图4-2GenerateRecordFile阶段的实现
4.4.2Phase1阶段
Phase1阶段重点在于如何进行内存排序,并写回到磁盘上。
这里我们采用了STL的sort函数帮助我们进行排序。
首先读入50MB记录,利用sort函数进行排序后,写到磁盘上生成1个有序的子记录文件。
重复进行20次,生成20个相应的子记录文件。
核心代码实现如图4-3所示,其中BLOCK_SIZE大小为50M,SUB_LIST_NUMBER大小为20。
图4-3Phase1阶段的实现
4.4.3Phase2阶段
Phase2阶段是本系统实现的难点所在。
主要的实现大致可以分为以下几部分进行讨论:
1)输入缓冲的实现
将Phase1阶段中得到的20个子记录文件的首字符分别读入长度为20的输入缓冲数组inputBuffer,核心代码实现如图4-4所示:
图4-4输入缓冲的实现
2)输出缓冲的实现
选取输入缓冲数组inputBuffer中主键属性key最小的那个缓冲区,输入到输出缓冲数组outputBuffer中,然后循环执行,核心代码实现如图4-5所示:
图4-5输出缓冲的实现
3)多路归并排序的实现
如果输出缓冲数组outputBuffer已经填满,此时可知输出缓冲是有序的,且之后的主键属性key的值都不会小于该输出缓冲区,这时我们即可将其输出到最后想要得到的结果文件上,核心代码实现如图4-6所示:
图4-6多路归并排序的实现
4)Phase2阶段的其他实现
我们将在“实验中遇到的问题和解决办法”这一章详细讨论Phase2阶段剩下来的难点实现。
5实验结果
1
2
3
4
5
5.150MB内存TPMMS实验结果
采用50MB内存块大小进行TPMMS实验的结果如图5-1所示:
图5-150MB内存TPMMS实验结果图
从上图可以看出,生成1GB大小10000000条记录的文件需要152秒,phase1阶段需要136秒,phase2阶段需要150秒。
所以整个排序过程需要286秒,即4分46秒的时间才能完成。
5.210MB内存TPMMS实验结果
我们将50MB内存缩减5倍,进行10MB内存块大小的TPMMS实验。
这将产生100个子记录文件。
实验结果如图5-2所示:
图5-210MB内存TPMMS实验结果图
生成1GB大小10000000条记录的文件所需时间不变,仍为152秒左右。
我们注重于phase1阶段和phase2阶段的所需时间。
从图中可以看出,phase1阶段需要147秒,phase2阶段需要152秒。
整个排序过程需要300秒,即5分钟的时间才能完成。
5.3100MB内存TPMMS实验结果
我们再将50MB内存增加2倍,进行100MB内存块大小的TPMMS实验。
这将产生10个子记录文件。
实验结果如图5-3所示:
图5-3100MB内存TPMMS实验结果图
生成1GB大小10000000条记录的文件所需时间不变,仍为152秒左右。
我们注重于phase1阶段和phase2阶段的所需时间。
从图中可以看出,phase1阶段需要124秒,phase2阶段需要130秒。
整个排序过程需要254秒,即4分14秒的时间才能完成。
5.4三者的比较
从上面的实验结果,我们可以很明显地看出,内存块大小越大,算法所需时间越少。
这是因为内存块越小,生成的子记录文件个数就越多,这样phase1阶段生成子记录文件的时间就增加了。
并且这还使得phase2阶段的输出缓冲区变小,导致多路归并时程序读写磁盘的次数增多,所以phase2阶段时间也增加了。
这样整个排序过程时间当然增加。
终上所述,当在理想条件下,我们应使用内存块大小较大的方法来进行TPMMS算法的实现。
在本章中TPMMS算法的性能为:
100MB优于50MB优于10MB。
所以在可能的情况下,应该考虑采纳100MB的TPMMS算法。
6实验遇到的问题和解决方法
1
2
3
4
5
6
6.1Phase2阶段遇到的问题和解决方法
前文已经详细描述了Phase2阶段的3个主要的实现阶段,但是仅仅依靠这3个阶段还不能完全实现Phase2阶段,必须解决以下几个关键问题才能完成Phase2阶段的所有任务。
6.1.1读完某个子记录文件后,输入缓冲的填充方法
当某个输入缓冲数组inputBuffer[i]相对应的子记录文件infp[i]已经读完时,我们就必须重新查找其余可用的子记录文件,按数组下标i搜索到第一个可用的文件infp[k]后,将它的第一个字节继续填充到输入缓冲数组inputBuffer[i]中。
特别的,当数组下标i超过子记录文件总数SUB_LIST_NUMBER(本实验中为20)时,我们就认为所有子记录文件已经读取完毕,这时可以设置一个bool型变量flag=true,进行标识。
核心代码实现如图6-1所示:
图6-1读完某个子记录文件后,输入缓冲的填充方法
6.1.2读完所有子记录文件后,处理最后一组输入缓冲数据的方法
利用在6.1.1中设置的bool型变量flag,当flag=true时,我们知道子记录文件已经全部读取完毕。
这时在输入缓冲数组inputBuffer中只剩下最后一组数据,并且根据Phase2阶段的定义,它们肯定比之前输入缓冲中的数据要大。
所以我们只需利用STL提供的sort函数对它们进行排序后,直接输出到最终结果文件即可。
核心代码实现如图6-2所示:
图6-1读完所有子记录文件后,处理最后一组输入缓冲数据的方法
6.2生成子记录文件名的方法
当我们生成子记录文件时,想要赋予文件类似于record_k.txt(k=i+1,0<=i<=19)的文件名。
由于在C语言中,不支持字符串和整数的直接连接。
在这里我们需要一个generateFileName函数,采用itoa函数将整数k=i+1转换成字符串,再连接到“record_”后面,从而得到想要的文件名。
核心代码实现如图6-3所示:
图6-3生成子记录文件名的方法
7代码附录
#include//forsortfunction
#include//forstrcpy
#include//forfscanf,fprintf,fopen
#include//forclock
usingnamespacestd;
/*definetheconstantsusedinthisprogram*/
constintMAX_RECORD_NUMBER=10000000;//maxrecordnumber
constintBLOCK_SIZE=500000;//mainmemoryblocksize
constintROW_NUMBER=95;//forrecordtofilltheother96bytes
constintSUB_LIST_NUMBER=(MAX_RECORD_NUMBER/BLOCK_SIZE);//sublistnumber
constintMAX=99999999;//forfunctionselectMinRecordtoinitializethevariable"min"
/*thedatastructrueofarecord*/
typedefstructrecord
{
intkey;//primarykey
charcontents[ROW_NUMBER+1];//content
}Record;
RecordsubRecord[BLOCK_SIZE];//mainmemorybuffer
RecordinputBuffer[BLOCK_SIZE+1];//inputbuffer
RecordoutputBuffer[BLOCK_SIZE+1];//outputbuffer
/*generateafileofMAX_RECORD_NUMBER(=10000000)records,
everyrecordis100bytes*/
voidgenerateFile(stringfileName)
{
//calculatetime
printf("Therecordsisnowundergenerating...\n");
clock_tstart,finish;
doubleduration;
start=clock();//starttime
//openfile
FILE*fp=fopen(fileName.c_str(),"w");
if(!
fp)//openfailed
{
printf("Filecouldnotbecreated!
\n");
fprintf(stderr,"Filecouldnotbecreated!
\n");
exit
(1);
}
//generaterandomintegersandrecords
srand((unsigned)time(NULL));//srandseed
for(inti=0;i{
if(i>0)
fprintf(fp,"\n");
intkey=rand();//primarykey,randominteger,4bytes
//writerecordtodisk,everyrecordhas100bytes
fprintf(fp,"%d",key);//writekeyasthefirst4bytes
for(intj=0;jfprintf(fp,"%c",'0');
}
fclose(fp);//closeoutputfile
//calculatetime
finish=clock();//finishtime
duration=(double)(finish-start)/CLOCKS_PER_SEC;//runtime
printf("Ittakes%fsecondstogenetatethewholerecords.\n",duration);
}
/*useforphase1oftwophasemultiwaymergesort
comparetworecordbyprimarykey,withascendingorder*/
boolcmp(constRecord&r1,constRecord&r2)
{
returnr1.key}
/*giveaninteger,togenerateafilename*/
stringgenerateFileName(inti)
{
charstr[20];//temporarycharaterarray
stringtemp="";//temporarystring
itoa(i+1,str,10);//storeintegerk+1toarraystr
temp=str;//convertarraystrtotemporarystring
temp="D:
/record_"+temp+".txt";//formthefilename
returntemp;//returnthetemporarystringoffilename
}
/*phase1oftwophasemultiwaymergesort
readrecordwithmaximumblocksizetomainmemory
andsortthembyprimarykey*/
voidphase1(stringfileName)
{
//openfile
FILE*infp=fopen(fileName.c_str(),"r");
if(!
infp)//openfailed
{
printf("File%scouldnotbeopened!
\n",fileName.c_str());
fprintf(stderr,"File%scouldnotbeopened!
\n",fileName.c_str());
exit
(1);
}
stringtemp="";//temporarystring
inti=0,j=0;
//calculatetime
printf("Thesortedlistofrecordsisnowundergenerating...\n");
clock_tstart,finish;
doubleduration;
start=clock();//starttime
charstr[ROW_NUMBER+10];
//readallrecordstomainmemory
for(intk=0;k{
//readrecordsofablocksizetomainmemory
for(i=0;i{
fgets(str,ROW_NUMBER+10,infp);
sscanf(str,"%d%s",&subRecord[i].key,subRecord[i].contents);
}
//useSTLalgorithmsorttosortrecords
sort(subRecord,subRecord+BLOCK_SIZE,cmp);
temp=generateFileName(k);//sortedlistname
FILE*outfp=fopen(temp.c_str(),"w");//openoutputfile
if(!
outfp)//openfailed
{
printf("File%scouldnotbeopened!
\n",temp.c_str());
fprintf(stderr,"File%scouldnotbeopened!
\n",temp.c_str());
exit
(1);
}
//writethesortedrecordstosublistfile
for(i=0;i{
if(i>0)
fprintf(outfp,"\n");
fprintf(outfp,"%d%s",subRecord[i].key,subRecord[i].contents);
}
printf("Thesortedlist%sgeneratedsuccessfully!
\n",temp.c_str());
fclose(outfp);//closeoutputstream
}
fclose(infp);//closeinputfile
//calculatetime
finish=clock();//finishtime
duration=(double)(finish-start)/CLOCKS_PER_SEC;//runtime
printf("Ittakes%fsecondstogenetatethesortedlistofrecords.\n",duration);
}
/*copyrecordr2torecordr1*/
voidcopyRecord(Record&r1,Record&r2)
{
r1.key=r2.key;
strcpy(r1.contents,r2.contents);
}
/*copyarecordtoinputbuffer*/
voidcopyToInputBuf