优秀的mpi编程指南.doc

上传人:asd****56 文档编号:70334000 上传时间:2023-01-19 格式:DOC 页数:12 大小:157.50KB
返回 下载 相关 举报
优秀的mpi编程指南.doc_第1页
第1页 / 共12页
优秀的mpi编程指南.doc_第2页
第2页 / 共12页
点击查看更多>>
资源描述

《优秀的mpi编程指南.doc》由会员分享,可在线阅读,更多相关《优秀的mpi编程指南.doc(12页珍藏版)》请在得力文库 - 分享文档赚钱的网站上搜索。

1、MPI编程指南一、 MPI概述1.1MPI的发展史MPI标准化涉及到大约60个国家的人们,他们主要来自于美国和欧洲的40个组织,这包括并行计算机的多数主要生产商,还有来自大学、政府实验室和工厂的研究者们。1992年4月,并行计算研究中心在Williamsburg,Virginia,召开了一个关于消息传递的标准的工作会议,会议上讨论了标准消息传递的必要的、基本的特点,并建立了工作组继续进行标准化工作。1992年10月,MPI的初步草稿MPI1形成,MPI1主要包含的是在Williamsburg工作组会议上讨论的基本消息传递的接口,因为它的基本目的就是促进讨论并继续此项工作,所以它主要集中在点对点

2、的通信。1993年1月,第一届MPI会议在Dallas举行,1993年2月MPI1修定版本公布;之后定期召开了一系列关于MPI的核心的研讨会;1993年11月MPI的草稿和概述分别发表于Supercomputing93和the proceedings。直到1994年5月,MPI标准正式发布。1994年7月发布了MPI标准的勘误表。现在该工作组正在着手扩展MPI、完善MPI,从事于MPI2(MPI的扩展标准)的制定工作。1.2MPI的优点可移植性和易于使用。以低级消息传递程序为基础的较高级和(或)抽象程序所构成的分布存储通信环境中,标准化的效益特别明显。MPI是被正式的详细说明的:它已经成为一个

3、标准。消息传递标准的定义能提供给生产商清晰定义的程序库,以便他们能有效地实现这些库或在某些情况下为库程序提供硬件支持,因此加强了可扩展性。MPI有完备的异步通信:使得send,recieve能与计算重叠。可以很有效的在MPP上或Cluster上用MPI编程:MPI的虚拟拓扑反映了应用程序所申请的一组结点的通信模式,在MPP上实现的MPI便可以利用这种信息来优化处理器间的通信路径。1.3主要内容 点对点通信(point_to_point communication) 群体操作(collective operations) 进程组(process groups) 通信上下文(communicati

4、on contexts) 进程拓扑结构(process topologies) 与Fortran 77和C语言的邦定(bindings for Fortran 77 and C) 环境的管理与查询(environmental management and inquiry) 轮廓管理(profiling interface)1.4MPI的各种实现在国外有许多自从MPI标准制定以来在各种机器上的MPI的portable的实现,并且他们仍从事于自己的MPI实现的性能改进,其中最著名的一些见表1。表1 国外MPI的各种实现名称单 位网 址 CHIMPU. of Edinburghftp.epcc.ed

5、.ac.uk/pub/chimp/. LAMOhio statetbag.osc.edu/pub/lam/ MPICHArgonne_Mississippi stateinfo.mcs.anl.gov/pub/mpi UnifyMississippi stateftp.erc.msstate.edu/unify二、MPI入门介绍以下均以MPICH为例。2.1 MPI的编程方式对于基本的应用,MPI同其它消息传送系统一样易于使用。下面是一个简单的基于C语言的MPI样本代码。它是SPMD方式的,即每个进程都执行该程序,通过返回的进程编号来区分不同的进程。该程序完成进程0向进程1传递数据buf。/*

6、 first.c */#include mpi.h/*MPI的头函数,提供基本的MPI定义和类型*/#include int main( argc, argv )int argc;char *argv;int rank, size, tag=333;int buf20MPI_Status status MPI_Init( &argc, &argv );/*MPI的初始化函数*/MPI_Comm_rank( MPI_COMM_WORLD, &rank );/*该进程的编号*/MPI_Comm_size( MPI_COMM_WORLD, &size );/*总的进程数目*/if (rank=0)

