【CUDA】 矩阵乘法 matMatMul

news2024/11/26 23:24:19

矩阵乘法 matMatMul

矩阵乘法是基本线性代数子程序(BLAS)的重要组成部分,而且线性代数中许多其他操作以此为基础。

图1是两个矩阵的乘法。

基础方法,正方形tile和长方形tile

基础方法

执行矩阵乘法的基础方法是使用单个线程执行输出矩阵的单个元素乘法。这意味着所需的线程数等于输出矩阵中的元素数。线程排列在二维网格中,每个线程分配一个唯一的索引。线程的索引用于访问输入矩阵的相应行和列。执行行和列的乘法,结果存储在输出矩阵的相应元素中。

这种方法的主要缺点是多个线程加载相同的行和列来计算它们的输出。图2是一个示例。

图2

正如上图所示,为了计算P0,0和P0,1,两个线程都需要加载整个M0。对于P0,0和P1,0也是如此。两个线程都需要加载整个N1列。这意味着线程将多次访问相同的内存位置。

正方形tile

为了避免这个问题,我们可以使用tile。tile是将输入矩阵的一个小部分加载到共享内存中。线程将把tile加载到共享内存中,然后执行乘法运算。图3描述了这种技术。

图3

kernel将计算分为几个阶段。每个阶段,线程将将M和N矩阵中的tile加载到共享内存中。然后,线程将执行tile的乘法,并将结果累积到输出矩阵的相应元素中。

通过这种技术,我们可以看到全局内存访问减少了TILE_WIDTH(图示)倍。

长方形tile

为了进一步减少全局内存访问,我们可以使用图4所示的矩形块。

图4

通过这种技术,我们增加了每个线程的工作量,以进一步减少全局内存访问的次数。kernel再次将计算分成多个阶段。在每个阶段中,线程将从中加载一个tile M 和两个tile N 到共享内存中。然后线程将对这些tile进行乘法运算,并将结果累加到输出矩阵的相应元素中。


Code

host代码使用随机值初始化输入矩阵,并调用kernel执行乘法运算。输入矩阵以线性格式存储。


#include <iostream>

#include <thrust/host_vector.h>
#include <thrust/device_vector.h>
#include <thrust/transform.h>


#include "mat_mat_mul_kernels.cuh"

