在归并排序中,很重要的一步是将两个排序数组合并成一个数组,这个操作叫merge。merge操作可以用来解决某些Top K问题。

问题描述

在哼唱搜索中,用户通过哼唱一个音乐片段去搜索与其相似的音乐。后台的实现主要有两个步骤:特征提取和特征匹配。特征提取是从原始波形音乐文件中提取最能代表音乐的特征。特征匹配就是利用提取的特征与特征库进行匹配,找到最相似的音乐。在实际情况中,特征库往往很大,目前商用的特征库已达千万级别,这样的规模已经远远超过单机的处理能力,所以需要利用集群进行特征的匹配。

解决方案

在集群的每个节点上各存放特征库的一部分,所有不相交的特征库子集构成完整的特征库。集群的每个节点首先从主节点接收经过特征提取的用户哼唱特征,然后与自己的部分特征库进行匹配,返回Top K。最后利用MPI的规约函数将每个节点上的Top K规约成一个Top K返回给用户。

具体实现

主要的代码就是利用自己定义的规约操作完成merge操作。

1.       特征匹配的结果是一个表示两个音乐相似性的距离,越小说明两首歌越相似。首先定义规约的数据结构:

typedef struct
{
double dis; //表示计算的距离
char name[256]; //特征库文件表示的音乐
}distance;

2.       利用定义的数据结构声明一个MPI类型:

void new_type(MPI_Datatype* ctype)
{
int blockcounts[2];
MPI_Datatype oldtypes[2];
MPI_Aint offsets[2]; blockcounts[0]=1;
blockcounts[1]=256; offsets[0]=0;
offsets[1]=sizeof(double); oldtypes[0]=MPI_DOUBLE;
oldtypes[1]=MPI_CHAR; MPI_Type_struct(2,blockcounts,offsets,oldtypes,ctype);
MPI_Type_commit(ctype);
}

我们采用的方法是利用MPI的MPI_Type_struct声明一个结构数据类型。在distance结构体中共有两个变量,所以MPI_Type_struct的中间三个参数都是长度为2的数组。这里需要注意的是,结构体的变量个数与结构体声明的个数无关,而与变量的类型数相关。例如结构体:

typedef struct
{
double x,y,z;
double velocity;
int n,type;
}Particle;

该结构体共有6个变量,但是在MPI结构类型中只有两个块{double,int},长度分别是{4,2}。

MPI_Type_struct的五个参数意义分别是:第一个参数指明结构体变量的块数,上面的两个例子都是2;第二个参数指明每个块的长度,上面的例子分别是{1,256}和{4,2};第三个参数指明每个块的偏移,简单的结构体可以利用sizeof获得,此外还可以利用MPI_Type_extent和MPI_Address获得;第四个参数指明每个块的变量类型;第五个参数就是根据我们声明的结构体返回的MPI变量类型。

3.       自定义归约操作实现merge:

void myProd(distance* in, distance* inout,int *len,MPI_Datatype* dptr)
{
int i,j,k; distance *result;
result=(distance*)malloc(sizeof(distance)*(*len)); for(i=0,j=0,k=0;i<*len;i++)
{
if(in[j].dis<inout[k].dis)
{
result[i].dis=in[j].dis;
strcpy(result[i].name,in[j].name);
j++;
}
else
{
result[i].dis=inout[k].dis;
strcpy(result[i].name,inout[k].name);
k++;
}
} for(int i=0;i<*len;i++)
{
inout[i].dis=result[i].dis;
strcpy(inout[i].name,result[i].name);
}
free(result);
}

用户自定义的归约操作是原型为:typedef void MPI_User_function(void *invec, void *inoutvec, int*len, MPI_Datatype *datatype);的函数。该函数有四个参数,第一个参数是数据输入,第二个是数据输入和输出,第三个参数是数据的长度,第四个是自定义归约操作的数据类型。

