vkCreateComputePipelines 耗时太长
vkCreateComputePipelines takes too long
我在编译 Vulkan 计算着色器时遇到了一个奇怪的问题。
我有这个着色器(甚至没有那么复杂)
#version 450
#extension GL_GOOGLE_include_directive : enable
//#extension GL_EXT_debug_printf : enable
#extension GL_KHR_shader_subgroup_basic : enable
#extension GL_KHR_shader_subgroup_arithmetic : enable
#define IS_AVAILABLE_BUFFER_ANN_ENTITIES
#define IS_AVAILABLE_BUFFER_GLOBAL_MUTABLES
#define IS_AVAILABLE_BUFFER_BONES
#define IS_AVAILABLE_BUFFER_WORLD
//#define IS_AVAILABLE_BUFFER_COLLISION_GRID
#include "descriptors_compute.comp"
layout (local_size_x_id = GROUP_SIZE_CONST_ID) in;
#include "utils.comp"
shared float[ANN_MAX_SIZE] tmp1;
shared float[ANN_MAX_SIZE] tmp2;
shared uint[ANN_TOUCHED_BLOCK_COUNT] touched_block_ids;
mat3 rotation_mat_from_yaw_and_pitch(vec2 yaw_and_pitch){
const vec2 Ss = sin(yaw_and_pitch); // let S denote sin(yaw) and s denote sin(pitch)
const vec2 Cc = cos(yaw_and_pitch); // let C denote cos(yaw) and c denote cos(pitch)
const vec4 Cs_cC_Sc_sS = vec4(Cc,Ss) * vec4(Ss.y,Cc,Ss.x);
return mat3(Cs_cC_Sc_sS.y,-Ss.y,-Cs_cC_Sc_sS.z,Cs_cC_Sc_sS.x,Cc.y,-Cs_cC_Sc_sS.w,Ss.x,0,Cc.x);
}
void main() {
const uint entity_id = gl_WorkGroupID.x;
const uint lID = gl_LocalInvocationID.x;
const uint entities_count = global_mutables.ann_entities;
if (entity_id < entities_count){
const AnnEntity entity = ann_entities[entity_id];
const Bone bone = bones[entity.bone_idx];
const mat3 rotation = rotation_mat_from_yaw_and_pitch(bone.yaw_and_pitch);
const uint BLOCK_TOUCH_SENSE_OFFSET = 0;
const uint LIDAR_LENGTH_SENSE_OFFSET = BLOCK_EXTENDED_SENSORY_FEATURES_LEN*ANN_TOUCHED_BLOCK_COUNT;
for(uint i=lID;i<ANN_LIDAR_COUNT;i+=GROUP_SIZE){
const vec3 rotated_lidar_direction = rotation * entity.lidars[i].direction;
const RayCastResult ray = ray_cast(bone.new_center, rotated_lidar_direction);
tmp1[LIDAR_LENGTH_SENSE_OFFSET+i] = ray.ratio_of_traversed_length;
}
for(uint i = lID;i<ANN_OUTPUT_SIZE;i+=GROUP_SIZE){
const AnnSparseOutputNeuron neuron = entity.ann_output[i];
float sum = neuron.bias;
for(uint j=0;j<neuron.incoming.length();j++){
sum += tmp1[neuron.incoming[j].src_neuron] * neuron.incoming[j].weight;
}
tmp2[i] = max(0,sum);//ReLU activation
}
vec2 rotation_change = vec2(0,0);
for(uint i = lID;i<ANN_OUTPUT_ROTATION_MUSCLES_SIZE;i+=GROUP_SIZE){
rotation_change += tmp2[ANN_OUTPUT_ROTATION_MUSCLES_OFFSET+i] * ANN_IMPULSES_OF_ROTATION_MUSCLES[i];
}
rotation_change = subgroupAdd(rotation_change);
if(lID==0){
bones[entity.bone_idx].yaw_and_pitch += rotation_change;
}
}
}
函数 ray_cast
可能是此着色器中最复杂的部分,但我还在许多其他即时编译的着色器中重用了这个完全相同的函数。我想知道 GL_KHR_shader_subgroup_arithmetic
是否会减慢 vkCreateComputePipelines
的速度,但如果删除它也没有什么区别。 Vulkan 需要一分多钟才能完成 vkCreateComputePipelines
。我还包含了一堆实用函数,但我只使用了那里的一些常量和 ray_cast
,因此 90% 的代码未使用,应该由 glslc 删除。会不会是 Vulkan 正在悄悄地尝试执行任何其他类型的优化并导致延迟?我认为所有的优化都是由 glslc 完成的,并且在 SPIR-V 上没有做太多的后处理。我用
顺便说一下,Nvidia 及其专有驱动程序。
我真的很困惑为什么这个着色器创建起来这么慢,即使我有其他着色器十倍长和更复杂,但它们会立即加载。
有什么方法可以分析这个吗?
经过仔细检查,我注意到通常为我的着色器生成的所有 SPIR-V 文件大约需要 10-30KB。然而,这个着色器需要178KB。
在 spirv-dis
的帮助下,我查看了生成的程序集,发现绝大多数操作码是 OpConstant
。这是因为我的结构看起来像
struct AnnSparseOutputNeuron{
AnnSparseConnection[ANN_LATENT_CONNECTIONS_PER_OUTPUT_NEURON] incoming;
float bias;
};
它们包含大数组。结果
const AnnEntity entity = ann_entities[entity_id];
和
const AnnSparseOutputNeuron neuron = entity.ann_output[i];
将被编译为许多操作码,这些操作码为数组的每个元素写入这些常量值。因此,与其编写
形式的代码
const A a = buffer_of_As[i];
f(a.some_filed)
最好用
f(buffer_of_As[i].some_filed)
这似乎解决了问题。我认为 glslc 会足够聪明来找出这样的优化,但显然不是。
我在编译 Vulkan 计算着色器时遇到了一个奇怪的问题。 我有这个着色器(甚至没有那么复杂)
#version 450
#extension GL_GOOGLE_include_directive : enable
//#extension GL_EXT_debug_printf : enable
#extension GL_KHR_shader_subgroup_basic : enable
#extension GL_KHR_shader_subgroup_arithmetic : enable
#define IS_AVAILABLE_BUFFER_ANN_ENTITIES
#define IS_AVAILABLE_BUFFER_GLOBAL_MUTABLES
#define IS_AVAILABLE_BUFFER_BONES
#define IS_AVAILABLE_BUFFER_WORLD
//#define IS_AVAILABLE_BUFFER_COLLISION_GRID
#include "descriptors_compute.comp"
layout (local_size_x_id = GROUP_SIZE_CONST_ID) in;
#include "utils.comp"
shared float[ANN_MAX_SIZE] tmp1;
shared float[ANN_MAX_SIZE] tmp2;
shared uint[ANN_TOUCHED_BLOCK_COUNT] touched_block_ids;
mat3 rotation_mat_from_yaw_and_pitch(vec2 yaw_and_pitch){
const vec2 Ss = sin(yaw_and_pitch); // let S denote sin(yaw) and s denote sin(pitch)
const vec2 Cc = cos(yaw_and_pitch); // let C denote cos(yaw) and c denote cos(pitch)
const vec4 Cs_cC_Sc_sS = vec4(Cc,Ss) * vec4(Ss.y,Cc,Ss.x);
return mat3(Cs_cC_Sc_sS.y,-Ss.y,-Cs_cC_Sc_sS.z,Cs_cC_Sc_sS.x,Cc.y,-Cs_cC_Sc_sS.w,Ss.x,0,Cc.x);
}
void main() {
const uint entity_id = gl_WorkGroupID.x;
const uint lID = gl_LocalInvocationID.x;
const uint entities_count = global_mutables.ann_entities;
if (entity_id < entities_count){
const AnnEntity entity = ann_entities[entity_id];
const Bone bone = bones[entity.bone_idx];
const mat3 rotation = rotation_mat_from_yaw_and_pitch(bone.yaw_and_pitch);
const uint BLOCK_TOUCH_SENSE_OFFSET = 0;
const uint LIDAR_LENGTH_SENSE_OFFSET = BLOCK_EXTENDED_SENSORY_FEATURES_LEN*ANN_TOUCHED_BLOCK_COUNT;
for(uint i=lID;i<ANN_LIDAR_COUNT;i+=GROUP_SIZE){
const vec3 rotated_lidar_direction = rotation * entity.lidars[i].direction;
const RayCastResult ray = ray_cast(bone.new_center, rotated_lidar_direction);
tmp1[LIDAR_LENGTH_SENSE_OFFSET+i] = ray.ratio_of_traversed_length;
}
for(uint i = lID;i<ANN_OUTPUT_SIZE;i+=GROUP_SIZE){
const AnnSparseOutputNeuron neuron = entity.ann_output[i];
float sum = neuron.bias;
for(uint j=0;j<neuron.incoming.length();j++){
sum += tmp1[neuron.incoming[j].src_neuron] * neuron.incoming[j].weight;
}
tmp2[i] = max(0,sum);//ReLU activation
}
vec2 rotation_change = vec2(0,0);
for(uint i = lID;i<ANN_OUTPUT_ROTATION_MUSCLES_SIZE;i+=GROUP_SIZE){
rotation_change += tmp2[ANN_OUTPUT_ROTATION_MUSCLES_OFFSET+i] * ANN_IMPULSES_OF_ROTATION_MUSCLES[i];
}
rotation_change = subgroupAdd(rotation_change);
if(lID==0){
bones[entity.bone_idx].yaw_and_pitch += rotation_change;
}
}
}
函数 ray_cast
可能是此着色器中最复杂的部分,但我还在许多其他即时编译的着色器中重用了这个完全相同的函数。我想知道 GL_KHR_shader_subgroup_arithmetic
是否会减慢 vkCreateComputePipelines
的速度,但如果删除它也没有什么区别。 Vulkan 需要一分多钟才能完成 vkCreateComputePipelines
。我还包含了一堆实用函数,但我只使用了那里的一些常量和 ray_cast
,因此 90% 的代码未使用,应该由 glslc 删除。会不会是 Vulkan 正在悄悄地尝试执行任何其他类型的优化并导致延迟?我认为所有的优化都是由 glslc 完成的,并且在 SPIR-V 上没有做太多的后处理。我用
顺便说一下,Nvidia 及其专有驱动程序。
我真的很困惑为什么这个着色器创建起来这么慢,即使我有其他着色器十倍长和更复杂,但它们会立即加载。
有什么方法可以分析这个吗?
经过仔细检查,我注意到通常为我的着色器生成的所有 SPIR-V 文件大约需要 10-30KB。然而,这个着色器需要178KB。
在 spirv-dis
的帮助下,我查看了生成的程序集,发现绝大多数操作码是 OpConstant
。这是因为我的结构看起来像
struct AnnSparseOutputNeuron{
AnnSparseConnection[ANN_LATENT_CONNECTIONS_PER_OUTPUT_NEURON] incoming;
float bias;
};
它们包含大数组。结果
const AnnEntity entity = ann_entities[entity_id];
和
const AnnSparseOutputNeuron neuron = entity.ann_output[i];
将被编译为许多操作码,这些操作码为数组的每个元素写入这些常量值。因此,与其编写
形式的代码const A a = buffer_of_As[i];
f(a.some_filed)
最好用
f(buffer_of_As[i].some_filed)
这似乎解决了问题。我认为 glslc 会足够聪明来找出这样的优化,但显然不是。