【nvidia CUDA 高级编程】NVSHMEM 直方图——分布式方法

news2024/12/25 10:04:55

博主未授权任何人或组织机构转载博主任何原创文章,感谢各位对原创的支持!
博主链接

本人就职于国际知名终端厂商,负责modem芯片研发。
在5G早期负责终端数据业务层、核心网相关的开发工作,目前牵头6G算力网络技术标准研究。


博客内容主要围绕:
       5G/6G协议讲解
       算力网络讲解(云计算,边缘计算,端计算)
       高级C语言讲解
       Rust语言讲解



NVSHMEM 直方图——分布式方法

在这里插入图片描述

PE:处理单元(process entity)

       我们来了解一下这个问题的另一种解决方法。之前的解决方案有一个特点,所有的直方图计算都在本地完成。然后同步所有线程并对结果做最终的归约。

       另一种方法就是分割直方图并把各个部分分配到不同的GPU上。当输入数据中的一个条目不属于该 GPU 上的直方图位置时,我们将自动递增远程 PE 中的相关直方图条目。然后我们必须在最后把直方图的各个部分拼接起来。我们将这种方法称为“分布式”方法。

请添加图片描述

在复制式和分布式方法之间的权衡

       与复制方法相比,我们在分布式方法中减少了直方图所需的 GPU 内存数量。我们还降低了直方图上的本地原子操作的压力。但反过来,这增加了消息传递的压力以及远程 GPU 上的原子操作的压力。

练习

       我们将直方图任意分割成与GPU的数量相同的段,并把这些段按顺序分配个不同的GPU。我们还将假设直方图的分段是在核函数内部实现的,这样我们就可用算数的方法计算出要将数据发送到哪个 PE(尽管更容易做的是,可将这步变成一般的情况,即该信息并非是事先已知的,仍需作为输入数据提供给核函数)。

       为了更新远程 PE 上的直方图,我们希望使用相当于 CUDA 的atomicAdd()的函数。与之相对应的 NVSHMEM 函数是 nvshmem_int_atomic_add()

nvshmem_int_atomic_add(destination, value, target_pe);

其中,

  • value:是要增加的数值;
  • target_pe:是要被更新的远程 PE;
  • destination必须是一个对称地址(如通过nvshmem_malloc()分配的地址);

       在拼接直方图的步骤中,我们有一个容易使用的API nvshmem_int_collect() ,它拼接所有 PE 上的数组,比如将来自 PE0 的数组放在第一部分,来自 PE1 的数组放在第二部分,以此类推。

nvshmem_int_collect(team, destination, source, nelems);

其中,

  • destination:存放已拼接好的数组(在所有 PE 上都相同);
  • source:是长度为nelems的源数组。由于直方图在 PE 之间均匀分布,所以目标数组的长度应为n_pes*nelems,与整个直方图的长度相匹配。
  • team:选择PE组。对于全局的集合操作,我们使用工作组NVSHMEM_TEAM_WORLD,它包含所有的 PE;

相关代码如下(file name:histogram_step2.cpp)

#include <iostream>
#include <cstdlib>
#include <chrono>

#include <nvshmem.h>
#include <nvshmemx.h>

inline void CUDA_CHECK (cudaError_t err) {
    if (err != cudaSuccess) {
        fprintf(stderr, "CUDA error: %s\n", cudaGetErrorString(err));
        exit(-1);
    }
}

#define NUM_BUCKETS   16
#define MAX_VALUE     1048576
#define NUM_INPUTS    65536

__global__ void histogram_kernel(const int* input, int* histogram, int N)
{
    int idx = threadIdx.x + blockIdx.x * blockDim.x;

    int n_pes = nvshmem_n_pes();
    int buckets_per_pe = NUM_BUCKETS / n_pes;

    if (idx < N) {
        int value = input[idx];

        // 计算“全局”直方图索引号
        int global_histogram_index = ((size_t) value * NUM_BUCKETS) / MAX_VALUE;

        // 找出直方图指数对应的 PE。
        // 假设每个 PE 的桶数量相同
        // 我们从包含第一个桶的 PE 0 开始
        // 直到第一个值为 1 / n_pes 的贮体为止
        // 对其他 PE 采用类似方法。我们可在这个阶段采取简单的
        // 整数除法。
        int target_pe = global_histogram_index / buckets_per_pe;

        // 现在求出 PE 的局部直方图索引号。
        // 我们只需要用 PE 的起始桶的偏离值即可。
        int local_histogram_index = global_histogram_index - target_pe * buckets_per_pe;

        nvshmem_int_atomic_add(&histogram[local_histogram_index], 1, target_pe);
    }
}