7、MPI_Send( buf, 20, MPI_Int, 1, tag, MPI_COMM_WORLD);/*发送buf到进程1*/if (rank=0) MPI_Recv( buf, 20, MPI_Int, 0, tag, MPI_COMM_WORLD, &status);/*从进程0接收buf*/MPI_Finalize();/*MPI的结束函数*/return 0;2.2 MPI的基本语句介绍1、MPI世界进程由一个唯一的“标识数”(整数)表示,进程的标识数为数0、1、2、N-1。MPI_COMM_WORLD指“MPI应用中的所有进程”,它称为通信子,并且提供消息传递所需的所有信息。可移

8、植库对通信子做更多的工作,以提供大多数其它系统所不能处理的同步保护。2、进入和退出MPI与其它系统一起,提供两个函数来初始化和结束MPI进程:int MPI_Init(int *argc, char *argv) /*初始化*/int MPI_Finalize(void) /*结束*/3、我是谁?他们是谁?典型地,并行程序中的进程需要知道它是谁(它的标识数)以及其它进程是怎样存在的。一个进程通过调用MPI_Comm_rank( )来发现它自己的标识数,用MPI_Comm_size( )来返回进程总数:int MPI_Group_size(MPI_Group group, int *size)

9、int MPI_Group_rank(MPI_Group group, int *rank) 4、发送消息消息是一组给定数据类型的元素。消息被发给一个指定的进程,而且用一个由用户说明的标识来标记。标识用来区分不同的由一个进程所能发送/接受的消息类型。int MPI_Send(void* buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm)5、接受消息接受进程说明标识和发送进程的标识数。MPI_ANY_TAG和MPI_ANY_SOURCE可选地用于接受任意标识和从任意发送进程而来的消息。int MPI_R

10、ecv(void* buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status)有关所接受消息的信息在一个状态变量status中返回。通过这些函数,你会准备编写任意的应用。在MPI中有许多其它、更特异的函数,但是迄今为止所有这些函数可以在这里提到的函数上构造。2.3 MPI的环境设置在并行机上的环境一般是设置好的,不需要变动。而在NOW上运行MPI程序需要设置一些配置文件:1、 由于加载程序到结点上运行调用了Unix系统的rsh命令,所以需要在每个结点上设置 .rho

11、sts文件,以使rsh能正确执行;2、 由于NOW环境的异构性,需要在启动时指定运行结点的体系结构;若未指定,是指使用与启动并行程序的结点具有相同体系结构的结点。在启动并行程序的机器里,具有相同体系结构的几台机器的名字存放在一个名为$MPICH/util/machines/machines.的文件中,一台机器的名字占有文件的一行,其中$MPICH是一个环境变量,指明MPICH软件安装后所在的目录。并行程序加载运行时是按照文件中机器名字的先后顺序依次加载的。2.4 MPI的编译和运行对于简单的程序,可以使用专门的编译命令。对于大的项目,最好使用标准的Makefile。MPICH提供的编译命令有m

12、picc和mpif77,它们分别是C和Fortran的编译命令:mpicc -o first first.c mpif77 -o first firstf.f 对于编译得到的目标程序,运行的命令为:mpirun arch xxx np yyy first其中xxx为machines.的,yyy为申请的进程数目。2.5例子1、 环的C语言实现#include mpi.h#include define T_SIZE 2000int main(argc,argv)int argc;char *argv;int ierr, prev, next, tag, rank, size;MPI_Status

13、status;double send_bufT_SIZE, recv_bufT_SIZE;MPI_Init(&argc,&argv);MPI_Comm_rank( MPI_COMM_WORLD, &rank);MPI_Comm_size( MPI_COMM_WORLD, &size);next = rank + 1;if (next size) next = 0;prev = rank 1;if (prev 0) prev = size 1;if (rank = 0) MPI_Send(send_buf, T_SIZE, MPI_DOUBLE, next, tag, MPI_COMM_WORL

14、D); MPI_Recv(recv_buf, T_SIZE, MPI_DOUBLE, prev, tag+1, MPI_COMM_WORLD, status); else MPI_Recv(recv_buf, T_SIZE, MPI_DOUBLE, prev, tag, MPI_COMM_WORLD, status); MPI_Send(recv_buf, T_SIZE, MPI_DOUBLE, next, tag+1, MPI_COMM_WORLD); MPI_Finalize();2、 环的Fortran语言实现 program ring include mpif.h integer ie

