写入 GPU 计算缓冲区的超级巨大瓶颈?
Super huge bottleneck writing to GPU Compute Buffer?
我正在 Unity 中制作一个计算着色器,但问题是我的代码有一个巨大的瓶颈,我基本上失去了 1000 倍的性能。
我已经创建了一些示例代码来演示该问题,代码的功能不相关并且没有太大意义。
在启用该行的情况下,我失去了写入计算缓冲区 cBuffer[id].vel += vel;
( 在着色器代码 中的大量性能,我得到了大约 40fps 与 pCount = (1024 * 256);
~256k (in c# code) 但如果我在着色器中禁用写入缓冲区行,我可以在 > 60fps 下执行 pCount = (1024 * 1024 * 64);
~64m,没问题。我想这是因为不同的线程试图写入同一内存并且必须等待其他线程完成,但是有没有什么办法可以更聪明地做到这一点?
Download Unity and Visual Studio Project files(unity 2017.3.0f3)
C# 代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class computeScript : MonoBehaviour
{
public ComputeShader cShader;
struct Particle
{
public Vector2 pos;
public Vector2 vel;
}
ComputeBuffer cBuffer;
const int pCount = (1024 * 256); // <--- set count
Particle[] particles = new Particle[pCount];
int kernelCSMain;
void Start ()
{
kernelCSMain = cShader.FindKernel("CSMain");
cShader.SetInt("pCount", pCount);
cBuffer = new ComputeBuffer(pCount, (sizeof(float) * 4), ComputeBufferType.Default);
for(int i = 0; i < pCount; i++)
{
particles[i] = new Particle();
particles[i].pos = new Vector2();
particles[i].vel = new Vector2();
}
cBuffer.SetData(particles);
}
void Update ()
{
cShader.SetBuffer(kernelCSMain, "cBuffer", cBuffer);
cShader.Dispatch(kernelCSMain, pCount / 1024, 1, 1);
}
void OnDestroy()
{
cBuffer.Release();
}
}
计算着色器代码:
#pragma kernel CSMain
struct Particle
{
float2 pos;
float2 vel;
};
RWStructuredBuffer<Particle> cBuffer;
int pCount;
[numthreads(1024,1,1)]
void CSMain (uint id : SV_DispatchThreadID)
{
float2 vel;
for (int i = 0; i < pCount; i++)
{
vel += (cBuffer[id].pos + cBuffer[i].pos);
}
cBuffer[id].vel += vel; // <---- this line is the issue
}
问题不出在写上,问题出在死码剔除上
如果我拿走你的代码,不写:
[numthreads(1024,1,1)]
void CSMain (uint id : SV_DispatchThreadID)
{
float2 vel;
for (int i = 0; i < pCount; i++)
{
vel += (cBuffer[id].pos + cBuffer[i].pos);
}
}
编译器将检测到 vel 未在任何地方使用(根据,未写入),因此将删除分配它的代码。
然后自该行:
vel += (cBuffer[id].pos + cBuffer[i].pos);
被移除(因为没有使用 vel),编译器检测到循环内容现在是空的,所以也摆脱了循环。
所以在你的例子中,注释行以一个什么都不做的空着色器结束。
为了演示它,这是使用 fxc 编译着色器的结果:
fxc cs.fx /O3 /Tcs_5_0 /ECSMain
首先启用写入:
cs_5_0
dcl_globalFlags refactoringAllowed
dcl_constantbuffer CB0[1], immediateIndexed
dcl_uav_structured u0, 16
dcl_input vThreadID.x
dcl_temps 2
dcl_thread_group 1024, 1, 1
ld_structured_indexable(structured_buffer, stride=16)
(mixed,mixed,mixed,mixed) r0.xy, vThreadID.x, l(0), u0.xyxx
mov r0.zw, l(0,0,0,0)
mov r1.x, l(0)
loop
ige r1.y, r1.x, cb0[0].x
breakc_nz r1.y
ld_structured_indexable(structured_buffer, stride=16)
(mixed,mixed,mixed,mixed) r1.yz, r1.x, l(0), u0.xxyx
add r1.yz, r0.xxyx, r1.yyzy
add r0.zw, r0.zzzw, r1.yyyz
iadd r1.x, r1.x, l(1)
endloop
ld_structured_indexable(structured_buffer, stride=16)
(mixed,mixed,mixed,mixed) r0.xy, vThreadID.x, l(8), u0.xyxx
add r0.xy, r0.zwzz, r0.xyxx
store_structured u0.xy, vThreadID.x, l(8), r0.xyxx
ret
// Approximately 15 instruction slots used
现在如果你评论你的写和 运行 相同的编译任务:
cs_5_0
dcl_globalFlags refactoringAllowed
dcl_thread_group 1024, 1, 1
ret
// Approximately 1 instruction slots used
此外,请注意,在您的情况下,您正在 运行 在计算中使用 n^2 算法,您的每个粒子都会相互检查(每 262144 个粒子),您正在执行68719476736 "iterations"(这解释了启用写入后性能的急剧下降)
我正在 Unity 中制作一个计算着色器,但问题是我的代码有一个巨大的瓶颈,我基本上失去了 1000 倍的性能。
我已经创建了一些示例代码来演示该问题,代码的功能不相关并且没有太大意义。
在启用该行的情况下,我失去了写入计算缓冲区 cBuffer[id].vel += vel;
( 在着色器代码 中的大量性能,我得到了大约 40fps 与 pCount = (1024 * 256);
~256k (in c# code) 但如果我在着色器中禁用写入缓冲区行,我可以在 > 60fps 下执行 pCount = (1024 * 1024 * 64);
~64m,没问题。我想这是因为不同的线程试图写入同一内存并且必须等待其他线程完成,但是有没有什么办法可以更聪明地做到这一点?
Download Unity and Visual Studio Project files(unity 2017.3.0f3)
C# 代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class computeScript : MonoBehaviour
{
public ComputeShader cShader;
struct Particle
{
public Vector2 pos;
public Vector2 vel;
}
ComputeBuffer cBuffer;
const int pCount = (1024 * 256); // <--- set count
Particle[] particles = new Particle[pCount];
int kernelCSMain;
void Start ()
{
kernelCSMain = cShader.FindKernel("CSMain");
cShader.SetInt("pCount", pCount);
cBuffer = new ComputeBuffer(pCount, (sizeof(float) * 4), ComputeBufferType.Default);
for(int i = 0; i < pCount; i++)
{
particles[i] = new Particle();
particles[i].pos = new Vector2();
particles[i].vel = new Vector2();
}
cBuffer.SetData(particles);
}
void Update ()
{
cShader.SetBuffer(kernelCSMain, "cBuffer", cBuffer);
cShader.Dispatch(kernelCSMain, pCount / 1024, 1, 1);
}
void OnDestroy()
{
cBuffer.Release();
}
}
计算着色器代码:
#pragma kernel CSMain
struct Particle
{
float2 pos;
float2 vel;
};
RWStructuredBuffer<Particle> cBuffer;
int pCount;
[numthreads(1024,1,1)]
void CSMain (uint id : SV_DispatchThreadID)
{
float2 vel;
for (int i = 0; i < pCount; i++)
{
vel += (cBuffer[id].pos + cBuffer[i].pos);
}
cBuffer[id].vel += vel; // <---- this line is the issue
}
问题不出在写上,问题出在死码剔除上
如果我拿走你的代码,不写:
[numthreads(1024,1,1)]
void CSMain (uint id : SV_DispatchThreadID)
{
float2 vel;
for (int i = 0; i < pCount; i++)
{
vel += (cBuffer[id].pos + cBuffer[i].pos);
}
}
编译器将检测到 vel 未在任何地方使用(根据,未写入),因此将删除分配它的代码。 然后自该行:
vel += (cBuffer[id].pos + cBuffer[i].pos);
被移除(因为没有使用 vel),编译器检测到循环内容现在是空的,所以也摆脱了循环。
所以在你的例子中,注释行以一个什么都不做的空着色器结束。
为了演示它,这是使用 fxc 编译着色器的结果:
fxc cs.fx /O3 /Tcs_5_0 /ECSMain
首先启用写入:
cs_5_0
dcl_globalFlags refactoringAllowed
dcl_constantbuffer CB0[1], immediateIndexed
dcl_uav_structured u0, 16
dcl_input vThreadID.x
dcl_temps 2
dcl_thread_group 1024, 1, 1
ld_structured_indexable(structured_buffer, stride=16)
(mixed,mixed,mixed,mixed) r0.xy, vThreadID.x, l(0), u0.xyxx
mov r0.zw, l(0,0,0,0)
mov r1.x, l(0)
loop
ige r1.y, r1.x, cb0[0].x
breakc_nz r1.y
ld_structured_indexable(structured_buffer, stride=16)
(mixed,mixed,mixed,mixed) r1.yz, r1.x, l(0), u0.xxyx
add r1.yz, r0.xxyx, r1.yyzy
add r0.zw, r0.zzzw, r1.yyyz
iadd r1.x, r1.x, l(1)
endloop
ld_structured_indexable(structured_buffer, stride=16)
(mixed,mixed,mixed,mixed) r0.xy, vThreadID.x, l(8), u0.xyxx
add r0.xy, r0.zwzz, r0.xyxx
store_structured u0.xy, vThreadID.x, l(8), r0.xyxx
ret
// Approximately 15 instruction slots used
现在如果你评论你的写和 运行 相同的编译任务:
cs_5_0
dcl_globalFlags refactoringAllowed
dcl_thread_group 1024, 1, 1
ret
// Approximately 1 instruction slots used
此外,请注意,在您的情况下,您正在 运行 在计算中使用 n^2 算法,您的每个粒子都会相互检查(每 262144 个粒子),您正在执行68719476736 "iterations"(这解释了启用写入后性能的急剧下降)