int main(int argc, char** argv) {
    // 初始化 NVSHMEM
    nvshmem_init();

    // 获取 NVSHMEM 处理元素 ID 和 PE 数量
    int my_pe = nvshmem_my_pe();
    int n_pes = nvshmem_n_pes();

    // 每个 PE(任意)选择与其 ID 对应的 GPU
    int device = my_pe;
    CUDA_CHECK(cudaSetDevice(device));

    // 每台设备处理 1 / n_pes 的部分工作。
    const int N = NUM_INPUTS / n_pes;

    // 在主机上构建直方图输入数据
    int* input = (int*) malloc(N * sizeof(int));

    // 为每个 PE 初始化一个不同的随机数种子。
    srand(my_pe);

    // 输入数据范围从 0 至 MAX_VALUE - 1 不等
    for (int i = 0; i < N; ++i) {
        input[i] = rand() % MAX_VALUE;
    }

    // 复制到设备
    int* d_input;
    d_input = (int*) nvshmem_malloc(N * sizeof(int));
    CUDA_CHECK(cudaMemcpy(d_input, input, N * sizeof(int), cudaMemcpyHostToDevice));

    // 分配直方图数组 - 大小等同于主机上的
    // 完整直方图,且只分配设备上每个 GPU 的相关部分。
    int* histogram = (int*) malloc(NUM_BUCKETS * sizeof(int));
    memset(histogram, 0, NUM_BUCKETS * sizeof(int));

    int buckets_per_pe = NUM_BUCKETS / n_pes;

    int* d_histogram;
    d_histogram = (int*) nvshmem_malloc(buckets_per_pe * sizeof(int));
    CUDA_CHECK(cudaMemset(d_histogram, 0, buckets_per_pe * sizeof(int)));

    // 此外,还要为连接分配完整大小的设备直方图
    int* d_concatenated_histogram = (int*) nvshmem_malloc(NUM_BUCKETS * sizeof(int));
    CUDA_CHECK(cudaMemset(d_concatenated_histogram, 0, NUM_BUCKETS * sizeof(int)));

    // 为合理准确的计时执行一次同步
    nvshmem_barrier_all();

    using namespace std::chrono;

    high_resolution_clock::time_point tabulation_start = high_resolution_clock::now();

    // 执行直方图
    int threads_per_block = 256;
    int blocks = (NUM_INPUTS / n_pes + threads_per_block - 1) / threads_per_block;

    histogram_kernel<<<blocks, threads_per_block>>>(d_input, d_histogram, N);
    CUDA_CHECK(cudaDeviceSynchronize());

    nvshmem_barrier_all();

    high_resolution_clock::time_point tabulation_end = high_resolution_clock::now();

    // 连接所有 PE

    high_resolution_clock::time_point combination_start = high_resolution_clock::now();

    nvshmem_int_collect(NVSHMEM_TEAM_WORLD, d_concatenated_histogram, d_histogram, buckets_per_pe);

    high_resolution_clock::time_point combination_end = high_resolution_clock::now();

    // 打印 PE 0 上的结果
    if (my_pe == 0) {
        duration<double> tabulation_time = duration_cast<duration<double>>(tabulation_end - tabulation_start);
        std::cout << "Tabulation time = " << tabulation_time.count() * 1000 << " ms" << std::endl << std::endl;

        duration<double> combination_time = duration_cast<duration<double>>(combination_end - combination_start);
        std::cout << "Combination time = " << combination_time.count() * 1000 << " ms" << std::endl << std::endl;

        // 将数据复制回主机
        CUDA_CHECK(cudaMemcpy(histogram, d_concatenated_histogram, NUM_BUCKETS * sizeof(int), cudaMemcpyDeviceToHost));

        std::cout << "Histogram counters:" << std::endl << std::endl;
        int num_buckets_to_print = 4;
        for (int i = 0; i < NUM_BUCKETS; i += NUM_BUCKETS / num_buckets_to_print) {
            std::cout << "Bucket [" << i * (MAX_VALUE / NUM_BUCKETS) << ", " << (i + 1) * (MAX_VALUE / NUM_BUCKETS) - 1 << "]: " << histogram[i];
            std::cout << std::endl;
            if (i < NUM_BUCKETS - NUM_BUCKETS / num_buckets_to_print - 1) {
                std::cout << "..." << std::endl;
            }
        }
    }

    free(input);
    free(histogram);
    nvshmem_free(d_input);
    nvshmem_free(d_histogram);

    // 最终确定 nvshmem
    nvshmem_finalize();

    return 0;
}

