Shader学习22:ComputeShader
概念理解
cpu-逻辑处理;gpu-渲染计算
当一个脚本上挂有一个m次的for循环,当场景中有n个该脚本时,就需要nm次,当这个值很大时,就容易遇到性能瓶颈。为了解决该问题,unity提供了ComputeShader这个解决方案。
cpu-一个教授,难易都能解决,但当数量大时也要花时间去算;computeShader-很多小学生,每个只负责简单计算,并行处理。在这种情况下,一群小学生 > 教授
BUT - SO
问题:cpu和gpu之间无法直接进行数据交互,所以如果要把数据给到gpu,就要通过显存上的Buffer(缓冲区),作为中转站。
所以,在cpu上new并绑定buffer,然后将需要进行循环计算的初始数据给到buffer,gpu就能从buffer中获取到数据 进行并行计算,算完后再将数据传回buffer,cpu再从buffer中拿结果后,会继续干自己主线程的事。
- ⚠️注:因为buffer只能存入值类型的数据,所以数据复杂时,需要构造struct
💡 显存介绍:
cpu的工作台是内存,gpu的工作台是显存,但他们只能读写自己的数据,所以cpu想让gpu帮忙计算东西的时候,需要把数据搬到显存上,gpu算完再搬回来显卡由 gpu芯片(负责计算) + 显存VRAM(存数据)组成,显存紧挨着芯片能让它读写非常快。
- ⚠️注:因为buffer只能存入值类型的数据,所以数据复杂时,需要构造struct
实战演习
基础实现
c#文件:
持有computeShader文件引用,准备输入数据
创建输入、输出buffer,传入数据,并将该buffer绑定到computeShader
输入输出buffer分开放,能避免同时读写的数据竞争。
创建buffer时的构造参数:(count, stride) —— (这块 Buffer 里有多少个元素,每个元素占多大)
启动computeShader,gpu执行计算
从buffer中拿到结果,释放buffer,继续自己的主线程
kernel【核心、内核】—— 核心函数解释:gpu上要执行的一个函数。用
#pragma kernel标记函数,再用FindKernel("CSMain")找到具体函数,然后用Dispatch让gpu执行computeShader文件:
创建输入、输出缓冲区
划分线程组。
线程组解释:
GPU 并不是一个线程一个线程地跑,而是把线程打包成固定大小的组一起执行。
同一个线程组内的线程可以共享一块很快的组共享内存,不同线程组间则是完全独立的[numthreads(8, 8, 1)] 先定义一个线程组里有多少个线程,再用Dispatch(groupX, groupY, groupZ)决定要启动多少个线程组,所以为了能够让所有数据都进行计算,线程组数的分配要向上取整。
线程组本身是个三维结构,选择几维的写法取决于本身自己的数据是几维的,目的是让代码更简洁。像是处理贴图这种二维数据,线程组就要构造成二维的分组(eg. 8,8,1),像是处理流体模拟这种三维的数据,就要构造成三维的分组
三维相乘 = 一个组内的线程数
❓ 分线程组的时候,一维是 64 1 1,二维 8 8 1,三维4 4 4,为什么乘起来都是64?
💡 和硬件有关。
小一点呢?可以看到这些gpu的厂商,每次执行的最小单位是32、64,如果给一个线程组里分48个线程,就一定会浪费25%的算力。
大一点呢?线程组越大,每个线程能分到的寄存器和共享内存就越少(它们是共享的资源池)。如果你的 shader 逻辑复杂、用的变量多,线程组太大反而会变慢(因为寄存器不够用,会溢出到慢速内存)。
——》64大概是一个比较平衡的选择,闭眼选不会出错。
数值能差多少
——借鱼群算法看性能优势
顺便调研一下:下次遇到批量计算,多少量级使用什么方法
flock.mp4
多少量级开始,使用compute shader优于cpu?
如果算上job,多少量级开始 使用compute shader优于job?
结论:
(1)单纯的话,大概100只开始,gpu优于cpu;
(2)但是如果加上job:
16以内的量级时,直接用最普通的cpu最合算;1000或者1500以内,用Job;再大就可以考虑使用compute shader了。
