CUDA C权威编程指南 第4章 全局内存

CUDA内存管理一文理清 CUDA内存管理 阅读详情
一、CUDA内存模型概述
1. CUDA内存模型

对于程序员来说,一般有两种类型的存储器:

· 可编程的:你需要显式地控制哪些数据存放在可编程内存中

· 不可编程的:你不能决定数据的存放位置,程序将自动生成存放位置以获得良好的性能

在CPU内存层次结构中,一级缓存和二级缓存都是不可编程的存储器。

CUDA内存模型提出了多种可编程内存的类型:寄存器、共享内存、本地内存、常量内存、纹理内存、全局内存。

一个核函数中的线程都有自己私有的本地内存。一个线程块有自己的共享内存,对同一线程块中所有线程都可见,其内容持续线程块的整个生命周期。所有线程都可以访问全局内存。所有线程都能访问的只读内存空间有:常量内存空间和纹理内存空间。全局内存、常量内存和纹理内存空间有不同的用途。纹理内存为各种数据布局提供了不同的寻址模式和滤波模式。

寄存器

寄存器是GPU上运行速度最快的内存空间。核函数中声明的一个没有其他修饰符的自变量,通常存储在寄存器中。在核函数声明的数组中,如果用于引用该数组的索引是常量且能在编译时确定,那么该数组也存储在寄存器中。

寄存器变量对于每个线程来说都是私有的,一个核函数通常使用寄存器来保存需要频繁访问的线程私有变量。寄存器变量与核函数的生命周期相同。一旦核函数执行完毕,就不能对寄存器变量进行访问了。

如果一个核函数使用了超过硬件限制数量的寄存器,则会用本地内存替代多占用的寄存器。这种寄存器溢出会给性能带来不利影响。nvcc编译器使用启发式策略来最小化寄存器的使用,以避免寄存器溢出。

本地内存

共享内存

在核函数中使用如下修饰符修饰的变量存放在共享内存中:__shared__

共享内存在核函数的范围内声明,其生命周期伴随着整个线程块。当一个线程块执行结束后,其分配的共享内存将被释放并重新分配给其他线程块。

共享内存是线程之间相互通信的基本方式。一个块内的线程通过使用共享内存中的数据可以相互合作。访问共享内存必须同步使用如下调用:

void __syncthreads();

该函数设立了一个执行障碍点,即同一个线程块中的所有线程必须在其他线程被允许执行前达到该处。为线程块中所有线程设立障碍点,这样可以避免潜在的数据冲突。

常量内存

常量内存驻留在设备内存中,并在每个SM专用的常量缓存中缓存。常量变量用如下修饰符来修饰:__constant__

常量变量必须在全局空间内和所有核函数之外进行声明。对于所有计算能力的设备,都只可以声明64KB的常量内存。常量内存是静态声明的,并对同一编译单元中的所有核函数可见。

核函数只能从常量内存中读取数据。因此,常量内存必须在主机端使用下面的函数来初始化:

cudaError_t cudaMemcpyToSymbol(const void* symbol, const void* src, size_t count);

这个函数将count个字节从src指向的内存复制到symbol指向的内存中,这个变量存放在设备的全局内存或常量内存中。在大多数情况下这个函数是同步的。

每从一个常量内存中读取一次数据,都会广播给线程束里的所有线程。

纹理内存

纹理内存驻留在设备内存中,并在每个SM的只读缓存中缓存。纹理内存是一种通过指定的只读缓存访问的全局内存。只读缓存包括硬件滤波的支持,它可以将浮点插入作为读过程的一部分来执行。纹理内存是对二维空间局部性的优化,所以线程束里使用纹理内存访问二维数据的线程可以达到最优性能。

全局内存

全局内存是GPU中最大、延迟最高并且最常使用的内存。global指的是其作用域和生命周期。它的声明可以在任何SM设备上被访问到,并且贯穿应用程序的整个生命周期。

一个全局内存变量可以被静态声明或动态声明。可以使用__device__修饰符在设备代码中静态地声明一个变量。

在主机端使用cudaMalloc函数分配全局内存,使用cudaFree函数释放全局内存。然后指向全局内存的指针就会作为参数传递给核函数。全局内存分配空间存在于应用程序的整个生命周期中,并且可以访问所有核函数中的所有线程。

全局内存常驻于设备内存中,可通过32字节、64字节或128字节的内存事务进行访问。这些内存事务必须自然对齐。

GPU缓存

GPU缓存是不可编程的内存。在GPU上有4种缓存:

文件作用域中的变量:可见性与可访问性

一般情况下,设备核函数不能访问主机变量,并且主机函数也不能访问设备变量。即使这些变量在同一文件作用域内被声明。

CUDA运行时API能够访问主机和设备变量。

二、内存管理
1. 内存分配和释放

在主机上使用下列函数分配全局内存:

cudaError_t cudaMalloc(void **devPtr, size_t count);

这个函数在设备上分配了count字节的全局内存,并用devptr指针返回该内存的地址。如果cudaMalloc函数执行失败则返回cudaErrorMemoryAllocation。在已分配的全局内存中的值不会被清除。你需要用从主机上传输的数据来填充所分配的全局内存,或用下列函数将其初始化:

cudaError_t cudaMemset(void *devPtr, int value, size_t count);

这个函数用存储在变量value中的值来填充从设备内存地址devPtr处开始的count字节。

一旦一个应用程序不再使用已分配的全局内存,那么可以用以下代码释放该内存空间:

cudaError_t cudaFree(void *devPtr);

这个函数释放了devPtr指向的全局内存,该内存在此前使用了一个设备分配函数(如cudaMalloc)来进行分配。否则,它将返回一个错误cudaErrorInvalidDevicePointer。如果地址空间已经被释放,那么cudaFree也返回一个错误。

设备内存的分配和释放操作成本较高,所有应用程序应重利用设备内存,以减少对整体性能的影响。

2. 内存传输

一旦分配好了全局内存,你就可以使用下列函数从主机向设备传输数据:

cudaError_t cudaMemcpy(void *dst, const void *src, size_t count, enum cudaMemcpyKind kind);

这个函数从内存位置src复制了count字节到内存位置dst。变量kind指定了复制的方向,可以有下列取值:

cudaMemcpyHostToHost

cudaMemcpyHostToDevice

cudaMemcpyDeviceToHost

cudaMemcpyDeviceToDevice

如果指针dst和src与kind指定的复制方向不一致,那么cudaMemcpy的行为就是未定义行为。这个函数在大多数情况下都是同步的。

3. 固定内存

分配的主机内存默认是pageable(可分页)。当从可分页主机内存传输数据到设备内存时,CUDA驱动程序首先分配临时页面锁定的或固定的主机内存,将主机源数据复制到固定内存中,然后从固定内存传输数据给设备内存。

CUDA运行时允许你使用如下指令直接分配固定主机内存:

cudaError_t cudaMallocHost(void **devPtr, size_t count);

这个函数分配了count字节的主机内存,这些内存是页面锁定的并且对设备来说是可访问的。由于固定内存能被设备直接访问,所以它能用比可分页内存高得多的带宽进行读写。

#include <cuda_runtime.h>
#include <stdio.h>

int main(int argc, char **argv)
{
    // set up device
    int dev = 0;
    CHECK(cudaSetDevice(dev));

    // memory size
    unsigned int isize = 1 << 22;
    unsigned int nbytes = isize * sizeof(float);

    // get device information
    cudaDeviceProp deviceProp;
    CHECK(cudaGetDeviceProperties(&deviceProp, dev));

    if (!deviceProp.canMapHostMemory)  // 检查设备是否支持固定内存映射
    {
        printf("Device %d does not support mapping CPU host memory!\n", dev);
        CHECK(cudaDeviceReset());
        exit(EXIT_SUCCESS);
    }

    printf("%s starting at ", argv[0]);
    printf("device %d: %s memory size %d nbyte %5.2fMB canMap %d\n", dev,
           deviceProp.name, isize, nbytes / (1024.0f * 1024.0f),
           deviceProp.canMapHostMemory);

    // allocate pinned host memory
    float *h_a;
    CHECK(cudaMallocHost ((float **)&h_a, nbytes));

    // allocate device memory
    float *d_a;
    CHECK(cudaMalloc((float **)&d_a, nbytes));

    // initialize host memory
    memset(h_a, 0, nbytes);

    for (int i = 0; i < isize; i++) h_a[i] = 100.10f;

    // transfer data from the host to the device
    CHECK(cudaMemcpy(d_a, h_a, nbytes, cudaMemcpyHostToDevice));

    // transfer data from the device to the host
    CHECK(cudaMemcpy(h_a, d_a, nbytes, cudaMemcpyDeviceToHost));

    // free memory
    CHECK(cudaFree(d_a));
    CHECK(cudaFreeHost(h_a));

    // reset device
    CHECK(cudaDeviceReset());
    return EXIT_SUCCESS;
}

与可分页内存相比,固定内存的分配和释放成本更高,但是它为大规模数据传输提供了更高的传输吞吐量。

4. 零拷贝内存

通常来说,主机不能直接访问设备变量,同时设备也不能直接访问主机变量。但有一个例外:零拷贝内存。主机和设备都可以访问零拷贝内存。

在CUDA核函数中使用零拷贝内存有以下几个优势。

零拷贝内存是固定(不可分页)内存,该内存映射到设备地址空间中。你可以通过下列函数创建一个到固定内存的映射:

cudaError_t cudaHostAlloc(void **pHost, size_t count, unsigned int flags);

