百万数据sum太慢_给大家表演一个n方过百万(利用GPU暴力计算多项式乘法)

(学生的小打小闹,请各位炼丹大佬绕行)

在2019年noi冬令营上,我们学会(fei)了如何利用cpu指令加速暴力,并达到n方过百万。今天,我在这里表演一个利用gpu暴力算法过百万级数据的多项式乘法。

感兴趣的可以自行百度cuda。

源文件1

#include "cuda_runtime.h"
#include "device_launch_parameters.h"

#include <stdio.h>
#include <iostream>
#include<windows.h>
/*懂的都懂*/
const int P = 998244353;
/*生成测试数据*/
void createData();
/*得到分块信息*/
void createDim(dim3& gridDim, dim3& blockDim);
/*为GPU分配空间*/
void allocDevice();
/*将数据传输到GPU*/
void copyFHTD();
/*核函数*/
__global__ void kernel(int* da, int* db, int* dc, int n);
/*将数据传输到CPU*/
void copyFDTH();
/*释放GPU空间*/
void freeDevice();
/*随机检查结果,若发现错误则返回错误在结果多项式中的下标。若没有错误返回-1*/
int check();
/*输出结果,仅用于调试时。然后清理申请的空间*/
void output();

/*GPU上储存数据*/
int* da, * db, * dc;
/*原多项式为n-1次,结果为n*2-1次*/
int n;
/*内存上储存数据*/
int* a, * b, * c;
/*计时用*/
long long startTime;
/*输出提示信息和时间*/
void outputTime(const char *str) {
    std::cout << str << ":" << GetTickCount64() - startTime << std::endl;
}
int main(){
    startTime = GetTickCount64();
    createData();
    dim3 gridDim, blockDim;
    createDim(gridDim, blockDim);
    allocDevice();
    copyFHTD();
    outputTime("start");
    kernel << <gridDim, blockDim >> > (da,db,dc,n);
    cudaDeviceSynchronize();
    outputTime("finish");
    copyFDTH();
    freeDevice();
    std::cout << check() << std::endl;
    output();
    return 0;
}


源文件2

//#define DEBUG

#include<cstdio>
#include<cstdlib>
#include<ctime>
#include<iostream>
#include "cuda_runtime.h"
#include "device_launch_parameters.h"


extern int n,*a,*b,*c;
extern int* da, * db, * dc;
extern const int P=998244353;

void createData() {
    srand(time(0));
#ifdef DEBUG
    n = 4;
#else
    n = 500000;
#endif
    a = new int[n];
    b = new int[n];
    c = new int[n * 2ll - 1];
    for (int i = 0; i < n; i++) {
        a[i] = rand() % 10;
        b[i] = rand() % 10;
    }
#ifdef DEBUG
    for (int i = 0; i < n; i++)
        printf("%d ", a[i]);
    printf("n");
    for (int i = 0; i < n; i++)
        printf("%d ", b[i]);
    printf("n");
#endif
}
void createDim(dim3& gridDim, dim3& blockDim) {
    blockDim.x = 32;
    int l = n * 2 - 1;
    gridDim.x = l / 32 + bool(l % 32);
}
void allocDevice() {
    cudaMalloc(&da, sizeof(int) * n);
    cudaMalloc(&db, sizeof(int) * n);
    cudaMalloc(&dc, sizeof(int) * (n * 2ll - 1));
}
void copyFHTD() {
    cudaMemcpy(da, a, sizeof(int) * n, cudaMemcpyHostToDevice);
    cudaMemcpy(db, b, sizeof(int) * n, cudaMemcpyHostToDevice);
    cudaMemcpy(dc, c, sizeof(int) * (n * 2ll - 1), cudaMemcpyHostToDevice);
}
__global__ void kernel(int *da,int *db,int *dc,int n) {
    int id = blockDim.x * blockIdx.x + threadIdx.x;
#ifdef DEBUG
    if (id == 0) {
        printf("n:%dn", n);
        for (int i = 0; i < n; i++)
            printf("%d ", da[i]);
        printf("n");
        for (int i = 0; i < n; i++)
            printf("%d ", db[i]);
        printf("n");
    }
#endif
    int sum = 0;
    /*第一个判断对GPU运行效率无影响。
    因为对于每一个线程来说,要么进去,要么不进去,而且一定会有线程进去。*/
    if (id < n * 2 - 1) {
        /*第二个判断有很大几率会导致双倍用时。
        对于一个warp,如果有的线程进入了第一个判断而其他线程进入了第二个判断,
        则会导致必须先等待第一个判断中的线程执行完毕,再执行第二个判断中的线程。
        在划分block时可以规避这个问题,但编码较为复杂我选择双倍。
        */
        if (id < n) {
            for (int i = 0; i <= id; i++) {
                sum += da[i] * db[id - i];
                sum %= P;
            }
        } else {
            for (int i = id - n + 1; i <= id; i++) {
                sum += da[i] * db[id - i];
                sum %= P;
            }
        }
        dc[id] = sum;
#ifdef DEBUG
        printf("%d:%dn", id, sum);
#endif
    }
}
void copyFDTH() {
    cudaMemcpy(c, dc, sizeof(int) * (n * 2ll - 1), cudaMemcpyDeviceToHost);
}
void freeDevice() {
    cudaFree(da);
    cudaFree(db);
    cudaFree(dc);
}
void output() {
#ifdef DEBUG
    for (int i = 0; i < n * 2ll - 1; i++)
        printf("%d ", c[i]);
    printf("n");
#endif
    delete a;
    delete b;
    delete c;
    a = b = c = 0;
}
int check() {
    int k,sum;
    for (int _ = 0; _ < 100; _++) {
        k = rand() % (n * 2 - 1);
        sum = 0;
        for (int i = 0; i <= k; i++) {
            if (k - i < n && i < n) {
                sum = (sum + a[i] * b[k - i]) % P;
            }
        }
        if (sum != c[k]) {
            std::cout << "need:" << sum << ";get:" << c[k] << std::endl;
            return k;
        }

    }
    return -1;
}

可以看出,再kernel函数中,我选择了最最最最暴力的方法进行计算。而且没有加入shared优化在n=5e5,即结果数组1e6的数据下跑出了1.3s的成绩,足以和NTT媲美。

RTX 2060牛逼!

1671e1f0a13031d00eec8176f434fd6d.png
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值