cuda编程学习——GPU加速/时间计时Clock 干货向(五)

news2024/11/29 12:33:30

前言

参考资料:

高升博客
《CUDA C编程权威指南》
以及 CUDA官方文档
CUDA编程:基础与实践 樊哲勇

文章所有代码可在我的GitHub获得,后续会慢慢更新

文章、讲解视频同步更新公众《AI知识物语》,B站:出门吃三碗饭

1:常用的计时方式

1cudaEvent_t start, stop;
2CHECK(cudaEventCreate(&start));
3CHECK(cudaEventCreate(&stop));
4CHECK(cudaEventRecord(start));
5cudaEventQuery(start); // 此处不能用 CHECK 宏函数(见第 4 章的讨论) 
6 
7 需要计时的代码块 
8 
9 CHECK(cudaEventRecord(stop));
10CHECK(cudaEventSynchronize(stop)); 
11 float elapsed_time;
12 CHECK(cudaEventElapsedTime(&elapsed_time, start, stop));
13printf("Time = %g ms.\n", elapsed_time);
14 
15CHECK(cudaEventDestroy(start));
16CHECK(cudaEventDestroy(stop));

第1行:定义2个cuda事件类型cudaEvent的变量start,stop
第2 3行:使用cudaEventCreate函数初始化两个变量
第4行:将 start 传入 cudaEventRecord 函数,在需要计时的代码块之前记录一个代表 开始的事件
第5行:对处于 TCC 驱动模式的 GPU 来说可以省略,但对处于 WDDM 驱动模式 的GPU来说必须保留
第7行:代表一个需要计时的代码块
第9行:将stop传入cudaEventRecord函数,在需要计时的代码块之后记录一个代表结 束的事件。
第10行: cudaEventSynchronize 函数让主机等待事件 stop 被记录完毕
第11-13行:调用 cudaEventElapsedTime 函数计算 start 和 stop 这两个事件之间的时 间差(单位是 ms)并输出到屏幕
第15-16行:调用 cudaEventDestroy 函数销毁 start 和 stop 这两个CUDA事件

2:测试add2cpu性能

100000000个元素相加,时间结果如下,在171ms上下


#include <math.h>
#include <stdio.h>
#include<stdint.h>
#include<cuda.h>
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <stdio.h>


#include <stdio.h>

#define CHECK(call)                                   \
do                                                    \
{                                                     \
    const cudaError_t error_code = call;              \
    if (error_code != cudaSuccess)                    \
    {                                                 \
        printf("CUDA Error:\n");                      \
        printf("    File:       %s\n", __FILE__);     \
        printf("    Line:       %d\n", __LINE__);     \
        printf("    Error code: %d\n", error_code);   \
        printf("    Error text: %s\n",                \
            cudaGetErrorString(error_code));          \
        exit(1);                                      \
    }                                                 \
} while (0)



#ifdef USE_DP
typedef double real;
const real EPSILON = 1.0e-15;
#else
typedef float real;
const real EPSILON = 1.0e-6f;
#endif

const int NUM_REPEATS = 10;
const real a = 1.23;
const real b = 2.34;
const real c = 3.57;
void add(const real* x, const real* y, real* z, const int N);
void check(const real* z, const int N);

int main(void)
{
    const int N = 100000000;
    const int M = sizeof(real) * N;
    real* x = (real*)malloc(M);
    real* y = (real*)malloc(M);
    real* z = (real*)malloc(M);

    for (int n = 0; n < N; ++n)
    {
        x[n] = a;
        y[n] = b;
    }

    float t_sum = 0;
    float t2_sum = 0;
    for (int repeat = 0; repeat <= NUM_REPEATS; ++repeat)
    {
        cudaEvent_t start, stop;
        CHECK(cudaEventCreate(&start));
        CHECK(cudaEventCreate(&stop));
        CHECK(cudaEventRecord(start));
        cudaEventQuery(start);

        add(x, y, z, N);

        CHECK(cudaEventRecord(stop));
        CHECK(cudaEventSynchronize(stop));
        float elapsed_time;
        CHECK(cudaEventElapsedTime(&elapsed_time, start, stop));
        printf("Time = %g ms.\n", elapsed_time);

        if (repeat > 0)
        {
            t_sum += elapsed_time;
            t2_sum += elapsed_time * elapsed_time;
        }

        CHECK(cudaEventDestroy(start));
        CHECK(cudaEventDestroy(stop));
    }

    const float t_ave = t_sum / NUM_REPEATS;
    const float t_err = sqrt(t2_sum / NUM_REPEATS - t_ave * t_ave);
    printf("Time = %g +- %g ms.\n", t_ave, t_err);

    check(z, N);

    free(x);
    free(y);
    free(z);
    return 0;
}