这个函数分配了count字节的主机内存,该内存是页面锁定的且设备可访问的。用这个函数分配的内存必须用cudaFreeHost函数释放。flags参数可以对已分配内存的特殊属性进一步进行配置:

· cudaHostAllocDefault

` cudaHostAllocPortable

` cudaHostAllocWriteCombined

` cudaHostAllocMapped

在进行频繁的读写操作时,使用零拷贝内存作为设备内存的补充将显著降低性能。因为每一次映射到内存的传输必须经过PCIe总线。

举例:用零拷贝内存加总数组

    // part 2: using zerocopy memory for array A and B
    // allocate zerocpy memory
    CHECK(cudaHostAlloc((void **)&h_A, nBytes, cudaHostAllocMapped));
    CHECK(cudaHostAlloc((void **)&h_B, nBytes, cudaHostAllocMapped));

    // initialize data at host side
    initialData(h_A, nElem);
    initialData(h_B, nElem);
    memset(hostRef, 0, nBytes);
    memset(gpuRef,  0, nBytes);

    // pass the pointer to device
    CHECK(cudaHostGetDevicePointer((void **)&d_A, (void *)h_A, 0));
    CHECK(cudaHostGetDevicePointer((void **)&d_B, (void *)h_B, 0));

    // add at host side for result checks
    sumArraysOnHost(h_A, h_B, hostRef, nElem);

    // execute kernel with zero copy memory
    sumArraysZeroCopy<<<grid, block>>>(d_A, d_B, d_C, nElem);

    // copy kernel result back to host side
    CHECK(cudaMemcpy(gpuRef, d_C, nBytes, cudaMemcpyDeviceToHost));

    // check device results
    checkResult(hostRef, gpuRef, nElem);

    // free  memory
    CHECK(cudaFree(d_C));
    CHECK(cudaFreeHost(h_A));
    CHECK(cudaFreeHost(h_B));

    free(hostRef);
    free(gpuRef);

5. 统一虚拟寻址(UVA)

在UVA之前,你需要管理哪些指针指向主机内存和哪些指针指向设备内存。有了UVA,主机内存和设备内存可以共享同一个虚拟地址空间。

通过UVA,由cudaHostAlloc分配的固定主机内存具有相同的主机和设备指针。因此,可以将返回的指针直接传递给核函数。

有了UVA,无须获取设备指针或管理物理上数据完全相同的两个指针。UVA会进一步简化前面的sumArrayZerocpy.cu示例:

6. 统一内存寻址

使用托管内存的程序行为与使用未托管内存的程序副本行为在功能上是一致的。但是,使用托管内存的程序可以利用自动数据传输和重复指针消除功能。

三、内存访问模式

CUDA执行模型的显著特征之一就是指令必须以线程束为单位进行发布和执行。存储操作也是同样。

全局内存通过缓存来实现加载/存储。核函数的内存请求通常是在DRAM设备和片上内存间以128字节或32字节内存事务来实现的。

所有对全局内存的访问都通过二级缓存,也有许多访问会通过一级缓存,这取决于访问类型和GPUU架构。如果这两级缓存都被用到,那么内存访问是由一个128字节的内存事务实现的。如果只使用了二级缓存,那么这个内存访问是由一个32字节的内存事务实现的。

在优化应用程序时,需要注意内存访问的两个特性:

· 对齐内存访问

· 合并内存访问

当设备内存事务的第一个地址是用于事务服务的缓存粒度的偶数倍时(32字节的二级缓存或128字节的一级缓存),就会出现对齐内存访问。运行非对齐的加载会造成带宽浪费。

当一个线程束中全部的32个线程访问一个连续的内存块时,就会出现合并内存访问。

1. 全局内存读取

在SM中,数据通过以下3种缓存/缓冲路径进行传输:

· 一级和二级缓存

· 常量缓存

· 只读缓存

一/二级缓存是默认路径。想要通过其他两种路径传递数据需要应用程序显式地说明。

全局内存加载是否会通过一级缓存取决于两个因素:设备端计算能力和编译器选项。

以下标志通知编译器禁用一级缓存:-Xptxas -dlcm=cg。如果一级缓存被禁用,所有对全局内存的加载请求将直接进入到二级缓存中,如果二级缓存缺失,则由DRAM完成请求。每一次内存事务可由一个、两个或四个部分执行,每个部分有32个字节。

一级缓存也可以使用下列标识符直接启用:-Xptxas -dlcm=ca。设置这个标志后,全局内存加载请求首先尝试通过一级缓存,如果一级缓存缺失,该请求转向二级缓存。如果二级缓存缺失,则请求由DRAM完成。在这种情况下,一个内存加载请求由一个128字节的设备内存事务实现。

内存加载访问模式

缓存加载

缓存加载操作经过一级缓存,在粒度为128字节的一级缓存行上由设备内存事务进行传输。缓存加载可以分为对齐/非对齐及合并/非合并。

CPU一级缓存和GPU一级缓存之间的差异

CPU一级缓存优化了时间和空间局部性。GPU一级缓存是专为空间局部性而不是为时间局部性设计的。

没有缓存的加载

没有缓存的加载在内存段的粒度上(32字节)而非缓存池的粒度(128字节)执行。更细粒度的加载可以为非对齐或非合并的内存访问带来更好的总线利用率。

只读缓存

只读缓存的加载粒度是32个字节。通常,对分散读取来说,这些更细粒度的加载要优于一级缓存。

有两种方式可以指导内存通过只读缓存进行读取:

· 使用函数_ldg

· 在间接引用的指针上使用修饰符

2. 全局内存写入

存储操作在32个字节段的粒度上被执行。内存事务可以被分为一段、两段或四段。例如,如果两个地址同属于一个128个字节区域,但是不属于一个对齐的64个字节区域,则会执行一个四段事务(也就是说,执行一个四段事务比执行两个一段事务效果更好)。

3. 结构体数组(AoS)与数组结构体(SoA)

许多并行编程范式,尤其是SIMD型范式,更倾向于使用SoA。在CUDA C编程中也普遍倾向于使用SoA,因为数据元素是为全局内存的有效访问而预先准备好的,而被相同内存操作引用的同字段数据元素在存储时是彼此相邻的。

4. 性能调整

优化设备内存带宽利用率有两个目标:

· 对齐及合并内存访问,以减少带宽的浪费

· 足够的并发内存操作,以隐藏内存延迟

实现并发内存访问最大化是通过以下方式获得的:

· 增加每个线程中执行独立内存操作的数量(展开)

· 对核函数启动的执行配置进行实验,以充分体现每个SM的并行性

四、核函数可达到的带宽

内存延迟:完成一次独立内存请求的时间

内存带宽:SM访问设备内存的速度

1. 内存带宽

一般有如下两种类型的带宽:

· 理论带宽:当前硬件可以实现的绝对最大带宽

· 有效带宽:核函数实际达到的带宽

(×2: 读+写)

2. 矩阵转置问题

假设矩阵存储在一个一维矩阵中,通过改变数组索引值来交换行和列的坐标,可以很容易得到转置矩阵。

void transposeHost(float *out, float *in, const int nx, const int ny) {
    for (int iy = 0; iy < ny; iy++) {
        for (int ix = 0; ix < nx; ix++) {
            out[ix * ny + iy] = in[iy * nx + ix];
        }
    }
}

如果禁用一级缓存加载,那么两种实现的性能在理论上是相同的。但是,如果启用一级缓存,那么第二种实现的性能表现会更好。按列读取操作是不合并的(因为带宽将会浪费在未被请求的字节上),将这些额外的字节存入一级缓存意味着下一个读操作可能会在缓存上执行而不在全局内存上执行。因为写操作不在一级缓存中缓存,所以对按列执行写操作的例子而言,任何缓存都没有意义。

第一种方法:

第二种方法:

为转置核函数设置性能的上限和下限

· 通过加载和存储行来拷贝矩阵(上限)。这样将模拟执行相同数量的内存操作作为转置,这样只能使用合并访问

· 通过加载和存储列来拷贝矩阵(下限)。这样将模拟执行相同数量的内存操作作为转置,但是只能使用交叉访问

核函数的实现如下:

__global__ void copyRow(float *out, float *in, const int nx, const int ny) {
    unsigned int ix = blockDim.x * blockIdx.x + threadIdx.x;
    unsigned int iy = blockDim.y * blockIdx.y + threadIdx.y;
    if (ix < nx && iy < ny) {
        out[iy * nx + ix] = in[iy * nx + ix];
    }
}

__global__ void copyCol(float *out, float *in, const int nx, const int ny) {
    unsigned int ix = blockDim.x * blockIdx.x + threadIdx.x;
    unsigned int iy = blockDim.y * blockIdx.y + threadIdx.y;
    if (ix < nx && iy < ny) {
        out[ix * ny + iy] = in[ix * ny + iy];
    }
}

在禁用ECC的Fermi M2090上两个拷贝核函数的性能(启用一级缓存)

朴素转置:读取行与读取列

基于行的朴素转置核函数,按行加载按列存储:

__global__ void transposeNaiveRow(float *out, float *in, const int nx, const int ny) {
    unsigned int ix = blockIdx.x * blockDim.x + threadIdx.x;
    unsigned int iy = blockIdx.y * blockDim.y + threadIdx.y;
    if (ix < nx && iy < ny) {
        out[ix * ny + iy] = in[iy * nx + ix];
    }
}

基于列的朴素转置核函数,按列加载按行存储:

__global__ void transposeNaiveCol(float *out, float *in, const int nx, const int ny) {
    unsigned int ix = blockDim.x * blockIdx.x + threadIdx.x;
    unsigned int iy = blockDim.y * blockIdx.y + threadIdx.y;
    if (ix < nx && iy < ny) {
        out[iy * nx + ix] = in[ix * ny + iy]
    }
}

展开转置:读取行与读取列

展开因子为4的基于行的实现:

__global__ void transposeUnroll4Row(float *out, float *in, const int nx, const int ny) {
    unsigned int ix = blockDim.x * blockIdx.x * 4 + threadIdx.x;
    unsigned int iy = blockDim.y * blockIdx.y + threadIdx.y;

    unsigned int ti = iy * nx + ix;  // access in rows
    unsigned int to = ix * ny + iy;  // access in columns

    if (ix + 3 * blockDim.x < nx && iy < ny) {
        out[to] = in[ti];
        out[to + ny * blockDim.x] = in[ti + blockDIm.x];
        out[to + ny * 2 * blockDim.x] = in[ti + 2 * blockDIm.x];
        out[to + ny * 3 * blockDim.x] = in[ti + 3 * blockDIm.x];
    } 
}

展开因子为4的基于列的实现:

__global__ void transposeUnroll4Col(float *out, float *in, const int nx, const int ny) {
    unsigned int ix = blockDim.x * blockIdx.x * 4 + threadIdx.x;
    unsigned int iy = blockDim.y * blockIdx.y + threadIdx.y;

    unsigned int ti = iy * nx + ix;  // access in rows
    unsigned int to = ix * ny + iy;  // access in columns

    if (ix + 3 * blockDim.x < nx && iy < ny) {
        out[ti] = in[to];
        out[ti + blockDim.x] = in[to + blockDIm.x * ny];
        out[ti + 2 * blockDim.x] = in[to + 2 * blockDIm.x * ny];
        out[ti + 3 * blockDim.x] = in[to + 3 * blockDIm.x * ny];
    } 
}

对角转置:读取行与读取列
使用瘦块来增加并行性

增加并行性最简单的方式是调整块的大小。

五、使用统一内存的矩阵加法

用托管内存分配来替换主机和设备内存分配,以消除重复指针:

因为核函数的启动与主机程序是异步的,所以在直接访问核函数输出之前,需要在主机端显式地同步。

如果在一个多GPU设备的系统上进行测试,托管应用需要附加的步骤。因为托管内存分配对系统中的所有设备是可见的,所以可以限制哪一个设备对应用程序可见,这样托管内存便可以只分配在一个设备上。为此,设置环境变量CUDA_VISIBLE_DEVICES来使一个GPU对CUDA应用程序可见:

$ export CUDA_VISIBLE_DEVICES=0

矩阵最初是在GPU上被分配的。这就要求底层系统在初始化之前,将矩阵中的数据从设备传输到主机中。故CPU数据初始化使用托管内存耗费的时间更长。

当CPU需要访问当前驻留在GPU中的托管内存时,统一内存使用CPU页面故障来触发设备到主机的数据传输。

【CUDA编程】CUDA内存模型 thread:每个线程都用自己的registers(寄存器)和local memory(局部内存) block:每个线程块(block)内都有自己的shared memory(共享内存),所有线程块内的所有线程共享这段内存资源 grid:每个grid都有自己的global memory(全局内存),constant memory(常量内存)和texture memory(纹理内存),不同线程块的线程都可使用。其中常量内存和纹理内存为只读内存空间。 阅读详情

相关推荐

CUDA C编程权威指南与实践

CUDA,全称Compute Unified Device Architecture,是由NVIDIA公司推出的一种用于通用并行计算的架构。它允许开发者利用NVIDIA的GPU进行大规模数据处理和计算,极大提高了计算效率和程序性能。CUDA技术自2007年首次推出后,经历了多次迭代和升级,现已发展成为并行计算领域的重要工具。从最初支持简单的图形处理任务,到现在支持复杂的科学计算,CUDA不仅在学术界得到广泛应用,也被工业界认可和采纳,成为高性能计算的首选技术之一。

weixin_35391606的博客 923

CUDA C编程权威指南Chapter1&2

基于这些坐标,你可以将部分数据分配给 不同的线程。全局类似于CPU的系统内存,而共享内存类似于CPU的缓存。然而GPU的共享内存可以由CUDA C的内核直接控制。构成,一个线程块包含一组线程,同一线程块内 的线程协作可以通过同步和共享内存来实现。不同块内的线程不能协作。同一网格中的所有线程共享相同 的全局内存空间。(线程格的维度,用每个线程格中的线程数来表示)(线程块的维度,用每个线程块中的线程数来表示)形式,一个线程块会被组织成线程的。一个线程格会被组织成线程块的。(线程块在线程格内的索引)

m0_73535031的博客 1045

图解“交易·支付·清结算·账务”全局

这个过程大致是这样的,用户先到平台选择喜欢的“餐品”,然后“下单”,生成交易“账单”,用户选择支付方式进行“支付”,支付成功后平台要履行承诺把餐送到,“履约”完成以后平台就开始进行各方利益的“清分”,计算清楚应给谁多少钱,并“记账”,最后将款项“结算”给商家,这个过程如图7所示。交易系统请求订单系统创建订单,因为不清楚平台和商家之间的清结算协议,所以暂且认为所有优惠由平台提供给用户,后续平台再基于协议跟商家之间做优惠的分摊,将上述商品、优惠、计价等信息集成到一起,就得到了完成的订单信息了,如表4所示。

wjianwei666的专栏 1万+

CUDA 编程上手指南:CUDA C 编程及 GPU 基本知识

作者丨科技猛兽 编辑丨极市平台 本文原创首发于极市平台,转载请获得授权并标明出处。 推荐大家关注极市平台公众号,每天都会更新最新的计算机视觉论文解读、综述盘点、调参攻略、面试经验等干货~ 目录 1 CPU 和 GPU 的基础知识 2 CUDA 编程的重要概念 3 并行计算向量相加 4 实践 4.1 向量相加 CUDA 代码 4.2 实践向量相加 5 给大家的一点参考资料 1 CPU 和 GPU 的基础知识 提到处理器结构,有2个指标是经常要考虑的:延迟和吞吐量。所谓延迟,是指从发出指令到最终返回结果中

极市平台的技术博客 2810

vcs 覆盖率收集1——三种覆盖率类型、covergroup、bins、数据采样、覆盖率选项

介绍各种覆盖率,以及如何编写覆盖率收集的代码

qq_42135020的博客 1万+

Multisim实现555计时器模拟简易电子琴

一. 元器件介绍 这里用到的元器件有: DIgital power(VCC)数字电源 频率计数器(XFC) 示波器(XSC) ground 数字地 Capacitan 电容器 Resistor 电阻器 555_VIRTUAL 555计时器 触发按键(DIPSW)(DPST)(SPST)

是你呀星途的博客 2万+

CUDA C编程入门

cuda 程序的基本步骤如下: 在 cpu 中初始化数据 将输入 transfer 到 GPU 中 利用分配好的 grid 和 block 启动 kernel 函数 将计算结果 transfer 到 CPU 中 释放申请的内存空间 从上面的步骤可以看出,一个 CUDA 程序主要包含两部分,第一部分运行在 CPU 上,称作 Host code,主要负责完成复杂的指令;第二部分运行在 GPU 上,称作 Device code,主要负责并行地完成大量的简单指令(如数值计算); 2. 基本设施 运

coder_Alger 的博客 7094

DLMS-Blue-Book蓝皮书—曲线类

attribute_index 1 指的是第 1 个属性(即 logical_name),attribute_index 2 指的是第 2 个属性,等等;如果其他“曲线通用类”对象捕获了这个缓冲区,它们的缓冲区也将被清空,以保证它们的缓冲区条目是同质的。如果捕获对象是曲线通用类的缓冲区,则 data_index 标识缓冲区(即列)中的捕获对象(即内部曲线通用类中的列);如果曲线通用类已排序,则调用捕获方法将把新条目存储在缓冲区中的适当位置,移动所有后续条目,并可能丢失最不相关的条目。

一个bug是bug,多个bug能work 1454

CUDA全局内存(CUDA C编程 权威指南)

访问共享内存必须同步。常量内存:使用__constant__来修饰常量变量,必须在全局空间内和核函数之外进行声明,大小只有64KB,常量内存时静态声明的,并对统一编译单元中所有的核函数可见。这种类型的内存分配对于主机(CPU)和设备(GPU)之间的异步内存传输非常有利,因为GPU可以直接访问它,无需将数据复制到可分页内存中的缓冲区。统一内存寻址提供了一个“单指针到数据”模型,在概念上类似于零拷贝内存,但零拷贝内存是在主机内存中进行分配,因此受到在PCIe总线上访问零拷贝内存的一个像,核函数性能具有高延迟。

qq_43616651的博客 1688

AI Agent的知识蒸馏链:多阶段LLM模型优化

随着大语言模型(LLM)规模的不断扩大,如何在保持模型性能的同时提高其部署效率成为关键挑战。本文旨在探讨通过知识蒸馏技术构建多阶段优化链,实现LLM模型的高效压缩和性能优化。研究范围涵盖从基础理论到实际应用的完整知识蒸馏流程,特别关注AI Agent场景下的特殊需求和解决方案。本文首先介绍知识蒸馏的基本概念和背景,然后详细阐述多阶段蒸馏链的核心原理。接着,我们将深入探讨算法实现细节和数学模型,提供完整的代码示例。最后讨论实际应用、工具资源和未来发展方向。

AI大模型应用之禅 353

飞牛fnNAS搭建私有云音乐库

本文介绍了在飞牛NAS上部署Navidrome音乐服务器的完整流程。首先创建音乐存储目录并安装Navidrome应用,然后配置管理员账号、语言设置和音乐路径。接着详细说明使用"音乐标签"软件刮削音乐元数据的方法,包括获取软件、导入音乐文件和自动匹配标签。针对远程访问需求,推荐使用ZeroTier实现内网穿透,并给出注册、创建网络及设备加入的具体步骤。最后介绍移动端使用"音流"APP连接Navidrome服务器的配置方法,实现手机端音乐播放。通过这套方案,用户可以搭建专

dgnankai的专栏 1829

CUDA C编程权威指南笔记

CUDA C编程权威指南笔记,浅谈CUDA编程的思想和原理

weixin_74277223的博客 1046

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

CUDA C编程权威指南习题解析

6.为执行核函数的每个线程提供了一个唯一的线程ID,通过内置变量threadIdx.x可以在内核中对线程进行访问。3.用cudaDeviceSynchronize 函数来替换hello.cu中的cudaDeviceReset函数,然后编译运行,看看会发生什么。4.参考1.3节,从编译器命令行中移除设备架构标志,然后按照下面的方式进行编译,看看会发生什么。2.从hello.cu中移除cudaDeviceReset函数,然后编译运行,看看会发生什么。(1)对于二维数据,沿x轴进行块划分。

谢白羽 1516

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

【亲测免费】 CUDA C编程权威指南简介

CUDA C编程权威指南简介 【下载地址】CUDAC编程权威指南简介 本资源是《CUDA C编程权威指南》系列的一部分,具体为第一部分(1/1)。本书面向希望深入学习和掌握CUDA技术的开发者,CUDA是一种由NVIDIA推出的通用并行计算平台和编程模型,它允许开发者利用GPU的强大计算能力来加速计算密集型任务 ...

gitblog_06620的博客 568

贝叶斯统计 [韦来生编]

贝叶斯(Bayes)统计是近几十年来迅速发展起来的数理统计的一个重要分支。贝叶斯方法与经典统计方法的主要不同之处是进行统计推断时除了利用样本信息外,还要利用参数的先验信息,因此可以提高统计推断或统计决策的效果。它在经济、金融、生物、医学、自然科学和社会科学等许多领域具有广泛的应用。贝叶斯方法的研究已渗透到了统计学的几乎所有领域。作者在给中国科学技术大学概率论与数理统计专业本科生开设的“数理统计”课程中有一章专门讲授“贝叶斯方法和统计决策理论”,近十年来也给中国科学技术大学概率统计专业研究生开设过几次“贝叶斯分析”课。本书是在对过去讲稿的内容作了适当的增减和调整的基础上完成的。  本书共分六章。第1章是绪论,介绍了贝叶斯统计的若干基本概念,同时对必要的数理统计的基础知识有重点地作了回顾。第二章介绍了确定先验分布的若干可供选择的方法。第三章介绍了几类常见统计模型参数的后验分布的主要结果和计算方法。第四章和第五章分别介绍了“贝叶斯统计推断”和“贝叶斯统计决策”的内容。第六章介绍了贝叶斯统计计算的若干方法,包括蒙特卡洛方法和马尔可夫链蒙特卡洛(MCMC)方法以及相关统计软件的简介。本书的每一章附右大量的例题和习题,书末附有常用的几个表格和部分习题解答供读者查用。  大约可在54学时内讲授本书的第1章至第六章的主要内容,教师可根据学时需要选讲本书第1章至第六章的部分内容。书中标“*”号的章节可略去不讲,留给读者作为阅读材料。第六章例题中的R代码和部分例题的数据文件等可从作者个人主页下载。  作者在编写本书的过程中参考了J.O.Berger教授的《统计决策论及贝叶斯分析》和茆诗松教授编写的《贝叶斯统计》等书中的一些内容,以及《贝叶斯分析》一书中由张伟平博士编写的有关贝叶斯统计计算的一些内容。在此表示衷心感谢。  本书编写准备过程中,中国科学技术大学统计与金融系的几个研究生帮助完成了本书部分中文的录入和排版,作者对他们的辛勤工作表示真诚的感谢。高等教育出版社为本书的出版给予了大力支持。在此一并致谢。

《CUDA C编程权威指南》学习笔记 chap4-6

《CUDA C编程权威指南》示例代码下载 第4章 全局内存 4.1 CUDA内存模型概述 1. 内存层次结构的优点 CUDA编程模型能将内存层次结构更好地呈现给用户,能让我们显式地控制 它的行为。 2. CUDA内存模型 CUDA内存模型提出了多种可编程内存的类型: ·寄存器 ·共享内存 ·本地内存 ·常量内存 ·纹理内存 ·全局内存 所有线程都可以访问全 局内存。所有线程都能访问的只读内存空间有:常量内存空间和纹理内存空间。全局内 存、常量内存和纹理内存空间有不同的用途。纹理内存为各种数据布局提供了不

ambition_zhou的博客 765

第4章 全局内存

学习CUDA内存模型; CUDA内存管理; 全局内存编程; 探索全局内存访问模式; 研究全局内存数据布局; 统一内存编程; 最大限度地提高全局内存吞吐量;

weixin_47955824的博客 3345

CUDA C编程权威指南 第3章 CUDA执行模型

CUDA编程模型中两个主要的抽象概念:内存层次结构和线程层次结构。它们能够控制大规模并行GPU。GPU架构是围绕一个流式多处理器(SM)的可扩展阵列搭建的。GPU中每一个SM都能支持数百个线程并发执行,每个GPU通常有多个SM,所以在一个GPU上并发执行数千个线程是可能的。当启动一个内核网格时,它的线程块被分布在了可用的SM上来执行。多个线程块可能会被分配到同一个SM上,而且是根据SM资源的可用性进行调度的。

weixin_44500921的博客 1043

cuda编程学习——CUDA全局内存性能优化(八)

CUDA C编程权威指南》以及 CUDA官方文档CUDA编程:基础与实践 樊哲勇文章、讲解视频同步更新公众《AI知识物语》,B站:出门吃三碗饭。

出门吃三碗饭的博客 1584

cuda c权威编程指南_CUDA编程入门(一)CUDA编程模型

前一段时间做实验,发现自己写的有些代码效率比较低,GPU利用率不高。而且现在逐渐觉得用Pytorch等框架有时不够自由,导致某些idea难以实现。所以最近在学习CUDA编程,这一系列文章会整理一下所学的东西,希望能和大家共同学习共同进步。如有文中错误的地方,希望大家不吝指出,谢谢!我个人主要是通过以下三本书进行学习的,建议初学者从《CUDA C编程权威指南》入手进行学习。CUDA并行程序设计——G...

weixin_39762075的博客 518

【快速上手】探索CUDA编程的奥秘:《CUDA C 编程指南》推荐

探索CUDA编程的奥秘:《CUDA C 编程指南》推荐 ????【免费下载地址】CUDAC编程指南程润伟资源下载 本仓库提供了一份珍贵的资源文件——《CUDA C 编程指南》,由美国华裔教授大牛程润伟最新力作。这本书是学习CUDA编程的必备经典图书,适合所有对CUDA编程感兴趣的开发者、研究人员和学生。 ...

gitblog_09774的博客 803

CUDA C变成权威指南 第二章 CUDA编程模型

CUDA6.0开始提出统一寻址(Unified Memory),它连接了主机内存和设备内存空间,可使用单个指针访问CPU和GPU内存。主机代码按照ANSI C标准进行编写,而设备代码使用CUDA C进行编写。一个典型的CUDA程序实现流程遵循以下模式。1. 把数据从CPU内存拷贝到GPU内存。2.调用核函数对存储在GPU内存中的数据进行操作。3.将数据从GPU内存传送回到CPU内存。

weixin_44500921的博客 837

【亲测免费】 探索CUDA并行编程的宝藏:《CUDA并行程序设计 GPU编程指南》与源码资源

探索CUDA并行编程的宝藏:《CUDA并行程序设计 GPU编程指南》与源码资源 【下载地址】CUDA并行程序设计资源下载 CUDA并行程序设计资源下载本仓库提供了一个重要的资源文件下载,内容包括《CUDA并行程序设计 GPU编程指南》522页的电子书以及《CUDA C编程权威指南》的源码 ...

gitblog_09736的博客 992

CUDA C 编程权威指南 第一章 基于CUDA的异构并行计算

CUDA(Compute Unified Device Architecture, 统一计算设备架构)是NVIDIA提出的并行计算架构,主要用来处理密集型及并行计算。GPU和CPU是两个独立的处理器,通过单个计算节点中的PCI- Express总线相连,GPU用来提高计算密集型应用程序中并行程序段的执行速度,CPU则负责管理设备端的资源。

weixin_44500921的博客 1474

CUDA C编程权威指南 第四章 全局内存

基础 一般内存的设计:寄存器->缓存->主存->磁盘存储器 GPU内存设计 修饰符 变量名 存储器 作用域 生命周期 float var 寄存器 线程 线程 float var[100] 本地 线程 线程 __shared__ float var + 共享 块 块 __device__ float var + 全局 全局 应用程序 __c...

Claroja 809
上一篇: CUDA C编程权威指南 第3章 CUDA执行模型
下一篇: 简述库文件
青禾子的夏
博客等级 码龄8年 405粉丝 68原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值