ir3: Prioritize max waves over doubling threadsize for non-fp16 shaders.

Improves average FPS across a set of 63 android and GL-with-zink traces by
1.9% (+/- 0.1%).  If we assume that SpaceEngine (most-improved traces by a
significant margin) is just an outlier, it still shows a .4% improvement.

Closes: #12747
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/35759>
This commit is contained in:
Emma Anholt
2025-06-23 11:58:26 -07:00
committed by Marge Bot
parent 963e9ae770
commit 5c0c3a20dd
+108 -2
View File
@@ -107,6 +107,96 @@ collect_reg_info(struct ir3_shader_variant *v,
}
}
/* Returns whether the shader uses a significant amount of 16-bit ALU ops, for
* the purposes of double_threadsize heuristics.
*/
static bool
uses_significant_16bit_alu(struct ir3_shader_variant *v)
{
uint32_t full = 0, half = 0;
foreach_block (block, &v->ir->block_list) {
foreach_instr (instr, &block->instr_list) {
/* Consider only ALU and EFU opcodes, not tex/buffer or most importantly meta ,*/
if (opc_cat(instr->opc) <= 4) {
bool is_half = false;
/* The size of the operation is determined by the src sizes (which
* must match) and there's an implicit conversion after the
* operation to the dest size. But cwabbott thinks a 32-bit
* upconversion does limit the throughput to being single-wide,
* since the HW can't write a 32-bit register in a single cycle in
* double-wide mode.
*/
foreach_dst(dst, instr) {
if (dst->flags & IR3_REG_HALF) {
foreach_src(src, instr) {
if (src->flags & IR3_REG_HALF) {
is_half = true;
break;
}
}
}
}
if (is_half)
half++;
else
full++;
}
}
}
/* We don't just check for nonzero 16-bit, because comparisons produce HALF
* results, so mostly-fp32 shaders will have a nonzero amount. So check if
* we have 1/5 half ALUs as a compromise. The number didn't matter much,
* because shaders in testing tended to be mostly fp16 for GLES2 and mostly
* fp32 otherwise.
*/
return full < (half * 4);
}
/**
* Use compiled shader parameters to determine if the shader should be run with
* double_threadsize, where the instructions and footprint in the register file
* expand to operate on (e.g. for a6xx+) 128 instead of 64 instances of the
* shader at a time. Sometimes doubling threadsize is impossible (when we run
* up against HW limits), and sometimes it is required to hit API requirements
* (large CS workgroup sizes), but most of the time we can choose.
*
* Doubling threadsize doesn't change the compiled shader instructions, but it
* has complicated effects on performance:
*
* - Non-16bit ALU instructions go from 1 cycle per ALU op to 2, while 16-bit
* instructions run at double rate and still take 1 cycle.
*
* - Increasing threadsize increases the latency of requests to the EFU or
* memory accesses.
*
* - Increasing threadsize can reduce the number of waves executing in parallel
* when we exceed the register file size, reducing the ability to hide latency
* by switching waves.
*
* - Increasing threadsize can increase the cost of dynamic branching because it
* increases the likelihood that the wave executes both sides of a branch.
* (this effect is expected to be tiny, see
* https://gfxstrand.net/faith/blog/2020/10/does-subgroup-wave-size-matter/)
*
* - Increasing threadsize reduces icache missees for large shaders by executing
* half the waves.
*
* - Increasing threadsize reduces the overhead of scalar operations, uniform
* branching, and uniform memory accesses that may exist in a shader by
* running those instructions half as often.
*
* - Increasing threadsize increases the overhead of small waves when the second
* half of the wave is unused -- we can detect this in compute shaders
* sometimes (small workgroup size set), but it is workload-dependent in pixel
* shaders.
*
* Thus, the performance impact of doubling threadsize is quite complicated, and
* we use heuristics when the choice is available to us.
*/
bool
ir3_should_double_threadsize(struct ir3_shader_variant *v, unsigned regs_count)
{
@@ -157,8 +247,24 @@ ir3_should_double_threadsize(struct ir3_shader_variant *v, unsigned regs_count)
}
FALLTHROUGH;
case MESA_SHADER_FRAGMENT: {
/* Check that doubling the threadsize wouldn't exceed the regfile size */
return regs_count * 2 <= compiler->reg_size_vec4;
/* One of the limits on maximum waves of the shader running in parallel is
* the register count used in the shader compared to the hardware's
* register file size. The absolute limit is if doubling the threadsize
* would exceed regfile size (regs*2 <= reg_size_vec4, producing just
* wave_granularity max_waves). However, testing on X1-85 found that the
* sweet spot for non-fp16 apps was when max_waves would still be >= 8
* (4*wave_granularity) -- presumably reduced waves meant less ability to
* hide latency through switching to another wave (and the increased
* shader complexity that comes with low max_waves probably also
* correlated with dynamic branching). For fp16 apps, the increased ALU
* rate made it worth it regardless.
*/
if (uses_significant_16bit_alu(v)) {
/* Check that doubling the threadsize wouldn't exceed the regfile size */
return regs_count * 2 <= compiler->reg_size_vec4;
} else {
return regs_count * 2 <= compiler->reg_size_vec4 / 4;
}
}
default: