CUDA学习笔记(十二) CUDA库简介

news2024/11/16 15:50:31

CUDA Libraries简介

 

上图是CUDA 库的位置,本文简要介绍cuSPARSE、cuBLAS、cuFFT和cuRAND,之后会介绍OpenACC。

  • cuSPARSE线性代数库,主要针对稀疏矩阵之类的。
  • cuBLAS是CUDA标准的线代库,不过没有专门针对稀疏矩阵的操作。
  • cuFFT傅里叶变换
  • cuRAND随机数

CUDA库和CPU编程所用到的库没有什么区别,都是一系列接口的集合,主要好处是,只需要编写host代码,调用相应API即可,可以节约很多开发时间。而且我们完全可以信任这些库能够达到很好的性能,写这些库的人都是在CUDA上的大能,一般人比不了。当然,完全依赖于这些库而对CUDA性能优化一无所知也是不行的,我们依然需要手动做一些改进来挖掘出更好的性能。

下图是《CUDA C编程》中提到的一些支持的库,具体细节可以在NVIDIA开发者论坛查看:

 

 

如果大家的APP属于上面库的应用范围,非常建议大家使用。

A Common Library Workflow

下面是一个使用CUDA库的具体步骤,当然,各个库的使用可能不尽相同,但是不会逃脱下面的几个步骤,差异基本上就是少了哪几步而已。

  1. 创建一个库的句柄来管理上下文信息。
  2. 分配device存储空间给输入输出。
  3. 如果输入的格式并不是库中API支持的需要做一下转换。
  4. 填充device Memory数据。
  5. 配置library computation以便执行。
  6. 调用库函数来让GPU工作。
  7. 取回device Memory中的结果。
  8. 如果取回的结果不是APP的原始格式,就做一次转换。
  9. 释放CUDA资源。
  10. 继续其它的工作。

下面是这几个步骤的一些细节解释:

Stage1:Creating a Library Handle

CUDA库好多都有一个handle的概念,其包含了该库的一些上下文信息,比如数据格式、device的使用等。对于使用handle的库,我们第一步就是初始化这么一个东西。一般的,我们可以认为这是一个存放在host对程序员透明的object,这个object包含了跟这个库相关联的一些信息。例如,我们可定希望所有的库的操作运行在一个特别的CUDA stream,尽管不同的库使用不同函数名字,但是大多数都会规定所有的库操作以一定的stream发生(比如cuSPARSE使用cusparseSetSStream、cuBLAS使用cublasSetStream、cuFFT使用cufftSetStream)。stream的信息就会保存在这个handle中。

Stage2:Allocating Device Memory

本文所讲的库,其device存储空间的分配依然是cudaMalloc或者库自己调用cudaMalloc。只有在使用多GPU编程的库时,才会使用一些定制的API来实现内存分配。

Stage3:Converting Inputs to a Library-Supported Format

如果APP的数据格式和库要求的输入格式不同的话,就需要做一次转化。比如,我们APP存储一个row-major的2D数组,但是库却要求一个column-major,这就需要做一次转换了。为了最优性能,我们应该尽量避免这种转化,也就是尽量和库的格式保持一致。

Stage4:Populating Device Memory with Inputs

完成上述三步后,就是将host的数据传送到device了,也就是类似cudaMemcpy的作用,之所说类似,是引文大部分库都有自己的API来实现这个功能,而不是直接调用cudaMemcpy。例如,当使用cuBLAS的时候,我们要将一个vector传送到device,使用的就是cubalsSetVector,当然其内部还是调用了cudaMemcpy或者其他等价函数来实现传输。

Stage5:Configuring the Library

有步骤3知道,数据格式是个明显的问题,库函数需要知道自己应该使用什么数据格式。某些情况下,类似数据维度之类的数据格式信息会直接当做函数参数配置,其它的情形下,就需要手动来配置下之前说的库的handle了。还有个别情况是,我们需要管理一些分离的元数据对象。

Stage6:Executing

执行就简单多了,做好之前的步骤,配置好参数,直接调用库API。

Stage7:Retrieving Results from Device Memory

这一步将计算结果从device送回host,当然还是需要注意数据格式,这一步就是步骤4的反过程。

Stage8:Converting Back to Native Format

如果计算结果和APP的原始数据格式不同,就需要做一次转化,这一步是步骤3的反过程。

Stage9:Releasing CUDA Resources

如果上面步骤使用的内存资源不再使用就需要释放掉,正如我们以前介绍的那样,内存的分配和释放是非常大的负担,所以希望尽可能的资源重用。比如device Memory、handles和CUDA stream这些资源。

Stage10:Continuing with the Application

继续干别的。

再次重申,上面的步骤可能会给大家使用库是非常麻烦低效的事儿,但其实这些步骤一般是冗余的,很多情况下,其中的很多步骤是不必要的,在下面的章节我们会介绍几个主要的库以及其简要使用,相信看过后,你就不会认为使用库得不偿失了。

THE CUSPARSE LIBRARY

cuSPARSE就是一个线性代数库,对稀疏矩阵之类的操作尤其独到的用法,使用很宽泛。他当对稠密和稀疏的数据格式都支持。

下图是该库的一些函数调用,从中可以对其功能有一个大致的了解。cuSPARSE将函数以level区分,所有level 1的function仅操作稠密和稀疏的vector。所有level2函数操作稀疏矩阵和稠密vector。所有level3函数操作稀疏和稠密矩阵。

 

cuSPARSE Data Storage Formats

稠密矩阵就是其中的值大部分非零。稠密矩阵所有值都是存储在一个多维的数组中的。相对而言,稀疏矩阵和vector中元素主要是零,所以其存储就可以做一些文章。比如我们可以仅仅保存非零值和其坐标。cuSPARSE支持很多种稀疏矩阵的存储方式,本文只介绍其中三种。

先看一下稠密(dens)矩阵的存储方式,图示很明显,不多说了:

 

Coordinate(COO)

对于稀疏矩阵中的每个非零值,COO方式都保存其行和列坐标,因此,当通过行列检索矩阵值的时候,如果该行列值没有在存储格式中匹配到的话,必然就是零了。

我们应该注意到了,所谓稀疏矩阵要稀疏到什么程度才能使用COO呢?这个需要具体问题具体分析了,主要跟元素数据类型和索引数据类型有关。比如,一个存储32-bit的浮点类型数据的稀疏矩阵,索引使用32-bit的整型格式,那么只有当非零数据少于于矩阵的三分之一的时候才会节约存储空间。

 

Compressed Sparse Row(CSR)

CSR和COO相似,唯一不同就是非零值的行索引。COO模式下,所有非零值都会对应一个int的行索引,而CSR则是存储一个偏移值,这个偏移值是所有属于同一行的值拥有的属性。如下图所示,相比COO,减少了row:

 

因为所有存储在同一行的数据在内存中是相邻的,要找到某一行对应的值只需要一个偏移量和length。例如,如果只想知道第三行的非零值,我们可以使用偏移量为2,length为2在V中检索,如下图所示:

 

对图中的C使用相同的偏移和length就能定位列索引,也就能完全确定一个value在矩阵中的位置。当存储一个很大的矩阵且相对来说每行数据都很多的时候,使用CSR比存储每个非零值的索引要有效得多。

现在我们要考虑这些偏移地址和length的存储了,最简单的方式是创建两个数组Ro和Rl,每个都对应一个nRows用作length。如果矩阵有大量的行就需要分配两个很大的数组。鉴于此,我们可以使用单独的一个length为nRows+1的数组R,第i行的偏移地址就存储在R[i]。第i行的长度可以通过比较R[I+1]和R[i]值来做出判断,还有就是R[i+1]是用来存储矩阵非零值的总数的。本例中R数组如下:

 

由上图知,0行的偏移地址是0,1行偏移地址是1,2行偏移地址是2,共有4个非零元素,我们可以找矩阵行为0的值及其列索引,由于R[1]-R[0]=1-0=1,说明第一行仅有一个非零值,其列索引为0,其值为3。

这样,对于每行都有多个非零值的稀疏矩阵存储,CSR比COO要节约空间。下图是CSR的完整示意图:

 

使用CSR格式稀疏矩阵的function很直观,首先,我们在host定义一个CSR格式的稀疏矩阵,其代码如下:

float *h_csrVals;
int *h_csrCols;
int *h_csrRows;

h_csrVals用来存储非零值个数,h_csrCols存储列索引,h_csrRows存储行偏移,接下来就是分配device内存之类的常规操作:

cudaMalloc((void **)&d_csrVals, n_vals * sizeof(float));
cudaMalloc((void **)&d_csrCols, n_vals * sizeof(int));
cudaMalloc((void **)&d_csrRows, (n_rows + 1) * sizeof(int));
cudaMemcpy(d_csrVals, h_csrVals, n_vals * sizeof(float),cudaMemcpyHostToDevice);
cudaMemcpy(d_csrCols, h_csrCols, n_vals * sizeof(int),cudaMemcpyHostToDevice);
cudaMemcpy(d_csrRows, h_csrRows, (n_rows + 1) * sizeof(int),cudaMemcpyHostToDevice);

上述三种(包括稠密矩阵)数据格式各有各擅长的方面。下图列出了cuSPARSE支持的一些数据格式以及各自的最佳使用场景:

 

Formatting Conversion with cuSPARSE

从前文可知,这个过程应该尽量避免,转换不仅需要有计算的开销,还有额外存储的空间浪费。还有就是在使用cuSPARSE也应该尽量发挥其在稀疏矩阵存储上的优势,因为好多APP的latency就是仅仅简单的使用稠密矩阵存储方式。因为cuSPARSE的数据格式众多,其用来转换的API也不少,下图列出了这些转换API。左边那一列是你要转换的目标格式,为空表示不支持两种数据格式的转换,尽管如此,你还可以通过多次转换来实现未显示支持的转换API,比如dense2bsr没有被支持,但是我们可以使用dense2csr和csr2bsr两个过程来达到目的。

 

Demonstrating cuSPARSE

这部分示例代码会涉及到矩阵向量相乘,数据格式转换,以及其他cuSPARSE的特征。

复制代码

// Create the cuSPARSE handle
cusparseCreate(&handle);
// Allocate device memory for vectors and the dense form of the matrix A
...
// Construct a descriptor of the matrix A
cusparseCreateMatDescr(&descr);
cusparseSetMatType(descr, CUSPARSE_MATRIX_TYPE_GENERAL);
cusparseSetMatIndexBase(descr, CUSPARSE_INDEX_BASE_ZERO);
// Transfer the input vectors and dense matrix A to the device
...
// Compute the number of non-zero elements in A
cusparseSnnz(handle, CUSPARSE_DIRECTION_ROW, M, N, descr, dA,M, dNnzPerRow, &totalNnz);
// Allocate device memory to store the sparse CSR representation of A
...
// Convert A from a dense formatting to a CSR formatting, using the GPU
cusparseSdense2csr(handle, M, N, descr, dA, M, dNnzPerRow,dCsrValA, dCsrRowPtrA, dCsrColIndA);
// Perform matrix-vector multiplication with the CSR-formatted matrix A
cusparseScsrmv(handle, CUSPARSE_OPERATION_NON_TRANSPOSE,M, N, totalNnz, &alpha, descr, dCsrValA, dCsrRowPtrA,dCsrColIndA, dX, &beta, dY);
// Copy the result vector back to the host
cudaMemcpy(Y, dY, sizeof(float) * M, cudaMemcpyDeviceToHost);

复制代码

上述代码的过程可以总结为:

  1. 使用cusparseCreate创建库的handle。
  2. 使用cudaMalloc分配device内存空间用来存储矩阵和向量,并分别使用dense和CSR两种格式存储。
  3. cusparseCreateMatDescr和cusparseSetMat*使用来配置矩阵属性的,cudaMemcpy用来拷贝数据到device,cusparseSdense2csr来生成CSR格式的数据,非零数据个数有cusparseSnnz计算得到。
  4. cusparseScsrmv是矩阵和向量乘操作函数。
  5. 再次使用cudaMemcpy将结果拷贝回host。
  6. 释放资源。

编译:

$ nvcc -lcusparse cusparse.cu –o cusparse

Important Topics in cuSPARSE Development

尽管cuSPARSE提供了一个相对来说最快速和简洁的方式以达到高性能的线性代数库,我们仍需要谨记cuSPARSE使用的一些关键点。

第一点就是,要保证正确的矩阵和向量的数据格式,cuSPARSE本身没有什么能力来检测出错误的或者不恰当的数据格式,而一次错误的格式操作就可能导致段错误,这也算是给自己debug提供一种方向吧,虽然段错误五花八门。对于矩阵和向量规模比较小的情况下,手动验证其数据格式还是可行的。我们可以将转换后的数据进行一次逆转换过程来和原始数据比对。

第二点是cuSPARSE的默认异步行为。当然这对于GPU编程来说,已经习以为常了,但是对于传统的host端阻塞式的数学库来说,GPU的计算结果会很有趣。对于cuSPARSE来说,如果使用了cudaMemcpy拷贝数据后,host会自动阻塞住,等待device的计算结果。但是如果cuSPARSE库被配置来使用CUDA steam和cudaMemcpyAsync,我们就需要多留一个心眼,使用确保正确的同步行为来获取device的计算结果。

最后一点比较新奇的是标量的使用,这里要使用标量的引用形式。如下代码中的beta变量:

float beta = 4.0f;
...
// Perform matrix-vector multiplication with the CSR-formatted matrix A
cusparseScsrmv(handle, CUSPARSE_OPERATION_NON_TRANSPOSE,
M, N, totalNnz, &alpha, descr, dCsrValA, dCsrRowPtrA,
dCsrColIndA, dX, &beta, dY);

如果不小心直接传递了beta这个参数,APP会报错(SEGFAULT),不注意的话这个bug很不好查。除此外,当标量作为输出参数时,可以使用指针。cuSPARSE提供了cusparseSetPointMode这个API来调整是否使用指针来获取计算结果。

THE cuBLAS LIBRARY

cuBLAS也是一个线代库,不同于cuSPARSE,cuBLAS传统线代库的接口,BLAS即Basic Linear Algebra Subprograms的意思。cuBLAS level1是专门的vector之间操作。level2是矩阵和向量之间的操作。level3是矩阵和矩阵之间的操作。相对于cuSPARSE,cuBLAS不支持稀疏矩阵数据格式,他只支持而且善于稠密矩阵和向量的使用。

由于BLAS库最初是由FORTRAN语言编写,他就是用了column-major和one-based的方式存储数据,而cuSPARSE则是使用的row-major。下图是这种方式的存储格式,一看便明:

 

我们可以比较下row-major和column-major将二维转化为一维的过程公式:

 

为了考虑兼容性,cuBLAS也使用了column-major的方式存储,所以,对于习惯C/C++的人来说,这可能比较让人困惑吧。

另一方面,就像C和其它语言那样,one-based索引意味着数组中第一个元素的引用使用1而不是0,也就是说,一个有N个元素的数组,其最后一个值的索引是N而不是N-1。

但是,cuBLAS没有办法决定C/C++(cuBLAS使用C/C++)编程的语境,所以他就必须使用zero-based索引,这就导致了一个奇怪的混乱情况,要满足FORTRAN的column-major,但one-based却不行。

cuBLAS提出了两个API,cuBLASASLegacy API是cuBLAS最开始的一个实现,已经废弃,当前使用cuBLAS API,二者差异很小。

看过接下来的内容,你会发现,cuBLAS的使用流程跟cuSPARSE有很多相同之处,所以对于这些库代码编写基本可以触类旁通。

Managing cuBLAS Data

相较于cuSPARSE,cuBLAS的数据格式要简单的多,所有操作都作用在稠密向量或矩阵。同样是使用cudaMalloc来分配device内存空间,但是使用cublasSetVector/cublasGetVector和cubalsSetMartix/cublasGetMartix在device和host之间传送数据(其实相对cuSPARSE也没多大差别)。本质上,这些API底层都是调用cudaMemcpy,而且,他们对Strided和unstrided数据都有很好的优化,比如下面的代码:

cublasStatus_t cublasSetMatrix(int rows, int cols, int elementSize,const void *A, int lda, void *B, int ldb);

这些参数大部分看名字就知道什么意思了,其中lda和ldb指明了源矩阵A和目的矩阵B的主维度(leading dimension),所谓主维就是矩阵的行总数,这个参数只在需要host矩阵一部分数据的时候很有用。也就是说,当需要完整的矩阵时,lda和ldb都应该是M。

如果我们使用一个稠密的二维column-major的矩阵A,其元素是单精度浮点类型,矩阵大小为MxN,则使用下面的函数传输矩阵:

cublasSetMatrix(M, N, sizeof(float), A, M, dA, M);

也可以如下传输一个只有一列的矩阵A到一个向量dV:

cublasStatus_t cublasSetVector(int n, int elemSize, const void *x, int incx,void *y, int incy);

x是host上源起始地址,y是device上目的起始地址,n是要传送数据的总数,elemSize是每个元素的大小,单位是byte,incx/incy是要传送的元素之间地址间隔,或者叫步调,传送一个单列长度M的column-major 矩阵A到向量dV如下:

cublasSetVector(M, sizeof(float), A, 1, dV, 1);

也可以如下传送一个单行的矩阵A到一个向量dV:

cublasSetVector(N, sizeof(float), A, M, dV, 1);

通过这些例子可以发现,使用cuBLAS要比cuSPARSE容易的多,所以除非我们的APP对稀疏矩阵需求比较大,一般都是用cuBLAS,保证性能的同时,还能提高开发效率。

Demonstrating cuBLAS

这部分代码主要关注cuBLAS的一些统一使用并理解他为什么易于使用。得益于GPU的高性能计算,表现要比在CPU上的BLAS号15倍,而且cuBLAS的开发也就比传统的BLAS稍微费事儿。

复制代码

// Create the cuBLAS handle
cublasCreate(&handle);
// Allocate device memory
cudaMalloc((void **)&dA, sizeof(float) * M * N);
cudaMalloc((void **)&dX, sizeof(float) * N);
cudaMalloc((void **)&dY, sizeof(float) * M);
// Transfer inputs to the device
cublasSetVector(N, sizeof(float), X, 1, dX, 1);
cublasSetVector(M, sizeof(float), Y, 1, dY, 1);
cublasSetMatrix(M, N, sizeof(float), A, M, dA, M);
// Execute the matrix-vector multiplication
cublasSgemv(handle, CUBLAS_OP_N, M, N, &alpha, dA, M, dX, 1,&beta, dY, 1);
// Retrieve the output vector from the device
cublasGetVector(M, sizeof(float), dY, 1, Y, 1);

复制代码

使用cuBLAS比较直观,易于理解,其使用步骤基本如下:

  1. 使用cublasCreateHandle创建handle。
  2. 使用cudaMalloc分配device资源。
  3. 使用cublasSetVector和cublasSetMartix向device填充数据。
  4. 使用cublasSgemv执行矩阵和向量的乘操作。
  5. 使用cublasGetVector获取计算结果。
  6. 释放资源。

编译命令:

$ nvcc -lcublas cublas.cu

Porting from BLAS

将一个传统的C实现的APP(使用BLAS库)转化为cuBLAS也是比较直观的,基本可以归纳为以下几步:

  1. 增加device Memory的分配操作(cudaMalloc)和其资源释放操作。
  2. 增加device和host之间数据传送的过程。
  3. 变更BLAS的API为等价的cuBLAS API。这一步比较麻烦,这里以之前的代码为例:

复制代码

// Allocate device memory
cudaMalloc((void **)&dA, sizeof(float) * M * N);
cudaMalloc((void **)&dX, sizeof(float) * N);
cudaMalloc((void **)&dY, sizeof(float) * M);
// Transfer inputs to the device
cublasSetVector(N, sizeof(float), X, 1, dX, 1);
cublasSetVector(M, sizeof(float), Y, 1, dY, 1);
cublasSetMatrix(M, N, sizeof(float), A, M, dA, M);
// Execute the matrix-vector multiplication
cublasSgemv(handle, CUBLAS_OP_N, M, N, &alpha, dA, M, dX, 1,&beta, dY, 1);
// Retrieve the output vector from the device
cublasGetVector(M, sizeof(float), dY, 1, Y, 1);

复制代码

其等价的BLAS代码是:

void cblas_sgemv(const CBLAS_ORDER order, const CBLAS_TRANSPOSE TransA,const MKL_INT M, const MKL_INT N, const float alpha, const float *A,const MKL_INT lda, const float *X, const MKL_INT incX, const float beta, float *Y,const MKL_INT incY);

二者还是有很多相似之处的,不同的是,BLAS有个order参数来使用户能够指定输入数据是row-major还是column-major。还有就是BLAS的beta和alpha没有使用引用形式,

4. 最后就是在实现功能后调节性能了,比如:

  • 复用device资源而不是释放。
  • device和host之间数据传输尽量减少冗余数据。
  • 使用stream-based执行来实现异步传输。

Important Topics in cuBLAS Development

相较于cuSPARSE,如果大家对BLAS熟悉的话,cuBLAS更容易上手。不过需要注意的是,虽然cuBLAS的行为更容易理解,但是有时候恰恰是这份理所当然的理解会造成一些认识误区,毕竟cuBLAS并不等于BLAS。

对于大部分习惯于row-major的编程语言,使用cuBLAS就得分外小心了,我们可能很熟悉将一个row-major的多维数组展开,但是过度到column-major会有点不适应,下面的宏定义可以帮我们实现row-major到column-major的转换:

#define R2C(r, c, nrows) ((c) * (nrows) + (r))

不过,当使用上述的宏时,仍然需要一些循环的顺序问题,对于C/C++程序猿来说,会经常用下面的代码:

for (int r = 0; r < nrows; r++) {
    for (int c = 0; c < ncols; c++) {
        A[R2C(r, c, nrows)] = ...
    }
}

代码当然没什么问题,但是却不是最优的,因为他在访问A的时候,不是线性扫描内存空间的。如果nrows非常大的话,cache命中率基本为零了。因此,我们需要下面这样的代码:

for (int c = 0; c < ncols; c++) {
    for (int r = 0; r < nrows; r++) {
        A[R2C(r, c, nrows)] = ...
    }
}

所以,做优化要步步小心,因为一个没注意,就有可能导致很差的cache命中。

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

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

相关文章

YOLOv5算法改进(20)— 如何去写YOLOv5相关的论文(包括论文阅读+规律总结+写作方法)

前言:Hello大家好,我是小哥谈。最近一直在阅读关于YOLOv5的相关论文,读着读着我发现一条可以发论文的规律,特此简单总结一下,希望能够对同学们有所启迪!🌈 前期回顾: YOLOv5算法改进(1)— 如何去改进YOLOv5算法

CanIf Transmit Buffering 机制说明

目录 前言未使能场景的报文发送机制使能场景的报文发送机制如何配置前言 在AUTOSAR CanIf 中,提供了发送的Buffering机制 。对于一个报文来说,Buffering起始于CanIf_Transmit,结束于CanIf_TxConfirmation 。 主要应用与一个MailBox 发送多个报文的场景,用于解决因为硬件发…

scp通过跳板机向服务器传文件的方法

scp上传 scp -P 端口号 要传的文件 服务器用户名服务器IP:服务器目录scp下载 scp -P 端口号 服务器用户名服务器IP:服务器目录 要下载的文件在实际情况下如果目标服务器无法通过ssh直接连接&#xff0c;需要跳板机才能连接&#xff0c;如何使用scp呢&#xff1f; 跳板机host…

​​​​​​​Python---练习:打印直角三角形(利用wihle循环嵌套)

案例&#xff1a; 打印直角三角形&#xff0c;特征&#xff1a;一共有5行&#xff0c;第1行&#xff0c;有1列。第2行&#xff0c;有2列&#xff0c;第3&#xff0c;有3列。 思考&#xff1a; pycharm里面&#xff0c;输出三角形&#xff0c;因为本来控制台就是长方形&#…

PLC单按钮启停算法汇总

单按钮启停在三菱PLC里可以通过简单的取反指令"ALT"实现,西门子PLC如何实现ALT指令,请参考下面文章链接,这篇博客我们汇总常用的单按钮启停实现方法,希望大家读了本篇博客后有所收获。 博途ALT指令 博途S7-1200/1500PLC 取反指令(ALT)-CSDN博客SMART PLC的ALT指…

62 最小路径和

最小路径和 题解1 DP 给定一个包含非负整数的 m x n 网格 grid &#xff0c;请找出一条 从左上角到右下角的路径&#xff0c;使得路径上的 数字总和为最小。 说明&#xff1a;每次只能向下或者向右移动一步。 题解1 DP class Solution { public:int minPathSum(vector&l…

从传统云架构到云原生生态体系架构的演进

文章目录 概述传统云架构&#xff1a;虚拟化的时代云原生生态体系架构的兴起容器化和微服务架构自动化和自动伸缩基础设施即代码云原生存储和数据库 云原生的影响结语 概述 随着科技的不断发展&#xff0c;云计算领域也经历了巨大的变革。这一演进的核心焦点是从传统云架构过渡…

活动回顾∣企企通亮相高质量企业数字化活动,深入探讨各领域采购数字化转型与变革

当前&#xff0c;以数字技术为代表的新一轮科技革新正在加速兴起&#xff0c;数字经济已成为推动我国社会经济发展的重要引擎&#xff0c;而数字化转型也成为构筑企业竞争新优势的有力支撑。 作为企业数字化采购与供应链协同服务的优秀厂商&#xff0c;企企通近期受邀参加了多场…

Redis为什么变慢了

