Rozważ te 3 trywialne, minimalne jądra. Ich użycie rejestru wynosi dużo wyżej niż się spodziewam. Czemu?cuda - minimalny przykład, wysokie użycie rejestru
A:
__global__ void Kernel_A()
{
//empty
}
odpowiadające PTX:
ptxas info : Compiling entry function '_Z8Kernel_Av' for 'sm_20'
ptxas info : Function properties for _Z8Kernel_Av
0 bytes stack frame, 0 bytes spill stores, 0 bytes spill loads
ptxas info : Used 2 registers, 32 bytes cmem[0]
B:
template<uchar effective_bank_width>
__global__ void Kernel_B()
{
//empty
}
template
__global__ void Kernel_B<1>();
odpowiadające PTX:
ptxas info : Compiling entry function '_Z8Kernel_BILh1EEvv' for 'sm_20'
ptxas info : Function properties for _Z8Kernel_BILh1EEvv
0 bytes stack frame, 0 bytes spill stores, 0 bytes spill loads
ptxas info : Used 2 registers, 32 bytes cmem[0]
C:
template<uchar my_val>
__global__ void Kernel_C
(uchar *const device_prt_in,
uchar *const device_prt_out)
{
//empty
}
odpowiadające PTX:
ptxas info : Compiling entry function '_Z35 Kernel_CILh1EEvPhS0_' for 'sm_20'
ptxas info : Function properties for _Z35 Kernel_CILh1EEvPhS0_
16 bytes stack frame, 0 bytes spill stores, 0 bytes spill loads
ptxas info : Used 10 registers, 48 bytes cmem[0]
Pytanie:
Dlaczego pusty jądra A i B używają 2 rejestrów? CUDA zawsze używa jednego niejawnego rejestru niejawnego, ale dlaczego są używane 2 dodatkowe rejestry jawne?
Kernel C jest jeszcze bardziej frustrujący. 10 rejestrów? Ale są tylko 2 wskaźniki. Daje to 2 * 2 = 4 rejestry dla wskaźników. Nawet jeśli istnieją dodatkowo 2 tajemnicze rejestry (sugerowane przez Kernel A i Kernel B), dałoby to 6 całkowite. Still znacznie mniej niż 10!
W przypadku jesteś zainteresowany, tutaj jest kod ptx
dla Kernel A. Kod ptx
dla Kernel B jest dokładnie taka sama, modulo wartości całkowite i nazw zmiennych.
.visible .entry _Z8Kernel_Av(
)
{
.loc 5 19 1
func_begin0:
.loc 5 19 0
.loc 5 19 1
func_exec_begin0:
.loc 5 22 2
ret;
tmp0:
func_end0:
}
I Kernel C ...
.weak .entry _Z35Kernel_CILh1EEvPhS0_(
.param .u64 _Z35Kernel_CILh1EEvPhS0__param_0,
.param .u64 _Z35Kernel_CILh1EEvPhS0__param_1
)
{
.local .align 8 .b8 __local_depot2[16];
.reg .b64 %SP;
.reg .b64 %SPL;
.reg .s64 %rd<3>;
.loc 5 38 1
func_begin2:
.loc 5 38 0
.loc 5 38 1
mov.u64 %SPL, __local_depot2;
cvta.local.u64 %SP, %SPL;
ld.param.u64 %rd1, [_Z35Kernel_CILh1EEvPhS0__param_0];
ld.param.u64 %rd2, [_Z35Kernel_CILh1EEvPhS0__param_1];
st.u64 [%SP+0], %rd1;
st.u64 [%SP+8], %rd2;
func_exec_begin2:
.loc 5 836 2
tmp2:
ret;
tmp3:
func_end2:
}
- Dlaczego najpierw zadeklarować zmiennej lokalnej pamięci (
.local
)? - Dlaczego te dwa wskaźniki (podane jako argumenty funkcji) są przechowywane w rejestrach? Czy nie ma dla nich specjalnej przestrzeni param?
- Być może dwa wskaźniki funkcji argumentu należą do rejestrów - to wyjaśnia dwie linie:
.reg .b64
. Ale jaka jest linia.reg .s64
? Dlaczego tam jest?
Robi się jeszcze gorzej:
D:
template<uchar my_val>
__global__ void Kernel_D
(uchar * device_prt_in,
uchar *const device_prt_out)
{
device_prt_in = device_prt_in + blockIdx.x*blockDim.x + threadIdx.x;
}
daje
ptxas info : Used 6 registers, 48 bytes cmem[0]
więc manipulowanie argument (wskaźnik) zmniejsza się od 10 do 6 rejestry?
usunąłem debugowanie „-G” i „-g” z flag kompilatora ... a mam taki sam efekt jak ty za Kernel C. – cmo
Nie mogę w to uwierzyć. Czy to naprawdę to? – cmo
Wygląda na to. Ponownie, PTX nie powie ci tego, co chcesz wiedzieć - obsługa debuggera powoduje, że asembler emituje więcej kodu instalacyjnego. Prawdopodobnie jest to źródło dodatkowych rejestrów. – talonmies