int main(int argc, char *argv[])
{

    int rows1, cols1rows2, cols2, t_x;

    if(argc != 5){
        std::cout << "Usage: " << argv[0] << " <rows1> <cols1rows2> <cols2> <t_x>" << std::endl;
        return 1;
    }

    rows1 = atoi(argv[1]);
    cols1rows2 = atoi(argv[2]);
    cols2 = atoi(argv[3]);
    t_x = atoi(argv[4]);

    thrust::host_vector<float> h_in_mat1(rows1 * cols1rows2);
    thrust::host_vector<float> h_in_mat2(cols1rows2 * cols2);
    thrust::host_vector<float> h_out_host(rows1 * cols2);

    srand(time(NULL));
    for(int i = 0; i < rows1 * cols1rows2; i++)
        h_in_mat1[i] = rand() / (float)RAND_MAX;

    for(int i = 0; i < cols1rows2 * cols2; i++)
        h_in_mat2[i] = rand() / (float)RAND_MAX;

    for(int i = 0; i < rows1; ++i)
        for(int j = 0; j < cols2; ++j)
            for(int k = 0; k < cols1rows2; ++k)
                h_out_host[i * cols2 + j] += h_in_mat1[i * cols1rows2 + k] * h_in_mat2[k * cols2 + j];

    thrust::device_vector<float> d_in_mat1 = h_in_mat1;
    thrust::device_vector<float> d_in_mat2 = h_in_mat2;

    thrust::device_vector<float> d_out(rows1 * cols2);

    dim3 blockDim(t_x, t_x);
    dim3 gridDim((cols2 + blockDim.x - 1) / blockDim.x, (rows1 + blockDim.y - 1) / blockDim.y);
    mat_mat_mul<float><<<gridDim, blockDim>>>(
        thrust::raw_pointer_cast(d_in_mat1.data()),
        thrust::raw_pointer_cast(d_in_mat2.data()),
        thrust::raw_pointer_cast(d_out.data()),
        rows1, cols1rows2, cols1rows2, cols2);

    bool success = true;
    for(int i = 0; i < rows1 * cols2; ++i)
        if(abs(h_out_host[i] - d_out[i]) >= 0.001){
            std::cout << "Error at " << i << ": " << h_out_host[i] << " != " << d_out[i] << " (Mat Mat Mul)" << std::endl;
            success = false;
            break;
        }
    if(success)
        std::cout << "Success (Mat Mat Mul)" << std::endl;


    blockDim = dim3(t_x, t_x);
    gridDim = dim3((cols2 + blockDim.x - 1) / blockDim.x, (rows1 + blockDim.y - 1) / blockDim.y);
    mat_mat_mul_tiles<float><<<gridDim, blockDim, 2 * blockDim.x * blockDim.x * sizeof(float)>>>(
        thrust::raw_pointer_cast(d_in_mat1.data()),
        thrust::raw_pointer_cast(d_in_mat2.data()),
        thrust::raw_pointer_cast(d_out.data()),
        rows1, cols1rows2, cols1rows2, cols2);

    success = true;
    for(int i = 0; i < rows1 * cols2; ++i)
        if(abs(h_out_host[i] - d_out[i]) >= 0.001){
            std::cout << "Error at " << i << ": " << h_out_host[i] << " != " << d_out[i] << " (Mat Mat Mul Tiles)" << std::endl;
            success = false;
            break;
        }
    if(success)
        std::cout << "Success (Mat Mat Mul Tiles)" << std::endl;

    
    blockDim = dim3(t_x, t_x);
    gridDim = dim3((cols2 + blockDim.x - 1) / blockDim.x / 2, (rows1 + blockDim.y - 1) / blockDim.y);
    mat_mat_mul_rec_tiles<float><<<gridDim, blockDim, 3 * blockDim.x * blockDim.x * sizeof(float)>>>(
        thrust::raw_pointer_cast(d_in_mat1.data()),
        thrust::raw_pointer_cast(d_in_mat2.data()),
        thrust::raw_pointer_cast(d_out.data()),
        rows1, cols1rows2, cols1rows2, cols2);

    success = true;
    for(int i = 0; i < rows1 * cols2; ++i)
        if(abs(h_out_host[i] - d_out[i]) >= 0.001){
            std::cout << "Error at " << i << ": " << h_out_host[i] << " != " << d_out[i] << " (Mat Mat Mul Rec Tiles)" << std::endl;
            success = false;
            break;
        }
    if(success)
        std::cout << "Success (Mat Mat Mul Rec Tiles)" << std::endl;

    return 0;

}

以下是kernel的展示。

基础方法

template<typename T> __global__
void mat_mat_mul(T *in_mat1, T *in_mat2, T *out_mat, 
                 int mat1_rows, int mat1_cols, 
                 int mat2_rows, int mat2_cols) {

    int row = blockIdx.y * blockDim.y + threadIdx.y;
    int col = blockIdx.x * blockDim.x + threadIdx.x;

    if (row >= mat1_rows || col >= mat2_cols) return;

    T sum = 0;
    for (int k = 0; k < mat1_cols; ++k)
        sum += in_mat1[row * mat1_cols + k] * in_mat2[k * mat2_cols + col];

    out_mat[row * mat2_cols + col] = sum;
}

在这种基本方法中,kernel非常简单。

线程首先计算它们的行和列索引。如果行或列索引大于输入矩阵的行数或列数,则线程返回。

int row = blockIdx.y * blockDim.y + threadIdx.y;
int col = blockIdx.x * blockDim.x + threadIdx.x;

if (row >= mat1_rows || col >= mat2_cols) return;

然后,这些线程会对行和列进行相乘,并将结果存储在输出矩阵的相应元素中。

T sum = 0;
for (int k = 0; k < mat1_cols; ++k)
    sum += in_mat1[row * mat1_cols + k] * in_mat2[k * mat2_cols + col];

out_mat[row * mat2_cols + col] = sum;

我们可以观察到,线程不仅会多次加载相同的行和列,而且它们还会以非合并的方式加载M的元素。这意味着线程将无法充分利用内存带宽。


正方形tile