一、Redis为什么变慢了 1.Redis真的变慢了吗? 对 Redis 进行基准性能测试 例如,我的机器配置比较低,当延迟为 2ms 时,我就认为 Redis 变慢了,但是如果你的硬件配置比较高,那么在你的运行环境下,可能延迟是 0.5ms 时就可以认为 Redis 变慢了。 所以,你只有了解了你的…

TechSmith Camtasia 2023 for Mac 屏幕录像视频录制编辑软件

​ TechSmith Camtasia for Mac 2023中文破解版 是一款专业的屏幕录像视频录制编辑软件&#xff0c;非常容易就可以获得精彩的截屏视频。创建引人注目的培训&#xff0c;演示和演示视频。Camtasia 屏幕录制软件简化&#xff0c;直观&#xff0c;让您看起来像专业人士。利用Camt…

图(graph)的遍历----深度优先(DFS)遍历

目录 前言 深度优先遍历&#xff08;DFS&#xff09; 1.基本概念 2.算法思想 3.二叉树的深度优先遍历&#xff08;例子&#xff09; 图的深度优先遍历 1.图(graph)邻接矩阵的深度优先遍历 思路分析 代码实现 2.图(graph)邻接表的深度优先遍历 思路分析 代码实现 递…

为什么需要it企业知识库?it企业知识库能带来什么?

在企业运营过程中&#xff0c;会产生大量的经营数据、管理规范、资料和文档等数据&#xff0c;但这些数据的产生时间和空间碎片化&#xff0c;数据来源和结构多种多样&#xff0c;信息关系也较为复杂。 it企业知识库 正是因为这些问题的存在&#xff0c;导致了企业信息管理零散…

【API篇】七、Flink窗口

文章目录 1、窗口2、分类3、窗口API概览4、窗口分配器 在批处理统计中&#xff0c;可以等待一批数据都到齐后&#xff0c;统一处理。但是在无界流的实时处理统计中&#xff0c;是来一条就得处理一条&#xff0c;那么如何统计最近一段时间内的数据呢&#xff1f; ⇒ 窗口的概念&…

ImportError: DLL load failed while importing MPI: 找不到指定的模块

在运行下面这行python代码时会报错 from mpi4py import MPI 原因就是缺少MPI模块 解决方法如下&#xff1a; 1.在MPI官网下载msmpisetup.exe和msmpisdk.msi两个文件&#xff0c;并且安装到默认路径下 2.添加环境变量 进入“控制面板——>高级系统设置——>环境变量”…

QWidget快速美化-蓝色分割线

将代码复制进Line的样式表 效果: 代码: Line{height:5background-color: rgba(255, 255, 255, 0);border-top:2px solid #52DCFE; }

SysTick—系统定时器

SysTick 简介 SysTick—系统定时器是属于CM3内核中的一个外设&#xff0c;内嵌在NVIC中。系统定时器是一个24bit 的向下递减的计数器&#xff0c;计数器每计数一次的时间为1/SYSCLK&#xff0c;一般我们设置系统时钟SYSCLK 等于72M。当重装载数值寄存器的值递减到0的时候&#…

基于蚁狮算法的无人机航迹规划-附代码

基于蚁狮算法的无人机航迹规划 文章目录 基于蚁狮算法的无人机航迹规划1.蚁狮搜索算法2.无人机飞行环境建模3.无人机航迹规划建模4.实验结果4.1地图创建4.2 航迹规划 5.参考文献6.Matlab代码 摘要&#xff1a;本文主要介绍利用蚁狮算法来优化无人机航迹规划。 1.蚁狮搜索算法 …

Tcl基础知识

一、概述 Tcl 语言的全称 Tool Command Language&#xff0c;即工具命令语言。这种需要在 EDA 工具中使用的相当之多&#xff0c;或者说几乎每个 EDA 工具都支持 Tcl 语言&#xff0c;并将它作为自己的命令shell。 静态时序分析中多用的 Synopsys Tcl 语言&#xff0c…

C++DAY50

源文件代码 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);if(!db.contains()){db QSqlDatabase::addDatabase("QSQLITE");db.setDatabaseName(&q…

Hikari 介绍

一、什么是数据库连接池 数据库连接池是一种管理和复用数据库连接的技术。在应用程序中&#xff0c;连接数据库是一项耗费资源和时间的操作。传统上&#xff0c;每次需要与数据库交互时&#xff0c;应用程序都会创建一个新的数据库连接&#xff0c;并在使用完成后关闭连接。然…