今天刷知乎看到个挺有意思的问题:「如何优雅地利用c++编程从1乘到20?」

如何优雅地利用c++编程从1乘到20?

我想这有啥难的,还能写出花来不成?结果看到高赞回答,感觉自己的智商有点不够用了。

随便来看一个高赞回答是怎么写的:

这个其实还算比较简单的,没啥难度,还有更晦涩的:

这个乍一看根本看不懂在写啥,当然平时也很少会写这种晦涩的代码。

CUDA花式整活!

今天我就教大家用CUDA来计算一下20的阶乘,就当作是CUDA的一个入门例子。

先来看看我的回答:

如何优雅地利用c++编程从1乘到20?

我提供了两种CUDA的实现方法。

方法1

#include <iostream>

typedef unsigned long long int ull;
const int N = 20;__device__ ull atomicMul(ull* address, ull val) {ull *address_as_ull = (ull *)address;ull old = *address_as_ull, assumed;do {assumed = old;old = atomicCAS(address_as_ull, assumed, val * assumed);} while (assumed != old);return old;
}__global__ void mul(ull *x) {ull i = threadIdx.x + 1;atomicMul(x, i);
}int main() {ull *x;cudaMallocManaged(&x, sizeof(ull));x[0] = 1;mul<<<1, N>>>(x);cudaDeviceSynchronize();std::cout << x[0] << std::endl;cudaFree(x);return 0;
}

这个方法使用原子操作,一共20个线程,每个线程负责一个乘数,然后一起乘到x[0]上。

但是由于并行执行,线程之间没有先后顺序,会导致同时乘的时候产生冲突,所以需要使用原子操作。在某一个线程将它的乘数乘到x[0]上时,不会被其他线程打断。也就是会加锁,同一时刻只会有一个线程在进行乘法操作。

但是由于CUDA只提供了加法和减法的原子操作(atomicAddatomicSub),所以得自己实现乘法的原子操作atomMul,利用的是atomicCAS操作,也就是compare and swap ,如果目标地址元素和待比较的元素相同,就进行元素的交换,否则不进行任何操作。

可以看出,在atomicMul函数的do while循环中,先用old变量保存x[0]处的当前值,这时候如果有其他线程在x[0]处写入了新的值,那么接下来该线程的atomicCAS操作就会判断元素不相同,不进行任何操作,重新执行下一轮循环。

方法2

#include <iostream>

typedef unsigned long long int ull;
const int N = 20;
const int WARP_SIZE = 32;__global__ void mul(ull *x) {int i = threadIdx.x;ull val = x[i];for (int mask = WARP_SIZE / 2; mask > 0; mask >>= 1)val *= __shfl_xor_sync(WARP_SIZE - 1, val, mask, WARP_SIZE);x[i] = val;
}int main() {ull *x;cudaMallocManaged(&x, WARP_SIZE * sizeof(ull));for (int i = 0; i < WARP_SIZE; ++i)x[i] = i < N ? i + 1 : 1;mul<<<1, WARP_SIZE>>>(x);cudaDeviceSynchronize();std::cout << x[0] << std::endl;cudaFree(x);return 0;
}

这种方法使用线程束原语__shfl_xor_sync,只要线程在同一个线程束中(32个线程),就可以获取其他线程的值,异或运算后写入指定地址。详细原理这里就不解释了,可以简单理解为:

  • 一共进行5轮操作。
  • 第一轮操作之后,下标为0-15的位置分别保存着下标0+1、2+3、一直到30+31的结果。
  • 第二轮操作之后,下标为0-7的位置分别保存着下标0+1+2+3、4+5+6+7、一直到28+29+30+31的结果。
  • 最后一轮之后,下标为0的位置保存着所有32个元素之和。

所以只需要在开始时,分配一个大小为32的数组,前20个元素分别保存1-20,后面12个元素是为了满足线程束大小32的条件,赋值为1就行了。

方法2改进

方法2需要额外开辟大小为32的数组,空间存在浪费,并且数组赋值也需要时间。

感谢 @NekoDaemon 老哥提供的优化建议,只需要在计算的时候根据线程号计算对应乘积元素就行,但是线程数仍然需要分配32个。

#include <iostream>

typedef unsigned long long int ull;
const int N = 20;
const int WARP_SIZE = 32;__global__ void mul(ull *x) {int i = threadIdx.x;ull val = i < N ? i + 1 : 1;for (int mask = WARP_SIZE / 2; mask > 0; mask >>= 1)val *= __shfl_xor_sync(WARP_SIZE - 1, val, mask, WARP_SIZE);if (!i) x[i] = val;
}int main() {ull *x;cudaMallocManaged(&x, sizeof(ull));mul<<<1, WARP_SIZE>>>(x);cudaDeviceSynchronize();std::cout << x[0] << std::endl;cudaFree(x);return 0;
}

执行结果

代码保存为run.cu,然后执行nvcc run.cu -o run,最后执行./run,就会出来结果2432902008176640000。

今天没有讲解CUDA编程的基础概念,适合有一定基础的同学阅读,如果有对CUDA感兴趣的同学,可以在评论区留言,下次专门写一个CUDA入门系列教程。关注我的公众号【算法码上来】,每天跟进最新文章。