void add(const real* x, const real* y, real* z, const int N)
{
    for (int n = 0; n < N; ++n)
    {
        z[n] = x[n] + y[n];
    }
}

void check(const real* z, const int N)
{
    bool has_error = false;
    for (int n = 0; n < N; ++n)
    {
        if (fabs(z[n] - c) > EPSILON)
        {
            has_error = true;
        }
    }
    printf("%s\n", has_error ? "Has errors" : "No errors");
}



在这里插入图片描述

3:测试add2gpu性能

100000000个元素相加,时间结果如下,在7.94ms上下
其中我们还可以通过优化 grid_size,以及block_size的取值来进一步提速
add<<<grid_size, block_size>>>(d_x, d_y, d_z, N);


#include <math.h>
#include <stdio.h>
#include <math.h>
#include <stdio.h>
#include<stdint.h>
#include<cuda.h>
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <stdio.h>

#define CHECK(call)                                   \
do                                                    \
{                                                     \
    const cudaError_t error_code = call;              \
    if (error_code != cudaSuccess)                    \
    {                                                 \
        printf("CUDA Error:\n");                      \
        printf("    File:       %s\n", __FILE__);     \
        printf("    Line:       %d\n", __LINE__);     \
        printf("    Error code: %d\n", error_code);   \
        printf("    Error text: %s\n",                \
            cudaGetErrorString(error_code));          \
        exit(1);                                      \
    }                                                 \
} while (0)


#ifdef USE_DP
    typedef double real;
    const real EPSILON = 1.0e-15;
#else
    typedef float real;
    const real EPSILON = 1.0e-6f;
#endif

const int NUM_REPEATS = 10;
const real a = 1.23;
const real b = 2.34;
const real c = 3.57;
void __global__ add(const real *x, const real *y, real *z, const int N);
void check(const real *z, const int N);

int main(void)
{
    const int N = 100000000;
    const int M = sizeof(real) * N;
    real *h_x = (real*) malloc(M);
    real *h_y = (real*) malloc(M);
    real *h_z = (real*) malloc(M);

    for (int n = 0; n < N; ++n)
    {
        h_x[n] = a;
        h_y[n] = b;
    }

    real *d_x, *d_y, *d_z;
    CHECK(cudaMalloc((void **)&d_x, M));
    CHECK(cudaMalloc((void **)&d_y, M));
    CHECK(cudaMalloc((void **)&d_z, M));
    CHECK(cudaMemcpy(d_x, h_x, M, cudaMemcpyHostToDevice));
    CHECK(cudaMemcpy(d_y, h_y, M, cudaMemcpyHostToDevice));

    const int block_size = 128;
    const int grid_size = (N + block_size - 1) / block_size;

    float t_sum = 0;
    float t2_sum = 0;
    for (int repeat = 0; repeat <= NUM_REPEATS; ++repeat)
    {
        cudaEvent_t start, stop;
        CHECK(cudaEventCreate(&start));
        CHECK(cudaEventCreate(&stop));
        CHECK(cudaEventRecord(start));
        cudaEventQuery(start);

        add<<<grid_size, block_size>>>(d_x, d_y, d_z, N);

        CHECK(cudaEventRecord(stop));
        CHECK(cudaEventSynchronize(stop));
        float elapsed_time;
        CHECK(cudaEventElapsedTime(&elapsed_time, start, stop));
        printf("Time = %g ms.\n", elapsed_time);

        if (repeat > 0)
        {
            t_sum += elapsed_time;
            t2_sum += elapsed_time * elapsed_time;
        }

        CHECK(cudaEventDestroy(start));
        CHECK(cudaEventDestroy(stop));
    }

    const float t_ave = t_sum / NUM_REPEATS;
    const float t_err = sqrt(t2_sum / NUM_REPEATS - t_ave * t_ave);
    printf("Time = %g +- %g ms.\n", t_ave, t_err);

    CHECK(cudaMemcpy(h_z, d_z, M, cudaMemcpyDeviceToHost));
    check(h_z, N);

    free(h_x);
    free(h_y);
    free(h_z);
    CHECK(cudaFree(d_x));
    CHECK(cudaFree(d_y));
    CHECK(cudaFree(d_z));
    return 0;
}