template<typename T> __global__
void mat_mat_mul_tiles(T *in_mat1, T *in_mat2, T *out_mat,
                       int mat1_rows, int mat1_cols,
                       int mat2_rows, int mat2_cols) {
    
    // Initialize shared memory
    int TILE_WIDTH = blockDim.x;
    extern __shared__ uint8_t shared_mem[];
    T *ds_mat1 = reinterpret_cast<T*>(shared_mem);
    T *ds_mat2 = reinterpret_cast<T*>(shared_mem + TILE_WIDTH * TILE_WIDTH * sizeof(T));
    
    int bx = blockIdx.x,  by = blockIdx.y;
    int tx = threadIdx.x, ty = threadIdx.y;

    int row = by * TILE_WIDTH + ty;
    int col = bx * TILE_WIDTH + tx;

    T out_value = 0;
    // Loop over the in_mat1 and in_mat2 tiles required to compute the out_mat element
    for (int ph = 0; ph < (mat1_cols + TILE_WIDTH - 1) / TILE_WIDTH; ++ph) {

        // Collaborative loading of in_mat1 and in_mat2 tiles into shared memory
        ds_mat1[ty * TILE_WIDTH + tx] = row < mat1_rows && ph * TILE_WIDTH + tx < mat1_cols ?
                                        in_mat1[row * mat1_cols + ph * TILE_WIDTH + tx] : 0;
                                        
        ds_mat2[ty * TILE_WIDTH + tx] = ph * TILE_WIDTH + ty < mat2_rows && col < mat2_cols ?
                                        in_mat2[(ph * TILE_WIDTH + ty) * mat2_cols + col] : 0;

        // Synchronize to make sure the tiles are loaded
        __syncthreads();

        // Compute the out_mat element
        for (int k = 0; k < TILE_WIDTH; ++k)
            out_value += ds_mat1[ty * TILE_WIDTH + k] * ds_mat2[k * TILE_WIDTH + tx];
        
        // Synchronize to make sure the out_mat element is computed
        // before other threads load new tiles
        __syncthreads();
    }

    // Store the out_mat element in out_mat
    if (row < mat1_rows && col < mat2_cols)
        out_mat[row * mat2_cols + col] = out_value;
}

在这种方法中,我们使用共享内存来存储输入矩阵的块。线程首先计算它们的行和列索引。如果行或列索引大于输入矩阵的行数或列数,则线程返回。

int row = by * TILE_WIDTH + ty;
int col = bx * TILE_WIDTH + tx;

if (row >= mat1_rows || col >= mat2_cols) return;

在每个阶段:

线程将tiles加载到共享内存中。 这些tiles以合并访存的方式加载。

// Collaborative loading of in_mat1 and in_mat2 tiles into shared memory
ds_mat1[ty * TILE_WIDTH + tx] = row < mat1_rows && ph * TILE_WIDTH + tx < mat1_cols ?
                                in_mat1[row * mat1_cols + ph * TILE_WIDTH + tx] : 0;

ds_mat2[ty * TILE_WIDTH + tx] = ph * TILE_WIDTH + ty < mat2_rows && col < mat2_cols ?
                                in_mat2[(ph * TILE_WIDTH + ty) * mat2_cols + col] : 0;

// Synchronize to make sure the tiles are loaded
__syncthreads();

当线程加载完tile后,它们会计算输出矩阵元素。线程通过将tiles的相应行和列相乘,并将结果相加来计算输出矩阵元素。

// Compute the out_mat element
for (int k = 0; k < TILE_WIDTH; ++k)
    out_value += ds_mat1[ty * TILE_WIDTH + k] * ds_mat2[k * TILE_WIDTH + tx];

// Synchronize to make sure the out_mat element is computed
// before other threads load new tiles
__syncthreads();

最后,线程将输出矩阵元素存储在输出矩阵中。

// Store the out_mat element in out_mat
out_mat[row * mat2_cols + col] = out_value;

长方形tiles