编译和运行命令:

nvcc -x cu -arch=sm_70 -rdc=true -I $NVSHMEM_HOME/include -L $NVSHMEM_HOME/lib -lnvshmem -lcuda -o histogram_step2 histogram_step2.cpp
nvshmrun -np $NUM_DEVICES ./histogram_step2

运行结果如下:

Tabulation time = 0.195561 ms

Combination time = 0.029666 ms

Histogram counters:

Bucket [0, 65535]: 4135
...
Bucket [262144, 327679]: 4028
...
Bucket [524288, 589823]: 4088
...
Bucket [786432, 851967]: 4100

比较复制式和分布式方法加粗样式

       到目前为止,我们一直专注于编写语法正确的代码,而没有考虑性能。现在我们来检查分布式和复制式方法的性能。在这两种情况下,改变NUM_BUCKETS参数和NUM_INPUTS参数,同时注意直方图建表和组合时间。是否一种方法比另一种快? 如果是,是否存在性能比率相反的情况?

为了方便起见,我们提供了以下两种实现的解决方案。

复制式方法

源代码如下:

#include <iostream>
#include <cstdlib>
#include <chrono>

#include <nvshmem.h>
#include <nvshmemx.h>

inline void CUDA_CHECK (cudaError_t err) {
    if (err != cudaSuccess) {
        fprintf(stderr, "CUDA error: %s\n", cudaGetErrorString(err));
        exit(-1);
    }
}

#define NUM_BUCKETS   16
#define MAX_VALUE     1048576
#define NUM_INPUTS    65536

__global__ void histogram_kernel(const int* input, int* histogram, int N)
{
    int idx = threadIdx.x + blockIdx.x * blockDim.x;

    if (idx < N) {
        int value = input[idx];

        int histogram_index = ((size_t) value * NUM_BUCKETS) / MAX_VALUE;

	    atomicAdd(&histogram[histogram_index], 1);
    }
}