void __global__ add(const real *x, const real *y, real *z, const int N)
{
    const int n = blockDim.x * blockIdx.x + threadIdx.x;
    if (n < N)
    {
        z[n] = x[n] + y[n];
    }
}

void check(const real *z, const int N)
{
    bool has_error = false;
    for (int n = 0; n < N; ++n)
    {
        if (fabs(z[n] - c) > EPSILON)
        {
            has_error = true;
        }
    }
    printf("%s\n", has_error ? "Has errors" : "No errors");
}


在这里插入图片描述

4: 提高算术复杂度 arithmetic2cpu

在计算过程使用了循环 、sqrt等方式,增加计算复杂性,设N为10000,
cpu运行时长从171ms增长到了370ms (虽然N减少了,但因为复杂度上去了,计算更加耗时)

#include <math.h>
#include <stdio.h>
#include <math.h>
#include <stdio.h>
#include<stdint.h>
#include<cuda.h>
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <stdio.h>

#define CHECK(call)                                   \
do                                                    \
{                                                     \
    const cudaError_t error_code = call;              \
    if (error_code != cudaSuccess)                    \
    {                                                 \
        printf("CUDA Error:\n");                      \
        printf("    File:       %s\n", __FILE__);     \
        printf("    Line:       %d\n", __LINE__);     \
        printf("    Error code: %d\n", error_code);   \
        printf("    Error text: %s\n",                \
            cudaGetErrorString(error_code));          \
        exit(1);                                      \
    }                                                 \
} while (0)

#ifdef USE_DP
typedef double real;
#else
typedef float real;
#endif

const int NUM_REPEATS = 10;
const real x0 = 100.0;
void arithmetic(real* x, const real x0, const int N);

int main(void)
{
    const int N = 10000;
    const int M = sizeof(real) * N;
    real* x = (real*)malloc(M);

    float t_sum = 0;
    float t2_sum = 0;
    for (int repeat = 0; repeat <= NUM_REPEATS; ++repeat)
    {
        for (int n = 0; n < N; ++n)
        {
            x[n] = 0.0;
        }

        cudaEvent_t start, stop;
        CHECK(cudaEventCreate(&start));
        CHECK(cudaEventCreate(&stop));
        CHECK(cudaEventRecord(start));
        cudaEventQuery(start);

        arithmetic(x, x0, N);

        CHECK(cudaEventRecord(stop));
        CHECK(cudaEventSynchronize(stop));
        float elapsed_time;
        CHECK(cudaEventElapsedTime(&elapsed_time, start, stop));
        printf("Time = %g ms.\n", elapsed_time);

        if (repeat > 0)
        {
            t_sum += elapsed_time;
            t2_sum += elapsed_time * elapsed_time;
        }

        CHECK(cudaEventDestroy(start));
        CHECK(cudaEventDestroy(stop));
    }

    const float t_ave = t_sum / NUM_REPEATS;
    const float t_err = sqrt(t2_sum / NUM_REPEATS - t_ave * t_ave);
    printf("Time = %g +- %g ms.\n", t_ave, t_err);

    free(x);
    return 0;
}

void arithmetic(real* x, const real x0, const int N)
{
    for (int n = 0; n < N; ++n)
    {
        real x_tmp = x[n];
        while (sqrt(x_tmp) < x0)
        {
            ++x_tmp;
        }
        x[n] = x_tmp;
    }
}

在这里插入图片描述

5: 提高算术复杂度 arithmetic2Gpu

在计算过程使用了循环 、sqrt等方式,增加计算复杂性,设N为10000,
gpu运行时长从7.94ms增长到了10.97ms(虽然N减少了,但因为复杂度上去了,计算更加耗时)

#include <math.h>
#include <stdio.h>
#include <math.h>
#include <stdio.h>
#include<stdint.h>
#include<cuda.h>
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <stdio.h>