merge操作就是将两个排好序的数组合并成一个,这里有一点不同的是:合并的结果长度和输入数据长度相同,也即两个Top K结果合并成一个Top K结果。输入和输出的数据类型即是我们之前声明的类型distance。合并代码和常规的合并代码类似,但稍有不同。由于第二个变量既表示输入有代表输出,所以我们无法进行原地merge操作,在此我们引入一个临时变量result,将merge的结果先放入到result变量,最后再将result的结果拷贝到inout数组中。虽然这样显得浪费空间,但是这保证了正确性。

4.       主代码调用:

int main(int argc,char *argv[])
{
int n, myid, numprocs;
float *query, t1,t2;
int qline,scaned_file, sum=0;
int *accum_length;
int *seq_length,*all_seq, *small;
distance* dist, result[20]; MPI_Init(&argc,&argv);
MPI_Comm_size(MPI_COMM_WORLD,&numprocs);
MPI_Comm_rank(MPI_COMM_WORLD,&myid); /*
* step 1
* master get the humming sequence from the user, and broadcast to all the other nodes
*/
if(myid==MASTER)
{
query=get_sequence(argv[1],&qline);
if(query==NULL)
{
perror("get query error!\n");
return -1;
}
} /*
* step 2
* broadcast the sequence length to all the processes
*/
MPI_Bcast(&qline,1,MPI_INT,MASTER,MPI_COMM_WORLD); /*
* step 3
* the slave processes allocate the space for the humming sequence
*/
if(myid!=MASTER)
{
query=(float*)malloc(sizeof(float)*qline);
} /*
* step 4
* broadcast the humming sequence to all the processes
*/
MPI_Bcast(query,qline,MPI_FLOAT,MASTER,MPI_COMM_WORLD); /*
* step 5
* get all the sequence in the given directory which has the library
*/
get_dir_seq(argv[2],&scaned_file); /*
* step 6
* calculate the distance between the query and the library sequence
*/
small=match(query,qline,scaned_file,all_seq,sum,seq_length,accum_length,0.2); /*
* step 7
* sort the distance on every process
*/
dist=sort(small,scaned_file); /*
* step 8
* reduce the result to the MASTER process
*/
MPI_Op myop;
MPI_Datatype ctype; MPI_Op_create((MPI_User_function*)myProd,1,&myop);
new_type(&ctype);
MPI_Reduce(dist,result,20,ctype,myop,MASTER,MPI_COMM_WORLD); /*
* step 9
* free the allocated space
*/
free_space(query,accum_length,seq_length,all_seq,dist,scaned_file); MPI_Op_free(&myop);
MPI_Finalize();
return 0;
}

主代码流程比较简单,从命令行获取要匹配的序列,然后将该序列从MASTER广播到所有的进程。每个进程利用广播的序列与特征库进行匹配,然后将结果进行排序。最后利用自定义归约操作将排好序的文件归约到MASTER进程。

mpi中利用自定义归约操作实现merge的更多相关文章

  1. (数据科学学习手札145)在Python中利用yarl轻松操作url

    本文示例代码已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 大家好我是费老师,在诸如网络爬虫.web应用开发 ...

  2. MVC中利用自定义的ModelBinder过滤关键字

    上一篇主要讲解了如何利用ActionFilter过滤关键字,这篇主要讲解如何利用自己打造的ModelBinder来过滤关键字. 首先,我们还是利用上一篇中的实体类,但是我们需要加上DataType特性 ...

  3. Django中利用type动态操作数据库表

    场景分析: 后台MySql数据库保存了一大批按股票代码命名的数据表,每张表保存的是每只股票的日线数据. stock_000002 stock_600030 stock_600020 ...一共3000 ...

  4. asp.net中利用JSON进行增删改查中运用到的方法

    //asp.net中 利用JSON进行操作, //增加: //当点击“增加链接的时候”,弹出增加信息窗口,然后,在窗体中输入完整信息,点击提交按钮. //这里我们需要考虑这些:我会进行异步提交,使用j ...

  5. 利用自定义动画 animate() 方法,实现某图书网站中“近 7 日畅销榜”中的图书无缝垂直向上滚动特效:当光标移入到图书上时,停止滚动,鼠标移开时,继续滚动

    查看本章节 查看作业目录 需求说明: 利用自定义动画 animate() 方法,实现某图书网站中"近 7 日畅销榜"中的图书无缝垂直向上滚动特效:当光标移入到图书上时,停止滚动,鼠 ...

  6. 巧妙利用JS中的自定义函数——化繁为简,提高效率

    利用自定义函数编写年月日时间表: (复杂写法)如下: <body>                <select id="year" size="1&q ...

  7. UWP中实现自定义标题栏

    UWP中实现自定义标题栏 0x00 起因 在UWP开发中,有时候我们希望实现自定义标题栏,例如在标题栏中加入搜索框.按钮之类的控件.搜了下资料居然在一个日文网站找到了一篇介绍这个主题的文章: http ...

  8. [转]Windows系统中监控文件复制操作的几种方式

    1. ICopyHook 作用: 监视文件夹和打印机移动,删除, 重命名, 复制操作. 可以得到源和目标文件名. 可以控制拒绝操作. 缺点: 不能对文件进行控制. 只对Shell文件操作有效, 对原生 ...

  9. Oracle 中利用闪回查询确定某表在某时间点之后的修改内容,并恢复至该时间点

    Oracle 中利用闪回查询确定某表在某时间点之后的修改内容: 1.查看 DELETE 及 UPDATE 操作修改的数据: SQL> SELECT * FROM tab AS OF TIMEST ...