int main(int argc, char** argv) {
    // 初始化 NVSHMEM
    nvshmem_init();

    // 获取 NVSHMEM 处理元素 ID 和 PE 数量
    int my_pe = nvshmem_my_pe();
    int n_pes = nvshmem_n_pes();

    // 每个 PE(任意)选择与其 ID 对应的 GPU
    int device = my_pe;
    CUDA_CHECK(cudaSetDevice(device));

    // 每台设备处理 1 / n_pes 的部分工作。
    const int N = NUM_INPUTS / n_pes;

    // 在主机上构建直方图输入数据
    int* input = (int*) malloc(N * sizeof(int));

    // 为每个 PE 初始化一个不同的随机数种子。
    srand(my_pe);

    // 输入数据范围从 0 至 MAX_VALUE - 1 不等
    for (int i = 0; i < N; ++i) {
        input[i] = rand() % MAX_VALUE;
    }

    // 复制到设备
    int* d_input;
    d_input = (int*) nvshmem_malloc(N * sizeof(int));
    CUDA_CHECK(cudaMemcpy(d_input, input, N * sizeof(int), cudaMemcpyHostToDevice));

    // 分配直方图数组
    int* histogram = (int*) malloc(NUM_BUCKETS * sizeof(int));
    memset(histogram, 0, NUM_BUCKETS * sizeof(int));

    int* d_histogram;
    d_histogram = (int*) nvshmem_malloc(NUM_BUCKETS * sizeof(int));
    CUDA_CHECK(cudaMemset(d_histogram, 0, NUM_BUCKETS * sizeof(int)));

    // 为合理准确的计时执行一次同步
    nvshmem_barrier_all();

    using namespace std::chrono;

    high_resolution_clock::time_point tabulation_start = high_resolution_clock::now();

    // 执行直方图
    int threads_per_block = 256;
    int blocks = (NUM_INPUTS / n_pes + threads_per_block - 1) / threads_per_block;

    histogram_kernel<<<blocks, threads_per_block>>>(d_input, d_histogram, N);
    CUDA_CHECK(cudaDeviceSynchronize());

    nvshmem_barrier_all();

    high_resolution_clock::time_point tabulation_end = high_resolution_clock::now();

    high_resolution_clock::time_point combination_start = high_resolution_clock::now();

    // 在所有 PE 上执行归约
    nvshmem_int_sum_reduce(NVSHMEM_TEAM_WORLD, d_histogram, d_histogram, NUM_BUCKETS);

    high_resolution_clock::time_point combination_end = high_resolution_clock::now();

    // 打印 PE 0 上的结果
    if (my_pe == 0) {
        duration<double> tabulation_time = duration_cast<duration<double>>(tabulation_end - tabulation_start);
        std::cout << "Tabulation time = " << tabulation_time.count() * 1000 << " ms" << std::endl << std::endl;

        duration<double> combination_time = duration_cast<duration<double>>(combination_end - combination_start);
        std::cout << "Combination time = " << combination_time.count() * 1000 << " ms" << std::endl << std::endl;

        // 将数据复制回主机
        CUDA_CHECK(cudaMemcpy(histogram, d_histogram, NUM_BUCKETS * sizeof(int), cudaMemcpyDeviceToHost));

        std::cout << "Histogram counters:" << std::endl << std::endl;
        int num_buckets_to_print = 4;
        for (int i = 0; i < NUM_BUCKETS; i += NUM_BUCKETS / num_buckets_to_print) {
            std::cout << "Bucket [" << i * (MAX_VALUE / NUM_BUCKETS) << ", " << (i + 1) * (MAX_VALUE / NUM_BUCKETS) - 1 << "]: " << histogram[i];
            std::cout << std::endl;
            if (i < NUM_BUCKETS - NUM_BUCKETS / num_buckets_to_print - 1) {
                std::cout << "..." << std::endl;
            }
        }
    }

    free(input);
    free(histogram);
    nvshmem_free(d_input);
    nvshmem_free(d_histogram);

    // 最终确定 nvshmem
    nvshmem_finalize();

    return 0;
}

编译和运行指令:

nvcc -x cu -arch=sm_70 -rdc=true -I $NVSHMEM_HOME/include -L $NVSHMEM_HOME/lib -lnvshmem -lcuda -o histogram_step1 histogram_step1.cpp
nvshmrun -np $NUM_DEVICES ./histogram_step1

运行结果如下:

Tabulation time = 0.035362 ms

Combination time = 0.039909 ms

Histogram counters:

Bucket [0, 65535]: 4135
...
Bucket [262144, 327679]: 4028
...
Bucket [524288, 589823]: 4088
...
Bucket [786432, 851967]: 4100

分布式方法

源代码如下:

#include <iostream>
#include <cstdlib>
#include <chrono>

#include <nvshmem.h>
#include <nvshmemx.h>

inline void CUDA_CHECK (cudaError_t err) {
    if (err != cudaSuccess) {
        fprintf(stderr, "CUDA error: %s\n", cudaGetErrorString(err));
        exit(-1);
    }
}

#define NUM_BUCKETS   16
#define MAX_VALUE     1048576
#define NUM_INPUTS    65536

__global__ void histogram_kernel(const int* input, int* histogram, int N)
{
    int idx = threadIdx.x + blockIdx.x * blockDim.x;

    int n_pes = nvshmem_n_pes();
    int buckets_per_pe = NUM_BUCKETS / n_pes;

    if (idx < N) {
        int value = input[idx];

        // 计算“全局”直方图索引号
        int global_histogram_index = ((size_t) value * NUM_BUCKETS) / MAX_VALUE;

        // 找出直方图指数对应的 PE。
        // 假设每个 PE 的桶数量相同
        // 我们从包含第一个桶的 PE 0 开始
        // 直到第一个值为 1 / n_pes 的贮体为止
        // 对其他 PE 采用类似方法。我们可在这个阶段采取简单的
        // 整数除法。
        int target_pe = global_histogram_index / buckets_per_pe;

        // 现在求出 PE 的局部直方图索引号。
        // 我们只需要用 PE 的起始桶的偏离值即可。
        int local_histogram_index = global_histogram_index - target_pe * buckets_per_pe;

        nvshmem_int_atomic_add(&histogram[local_histogram_index], 1, target_pe);
    }
}