#define CHECK(call)                                   \
do                                                    \
{                                                     \
    const cudaError_t error_code = call;              \
    if (error_code != cudaSuccess)                    \
    {                                                 \
        printf("CUDA Error:\n");                      \
        printf("    File:       %s\n", __FILE__);     \
        printf("    Line:       %d\n", __LINE__);     \
        printf("    Error code: %d\n", error_code);   \
        printf("    Error text: %s\n",                \
            cudaGetErrorString(error_code));          \
        exit(1);                                      \
    }                                                 \
} while (0)
#ifdef USE_DP
typedef double real;
#else
typedef float real;
#endif

const int NUM_REPEATS = 10;
const real x0 = 100.0;
void arithmetic(real* x, const real x0, const int N);

int main(void)
{
    const int N = 10000;
    const int M = sizeof(real) * N;
    real* x = (real*)malloc(M);

    float t_sum = 0;
    float t2_sum = 0;
    for (int repeat = 0; repeat <= NUM_REPEATS; ++repeat)
    {
        for (int n = 0; n < N; ++n)
        {
            x[n] = 0.0;
        }

        cudaEvent_t start, stop;
        CHECK(cudaEventCreate(&start));
        CHECK(cudaEventCreate(&stop));
        CHECK(cudaEventRecord(start));
        cudaEventQuery(start);

        arithmetic(x, x0, N);

        CHECK(cudaEventRecord(stop));
        CHECK(cudaEventSynchronize(stop));
        float elapsed_time;
        CHECK(cudaEventElapsedTime(&elapsed_time, start, stop));
        printf("Time = %g ms.\n", elapsed_time);

        if (repeat > 0)
        {
            t_sum += elapsed_time;
            t2_sum += elapsed_time * elapsed_time;
        }

        CHECK(cudaEventDestroy(start));
        CHECK(cudaEventDestroy(stop));
    }

    const float t_ave = t_sum / NUM_REPEATS;
    const float t_err = sqrt(t2_sum / NUM_REPEATS - t_ave * t_ave);
    printf("Time = %g +- %g ms.\n", t_ave, t_err);

    free(x);
    return 0;
}

void arithmetic(real* x, const real x0, const int N)
{
    for (int n = 0; n < N; ++n)
    {
        real x_tmp = x[n];
        while (sqrt(x_tmp) < x0)
        {
            ++x_tmp;
        }
        x[n] = x_tmp;
    }
}

在这里插入图片描述

6:实验结果:

运行设环,Nvidia11.6,显卡3050Ti

                  1w次循环复杂运算       1亿元素相加
CPU                 370ms                171ms
 

GPU                 10.97ms              7.94ms

7:总结(优化性能)

优化性能必要条件:

(1)数据传输比例较小。
(2) 核函数的算术强度较高。
(3)核函数中定义的线程数目较多。

编程手段:

• 减少主机与设备之间的数据传输
• 提高核函数的算术强度
• 增大核函数的并行规模

8:拓展

(1)数据传输的比例
如果一个程序的目的仅仅是计算两个数组的和,那么 用GPU可能比用CPU还要慢。这是因为,花在数据传输(CPU与GPU之间)上的时间比计算(求和)本身还要多很多。GPU计算核心和设备内存之间数据传输的峰值理论带宽要 远高于 GPU 和 CPU 之间数据传输的带宽。
设计算任务不是做一次数组相加的计算,而是做10000次数组相加的计算,而且只需 要在程序的开始和结束部分进行数据传输,那么数据传输所占的比例将可以忽略不计。此时,整个 CUDA 程序的性能就大为提高。

(2)算术强度
数组相加的问题之 所以很难得到更高的加速比,是因为该问题的算术强度(arithmetic intensity)不高。一个 计算问题的算术强度指的是其中算术操作的工作量与必要的内存操作的工作量之比。
例如, 在数组相加的问题中,在对每一对数据进行求和时需要先将一对数据从设备内存中取出来, 然后对它们实施求和计算,最后再将计算的结果存放到设备内存。这个问题的算术强度其 实是不高的,因为在取两次数据、存一次数据的情况下只做了一次求和计算。在CUDA中,设备内存的读、写都是代价高昂(比较耗时)的。