15、rr call MPI_INIT(ierr) call test_ring call MPI_FINALIZE(ierr) end subroutine test_ring include mpif.h integer T_SIZE parameter (T_SIZE=2000) integer ierr, prev, next, tag, rank, size, status(MPI_STATUS_SIZE) real send_buf( T_SIZE ), recv_buf ( T_SIZE ) call MPI_COMM_RANK( MPI_COMM_WORLD, rank, ierr

16、) call MPI_COMM_SIZE( MPI_COMM_WORLD, size, ierr ) next = rank + 1 if (next .ge. size) next = 0 prev = rank - 1 if (prev .lt. 0) prev = size - 1 if (rank .eq. 0) then call MPI_SEND(send_buf, T_SIZE, MPI_REAL, next, tag, MPI_COMM_WORLD, ierr) call MPI_RECV(recv_buf, T_SIZE, MPI_REAL, prev, tag+1, MPI

17、_COMM_WORLD, status, ierr) else call MPI_RECV(recv_buf, T_SIZE, MPI_REAL, prev, tag, MPI_COMM_WORLD, status, ierr) call MPI_SEND(recv_buf, T_SIZE, MPI_REAL, next, tag+1, MPI_COMM_WORLD, ierr) end if3、 PI的C语言实现#include mpi.h#include #include double f(a)double a;return (4.0 / (1.0 + a*a);int main(argc

18、,argv)int argc;char *argv; int done = 0, n=100, myid, numprocs, i; double PI25DT = 3.141592653589793238462643; double mypi, pi, h, sum, x, a,startwtime, endwtime; MPI_Init(&argc,&argv); MPI_Comm_size(MPI_COMM_WORLD,&numprocs); MPI_Comm_rank(MPI_COMM_WORLD,&myid); if (myid = 0) startwtime = MPI_Wtime