int main(int argc, char** argv) {
    // 初始化 NVSHMEM
    nvshmem_init();

    // 获取 NVSHMEM 处理元素 ID 和 PE 数量
    int my_pe = nvshmem_my_pe();
    int n_pes = nvshmem_n_pes();

    // 每个 PE(任意)选择与其 ID 对应的 GPU
    int device = my_pe;
    CUDA_CHECK(cudaSetDevice(device));

    // 每台设备处理 1 / n_pes 的部分工作。
    const int N = NUM_INPUTS / n_pes;

    // 在主机上构建直方图输入数据
    int* input = (int*) malloc(N * sizeof(int));

    // 为每个 PE 初始化一个不同的随机数种子。
    srand(my_pe);

    // 输入数据范围从 0 至 MAX_VALUE - 1 不等
    for (int i = 0; i < N; ++i) {
        input[i] = rand() % MAX_VALUE;
    }

    // 复制到设备
    int* d_input;
    d_input = (int*) nvshmem_malloc(N * sizeof(int));
    CUDA_CHECK(cudaMemcpy(d_input, input, N * sizeof(int), cudaMemcpyHostToDevice));

    // 分配直方图数组 - 大小等同于主机上的
    // 完整直方图,且只分配设备上每个 GPU 的相关部分。
    int* histogram = (int*) malloc(NUM_BUCKETS * sizeof(int));
    memset(histogram, 0, NUM_BUCKETS * sizeof(int));

    int buckets_per_pe = NUM_BUCKETS / n_pes;

    int* d_histogram;
    d_histogram = (int*) nvshmem_malloc(buckets_per_pe * sizeof(int));
    CUDA_CHECK(cudaMemset(d_histogram, 0, buckets_per_pe * sizeof(int)));

    // 此外,还要为连接分配完整大小的设备直方图
    int* d_concatenated_histogram = (int*) nvshmem_malloc(NUM_BUCKETS * sizeof(int));
    CUDA_CHECK(cudaMemset(d_concatenated_histogram, 0, NUM_BUCKETS * sizeof(int)));

    // 为合理准确的计时执行一次同步
    nvshmem_barrier_all();

    using namespace std::chrono;

    high_resolution_clock::time_point tabulation_start = high_resolution_clock::now();

    // 执行直方图
    int threads_per_block = 256;
    int blocks = (NUM_INPUTS / n_pes + threads_per_block - 1) / threads_per_block;

    histogram_kernel<<<blocks, threads_per_block>>>(d_input, d_histogram, N);
    CUDA_CHECK(cudaDeviceSynchronize());

    nvshmem_barrier_all();

    high_resolution_clock::time_point tabulation_end = high_resolution_clock::now();

    // 连接所有 PE

    high_resolution_clock::time_point combination_start = high_resolution_clock::now();

    nvshmem_int_collect(NVSHMEM_TEAM_WORLD, d_concatenated_histogram, d_histogram, buckets_per_pe);

    high_resolution_clock::time_point combination_end = high_resolution_clock::now();

    // 打印 PE 0 上的结果
    if (my_pe == 0) {
        duration<double> tabulation_time = duration_cast<duration<double>>(tabulation_end - tabulation_start);
        std::cout << "Tabulation time = " << tabulation_time.count() * 1000 << " ms" << std::endl << std::endl;

        duration<double> combination_time = duration_cast<duration<double>>(combination_end - combination_start);
        std::cout << "Combination time = " << combination_time.count() * 1000 << " ms" << std::endl << std::endl;

        // 将数据复制回主机
        CUDA_CHECK(cudaMemcpy(histogram, d_concatenated_histogram, NUM_BUCKETS * sizeof(int), cudaMemcpyDeviceToHost));

        std::cout << "Histogram counters:" << std::endl << std::endl;
        int num_buckets_to_print = 4;
        for (int i = 0; i < NUM_BUCKETS; i += NUM_BUCKETS / num_buckets_to_print) {
            std::cout << "Bucket [" << i * (MAX_VALUE / NUM_BUCKETS) << ", " << (i + 1) * (MAX_VALUE / NUM_BUCKETS) - 1 << "]: " << histogram[i];
            std::cout << std::endl;
            if (i < NUM_BUCKETS - NUM_BUCKETS / num_buckets_to_print - 1) {
                std::cout << "..." << std::endl;
            }
        }
    }

    free(input);
    free(histogram);
    nvshmem_free(d_input);
    nvshmem_free(d_histogram);

    // 最终确定 nvshmem
    nvshmem_finalize();

    return 0;
}