(3)并行规模:
并行规模可用 GPU 中总的线程数目来衡量。
从硬件的角度来看,一个GPU由多个流多处理器(streaming multiprocessor,SM)构成,而每个SM中有若干CUDA核心。每个SM是相对独立的。从开普勒架构到伏特架 构,一个SM中最多能驻留(reside)的线程个数是 2048。对于图灵架构,该数目是 1024。 一块GPU中一般有几个到几十个SM(取决于具体的型号)。所以,一块GPU一共可以驻 留几万到几十万个线程。如果一个核函数中定义的线程数目远小于这个数的话,就很难得到很高的加速比。
在这里插入图片描述

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

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

相关文章

路径规划算法:基于花授粉优化的路径规划算法- 附代码

路径规划算法&#xff1a;基于花授粉优化的路径规划算法- 附代码 文章目录 路径规划算法&#xff1a;基于花授粉优化的路径规划算法- 附代码1.算法原理1.1 环境设定1.2 约束条件1.3 适应度函数 2.算法结果3.MATLAB代码4.参考文献 摘要&#xff1a;本文主要介绍利用智能优化算法…

面试官:这么简单的二叉树算法都不会?

今天我们来看一个有趣的算法题&#xff0c;也是一道高频面试题。这个题目是leetcode的第572题&#xff0c;要求是这样的&#xff1a;给定两颗二叉树A和B&#xff0c;判断B是否是A的子树。在下面这个例子中可以看到B是A的子树。 想一想该怎样解决这个问题呢&#xff1f;如果B是A…

Python丨tkinter开发常用的29种功能用法(建议码住)

在Python软件开发中&#xff0c;tkinter中command功能的作用是为按钮、菜单等组件绑定回调函数&#xff0c;用户操作该组件时会触发相应的函数执行。 本文涵盖了各种组件和功能&#xff1a; 1、为Button组件&#xff08;按钮&#xff09;绑定回调函数 import tkinter as tk …

模拟量偏差报警功能块(SCL代码)

工业模拟量采集的相关基础知识,可以查看专栏的系列文章,这里不再赘述,常用链接如下: PLC模拟量采集算法数学基础(线性传感器)_plc傳感器數據轉化_RXXW_Dor的博客-CSDN博客模拟量采集库如何设计,具体算法代码请参看我的另一篇博文:PLC模拟量输入 模拟量转换FC:S_ITR_R…

栈和队列(详解)

&#x1f355;博客主页&#xff1a;️自信不孤单 &#x1f36c;文章专栏&#xff1a;数据结构与算法 &#x1f35a;代码仓库&#xff1a;破浪晓梦 &#x1f36d;欢迎关注&#xff1a;欢迎大家点赞收藏关注 文章目录 &#x1f353;栈1. 栈的概念及结构2. 栈的实现2.1 初始化栈2.…

MySQL运维篇(三)

五.读写分离 5.1 介绍 读写分离,简单地说是把对数据库的读和写操作分开,以对应不同的数据库服务器。主数据库提供写操作&#xff0c;从数据库提供读操作&#xff0c;这样能有效地减轻单台数据库的压力。 通过MyCat即可轻易实现上述功能&#xff0c;不仅可以支持MySQL&#x…

【论文总结】Composition Kills: A Case Study of Email Sender Authentication

构成杀伤力&#xff1a; 电子邮件发送者认证的案例研究 摘要 基于组件的软件设计是构建现代软件系统的一种主要工程方法。然而&#xff0c;由于不同组件之间对信息的解释可能不一致&#xff0c;这种编程范式产生了安全问题。在本文中&#xff0c;我们利用这种不一致来识别电子…

双列集合 JAVA

双列集合 一次需要添加一对数据&#xff0c;分别为键和值键不可以重复&#xff0c;值可以重复键和值是一一对应的&#xff0c;每一个键只可以找到自己对应的值键值对在java中也叫做Entry对象 #mermaid-svg-zKLj0vUbRaN9zlse {font-family:"trebuchet ms",verdana,ar…

SpringBoot2-基础入门(二)

SpringBoot2 - 基础入门&#xff08;二&#xff09; 了解自动装配原理 文章目录 SpringBoot2 - 基础入门&#xff08;二&#xff09;了解自动装配原理一、依赖管理1.1 父项目做依赖管理1.2 starer场景启动器 2、自动配置2.1 自动配置依赖2.2 组件扫描 3、配置文件3.1 各种配置…

【软件测试知识】

