├── README.md └── main.cpp /README.md: -------------------------------------------------------------------------------- 1 | ## 前言 2 | + **团队名**:景 3 | + **成绩**:粤港澳复赛A榜第2,全国第12 4 | + **分工**: 5 | (1)[KITE-HZ](https://github.com/KITE-HZ "黄老哥")作为队长承担了大部分的编程工作和优化工作; 6 | (2)[Buerzhu](https://github.com/Buerzhu/ "Buerzhu")承担了多线程的优化工作以及部分编程和算法的优化工作; 7 | (3)[cxyanhk](https://github.com/cxyanhk "严老哥")承担了部分IO优化工作和部分算法优化工作; 8 | 9 | 10 | ## 优化 11 | + **找环算法优化** 12 | (1)基础是4+3,即在开始找环之前,先构建一个P3来存储路径倒数三个节点的信息; 13 | (2)在构建P3的时候顺便用反向邻接表构建P1,P1找环比遍历子节点来寻找是否有头结点的找环方式更加高效; 14 | (3)对路径中的第2、3、4、5使用P3来找环,可以同时减少路径上第4、5、6、7节点通过P1找环的工作; 15 | (4)在反向遍历3层构建P3的过程中顺便统计有哪些点与起点的距离小于等于3,并利用这一点在正向遍历过程中剪枝,具体例子如下: 16 | ```cpp 17 | //对于第五层的点,如果该点距离起点的距离大于3,则该点无环 18 | if(target.nodeLen[m]!=3 || G[l_it].first <= head || ccm*leftright*ccL) 19 | continue; 20 | ``` 21 | 22 | + **数据结构优化** 23 | (1)使用数组来代替vector,减少扩容开销; 24 | (2)类似的,使用一维和二维数组来存储邻接表和P3等信息,即尽量减少STL容器的使用; 25 | (3)在节点去重工作的排序过程`sort(temp,temp+sumID)`中,由于temp中重复的节点过多,会大大增加排序时间,因此使用一个`bool idUnique[MAX_ID]`数组来存储那些已经在temp内的节点,从而避免重复存储。通过多次提交证明,大部分节点id大小在1亿以下; 26 | (4)类似的,对小于1亿的节点使用数组进行hash映射,也可以减少部分映射时间; 27 | (5)对前向邻接表排序后再构建后向邻接表,从而使得后向邻接表有序,减少部分排序工作; 28 | (6)为了节省空间和寻址时间,在满足功能的情况下尽量使用较小的数据结构,例如,使用uint8_t来存储每个节点对应的字符串长度; 29 | 30 | + **多线程优化** 31 | (1)使用抢占式负载均衡方式进行多线程找环,一次任务分块的节点数为100; 32 | (2)采用atomic_flag来构造自旋锁,可以避免互斥锁的系统调用的开销; 33 | (3)通过多次提交证明,6线程工作效果较佳; 34 | 35 | + **其他优化** 36 | (1)尽量使用局部变量; 37 | (2)使用memcpy来代替部分赋值工作; 38 | (3)使用循环展开; 39 | (4)利用计算机系统的空间局部性合理设计数据结构; 40 | (5)读取txt使用mmap; 41 | (6)写入使用fwrite; 42 | (7)在if判断中注意判断条件的顺序; 43 | (8)将dfs递归改为迭代,减少函数调用的开销; 44 | (9)用查表方式来代替部分判断 45 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | //#include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | using namespace std; 22 | #define INF 0x7fffffff 23 | #define MAX_ID 300000000 //id阈值,在映射过程中小于该阈值的id使用数组映射,大于该id的使用unorder_map映射 24 | #define MAX_G 201326592 //1.5G,用一个一维数组G来存储所有节点的子节点信息,预先分配1.5G空间;用一个一维数组GR来存储所有节点的父节点信息,预先分配1.5G空间 25 | #define MAX_EDGE 2000001 //设置最大边数为200w 26 | #define MAXN 2000001 //设置最大节点数为200w 27 | #define MAX_LEN 7 28 | #define MIN_LEN 3 29 | 30 | #define ansLen3 33*4000000 //level3,为每个线程中长度为3的环预先分配的空间,单位为字节 31 | #define ansLen4 44*4000000 //level4,为每个线程中长度为4的环预先分配的空间,单位为字节 32 | #define ansLen5 55*4000000 //level5,为每个线程中长度为5的环预先分配的空间,单位为字节 33 | #define ansLen6 66*4000000 //level6,为每个线程中长度为6的环预先分配的空间,单位为字节 34 | #define ansLen7 77*4000000 //level7,为每个线程中长度为7的环预先分配的空间,单位为字节 35 | 36 | #define likely(x) __builtin_expect(!!(x), 1) 37 | #define unlikely(x) __builtin_expect(!!(x), 0) 38 | 39 | #define THREAD_NUM 6//线程池线程数 40 | #define BLOCKS 100//线程单次任务竞争和处理的id数目 41 | //#define PRINT 42 | 43 | 44 | pair G[MAX_G]; //存储每个节点的出度子节点和转账值 45 | pair GR[MAX_G]; //存储每个节点的入度父节点和转账值 46 | 47 | char idComma[MAXN][15]; //存储节点id的字符串形式,后跟逗号字符 48 | char idLF[MAXN][15]; //存储节点id的字符串形式,后跟换行字符 49 | char idCharLen[MAXN]; //存储每个节点转换为字符串之后的字符串长度 50 | char* addrOffset[MAX_LEN-MIN_LEN+1][MAXN]; //每个节点环存储的内存偏移地址,第一维表示环长度(长度为3的环对应索引为0),第二维表示映射后的节点id(映射后的节点id从1开始) 51 | char* addrStart[MAX_LEN-MIN_LEN+1][MAXN]; //每个节点环存储的内存起始地址,第一维表示环长度(长度为3的环对应索引为0),第二维表示映射后的节点id(映射后的节点id从1开始) 52 | 53 | int GInd[MAXN][2]; //每个节点在pair G[MAX_G]存储的出度子节点的占用的空间的地址,[0]表示占用空间的首地址,[1]表示占用空间的尾地址 54 | int GRInd[MAXN][2]; //每个节点在pair GR[MAX_G]存储的入度父节点的占用的空间的地址,[0]表示占用空间的首地址,[1]表示占用空间的尾地址 55 | int idInDeg[MAXN];//每个节点的入度数 56 | int idOutDeg[MAXN];//每个节点的出度数 57 | int nodePairs[MAX_EDGE*3]; //存储的是未映射的id信息和转账信息,即从test_data.txt读取的每一行的信息 58 | int temp[MAX_EDGE*2]; //用于存储nodePairs的id信息 59 | bool idUnique[MAX_ID]; //标志位,标志某id之前是否被访问过 60 | int idMapArr[MAX_ID]; //hash数组,用该数组来对小于MAX_ID的id进行id映射,大于MAX_ID的使用unordered_map进行映射 61 | 62 | int minMoney=1000; 63 | int blockSize; //所有节点划分后的任务块数 64 | int edgeNum; //边数 65 | int nodeNum; //节点数 66 | int idFreeStart=1;//当前未分配到线程处理的id的最小节点 67 | 68 | std::atomic_flag flag(ATOMIC_FLAG_INIT);//原子变量初始化,利用该变量的原子性来制造自旋锁 69 | 70 | struct timeval startTime; 71 | struct timeval endTime; 72 | 73 | 74 | struct P3 75 | { //P3结构体 76 | int id[2]; //id信息 77 | int cc[2]; //转账信息 78 | }; 79 | 80 | //线程初始化 81 | struct Thread 82 | { 83 | pthread_t tid; 84 | 85 | //存储对应长度的环 86 | char ans3[ansLen3]; 87 | char ans4[ansLen4]; 88 | char ans5[ansLen5]; 89 | char ans6[ansLen6]; 90 | char ans7[ansLen7]; 91 | 92 | int8_t nodeLen[MAXN]; 93 | 94 | int pMapLen[MAXN];//P1 95 | int pMapMoney[MAXN];//存路径的第一个money 96 | 97 | P3 pMap3[15000][200]; 98 | int pMap3Len[MAXN][3];//pMap3Len[i][0]存i的对应起点,pMap3Len[i][1]存k的个数,pMap3Len[i][2]存i在pMap的位置 99 | int iP3[MAXN]; 100 | int resCnt; 101 | 102 | }; 103 | 104 | 105 | void setTime() { 106 | gettimeofday(&startTime, NULL); 107 | } 108 | 109 | int getTime() { 110 | gettimeofday(&endTime, NULL); 111 | return int(1000 * (endTime.tv_sec - startTime.tv_sec) + (endTime.tv_usec - startTime.tv_usec) / 1000); 112 | } 113 | 114 | //读取test_data.txt的每一行信息并存储到nodePairs中 115 | void readTxtMmap(string& inputFile){ 116 | int fd = open(inputFile.c_str(), O_RDONLY); 117 | struct stat sb; 118 | fstat(fd, &sb); 119 | char *buffer = (char *)mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0); 120 | if(buffer==nullptr || buffer==(void*)-1){ 121 | close(fd); 122 | exit(-1); 123 | } 124 | close(fd); 125 | 126 | int val=0; 127 | edgeNum=0; 128 | bool flag=true; 129 | while(*buffer) 130 | { 131 | if(*buffer<'0') // || *buffer>'9' 132 | { 133 | if(flag) 134 | { 135 | 136 | nodePairs[edgeNum]=val; 137 | ++edgeNum; 138 | val=0; 139 | flag=false; 140 | } 141 | } 142 | else 143 | { 144 | val = val*10 + (*buffer - '0'); 145 | //val = (val<<3) + (val<<1) + (*buffer - '0'); 146 | flag=true; 147 | } 148 | *buffer++; 149 | } 150 | munmap(buffer, sb.st_size); 151 | } 152 | 153 | 154 | Thread* threadPool; 155 | 156 | void buildGraph(string &inputFile){ 157 | 158 | setTime(); 159 | readTxtMmap(inputFile); 160 | threadPool=new Thread[THREAD_NUM]; 161 | 162 | int sumID=0; 163 | //这一步操作是为了减少temp中的重复id数,从而减少sort排序的时间 164 | for(int i=0,j=0;i idMap; 208 | char str[]="0123456789"; 209 | int num,k; 210 | //进行id映射并把id转换为字符串形式,方便后面环的存储 211 | for(int i=0;i=0) 221 | { 222 | if(num<10) 223 | { 224 | idComma[i+1][k++]=str[num]; 225 | idComma[i+1][k++]=','; 226 | idComma[i+1][k]='\0'; 227 | break; 228 | } 229 | idComma[i+1][k++]=str[num%10]; 230 | num/=10; 231 | } 232 | for(int j=0;j<=(k-2)/2;++j) 233 | { 234 | swap(idComma[i+1][j],idComma[i+1][k-2-j]); 235 | } 236 | idCharLen[i+1]=k; 237 | } 238 | for(int i=1;i<=nodeNum;++i) 239 | { 240 | memcpy(idLF[i],idComma[i],idCharLen[i]); 241 | idLF[i][idCharLen[i]-1]='\n'; 242 | } 243 | 244 | //nodePairs存储映射后的节点id信息 245 | for(int i=0;i tmpPair; 271 | //在G中存储每个节点的子节点信息 272 | for(int i=0;i1){ 297 | sort(G+GInd[i][0],G+GInd[i][1]); 298 | } 299 | } 300 | 301 | int u,v,c; 302 | //在GR中存储每个节点的父节点信息 303 | for(int i=1;i<=nodeNum;++i){ 304 | for(int j=GInd[i][0];jnodeNum) 384 | break; 385 | 386 | if(idOutDeg[i]>0) 387 | { 388 | head=i; 389 | 390 | for(int i_it=0;i_itleft*maxccj || right*cc3right*cc2) 413 | continue; 414 | 415 | idInDegkk2=GRInd[kk2][1]; 416 | for(k1=GRInd[kk2][0];k1right*cc1) 421 | continue; 422 | 423 | if(target.pMap3Len[kk1][0]!=head){ 424 | target.pMap3Len[kk1][0]=head; 425 | target.pMap3Len[kk1][1]=0; 426 | target.pMap3Len[kk1][2]=iP3Len; 427 | target.iP3[iP3Len]=kk1;//存kk2,同事避免重复 428 | target.nodeLen[kk1]=3; 429 | ++iP3Len; 430 | 431 | } 432 | pInd=target.pMap3Len[kk1][1]; 433 | pInd2=target.pMap3Len[kk1][2]; 434 | target.pMap3[pInd2][pInd].id[0]=kk2; 435 | target.pMap3[pInd2][pInd].id[1]=kk3; 436 | target.pMap3[pInd2][pInd].cc[0]=cc1; 437 | target.pMap3[pInd2][pInd].cc[1]=cc3; 438 | ++target.pMap3Len[kk1][1]; 439 | } 440 | } 441 | } 442 | 443 | for(int pi=0;pi1){ 447 | sort(target.pMap3[pi],target.pMap3[pi]+pAddr,cmp);//仅P3排序 448 | } 449 | } 450 | 451 | 452 | //开始寻环 453 | vis[i]=true; 454 | path0=i; 455 | idOutDegi=GInd[i][1]; 456 | for(i_it=GInd[i][0];i_itccjRight || ccjLeftright*ccLast) 477 | continue; 478 | 479 | path2=target.pMap3[pAddr][z].id[0]; 480 | path3=target.pMap3[pAddr][z].id[1]; 481 | memcpy((void *) addrOffset1,idComma[path0], idCharLen[path0]); 482 | addrOffset1 += idCharLen[path0]; 483 | memcpy((void *) addrOffset1,idComma[path1], idCharLen[path1]); 484 | addrOffset1 += idCharLen[path1]; 485 | memcpy((void *) addrOffset1,idComma[path2], idCharLen[path2]); 486 | addrOffset1 += idCharLen[path2]; 487 | memcpy((void *) addrOffset1,idLF[path3], idCharLen[path3]); 488 | addrOffset1 += idCharLen[path3]; 489 | ++localResCnt; 490 | } 491 | } 492 | 493 | idOutDegj=GInd[j][1]; 494 | for(j_it=GInd[j][0];j_itccjRight) 498 | continue; 499 | k=G[j_it].first; 500 | 501 | vis[k]=true; 502 | path2=k; 503 | 504 | if(target.pMapLen[k]==head) 505 | { 506 | ccLast=target.pMapMoney[k]; 507 | if(ccLast*left>=cck && ccLast<=right*cck && ccjLeft>=ccLast && ccj<=right*ccLast) 508 | { 509 | memcpy((void *) addrOffset0,idComma[path0], idCharLen[path0]); 510 | addrOffset0 += idCharLen[path0]; 511 | memcpy((void *) addrOffset0,idComma[path1], idCharLen[path1]); 512 | addrOffset0 += idCharLen[path1]; 513 | memcpy((void *) addrOffset0,idLF[path2], idCharLen[path2]); 514 | addrOffset0 += idCharLen[path2]; 515 | ++localResCnt; 516 | } 517 | } 518 | 519 | if(target.pMap3Len[k][0]==head)//level6 520 | { 521 | pAddr=target.pMap3Len[k][2]; 522 | pMap3Lenmm=target.pMap3Len[k][1]; 523 | for(z=0;zright*cck || ccjLeftright*ccLast) 531 | continue; 532 | 533 | path3=target.pMap3[pAddr][z].id[0]; 534 | path4=target.pMap3[pAddr][z].id[1]; 535 | memcpy((void *) addrOffset2,idComma[path0], idCharLen[path0]); 536 | addrOffset2 += idCharLen[path0]; 537 | memcpy((void *) addrOffset2,idComma[path1], idCharLen[path1]); 538 | addrOffset2 += idCharLen[path1]; 539 | memcpy((void *) addrOffset2,idComma[path2], idCharLen[path2]); 540 | addrOffset2 += idCharLen[path2]; 541 | memcpy((void *) addrOffset2,idComma[path3], idCharLen[path3]); 542 | addrOffset2 += idCharLen[path3]; 543 | memcpy((void *) addrOffset2,idLF[path4], idCharLen[path4]); 544 | addrOffset2 += idCharLen[path4]; 545 | ++localResCnt; 546 | } 547 | } 548 | 549 | idOutDegk=GInd[k][1]; 550 | for(k_it=GInd[k][0];k_itright*cck)//////////////////// 555 | continue; 556 | 557 | if(vis[l]==false) 558 | { 559 | vis[l]=true; 560 | path3=l; 561 | if(target.pMap3Len[l][0]==head)//level7 562 | { 563 | pAddr=target.pMap3Len[l][2]; 564 | pMap3Lenmm=target.pMap3Len[l][1]; 565 | for(z=0;zright*ccL || ccjLeftright*ccLast) 573 | continue; 574 | 575 | path4=target.pMap3[pAddr][z].id[0]; 576 | path5=target.pMap3[pAddr][z].id[1]; 577 | 578 | memcpy((void *) addrOffset3,idComma[path0], idCharLen[path0]); 579 | addrOffset3 += idCharLen[path0]; 580 | memcpy((void *) addrOffset3,idComma[path1], idCharLen[path1]); 581 | addrOffset3 += idCharLen[path1]; 582 | memcpy((void *) addrOffset3,idComma[path2], idCharLen[path2]); 583 | addrOffset3 += idCharLen[path2]; 584 | memcpy((void *) addrOffset3,idComma[path3], idCharLen[path3]); 585 | addrOffset3 += idCharLen[path3]; 586 | memcpy((void *) addrOffset3,idComma[path4], idCharLen[path4]); 587 | addrOffset3 += idCharLen[path4]; 588 | memcpy((void *) addrOffset3,idLF[path5], idCharLen[path5]); 589 | addrOffset3 += idCharLen[path5]; 590 | ++localResCnt; 591 | } 592 | } 593 | 594 | idOutDegl=GInd[l][1]; 595 | for(l_it=GInd[l][0];l_itright*ccL) 600 | continue; 601 | 602 | if(vis[m]==false)//level5 603 | { 604 | vis[m]=true; 605 | path4=m; 606 | 607 | pAddr=target.pMap3Len[m][2]; 608 | pMap3Lenmm=target.pMap3Len[m][1]; 609 | for(z=0;zright*ccm || ccjLeftright*ccLast) 617 | continue; 618 | 619 | path5=target.pMap3[pAddr][z].id[0]; 620 | path6=target.pMap3[pAddr][z].id[1]; 621 | 622 | memcpy((void *) addrOffset4,idComma[path0], idCharLen[path0]); 623 | addrOffset4 += idCharLen[path0]; 624 | memcpy((void *) addrOffset4,idComma[path1], idCharLen[path1]); 625 | addrOffset4 += idCharLen[path1]; 626 | memcpy((void *) addrOffset4,idComma[path2], idCharLen[path2]); 627 | addrOffset4 += idCharLen[path2]; 628 | memcpy((void *) addrOffset4,idComma[path3], idCharLen[path3]); 629 | addrOffset4 += idCharLen[path3]; 630 | memcpy((void *) addrOffset4,idComma[path4], idCharLen[path4]); 631 | addrOffset4 += idCharLen[path4]; 632 | memcpy((void *) addrOffset4,idComma[path5], idCharLen[path5]); 633 | addrOffset4 += idCharLen[path5]; 634 | memcpy((void *) addrOffset4,idLF[path6], idCharLen[path6]); 635 | addrOffset4 += idCharLen[path6]; 636 | ++localResCnt; 637 | 638 | } 639 | 640 | vis[m]=false; 641 | } 642 | } 643 | vis[l]=false; 644 | } 645 | } 646 | vis[k]=false; 647 | } 648 | vis[j]=false; 649 | } 650 | //vis[i]=false; 651 | } 652 | 653 | if(idStart==idEnd-1 || i==nodeNum){ 654 | addrStart[0][i]=addrBlock0; 655 | addrStart[1][i]=addrBlock1; 656 | addrStart[2][i]=addrBlock2; 657 | addrStart[3][i]=addrBlock3; 658 | addrStart[4][i]=addrBlock4; 659 | 660 | addrOffset[0][i]=addrOffset0; 661 | addrOffset[1][i]=addrOffset1; 662 | addrOffset[2][i]=addrOffset2; 663 | addrOffset[3][i]=addrOffset3; 664 | addrOffset[4][i]=addrOffset4; 665 | } 666 | 667 | idStart++; 668 | } 669 | 670 | target.resCnt = localResCnt; 671 | 672 | #ifdef PRINT 673 | cout<<"Index "<=0;i--)//等待所有子线程终止并回收线程资源 730 | { 731 | pthread_join(threadPool[i].tid,NULL); 732 | } 733 | } 734 | 735 | 736 | //子线程的工作函数 737 | void* work(void* arg) 738 | { 739 | int* p=(int*)arg; 740 | int index=*p;//index是子线程在线程池中的序号 741 | doDFS(index); 742 | return NULL; 743 | } 744 | //P3的sort排序规则函数 745 | bool cmp(P3 a,P3 b) 746 | { 747 | return (a.id[0]!=b.id[0])?(a.id[0]