编译和运行命令:

nvcc -x cu -arch=sm_70 -rdc=true -I $NVSHMEM_HOME/include -L $NVSHMEM_HOME/lib -lnvshmem -lcuda -o histogram_step1 histogram_step2.cpp
nvshmrun -np $NUM_DEVICES ./histogram_step2

运行结果如下:

Tabulation time = 0.18831 ms

Combination time = 0.028852 ms

Histogram counters:

Bucket [0, 65535]: 4135
...
Bucket [262144, 327679]: 4028
...
Bucket [524288, 589823]: 4088
...
Bucket [786432, 851967]: 4100


在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/164237.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Redis基础命令操作五之集合类型ZSET

ZSET 命令举例说明ZADD ZADD [KEY][序号1][序号1的值]集合中添加元素ZREMZREM [KEY][序号的值]移除集合中元素ZRANGEZRANGE [KEY][下标1][下标2]获取指定区间集合元素ZRAGNEBYSCOREZRANGEBYSOCRE [KEY] -INF INF集合中按照序号从小到大排列ZREVRANGEZREVRANGE [key][序…

四,Spring注解开发

Spring day04 1 Spring基于注解的开发 XML方式配置bean存在的问题&#xff1a;开发效率低下。Spring2.x提供了开发效率更高的注解式配置。注解开发替换XML配置的好处&#xff1a;简化编程&#xff0c;提高开发效率。 XML方式&#xff1a;配置繁琐&#xff0c;但功能强大&…

测试开发的一次实践总结

这些年&#xff0c;测开越来越火&#xff0c;火的原因之一就是因为大部分公司都有设测开岗位并有招聘需求。那测试开发到底是做什么&#xff0c;和测试又有什么区别呢&#xff1f;接下来&#xff0c;说说我对测开的理解与实际工作的总结。 01—测试开发的理解 测试分类 从市场…

测试碎碎念(基础篇_1)

一、软件测试1.1 什么是测试测试行为 在生活中是十分常见的~在生活中&#xff0c;我们有许多 "测试" 的行为&#xff0c;比如说&#xff0c;在坐地铁之前&#xff0c;需要用金属探测仪在身上刷一下&#xff0c;需要把身上的背包等物品放在闸机上过一下~比如说&#x…

Rockchip开发系列 - 8. IO电源域配置

By: fulinux E-mail: fulinux@sina.com Blog: https://blog.csdn.net/fulinus 喜欢的盆友欢迎点赞和订阅! 你的喜欢就是我写作的动力! 目录 RK3566 RK3568 IO 电源域配置指南概述第一步:获取硬件原理图并确认硬件电源的设计方案第二步:查找对应的内核dts配置文件第三步:修…

Open3D 网格滤波(Python版本)

文章目录 一、简介二、滤波2.1 均值滤波2.2 Laplacian滤波2.3 Taubin滤波三、实现效果参考资料一、简介 网格数据的滤波其本质上仍是针对点的滤波过程,具体的过程如下所示。 二、滤波 2.1 均值滤波 如下公式所示,均值滤波其实就是该点与其邻近点之间的平均值: Open3D中的相…

在Ubuntu上安装OpenShift并使用

服务器信息 在阿里云买了个抢占式的服务器&#xff0c;地区为华南广州&#xff0c;系统为Ubuntu 20.04&#xff0c;8核16GB。 安装Docker 命令如下&#xff1a; $ apt-get update -y $ apt-get upgrade -y $ apt-get install -y docker.io 安装成功后&#xff0c;检查一下版…

2023 年你应该知道的所有机器学习算法

在过去的几年里&#xff0c;根据自己的工作经验&#xff0c;整理了我认为最重要的机器学习算法。 通过这个&#xff0c;我希望提供一个工具和技术的存储库&#xff0c;以便您可以解决各种数据科学问题&#xff01; 让我们深入研究六种最重要的机器学习算法&#xff1a; 解释…

状态机原理

前言状态机在实际工作开发中应用非常广泛&#xff0c;在刚进入公司的时候&#xff0c;根据公司产品做流程图的时候&#xff0c;发现自己经常会漏了这样或那样的状态&#xff0c;导致整体流程会有问题&#xff0c;后来知道了状态机这样的东西&#xff0c;发现用这幅图就可以很清…

简单步骤比别人抢红包快一步

&#x1f935;‍♂️ 个人主页老虎也淘气 个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f44d;&#x1f3fb; 收藏…

Slurm中集群配置文件slurm.conf

1.slurm.conf简介slurm.conf是一个ASCII文件&#xff0c;它描述了一般的Slurm 配置信息、要管理的节点、有关如何将这些节点分组到分区中&#xff0c;以及各种调度与这些分区关联的参数。此文件应为在群集中的所有节点上保持一致。可以通过设置SLURM_CONF在执行时修改文件位置 …

203:vue+openlayers 地图旋转移动动画、CSS缩放动画,介绍animate的使用方法

第203个 点击查看专栏目录 本示例的目的是介绍如何在vue+openlayers项目中创建动画,地图上使用的是view中的animate方法, CSS中使用的是keyframes ,animation,transform等方法。这两将两者融合在一个示例中,api用的不全,但是能起到一个抛转引玉的作用。 地图 view.anima…

Java while和do while循环详解

循环是程序中的重要流程结构之一。循环语句能够使程序代码重复执行&#xff0c;适用于需要重复一段代码直到满足特定条件为止的情况。所有流行的编程语言中都有循环语句。Java 中采用的循环语句与C语言中的循环语句相似&#xff0c;主要有 while、do-while 和 for。另外 Java 5…

ROS2机器人编程简述humble-第一章-Introduction

ROS2机器人编程简述新书推荐-A Concise Introduction to Robot Programming with ROS2学习笔记流水账-推荐阅读原书。第一章&#xff1a;简要介绍宏观概念&#xff0c;配置编译一下本书配套的源码包。支持版本个人测试foxy和humble全部都OK。硬件软件机器人应用关系如下图所示&…

【阶段四】Python深度学习01篇:深度学习基础知识:神经网络历史及优势、神经网络基础单元与梯度下降:正向传播和反向传播

本篇的思维导图: 神经网络历史及优势 1958年,计算机科学家罗森布拉特(Rosenblatt)就提出了一种具有单层网络特性的神经网络结构,称为“感知器”(perceptron)。感知器出现之后很受瞩目,大家对它的期望很高。然而好景不长—一段时间后,人们发现感知器的实用性很…

2022.12 青少年机器人技术等级考试理论综合试卷(一级)

2022年12月 青少年机器人技术等级考试理论综合试卷&#xff08;一级&#xff09; 分数&#xff1a; 100 题数&#xff1a; 45 一、 单选题(共 30 题&#xff0c; 共 60 分) 1.下列哪个是机器人?&#xff08; &#xff09; A.a B.b C.c D.d 标准答案&#xff1a; C 2.机器人的电…

1-计算机系统概述(CO)

计算机组成原理&#xff1a;实现计算机体系结构所体现的属性&#xff0c;具体指令的实现对程序员透明&#xff0c;即研究如何用硬件实现所定义的接口 计算机系统硬件&#xff08;计算机的实体&#xff0c;如主机、外设&#xff09;软件&#xff08;由具有各类特殊功能的程序组…

【博客587】ipvs hook点在netfilter中的位置以及优先级

ipvs hook点在netfilter中的位置以及优先级 1、netfilter栈全景图 2、Netfilter hooks 五个hook点&#xff1a; 每个 hook 在内核网络栈中对应特定的触发点位置&#xff0c;以 IPv4 协议栈为例&#xff0c;有以下 netfilter hooks 定义&#xff1a; NF_INET_PRE_ROUTING:…

深入理解数据结构 —— 差分

什么是差分 对于一个数组a&#xff1a;a1,a2,a3...an 我们构造一个数组b&#xff1a;b1,b2,b3...bn 使得数组a是数组b的前缀和数组&#xff0c;即ai b1 b2 ... bi 则数组b就是数组a的差分 差分有什么用 当我们得到数组b后&#xff0c;只用对b求一遍前缀和&#xff0c;…

使用ChatGPT智能搜索论文

对于天天查找论文的小伙伴来说&#xff0c;有一个好用的搜索工具&#xff0c;那简直不要太开心&#xff0c;效率妥妥的上升。但现实结果却是&#xff0c;要么搜索工具不给力&#xff0c;要么自己输入的关键词不起作用&#xff0c;反正&#xff0c;自己脑海里想找寻的论文和搜索…