如何花式计算20的阶乘?相关推荐

  1. python利用函数求20的阶乘_请运用reduce函数,计算20的阶乘,慕课网2-19

    # 从functools 中导入reduce函数 from functools import reduce def use_reduce(data): # 使用result接收reduce实现20的阶 ...

  2. C语言 1A gt $20,C语言输出 1到20 的阶乘之和

    除了调用库,绝对找不到比这更精简的代码了. #include #include long long getdata(long long n){ long long sum = 1; while(n){ ...

  3. 第十周 计算1到20的阶乘的和

    1./* 2.*程序的版权和版本声明部分: 3.*Copyright(c)2013,烟台大学计算机学院学生 4.*All rights reserved. 5.*文件名称: 6.*作者:尚振伟 7.* ...

  4. 综合训练3 计算1~20的阶乘的倒数之和

    综合训练3 计算1~20的阶乘的倒数之和 编写Java程序,使用while循环语句计算1+1/2!+1/3!+-+1/20!之和. /*综合训练3 计算1~20的阶乘的倒数之和* 编写Java程序,使 ...

  5. 数据结构实验--大数运算之计算n的阶乘 (n≥20)

    一.问题描述 大数运算--计算n的阶乘 (n≥20). [基本要求] (1)数据的表示和存储: ①累积运算的中间结果和最终的计算结果的数据类型要求是整型--这是问题本身的要求. ②试设计合适的存储结构 ...

  6. 用C语言计算1到20的阶乘之和,用C语言计算1~20的阶乘之和

    昨天(2018/12/7)在做C语言的课后练习题的时候,有一道题要求我们计算1~20的阶乘之和.代码很快就写出来了,考虑到结果的值会比较大,而在Windows操作系统下,int 类型和 long 类型 ...

  7. C—计算10的阶乘以及1到10的阶乘之和

    计算10的阶乘 //计算10的阶乘 int main() {long long ll = 1;int i = 10;for (i = 10; i >= 1; i--) {ll = ll * i; ...

  8. 突破数据极限:计算24的阶乘和n的m次方。

    这里写自定义目录标题 欢迎使用Markdown编辑器 新的改变 功能快捷键 合理的创建标题,有助于目录的生成 如何改变文本的样式 插入链接与图片 如何插入一段漂亮的代码片 生成一个适合你的列表 创建一 ...

  9. raptor阶乘相加流程图_从键盘输入正整数n的值,计算n的阶乘,例如:输入5,输出120。请据此画出程序流程图。 (上传Raptor软件流程图和运行结果的截图)_学小易找答案...

    [单选题]对现值指数法来说,当原始投资额现值相等的时候,实质上是( ). [单选题]某公司打算投资一个项目,预计该项目需固定资产投资400万元,预计可使用5年.项目预计流动资产需用额为160万元,流动 ...

  10. c语言 n阶阶乘尾0个数,计算n的阶乘(n!)末尾0的个数

    题目: 给定一个正整数n,请计算n的阶乘n!末尾所含有"0"的个数. 举例: 5!=120,其末尾所含有的"0"的个数为1: 10!= 3628800,其末尾所 ...

最新文章

  1. 交互两个数(不引入第三个变量)
  2. docker错误:Error response from daemon: Cannot start container
  3. hdu 4472 Count(递推即dp)
  4. WCF rest 的帮助页面和缓存机制
  5. 学习《css世界》笔记之content自动添加开启闭合符号
  6. 【数据结构与算法-java实现】二 复杂度分析(下):最好、最坏、平均、均摊时间复杂度的概念
  7. 小白设计模式:责任链模式
  8. 《游戏改变企业》一一第 2 章 实时的经济信息反馈(为什么游戏很重要) 游戏改变企业...
  9. Java 执行SQL脚本文件
  10. VISIO各种图标超全(IT行业专用网络及硬件)_5G行业应用规划设计思路探讨
  11. 2019长江课堂作业答案_2019版长江课堂作业答案语文四年级
  12. python对txt文件进行处理实战_python数据处理实战
  13. 如何用excel实现并列排序
  14. 日常活动--英文短句
  15. 面试时,可以问面试官问题总结
  16. 医疗器械小程序或手机APP软件开发方案
  17. 【运筹学】对偶理论总结 ( 对称性质 | 弱对偶定理 | 最优性定理 | 强对偶性 | 互补松弛定理 ) ★★★
  18. VPC是什么,VPC详解
  19. 阿里巴巴收购网易考拉,定了!
  20. RoseHA9.0 for WindowsServer2008R2 配合sqlserver安装配置

热门文章

  1. http请求,普通的get和post方法
  2. Java事务之八——分布式事务(Spring+JTA+Atomikos+Hibernate+JMS)
  3. Build Settings发布设置
  4. 2017-12-3 Crontab(字符串处理)
  5. linux之C编程学习——getchar()和getch()
  6. ORACLE成果,天天10问(四)
  7. (宏)Word 仅修改选中图片的尺寸
  8. mysql 开启binlog
  9. MySQL Innodb Engine -- 文件格式(innodb_file_format)
  10. Whl自助搜索下载器