template<typename T> __global__
void mat_mat_mul_rec_tiles(T* in_mat1, T* in_mat2, T* out_mat,
    int mat1_rows, int mat1_cols,
    int mat2_rows, int mat2_cols) {

    // Initialize shared memory
    int TILE_WIDTH = blockDim.x;
    extern __shared__ uint8_t shared_mem[];
    T* ds_mat1 = reinterpret_cast<T*>(shared_mem);
    T* ds_mat2 = reinterpret_cast<T*>(shared_mem + TILE_WIDTH * TILE_WIDTH * sizeof(T));
    T* ds_mat3 = reinterpret_cast<T*>(shared_mem + 2 * TILE_WIDTH * TILE_WIDTH * sizeof(T));

    int bx = blockIdx.x, by = blockIdx.y;
    int tx = threadIdx.x, ty = threadIdx.y;

    int row = by * TILE_WIDTH + ty;
    int col = bx * TILE_WIDTH * 2 + tx;

    T out_value1 = 0;
    T out_value2 = 0;
    // Loop over the in_mat1 and in_mat2 tiles required to compute the out_mat element
    for (int ph = 0; ph < (mat1_cols + TILE_WIDTH - 1) / TILE_WIDTH; ++ph) {

        // Collaborative loading of in_mat1 and in_mat2 tiles into shared memory
        ds_mat1[ty * TILE_WIDTH + tx] = row < mat1_rows&& ph* TILE_WIDTH + tx < mat1_cols ?
            in_mat1[row * mat1_cols + ph * TILE_WIDTH + tx] : 0;

        ds_mat2[ty * TILE_WIDTH + tx] = ph * TILE_WIDTH + ty < mat2_rows&& col < mat2_cols ?
            in_mat2[(ph * TILE_WIDTH + ty) * mat2_cols + col] : 0;

        ds_mat3[ty * TILE_WIDTH + TILE_WIDTH + tx] = ph * TILE_WIDTH + ty < mat2_rows&& TILE_WIDTH + col < mat2_cols ?
            in_mat2[(ph * TILE_WIDTH + ty) * mat2_cols + TILE_WIDTH + col] : 0;

        // Synchronize to make sure the tiles are loaded
        __syncthreads();

        // Compute the out_mat element
        for (int k = 0; k < TILE_WIDTH; k++) {
            out_value1 += ds_mat1[ty * TILE_WIDTH + k] * ds_mat2[k * TILE_WIDTH + tx];
            out_value2 += ds_mat1[ty * TILE_WIDTH + k] * ds_mat3[k * TILE_WIDTH + TILE_WIDTH + tx];
        }

        // Synchronize to make sure the Pvalues are computed
        // before other threads load new tiles
        __syncthreads();
    }

    // Store the Pvalues in out_mat
    if (row < mat1_rows && col < mat2_cols)
        //out_mat[row][col];
        out_mat[row * mat2_cols + col] = out_value1;

    if (row < mat1_rows && TILE_WIDTH + col < mat2_cols)
        //out_mat[row][TILE_WIDTH + col];
        out_mat[row * mat2_cols + TILE_WIDTH + col] = out_value2;
}

这个kernel函数几乎与正方形tile函数相同。

首先,线程计算出他们将计算的输出矩阵元素的行和列。

int row = by * TILE_WIDTH     + ty;
int col = bx * TILE_WIDTH * 2 + tx;

然后线程将tile加载到共享内存中。唯一的区别是N加载2个tile。

// Collaborative loading of in_mat1 and in_mat2 tiles into shared memory
        ds_mat1[ty * TILE_WIDTH + tx] = row < mat1_rows&& ph* TILE_WIDTH + tx < mat1_cols ?
            in_mat1[row * mat1_cols + ph * TILE_WIDTH + tx] : 0;

        ds_mat2[ty * TILE_WIDTH + tx] = ph * TILE_WIDTH + ty < mat2_rows&& col < mat2_cols ?
            in_mat2[(ph * TILE_WIDTH + ty) * mat2_cols + col] : 0;

        ds_mat3[ty * TILE_WIDTH + TILE_WIDTH + tx] = ph * TILE_WIDTH + ty < mat2_rows&& TILE_WIDTH + col < mat2_cols ?
            in_mat2[(ph * TILE_WIDTH + ty) * mat2_cols + TILE_WIDTH + col] : 0;

然后线程计算两个输出矩阵元素。

// Compute the out_mat element
        for (int k = 0; k < TILE_WIDTH; k++) {
            out_value1 += ds_mat1[ty * TILE_WIDTH + k] * ds_mat2[k * TILE_WIDTH + tx];
            out_value2 += ds_mat1[ty * TILE_WIDTH + k] * ds_mat3[k * TILE_WIDTH + TILE_WIDTH + tx];
        }

最后,存储两个输出矩阵元素。

// Store the Pvalues in out_mat
if (row < mat1_rows && col < mat2_cols)
  //out_mat[row][col];
    out_mat[row * mat2_cols + col] = out_value1;

if (row < mat1_rows && TILE_WIDTH + col < mat2_cols)
  //out_mat[row][TILE_WIDTH + col];
    out_mat[row * mat2_cols + TILE_WIDTH + col] = out_value2;