目录 软件测试软件测试模型瀑布模型V 模型W 模型敏捷开发模型 软件开发流程软件测试方法白盒测试黑盒测试 软件测试 软件测试模型 说到开发模型&#xff0c;从软件发展来看&#xff0c;比较典型的有瀑布模型&#xff0c;V 模型和 W 模型以及 敏捷开发模型。并不是说开发模型的…

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、

&#x1f600;如果对你有帮助的话&#x1f60a; &#x1f33a;为博主点个赞吧 &#x1f44d; &#x1f44d;点赞是对博主最大的鼓励&#x1f60b; &#x1f493;爱心发射~&#x1f493; 目录 一、发送邮件1、启用客户端SMTP服务2、导入jar包3、邮箱参数配置MailClientdemo.html…

第13届蓝桥杯Scratch省赛真题集锦

编程题 第 1 题 问答题 报数游戏 题目说明 背景信息: 5个男生和3个女生&#xff0c;8个人围成一个圆圈&#xff0c;给定一个数字n (2 小于等于n 小于等于5)。从第一个开始依次报数&#xff0c;当报数为n时&#xff0c;这个人离开圆圈。然后下一个从1开始报数&#xff0c;再次报…

MySQL---使用索引优化、大批量插入数据优化

1. 使用索引优化 索引是数据库优化最常用也是最重要的手段之一, 通过索引通常可以帮助用户解决大多数的MySQL 的性能优化问题&#xff1a; create table tb_seller (sellerid varchar (100),name varchar (100),nickname varchar (50),password varchar (60),status varchar…

高级Java多线程面试题及回答

高级Java多线程面试题及回答 1)现在有T1、T2、T3三个线程&#xff0c;你怎样保证T2在T1执行完后执行&#xff0c;T3在T2执行完后执行? 这个线程问题通常会在第一轮或电话面试阶段被问到&#xff0c;目的是检测你对”join”方法是否熟悉。这个多线程问题比较简单&#xff0c;可…

网络安全的红利还能吃几年?

在我看来这是一个伪命题&#xff0c;因为网络安全的核心和本质是持续对抗&#xff0c;只要威胁持续存在&#xff0c;网络安全的红利就会持续存在&#xff01; 对于网络安全新入行的同学们来说&#xff0c;这是一个最坏的时代&#xff0c;因为你只能自己搭环境才能重现那些大牛们…

网络编程 lesson5 IO多路复用

select 当需要在一个或多个文件描述符上等待事件发生时&#xff0c;可以使用select函数。 select函数是一个阻塞调用&#xff0c;它会一直等待&#xff0c;直到指定的文件描述符上有事件发生或超时。 select函数详解 int select(int nfds, fd_set *readfds, fd_set *writefd…

初识SPDK,从SPDK的软件架构到使用实操

相信很多做存储的同学都听说过SPDK,它是Intel开发的一套开源存储栈。SPDK的全称为存储高性能开发包(Storage Performance Development Kit),从名称可以看出SPDK其实就是一个第三方的程序库。但是这个程序库却是非常强大的,下图是SPDK的软件模块图,从该图可以看出,几乎囊…

Linux---用户管理命令(useradd、userdel、usermod、passwd、id)

1. 用户与用户组 Linux系统是一个多用户多任务的分时操作系统&#xff0c;任何一个要使用系统资源的用户&#xff0c;都必须首先向 系统管理员申请一个账号&#xff0c;然后以这个账号的身份进入系统。 Linux系统中可以&#xff1a; 配置多个用户、配置多个用户组、用户可以…

什么?电路板上还要喷漆?

什么是三防漆&#xff1f; 三防漆是一种特殊配方的涂料&#xff0c;用于保护线路板及其相关设备免受环境的侵蚀。三防漆具有良好的耐高低温性能&#xff1b;其固化后成一层透明保护膜&#xff0c;具有优越的绝缘、防潮、防漏电、防震、防尘、防腐蚀、防老化、耐电晕等性能。 在…

MIT6824——lab4(实现一个分片kv存储)的一些实现,问题,和思考

Part A 分片控制器 1. 整体思路 和lab3A一样&#xff0c;shardctler也是一个服务&#xff0c;由客户端调用。这个服务建立在raft集群上&#xff0c;保证容错。 shardctler也应该保证线性一致性和重复请求的问题&#xff0c;因此也需要记录clientid和messageid。 shardctler保…