19、(); h = 1.0 / (double) n; sum = 0.0; for (i = myid + 1; i = n; i += numprocs) x = h * (double)i - 0.5); sum += f(x); mypi = h * sum; MPI_Reduce(&mypi, &pi, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD); if (myid = 0) printf(pi is %.16f, Error is %.16fn, pi, fabs(pi - PI25DT); endwtime = MPI_Wtime(); pr

20、intf(wall clock time = %fn, endwtime-startwtime); MPI_Finalize();4、 计算PI的Fortran语言实现 program main include mpif.h double precision PI25DT parameter (PI25DT = 3.141592653589793238462643d0) double precision mypi, pi, h, sum, x, f, a integer n, myid, numprocs, i, rc f(a) = 4.d0 / (1.d0 + a*a) call MPI_I

21、NIT( ierr ) call MPI_COMM_RANK( MPI_COMM_WORLD, myid, ierr ) call MPI_COMM_SIZE( MPI_COMM_WORLD, numprocs, ierr ) n=100 h = 1.0d0/n sum = 0.0d0 do 20 i = myid+1, n, numprocs x = h * (double(i) - 0.5d0) sum = sum + f(x) 20 continue mypi = h * sum call MPI_REDUCE(mypi,pi,1,MPI_DOUBLE_PRECISION,MPI_SUM

22、,0, MPI_COMM_WORLD, ierr) if (myid .eq. 0) then write(6, 97) pi, abs(pi - PI25DT) 97 format( pi is : , F18.16, Error is: , F18.16) endif 30 call MPI_FINALIZE(ierr) end三、 MPI中级介绍3.1基本概念1. 消息包在消息传递过程中,发送和接收的消息除了数据部分外, 消息还带有用于识别消息和选择接收消息的信息。这个信息是由确定的域组成的,我们称为信封,这些域有:source(源)、destination(目的)、tag(标识)、co

23、mmunicator(通信子)。消息的source隐含地由消息发送者的标识来决定。其他域是由发送操作的参数决定。整型值的消息tag能被程序用于识别不同的消息,有效标识值的范围依赖于实现。2. 组和通信子一个组(group)是进程的一个有序集。组内的每个进程与一个整数rank相联系。序列号是连续的并从0开始。组用模糊的组对象来描述,因此不能直接从一个进程到另一个进程传送。可在一个通信子中使用组来描述通信空间中的参与者并对这些参与者进行分级(rank)。上下文是通信子所具有的一个特性,它允许对通信空间进行划分。一个上下文所发送的消息不能被另一个上下文所接收。一个通信子(communicator)指

24、定一个通信操作的上下文。每个通信上下文提供一个单独的“通信全域”;消息总是在一个的上下文内被发送和接收,不同的上下文发送的消息互不干涉。通信子也指定共享这个通信上下文的进程组。这个进程组被编号,并且进程是由这个组中的进程号标识。一个预定义的通信子MPI_COMM_WORLD是由MPI提供。MPI初始化后, 它允许和可存取的全部进程通信, 进程是由他们在MPI_COMM_WORLD组中的进程号所标识。3. 基本数据类型异构计算要求包含消息的数据是有类型的或者已描述的,使得它的机器的表达可以在计算机体系结构之间转换。MPI系统地描述了消息的数据类型,从简单的原始机器类型到复杂的结构、数组和下标。与

25、C语言邦定的基本数据类型:MPI_CHARSigned charMPI_SHORTSigned short intMPI_INTSigned intMPI_LONGSigned long intMPI_UNSIGNED_CHARUnsigned charMPI_UNSIGNED_SHORTUnsigned short intMPI_UNSIGNEDUnsigned intMPI_UNSIGNED_LONGUnsigned long intMPI_FLOATFloatMPI_DOUBLEDoubleMPI_LONG_DOUBLELong doubleMPI_BYTEMPI_PACKED 与For

26、tran语言邦定的基本数据类型:MPI_INTEGERINTEGERMPI_REALREALMPI_DOUBLE_PRECISIONDOUBLE PRECISIONMPI_COMPLEXCOMPLEXMPI_DOUBLE_COMPLEXDOUBLE COMPLEXMPI_LOGICALLOGICALMPI_CHARACTERCHARCTER(1)MPI_BYTEMPI_PACKED 3.2点对点通信点对点通信是MPI中比较复杂的一部分,它有两种消息传递的机制:阻塞的和非阻塞的。对于阻塞的方式,它必须等到消息从本地送出之后,才可以执行后续的语句,保证了消息缓冲区等资源的可再用性;而非阻塞的方式不

27、须等到消息从本地送出,就可以执行后续的语句,从而允许通信和计算的重叠,利用合适的硬件可使得计算和数据通信同时执行,但是非阻塞调用的返回并不保证资源的可再用性。点对点通讯的发送和接收语句必须是匹配的,为了区分不同进程或同一进程发送来的不同消息,可以采用通讯体和标志位(tag)来实现相应的语句的匹配,也可以接收不确定source和tag的消息,例如MPI_ANY_SOURCE, MPI_ANY_TAG。1、 阻塞通信对于阻塞的标准通信,MPI_Send和MPI_Recv函数的格式为int MPI_Send(void* buf, int count, MPI_Datatype datatype, i

28、nt dest, int tag, MPI_Comm comm)int MPI_Recv(void* buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status)发送的含义是将包含count个datatype类型的首地址为buf的消息发送到dest进程,该消息的是与标志tag和通信体comm封装在一块的;接收的含义是将source进程接收标志为tag和通信体为comm的消息,将该消息写入首地址为buf 的缓冲区,返回值status中包含大小、标志、源进程等信息。在C语

29、言中, status是一个结构, 其包含两个域, 名字为 MPI_SOURCE和MPI_TAG, 该结构可以包含附加域。在Fortran中,status是一个MPI_STATUS_SIZE大小的INTEGER型数组,status(MPI_SOURCE)和status(MPI_TAG)分别包含被接收消息的源和标识。2、 非阻塞通信对于非阻塞的标准通信,MPI_Isend和MPI_Irecv函数的格式为int MPI_Isend(void* buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm, MPI_Re

30、quest *request) int MPI_Irecv(void* buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Request *request)对于非阻塞通信,可以用MPI_WAIT,MPI_TEST等来结束非阻塞通信, int MPI_Wait(MPI_Request *request, MPI_Status *status) int MPI_Test(MPI_Request *request,int *flag, MPI_Status *status)其中MPI_Test

31、用来检测非阻塞操作提交的任务是否结束并立即返回,MPI_Wait则一直要等到非阻塞操作提交的任务结束才返回。我们可以认为:阻塞通信非阻塞通信 + MPI_Wait;MPI_Wait = while (flag=0) MPI_Test。3、 消息的检测MPI_Probe和MPI_Iprobe操作允许在没有实际收到输入消息的情况下对其进行检查。用户于是可以根据这次返回的信息决定如何接收他们(该信息一般用status返回),或可以根据被检查消息的长度分配结束缓冲区大小,或进行条件分支等。他们的格式如下:int MPI_Probe(int source, int tag, MPI_Comm comm,

32、 MPI_Status *status)int MPI_Iprobe(int source, int tag, MPI_Comm comm, int *flag, MPI_Status *status)如果MPI_Iprobe返回flag=true,则状态目标(status)才能够被获取source,tag及消息的长度。而MPI_Probe是一个阻塞的语句,它必须等到相匹配的消息(source,tag)到达才完成。4、点对点通信的例子#include #include mpi.hint main(argc, argv)int argc;char* argv;int locId,data100,

33、 tag=8888;MPI_Status status;MPI_Init(&argc, &argv) ;MPI_Comm_rank(MPI_COMM_WORLD, &locId) ;if(locId = 0) MPI_Request events;MPI_Isend(data, 100, MPI_INT, 1, tag , MPI_COMM_WORLD, &events) ;/ int MPI_Isend(void* buf, int count, MPI_Datatype datatype, int dest, int tag, / MPI_Comm comm, MPI_Request *r

34、equest) 非阻塞通信MPI_Wait(&events, &status) ;if(locId = 1) MPI_Probe(MPI_ANY_SOURCE, tag, MPI_COMM_WORLD, &status);if (status.MPI_SOURCE=0)MPI_Recv(data, 100, MPI_INT, 0, tag, MPI_COMM_WORLD, &status) ; MPI_Finalize() ;3.3群体通信群体通信意味着一个通信子中的所有进程调用同一例程,所有的群体操作都是阻塞的,它包括如下一些:同步(barrier)从一个进程到本组内的所有进程的播送(bro

35、adcast)(如图 3.3 的(a)从本组所有处理收集数据到一个进程(gather)(如图 3.3 的(b)从一个进程分散数据到本组内的所有进程(sactter)(如图 3.3 的(b)将gather的数据不是送到某一进程,而是要送到所有本组内的进程(allgather) (如图 3.3 的(c)组内的多对多的分散/收集(alltoall)(如图 3.3 的(d)求和,最大值,最小值及用户定义的函数等的reduce操作scan或prefix操作图 3.3 群体操作的图例1、同步int MPI_Barrier(MPI_Comm comm)它使得调用者阻塞,直到该通信子内所有进程都调用它。2、广

36、播int MPI_Bcast(void *buffer, int count, MPI_Datatype datatype, int root, MPI_Comm comm);所有进程使用同一计数、数据类型、根和通信子。在操作前,根缓冲区包含一个消息。操作后,所有缓冲区包含来自根进程的消息。3、散播int MPI_Scatter(void *sndbuf, int sndcnt, MPI_Datatype sndtype, void *rcvbuf, int rcvcnt, MPI_Datatype rcvtype, int root, MPI_Comm comm);所有进程使用同一计数、数据

37、类型、根和通信子。在操作前,根发送缓冲区包含长度为sndcnt * N的消息,这里N是进程数目。操作后,相等地划分消息,并且分散到随后标识数序的所有进程(包括根)。4、归约int MPI_Reduce(void *sndbuf, void *rcvbuf, int count, MPI_Datatype datatype, MPI_Op op, int root, MPI_Comm comm);所有进程使用同一计数、数据类型、根和通信子。操作后,根进程在它的接受缓冲区中有所有进程的发送缓冲区的归约结果,包括:MPI_MAX, MPI_MIN, MPI_SUM, MPI_PROD, MPI_LA

38、ND, MPI_BAND, MPI_LOR, MPI_BOR, MPI_LXOR, MPI_BXOR,或者是用户定义的归约函数。5、收集int MPI_Gather(void *sndbuf, int sndcnt, MPI_Datatype sndtype, void *rcvbuf, int rcvcnt, MPI_Datatype rcvtype, int root, MPI_Comm comm);所有进程使用同一计数、数据类型、根和通信子。此例程是MPI_Scatter()的相反:操作后,根进程在它的接受缓冲区中包含所有进程的发送缓冲区的连接(包括它自己),所有消息长度为rcvcnt

39、* N, 这里N是进程数目。按照随后的标识数序收集消息。6、群体通信的例子 下面简单的代码段使用了四个基本的集合例程以操纵一个静态的已划分的规则区域(这里是一维)。全域的长度从根进程广播到所有其它进程。初始数据集在进程间分配(分散)。在每一计算步骤之后,确定全局的最大数并由根所使用。根然后收集最终的数据集。#include int i,myrank,size,root,full_domain_length,sub_domain_length; double global_max,local_max,*full_domain, *sub_domain; MPI_Comm_rank(MPI_COM

40、M_WORLD, &myrank); MPI_Comm_size(MPI_COMM_WORLD, &size); root = 0; if (myrank = root) get_full_domain(&full_domain, &full_domain_length); MPI_Bcast(&full_domain_length, 1, MPI_INT, root, MPI_COMM_WORLD); sub_domain_length = full_domain_length / size; sub_domain = (double *) malloc(sub_domain_length

41、* sizeof(double);MPI_Scatter(full_domain, sub_domain_length, MPI_DOUBLE, sub_domain, sub_domain_length, MPI_DOUBLE, root, MPI_COMM_WORLD); compute(sub_domain, sub_domain_length, &local_max); MPI_Reduce(&local_max, &global_max, 1, MPI_DOUBLE, MPI_MAX, root, MPI_COMM_WORLD);MPI_Gather(sub_domain, sub_

42、domain_length, MPI_DOUBLE, full_domain, sub_domain_length, MPI_DOUBLE, root, MPI_COMM_WORLD);3.3环境管理与查询MPI的环境管理与查询包括:设置正确的参数、执行环境(出错句柄),以及进入MPI和退出MPI等。这些函数对于写正确的、健壮的程序很重要,特别是对于建筑更高层的可移植的消息传递的程序尤其重要。1、 时钟MPI定义了一个计时器,因为对并行程序计时在“性能调试”中是很重要的,而已存在的计时器要么是不方便的,要么是没能提供对高分辨率计时器的足够的访问。double MPI_Wtime(void)它返

43、回当前的系统的墙壁时钟的时间,是一个浮点的秒数。double MPI_Wtick()MPI_Witck返回秒中MPI_WTIME的精度。即,它返回连续时钟滴答的秒数,是一双精度值。例如,如果由硬件实现的时钟作为每毫秒递增的计数器,那么由MPI_WTICK返回的值应为10e-3。2、 MPI的初始与结束int MPI_Init(int *argc,char *argv)int MPI_Finalize(void)它是MPI的初始化函数及MPI的结束函数,所有的MPI程序必须含有MPI_Init,并且必须放在所有MPI的其它调用之前,MPI_Finalize清除所有的MPI的申明,用户必须确保所有的通信必须在调用MPI_Finalize之前完成。四、 MPI高级介绍

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 技术资料 > 其他杂项

本站为文档C TO C交易模式,本站只提供存储空间、用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。本站仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知得利文库网,我们立即给予删除!客服QQ:136780468 微信:18945177775 电话:18904686070

工信部备案号:黑ICP备15003705号-8 |  经营许可证:黑B2-20190332号 |   黑公网安备:91230400333293403D

© 2020-2023 www.deliwenku.com 得利文库. All Rights Reserved 黑龙江转换宝科技有限公司 

黑龙江省互联网违法和不良信息举报
举报电话:0468-3380021 邮箱:hgswwxb@163.com