性能分析

运行时间:

矩阵维度:1024 × 1024

线程块维度:32 × 32

可见使用共享内存可以有效降低运行速度。但长方形tile反而耗时更久。因为L1缓存与共享内存公用硬件空间。可能共享内存占据大部分空间,而L1缓存所剩无几,从而导致长方形tile耗时更久。具体情况需要做性能分析,之后再补充。也许是长方形tile的复杂性增加。kernel引入了许多分支和同步点。这可能导致耗时更久。

笔者采用设备:RTX3060 6GB

PMPP项目提供的分析

kernel的性能是使用NvBench项目在多个gpu中测量的。研究的性能测量方法有:

内存带宽:每秒传输的数据量。

内存带宽利用率:占用内存带宽的百分比。

基础方法

正方形tile

长方形tile

参考文献:

1、大规模并行处理器编程实战(第2版)

2、PPMP

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

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

相关文章

2021-06-15 protues(ISIS)脉冲发生器仿真仪表使用

缘由这个脉冲发生器怎么连线_编程语言-CSDN问答

STM32智能交通管理系统教程

目录 引言环境准备智能交通管理系统基础代码实现&#xff1a;实现智能交通管理系统 4.1 数据采集模块 4.2 数据处理与分析 4.3 控制系统实现 4.4 用户界面与数据可视化应用场景&#xff1a;交通管理与优化问题解决方案与优化收尾与总结 1. 引言 智能交通管理系统利用STM32嵌…

【对顶堆 优先队列】295. 数据流的中位数

本文涉及知识点 对顶堆 优先队列 LeetCode295. 数据流的中位数 中位数是有序整数列表中的中间值。如果列表的大小是偶数&#xff0c;则没有中间值&#xff0c;中位数是两个中间值的平均值。 例如 arr [2,3,4] 的中位数是 3 。 例如 arr [2,3] 的中位数是 (2 3) / 2 2.5 …

网络漏洞挖掘实测报告

关于作者&#xff1a;个人主页 网络漏洞挖掘实测报告 一、前言 网络漏洞挖掘是信息安全领域中至关重要的一环。通过挖掘和修复漏洞&#xff0c;可以有效地保护系统免受潜在的攻击和破坏。本报告旨在记录一次完整的网络漏洞挖掘实测过程&#xff0c;包括实施方法、过程、结果以…

免杀笔记 ----> ShellCode Loader !!!

学了那么久的前置知识&#xff0c;终于到了能上线的地方了&#xff01;&#xff01;&#xff01; 不过这里还没到免杀的部分&#xff0c;距离bypass一众的杀毒软件还有很长的路要走&#xff01;&#xff01; 目录 1.ShellCode 2.ShellCode Loader的概念 3.可读可写可…

Android实现获取本机手机号码

和上次获取设备序列号一样&#xff0c;仍然是通过无障碍服务实现&#xff0c;在之前的代码基础上做了更新。代码和demo如下&#xff1a; package com.zwxuf.lib.devicehelper;import android.accessibilityservice.AccessibilityService; import android.app.Activity; import…

Java中使用arima预测未来数据

看着已经存在的曲线图数据&#xff0c;想预估下后面曲线图的数据。 import java.util.Vector;public class AR {double[] stdoriginalData{};int p;ARMAMath armamathnew ARMAMath();/*** AR模型* param stdoriginalData* param p //p为MA模型阶数*/public AR(double [] stdori…

Dungeonborne延迟高?降低Dungeonborne延迟的方法分享

Dungeonborne是Mithril Interactive开发并发行的一款沉浸式第一人称 PvPvE 地下城探险游戏。Dungeonborne的魅力并不仅仅在于战斗和冒险。游戏中的剧情设计同样引人入胜&#xff0c;每个NPC都有自己独特的故事和背景&#xff0c;玩家在与他们交流的过程中&#xff0c;不仅能了解…

Tomcat(+Servlet)笔记+代码

Tomcat安装和配置 安装在不含中文的路径&#xff0c;路径不能太长 Apache 官网&#x1f447; Apache Tomcat - Welcome! 配置部分 点击下图红框处&#xff0c;找到Tomcat安装位置 添加项目的文件 配好的话&#xff0c;红框这里有个猫 代码部分 新建jsp文件&#xff0c;里…

【码银送书第二十二期】《Python数据分析从入门到精通(第2版)》

