我对唯一的答案不满意,所以我会详细说明。
单独添加“allInvocationsARB”不会提高性能(更新:可以,请参阅答案底部)。
正如 OP 所说,如果波前中没有线程为真,GPU 将已经执行跳过。
那么 allInvocationsARB 如何帮助提高性能?
首先你需要改变你的算法。我将使用一个例子。
假设您有 64 个项目要处理。和一个 64x1x1 线程的线程组(又名波前又名扭曲)。
原始计算着色器如下所示:
void main()
{
for( int i=0; i<64; ++i )
{
doExpensiveOperation( data[i], outResult[gl_GlobalInvocationID.x * 64u + i] );
}
}
也就是说,我们调用了 64 个线程,每个线程迭代 64 次;从而产生 4096 个结果的输出。
但是有一种快速的方法可以检查我们是否应该跳过这种昂贵的操作。所以我们改为优化它:
void main()
{
for( int i=0; i<64; ++i )
{
if( needsToBeProccessed( data[i] ) )
doExpensiveOperation( data[i], outResult[gl_GlobalInvocationID.x * 64u + i] );
}
}
但问题是:假设需要对所有 64 个工作项返回 false。
整个波前将执行 64 次迭代,并跳过 64 次昂贵的操作。
有更好的方法来解决这个问题。它是通过预先强制每个线程处理单个项目:
bool cannotSkip = needsToBeProccessed( data[gl_LocalInvocationIndex], gl_LocalInvocationIndex );
这里,我们使用 gl_LocalInvocationIndex 代替 i。
这样,每个线程读取 1 个工作项。
现在,当我们使用此更改加上 anyInvocationARB 时,我们最终得到:
void main()
{
bool cannotSkip = needsToBeProccessed( data[gl_LocalInvocationIndex], gl_LocalInvocationIndex );
if( anyInvocationARB( cannotSkip ) )
{
for( int i=0; i<64; ++i )
{
if( needsToBeProccessed( data[i] ) )
doExpensiveOperation( data[i], outResult[gl_GlobalInvocationID.x * 64u + i] );
}
}
}
因为 needsToBeProccessed 对所有线程都返回 false,所以 anyInvocationARB 将返回 false。
最终,着色器只调用了一次 needsToBeProccessed() 而不是 64 次。
这就是我们加快处理时间的方式。
这仅在我们或多或少确定大多数情况下 anyInvocationARB 将返回 false 时才有效。
如果它总是返回 true,那么我们最终会得到一个稍微慢一点的计算着色器,因为现在需要调用 65 次(而不是 64 次),而 doExpensiveOperation 将被调用 64 次。
更新:我意识到我一开始犯了一个错误:简单地添加“allInvocationsARB”可以提高性能。
这是因为没有它,您将执行动态分支。而当使用 allInvocationsARB 时,将使用静态分支。有什么区别?
考虑以下示例:
void main()
{
outResult[gl_LocalInvocationIndex] = 0;
if( gl_LocalInvocationIndex == 0 )
outResult[gl_LocalInvocationIndex] = 5;
}
这是一个动态分支。
GPU 必须在调度结束时保证 outResult[0] == 5 并且对于所有其他元素 outResult[i] == 0。
也就是说,GPU 必须跟踪(也称为执行掩码)哪些线程在分支中是活动的,哪些不是。波前中的非活动线程将执行指令,但它们的结果将被屏蔽掉,就好像它从未发生过一样。
现在让我们看看如果我们添加 anyInvocationARB 会发生什么:
void main()
{
outResult[gl_LocalInvocationIndex] = 0;
if( anyInvocationARB( gl_LocalInvocationIndex == 0 ) )
outResult[gl_LocalInvocationIndex] = 5;
}
现在这很有趣,因为结果将是特定于 GPU 的:
假设线程组大小为 64x1x1。
- AMD GCN 使用 64 个线程的波前。
- NVIDIA 目前使用 32 个线程的波前(NV 术语中的“扭曲”)。
现在:
- 如果您在 AMD 上运行此代码,outResult[i] == 5。
- 如果您在 NVIDIA 上运行此代码,则第一个范围为 [0; 32) 将产生 outResult[i] == 5;但第二个范围 [32; 64) 将产生 outResult[i] == 0。
但更重要的是,这是一个静态分支,因此 GPU 没有动态分支的开销,因为动态分支需要跟踪非活动线程来屏蔽结果。因此,只需添加 anyInvocationARB() 可以提高性能,但请注意,如果您不小心,它也会以 GPU 特定的方式影响结果。
在某些情况下它并不重要,例如,如果您确定对所有值运行代码将始终产生相同的结果。
例如:
void main()
{
outResult[gl_LocalInvocationIndex] = 5;
isDirty[gl_LocalInvocationIndex] = false;
if( gl_LocalInvocationIndex == 0 )
{
outResult[0] = 67;
isDirty[0] = true;
}
if( anyInvocationARB( isDirty[gl_LocalInvocationIndex] ) )
outResult[gl_LocalInvocationIndex] = 5;
}
在这种情况下,我们的代码和算法的性质保证在调度后 outResult[i] == 5 无论是否存在 anyInvocationARB。因此,anyInvocationARB 可用于通过使用静态分支而不是动态分支来提高性能。
当然,虽然简单地添加 anyInvocationARB 确实可以提高性能,但巨大改进的最佳方法是按照本答案前半部分所述的方式利用它。