随机推荐

  1. iOS内存管理 ARC与MRC

    想驾驭一门语言,首先要掌握它的内存管理特性.iOS开发经历了MRC到ARC的过程,下面就记录一下本人对iOS内存管理方面的一些理解. 说到iOS开发,肯定离不开objective-c语言(以下简称OC ...

  2. BZOJ 1642: [Usaco2007 Nov]Milking Time 挤奶时间( dp )

    水dp 先按开始时间排序 , 然后dp. dp( i ) 表示前 i 个时间段选第 i 个时间段的最优答案 , 则 dp( i ) = max( dp( j ) ) + w_i ( 0 < j ...

  3. 类似jquery的一个demo

    通过以下的demo,可以大体知道jquery的一些组织结构以及一些实现方法. 实际上jquery就是一个全局变量,只是在这个变量上添加了各种属性和方法. 首先我们要理解什么是匿名函数自执行,简单点就是 ...

  4. HTML5 总结-音频-2

    HTML5 音频 音频格式 当前,audio 元素支持三种音频格式:   IE 9 Firefox 3.5 Opera 10.5 Chrome 3.0 Safari 3.0 Ogg Vorbis   ...

  5. Introduction to Guid ( globally unique identifier )

    什么是 GUID? 全球唯一标识符 (GUID) 是一个字母数字标识符,用于指示产品的唯一性安装. 在许多流行软件应用程序(例如 Web 浏览器和媒体播放器)中,都使用 GUID. GUID 的格式为 ...

  6. QT直接支持GB18030,附它对此编码的一堆相关文档描述

    http://doc.qt.io/qt-5/qtextcodec.html http://doc.qt.io/qt-5/codec-gbk.html

  7. Linux部署ASP.NET 5 (vNext)

    原文:Linux部署ASP.NET 5 (vNext) ASP.NET 5 (vNext) Linux部署   引言 工欲善其事,必先利其器. 首先,我们先明确下以下基本概念 Linux相关 Ubun ...

  8. fedora21 codeblocks在编辑装态下无法输入

    来自:http://forum.ubuntu.com.cn/viewtopic.php?f=88&t=284409   用codeblocks,突然发现怎么敲键盘都不能输入 搜索后得知: Co ...

  9. Internet Explorer 11(IE11)无法切换第三方输入法

    Windows 8.1搭载了新的IE11版本,还发布了IE11 for Windows 7. IE11除了支持全尺寸Win设备以外,还比IE10更快速流畅,支持3D等高性能的浏览体验.全新F12开发者 ...

  10. 技术贴:解码时AVC1和H264的差别

    我一直疑问为什么有些视频解码时显示格式是:H264,大部分又是:AVC1 我在搜索编程资料时在微软的msdn上发现的: 原文:http://msdn.microsoft.com/en-us/libra ...