&#x1f490;大家好&#xff01;我是码银~&#xff0c;欢迎关注&#x1f490;&#xff1a; CSDN&#xff1a;码银 公众号&#xff1a;码银学编程 前言 &#x1f340;丛书说明&#xff1a;“软件开发视频大讲堂‘’丛书第1版于2008年8月出版&#xff0c;因其编写细腻、易学实用…

字符串函数5-9题(30 天 Pandas 挑战)

字符串函数 1. 相关知识点1.5 字符串的长度条件判断1.6 apply映射操作1.7 python大小写转换1.8 正则表达式匹配2.9 包含字符串查询 2. 题目2.5 无效的推文2.6 计算特殊奖金2.7 修复表中的名字2.8 查找拥有有效邮箱的用户2.9 患某种疾病的患者 1. 相关知识点 1.5 字符串的长度条…

Orangepi配合IIC驱动OLED屏幕

目录 一、OLED屏幕 二、Orangepi的IIC接口及OLED屏幕硬件接线 2.1 Orangepi的IIC接口&#xff1a; 2.2 Orangepi与OLED屏幕硬件接线&#xff1a; 三、wiringPi库示例代码 3.1 wiringPi库OLED屏幕示例代码&#xff1a; 3.2 OLED显示自己想要的字符&#xff1a; 一、OLED屏…

E2.【C语言】练习:static部分

#include <stdio.h> int sum(int a) {int c 0;static int b 3;c 1;b 2;return (a b c); } int main() {int i;int a 2;for (i 0; i < 5;i){printf("%d ", sum(a));} } 求执行结果 c是auto类变量(普通的局部变量)&#xff0c;自动产生&#xff0c…

第26篇 寻找最大数<一>

Q&#xff1a;如何设计一段汇编语言子程序并调用来寻找一组数中的最大数呢&#xff1f; A&#xff1a;基本原理&#xff1a;可以使用子程序LARGE实现找到列表中最大数的功能。主程序通过寄存器将列表的条目数和起始地址作为参数传递给子程序&#xff0c;子程序通过寄存器将最大…

qt 如果把像素点数据变成一个图片

1.概要 图像的本质是什么&#xff0c;就是一个个的像素点&#xff0c;对与显示器来说就是一个二维数组。无论多复杂的图片&#xff0c;对于显示器来说就是一个二维数组。 2.代码 #include "widget.h"#include <QApplication> #include <QImage> #incl…

Springboot学习之用EasyExcel4导入导出数据(基于MyBatisPlus)

一、POM依赖 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><m…

Linux系统之安装Ninvaders太空入侵者小游戏

Linux系统之安装Ninvaders太空入侵者小游戏 一、Ninvaders小游戏介绍1.1 Ninvaders小游戏简介1.2 项目预览 二、本次实践介绍2.1 本地环境规划2.2 本次实践介绍 三、检查系统镜像源3.1 检查系统镜像源3.2 更新软件列表 四、安装Ninvaders4.1 安装Ninvaders4.2 启动Ninvaders游戏…

Python酷库之旅-第三方库Pandas(004)

目录 一、用法精讲 5、pandas.DataFrame.to_csv函数 5-1、语法 5-2、参数 5-3、功能 5-4、返回值 5-5、说明 5-6、用法 5-6-1、代码示例 5-6-2、结果输出 6、pandas.read_fwf函数 6-1、语法 6-2、参数 6-3、功能 6-4、返回值 6-5、说明 6-6、用法 6-6-1、代码…

视频共享融合赋能平台LnyonCVS国标视频监控平台包含哪些功能

随着国内视频监控应用的迅猛发展&#xff0c;系统接入规模不断扩大。不同平台提供商的接入协议各不相同&#xff0c;导致终端制造商在终端维护时需要针对不同平台的软件版本提供不同的维护&#xff0c;资源造成了极大的浪费。 为响应国家对重特大事件通过视频监控集中调阅来掌…

数字信号处理实验二(模拟信号采样与重构及频谱分析FFT)

模拟信号采样与重构及频谱分析FFT&#xff08;2学时&#xff09; 要求&#xff1a; 对一模拟信号进行采样&#xff1b;对该采样信号进行重构&#xff1b;分析它们的频谱特征。目的&#xff1a; 熟悉MATLAB命令和编辑、运行、调试环境&#xff1b;掌握采样定理及对信号的频谱分析…