CUDA 常量内存分配是如何工作的?
Posted
技术标签:
【中文标题】CUDA 常量内存分配是如何工作的?【英文标题】:How CUDA constant memory allocation works? 【发布时间】:2013-01-07 03:08:21 【问题描述】:我想了解一下如何分配常量内存(使用 CUDA 4.2)。我知道总可用的常量内存是 64KB。但是这个内存是什么时候在设备上实际分配的呢?此限制适用于每个内核、cuda 上下文还是整个应用程序?
假设.cu
文件中有多个内核,每个内核使用的常量内存都少于 64K。但总的恒定内存使用量超过 64K。是否可以按顺序调用这些内核?如果使用不同的流同时调用它们会发生什么?
如果有一个大型 CUDA 动态库,其中包含许多内核,每个内核都使用不同数量的常量内存?
如果有两个应用程序都需要一半以上的可用常量内存会怎样?第一个应用程序运行良好,但第二个应用程序何时会失败?在应用启动、cudaMemcpyToSymbol()
调用或内核执行时?
【问题讨论】:
恒定内存是上下文而不是特定内核的属性。在较新的硬件上,内核不会“使用”超出其参数列表的常量内存,并且始终限制为最大 4kb。 @talonmies... 不是恒定内存 64 KB 吗? @sgar91:是的。但我没有说别的。我所说的是,在 Fermi/Kepler 上,内核参数驻留在常量内存中,并且每个内核的最大限制为 4kb。 This Q/A 有关于常量内存的有趣信息。但是,它并没有说明当您尝试使用超过 64KB 时实际会发生什么。 【参考方案1】:Parallel Thread Execution ISA Version 3.1 第 5.1.3 节讨论常量库。
常量内存有大小限制,目前限制为 64KB 可用于保存静态大小的常量变量。有一个 额外的 640KB 常量内存,组织为十个独立的 64KB 地区。驱动程序可以分配和初始化常量缓冲区 这些区域并将指向缓冲区的指针作为内核函数传递 参数。由于十个区域不连续,驱动程序 必须确保分配常量缓冲区,以便每个缓冲区 完全适合 64KB 区域且不跨越区域 边界。
可以用一个简单的程序来说明常量内存的使用。
__constant__ int kd_p1;
__constant__ short kd_p2;
__constant__ char kd_p3;
__constant__ double kd_p4;
__constant__ float kd_floats[8];
__global__ void parameters(int p1, short p2, char p3, double p4, int* pp1, short* pp2, char* pp3, double* pp4)
*pp1 = p1;
*pp2 = p2;
*pp3 = p3;
*pp4 = p4;
return;
__global__ void constants(int* pp1, short* pp2, char* pp3, double* pp4)
*pp1 = kd_p1;
*pp2 = kd_p2;
*pp3 = kd_p3;
*pp4 = kd_p4;
return;
为compute_30、sm_30编译这个并执行cuobjdump -sass <executable or obj>
反汇编你应该看到
Fatbin elf code:
================
arch = sm_30
code version = [1,6]
producer = cuda
host = windows
compile_size = 32bit
identifier = c:/dev/constant_banks/kernel.cu
code for sm_30
Function : _Z10parametersiscdPiPsPcPd
/*0008*/ /*0x10005de428004001*/ MOV R1, c [0x0] [0x44]; // stack pointer
/*0010*/ /*0x40001de428004005*/ MOV R0, c [0x0] [0x150]; // pp1
/*0018*/ /*0x50009de428004005*/ MOV R2, c [0x0] [0x154]; // pp2
/*0020*/ /*0x0001dde428004005*/ MOV R7, c [0x0] [0x140]; // p1
/*0028*/ /*0x13f0dc4614000005*/ LDC.U16 R3, c [0x0] [0x144]; // p2
/*0030*/ /*0x60011de428004005*/ MOV R4, c [0x0] [0x158]; // pp3
/*0038*/ /*0x70019de428004005*/ MOV R6, c [0x0] [0x15c]; // pp4
/*0048*/ /*0x20021de428004005*/ MOV R8, c [0x0] [0x148]; // p4
/*0050*/ /*0x30025de428004005*/ MOV R9, c [0x0] [0x14c]; // p4
/*0058*/ /*0x1bf15c0614000005*/ LDC.U8 R5, c [0x0] [0x146]; // p3
/*0060*/ /*0x0001dc8590000000*/ ST [R0], R7; // *pp1 = p1
/*0068*/ /*0x0020dc4590000000*/ ST.U16 [R2], R3; // *pp2 = p2
/*0070*/ /*0x00415c0590000000*/ ST.U8 [R4], R5; // *pp3 = p3
/*0078*/ /*0x00621ca590000000*/ ST.64 [R6], R8; // *pp4 = p4
/*0088*/ /*0x00001de780000000*/ EXIT;
/*0090*/ /*0xe0001de74003ffff*/ BRA 0x90;
/*0098*/ /*0x00001de440000000*/ NOP CC.T;
/*00a0*/ /*0x00001de440000000*/ NOP CC.T;
/*00a8*/ /*0x00001de440000000*/ NOP CC.T;
/*00b0*/ /*0x00001de440000000*/ NOP CC.T;
/*00b8*/ /*0x00001de440000000*/ NOP CC.T;
...........................................
Function : _Z9constantsPiPsPcPd
/*0008*/ /*0x10005de428004001*/ MOV R1, c [0x0] [0x44]; // stack pointer
/*0010*/ /*0x00001de428004005*/ MOV R0, c [0x0] [0x140]; // p1
/*0018*/ /*0x10009de428004005*/ MOV R2, c [0x0] [0x144]; // p2
/*0020*/ /*0x0001dde428004c00*/ MOV R7, c [0x3] [0x0]; // kd_p1
/*0028*/ /*0x13f0dc4614000c00*/ LDC.U16 R3, c [0x3] [0x4]; // kd_p2
/*0030*/ /*0x20011de428004005*/ MOV R4, c [0x0] [0x148]; // p3
/*0038*/ /*0x30019de428004005*/ MOV R6, c [0x0] [0x14c]; // p4
/*0048*/ /*0x20021de428004c00*/ MOV R8, c [0x3] [0x8]; // kd_p4
/*0050*/ /*0x30025de428004c00*/ MOV R9, c [0x3] [0xc]; // kd_p4
/*0058*/ /*0x1bf15c0614000c00*/ LDC.U8 R5, c [0x3] [0x6]; // kd_p3
/*0060*/ /*0x0001dc8590000000*/ ST [R0], R7;
/*0068*/ /*0x0020dc4590000000*/ ST.U16 [R2], R3;
/*0070*/ /*0x00415c0590000000*/ ST.U8 [R4], R5;
/*0078*/ /*0x00621ca590000000*/ ST.64 [R6], R8;
/*0088*/ /*0x00001de780000000*/ EXIT;
/*0090*/ /*0xe0001de74003ffff*/ BRA 0x90;
/*0098*/ /*0x00001de440000000*/ NOP CC.T;
/*00a0*/ /*0x00001de440000000*/ NOP CC.T;
/*00a8*/ /*0x00001de440000000*/ NOP CC.T;
/*00b0*/ /*0x00001de440000000*/ NOP CC.T;
/*00b8*/ /*0x00001de440000000*/ NOP CC.T;
.....................................
我在 SASS 的右侧进行了注释。
在 sm30 上,您可以看到参数在从偏移量 0x140 开始的常量 bank 0 中传递。
用户定义的常量变量在常量库 3 中定义。
如果你执行cuobjdump --dump-elf <executable or obj>
你可以找到其他有趣的常量信息。
32bit elf: abi=6, sm=30, flags = 0x1e011e
Sections:
Index Offset Size ES Align Type Flags Link Info Name
1 34 142 0 1 STRTAB 0 0 0 .shstrtab
2 176 19b 0 1 STRTAB 0 0 0 .strtab
3 314 d0 10 4 SYMTAB 0 2 a .symtab
4 3e4 50 0 4 CUDA_INFO 0 3 b .nv.info._Z9constantsPiPsPcPd
5 434 30 0 4 CUDA_INFO 0 3 0 .nv.info
6 464 90 0 4 CUDA_INFO 0 3 a .nv.info._Z10parametersiscdPiPsPcPd
7 4f4 160 0 4 PROGBITS 2 0 a .nv.constant0._Z10parametersiscdPiPsPcPd
8 654 150 0 4 PROGBITS 2 0 b .nv.constant0._Z9constantsPiPsPcPd
9 7a8 30 0 8 PROGBITS 2 0 0 .nv.constant3
a 7d8 c0 0 4 PROGBITS 6 3 a00000b .text._Z10parametersiscdPiPsPcPd
b 898 c0 0 4 PROGBITS 6 3 a00000c .text._Z9constantsPiPsPcPd
.section .strtab
.section .shstrtab
.section .symtab
index value size info other shndx name
0 0 0 0 0 0 (null)
1 0 0 3 0 a .text._Z10parametersiscdPiPsPcPd
2 0 0 3 0 7 .nv.constant0._Z10parametersiscdPiPsPcPd
3 0 0 3 0 b .text._Z9constantsPiPsPcPd
4 0 0 3 0 8 .nv.constant0._Z9constantsPiPsPcPd
5 0 0 3 0 9 .nv.constant3
6 0 4 1 0 9 kd_p1
7 4 2 1 0 9 kd_p2
8 6 1 1 0 9 kd_p3
9 8 8 1 0 9 kd_p4
10 16 32 1 0 9 kd_floats
11 0 192 12 10 a _Z10parametersiscdPiPsPcPd
12 0 192 12 10 b _Z9constantsPiPsPcPd
每次启动都会对内核参数常量库进行版本控制,以便可以执行并发内核。编译器和用户常量是每个 CUmodule。开发人员有责任管理这些数据的一致性。例如,开发人员必须确保 cudaMemcpyToSymbol 以安全的方式更新。
【讨论】:
谢谢!我只熟悉运行时 API,所以我会做一些研究来解释你的答案。我知道每个 CUmodule 有 10 个 64k 银行和恒定的内存分配,但我仍然不清楚这些如何回答我最初的问题......以上是关于CUDA 常量内存分配是如何工作的?的主要内容,如果未能解决你的问题,请参考以下文章