六. 部署分类器-preprocess-speed-compare

news2024/9/23 17:23:23

目录

    • 前言
    • 0. 简述
    • 1. 案例运行
    • 2. 代码分析
      • 2.1 main.cpp
      • 2.2 preprocess.cpp
    • 3. 补充说明
    • 结语
    • 下载链接
    • 参考

前言

自动驾驶之心推出的 《CUDA与TensorRT部署实战课程》,链接。记录下个人学习笔记,仅供自己参考

本次课程我们来学习课程第六章—部署分类器,一起来学习 CPU 端图像预处理方法以及速度对比

课程大纲可以看下面的思维导图

在这里插入图片描述

0. 简述

本小节目标:学习 CPU 端 bgr2rgb + normalization + hwc2chw 等图像预处理操作以及它们的性能比较

这节课程开始我们进入第六章节—部署分类器,这个章节偏实战,为大家准备了几个案例:

  • 6.0-preprocess-speed-compare
  • 6.1-deploy-classification
  • 6.2-deploy-classification-advanced
  • 6.3-int8-calibration
  • 6.4-trt-engine-inspector

第六章节准备的案例一共是五个,第一个是 preprocess-speed-compare,这个小节主要教大家如果用 CPU 做图像预处理都有哪些方法,哪种方法访问图像速度更快;6.1 小节主要给大家介绍初步的分类器部署该怎么做,这个小节为了方便大家理解整个代码写得比较简单,也没有涉及到任何 C++ 的设计模式,所以整个代码看起来有很多缺陷;6.2 小节是针对 6.1 小节的一个扩展,主要是把 6.1 小节中的很多问题给解决掉,另外我们自己在写推理框架的时候应该考虑哪些东西

6.3 小节给大家介绍 int8 calibration,我们在前面或多或少都有涉及到量化这个概念,其中校准是量化的一个重要环节,校准包括很多校准器比如 MinMaxCalibrator、EntropyCalibrator、LegacyCalibrator 等等,我们在部署时该如何使用这些校准器呢?选择哪个校准器呢?这都是我们在 6.3 小节需要讨论的问题;最后 6.4 小节主要给大家介绍 TensorRT 官方工具 trt-engine-explorer,这个工具主要是帮助大家观察经过 TensorRT 优化前后的推理引擎在架构上有什么不同,去理解 TensorRT 做了哪些优化,哪些层融合了,哪些节点添加了,哪些节点被删除了,这个方便我们更好的去理解 TensorRT 的优化,同时可以帮助我们分析 TensorRT 中哪些优化是可以进一步改善的

后续的一些案例主要是讲 Transformer 的一些部署以及面临的一些问题,比如 attention 计算瓶颈,LayerNormalization 这种节点和 CNN 中 Conv 这种节点相比推理性能差异又在哪里,还有纯 Transformer 模型和纯 CNN 模型以及 CNN+Transformer 模型相比它们的计算效率以及计算密度有什么不同(目前 2024/8/17 尚未更新

下面我们开始本次课程的学习🤗

1. 案例运行

在正式开始课程之前,博主先带大家跑通 6.0-preprocess-speed-compare 这个小节的案例🤗

源代码获取地址:https://github.com/kalfazed/tensorrt_starter

首先大家需要把 tensorrt_starter 这个项目给 clone 下来,指令如下:

git clone https://github.com/kalfazed/tensorrt_starter.git

也可手动点击下载,点击右上角的 Code 按键,将代码下载下来。至此整个项目就已经准备好了。也可以点击 here 下载博主准备好的源代码(注意代码下载于 2024/7/14 日,若有改动请参考最新

整个项目后续需要使用的软件主要有 CUDA、cuDNN、TensorRT、OpenCV,大家可以参考 Ubuntu20.04软件安装大全 进行相应软件的安装,博主这里不再赘述

假设你的项目、环境准备完成,下面我们一起来运行下 6.0-preprocess-speed-compare 小节案例代码

我们需要修改下整体的 Makefile.config,指定一些库的路径:

# tensorrt_starter/config/Makefile.config
# CUDA_VER                    :=  11
CUDA_VER                    :=  11.6
    
# opencv和TensorRT的安装目录
OPENCV_INSTALL_DIR          :=  /usr/local/include/opencv4
# TENSORRT_INSTALL_DIR        :=  /mnt/packages/TensorRT-8.4.1.5
TENSORRT_INSTALL_DIR        :=  /home/jarvis/lean/TensorRT-8.6.1.6

Note:大家查看自己的 CUDA 是多少版本,修改为对应版本即可,另外 OpenCV 和 TensorRT 修改为你自己安装的路径即可

接着我们就可以来执行编译,指令如下:

make -j64

输出如下:

在这里插入图片描述

接着执行:

./bin/trt-infer

输出如下:

在这里插入图片描述

我们这里对比了 CPU 端进行 bgr2rgb+norm+hwc2chw 预处理的五种不同方法执行的速度对比,在终端可以看到各个方法处理的时间,另外在 data 文件夹下保存着处理后的图片,如下所示:

在这里插入图片描述

Note:博主的 CPU 是 12th Gen Intel® Core™ i5-12400F,每次执行结果都不一样会有波动这个很正常,不同 CPU 测量出来的结果可能会有差异,另外图像分辨率不同时间也会有差异

如果大家能够看到上述输出结果,那就说明本小节案例已经跑通,下面我们就来看看具体的代码实现

2. 代码分析

2.1 main.cpp

我们先从 main.cpp 看起:

#include <iostream>
#include <memory>

#include "model.hpp"
#include "utils.hpp"
#include "timer.hpp"
#include "preprocess.hpp"

using namespace std;

void bgr2rgb_cpu_speed_test(){
    Timer   timer;
    string  imagePath = "data/fox.png";
    string  savePath  = "";
    cv::Mat src       = cv::imread(imagePath);
    cv::Mat tar(src.rows, src.cols, CV_8UC3);

    LOG("Starting cpu bgr2rgb speed test...");

    timer.start_cpu();
    preprocess_cv_cvtcolor(src, tar);
    timer.stop_cpu();
    timer.duration_cpu<Timer::ms>("Using cv::cvtcolor takes");
    savePath  = "data/fox-cvtcolor.png";
    cv::imwrite(savePath, tar);

    timer.start_cpu();
    preprocess_cv_mat_at(src, tar);
    timer.stop_cpu();
    timer.duration_cpu<Timer::ms>("Using cv::Mat::at takes");
    savePath  = "data/fox-mat-at.png";
    cv::imwrite(savePath, tar);

    timer.start_cpu();
    preprocess_cv_mat_iterator(src, tar);
    timer.stop_cpu();
    timer.duration_cpu<Timer::ms>("Using cv::MatIterator_ takes");
    savePath  = "data/fox-mat-iterator.png";
    cv::imwrite(savePath, tar);

    timer.start_cpu();
    preprocess_cv_mat_data(src, tar);
    timer.stop_cpu();
    timer.duration_cpu<Timer::ms>("Using cv::Mat data takes");
    savePath  = "data/fox-mat-data.png";
    cv::imwrite(savePath, tar);

    timer.start_cpu();
    preprocess_cv_pointer(src, tar);
    timer.stop_cpu();
    timer.duration_cpu<Timer::ms>("Using pointer takes");
    savePath  = "data/fox-pointer.png";
    cv::imwrite(savePath, tar);
}

void bgr2rgb_norm_hwc2chw_cpu_speed_test(){
    Timer   timer;
    string  imagePath = "data/fox.png";
    cv::Mat src       = cv::imread(imagePath);
    int     size      = src.cols * src.rows * src.channels();
    float*  tar       = (float*)malloc(size * sizeof(float));
    int     width     = 224;
    int     height    = 224;
    int     channel   = 3;
    int     classes   = 1000;
    float   mean[3]   = {0.406, 0.456, 0.485};
    float   std[3]    = {0.225, 0.224, 0.229};

    cv::resize(src, src, cv::Size(width, height));
    LOG("Starting cpu bgr2rgb + normalization + hwc2chw speed test...");

    timer.start_cpu();
    preprocess_cv_mat_at(src, tar, mean, std);
    timer.stop_cpu();
    timer.duration_cpu<Timer::ms>("Using cv::Mat::at takes");

    timer.start_cpu();
    preprocess_cv_mat_iterator(src, tar, mean, std);
    timer.stop_cpu();
    timer.duration_cpu<Timer::ms>("Using cv::MatIterator_ takes");

    timer.start_cpu();
    preprocess_cv_mat_data(src, tar, mean, std);
    timer.stop_cpu();
    timer.duration_cpu<Timer::ms>("Using cv::Mat data takes");

    timer.start_cpu();
    preprocess_cv_pointer(src, tar, mean, std);
    timer.stop_cpu();
    timer.duration_cpu<Timer::ms>("Using pointer takes");

    timer.start_cpu();
    preprocess_cv_array(src, tar, mean, std);
    timer.stop_cpu();
    timer.duration_cpu<Timer::ms>("Using array takes");    
}

int main(int argc, char const *argv[])
{
    // bgr2rgb_cpu_speed_test();
    bgr2rgb_norm_hwc2chw_cpu_speed_test();
}

上述代码主要用于测试不同方法在 OpenCV 中进行图像预处理操作时的速度,主要有 bgr2rgb_cpu_speed_test()bgr2rgb_norm_hwc2chw_cpu_speed_test() 两个函数,它们分别测量了不同方法在特定预处理操作中的耗时情况

bgr2rgb_cpu_speed_test() 函数

  • 该函数测试了使用不同方法将图像从 BGR 色彩空间转换为 RGB 色彩空间的速度
  • preprocess_cv_cvtcolor() 方法
    • 使用 cv::cvtColor 函数将图像从 BGR 转换为 RGB
  • preprocess_cv_mat_at() 方法
    • 使用 cv::Mat::at 方法逐元素访问并修改图像
  • preprocess_cv_mat_iterator() 方法
    • 使用 cv::MatIterator_ 遍历图像矩阵
  • preprocess_cv_mat_data() 方法
    • 直接通过指针访问图像数据
  • preprocess_cv_pointer() 方法
    • 手动通过指针运算来访问和修改元素

bgr2rgb_norm_hwc2chw_cpu_speed_test() 函数

  • 该函数测试了进行 BGR 到 RGB 转换、归一化以及 HWC 到 CHW 转换组合操作的速度
  • preprocess_cv_mat_at() 方法
    • 使用 cv::Mat::at 方式访问和处理像素
  • preprocess_cv_mat_iterator() 方法
    • 使用 cv::MatIterator_ 进行像素处理
  • preprocess_cv_mat_data() 方法
    • 通过指针直接访问图像数据进行处理
  • preprocess_cv_pointer() 方法
    • 使用手动指针运算

在 main.cpp 中我们实现了一个初步的在 CPU 端的前处理的性能比较,比较的是对于访问 CV::Mat 的数据,哪一种方式会比较快。因为有的时候我们可能会考虑在 CPU 上做前处理,比如说图像前处理放在 GPU 上时并不能充分把硬件资源吃满,导致硬件资源浪费,如果出现这种情况的话,我们可能会考虑把前处理放在 CPU 上,而把 DNN 的 forward 部分放在 GPU 上进行异步的推理

这里主要比较四种方法:

  • 使用 cv::Mat::at
  • 使用 cv::MatIterator_
  • 使用 cv::Mat.data
  • 使用 cv::Mat.ptr

同时我们在 main.cpp 中也比较了一下 CPU 端做 bgr2rgb 和 bgr2rgb + normalization + hwc2chw 的性能比较

2.2 preprocess.cpp

下面我们一起来看具体的函数实现,我们先从 bgr2rgb 函数中的方法实现看起

preprocess_cv_cvtcolor() 方法具体实现代码如下:

void preprocess_cv_cvtcolor(cv::Mat src, cv::Mat tar){
    cv::cvtColor(src, tar, cv::COLOR_RGB2BGR);
}

该方法非常的简单,主要是利用 OpenCV 提供的 cv::cvtColor 函数来完成从 RGB 到 BGR 色彩空间的转换

preprocess_cv_mat_at() 方法具体实现代码如下:

void preprocess_cv_mat_at(cv::Mat src, cv::Mat tar){
    for (int i = 0; i < src.rows; i++) {
        for (int j = 0; j < src.cols; j++) {
            tar.at<cv::Vec3b>(i, j)[2] = src.at<cv::Vec3b>(i, j)[0];
            tar.at<cv::Vec3b>(i, j)[1] = src.at<cv::Vec3b>(i, j)[1];
            tar.at<cv::Vec3b>(i, j)[0] = src.at<cv::Vec3b>(i, j)[2];
        }
    }
}

该方法通过双重循环遍历图像中的每一个像素,外层循环遍历图像每一行,内层循环遍历图像每一列,接着通过 .at<cv::Vec3b> 访问目标图像和源图像中的每一个像素并进行相应的通道转换,其中 cv::Vec3b 表示一个包含三个 uchar 值的向量即一个像素,它对应 BGR 三个通道的值

这种方法的优点在于直接访问和操作,可以允许你精确地控制每个像素地操作,在需要对像素级别进行复杂的自定义操作时非常有用,但是它的速度较慢,当图像较大时这种方法的效率会成为一个瓶颈

preprocess_cv_mat_iterator() 方法具体实现代码如下:

void preprocess_cv_mat_iterator(cv::Mat src, cv::Mat tar){
    cv::MatIterator_<cv::Vec3b> src_it = src.begin<cv::Vec3b>();
    cv::MatIterator_<cv::Vec3b> tar_it = tar.begin<cv::Vec3b>();
    cv::MatIterator_<cv::Vec3b> end    = src.end<cv::Vec3b>();
    for (; src_it != end; src_it++, tar_it++) {
        (*tar_it)[2] = (*src_it)[0];
        (*tar_it)[1] = (*src_it)[1];
        (*tar_it)[0] = (*src_it)[2];
    }
}

该方法利用 OpenCV 提供的 cv::MatIterator_ 迭代器来遍历和操作图像的每个像素,相较于 cv::Mat::at 方法,迭代器提供了一种更直接的方式来遍历矩阵,并且在某些情况下可以提高代码的执行效率。首先我们初始化三个迭代器,其中 src_it 指向源图像的起始位置,tar_it 指向目标图像的起始位置,end 指向源图像的结束位置,接着我们进行迭代循环和转换操作

该方法使用迭代器的方式使得代码更加简洁,并且迭代器的运算通常比 cv::Mat::at 更加高效,但是它的性能未必最佳,对于需要更高性能的场景直接操作指针或 cv::Mat::data 可能更合适

preprocess_cv_mat_data() 方法具体实现如下:

void preprocess_cv_mat_data(cv::Mat src, cv::Mat tar){
    int height   = src.rows;
    int width    = src.cols;
    int channels = src.channels();

    for (int i = 0; i < height; i ++) {
        for (int j = 0; j < width; j ++) {
            int index = i * width * channels + j * channels;
            tar.data[index + 2] = src.data[index + 0];
            tar.data[index + 1] = src.data[index + 1];
            tar.data[index + 0] = src.data[index + 2];
        }
    }
}

该方法直接使用指针访问图像数据,逐个像素进行 BGR 到 RGB 的转换,相比于之前的方法,这种方式更加接近底层操作,能够提高更高的性能,但代码的可读性相对较低

首先外层循环遍历图像的每一行,内层循环遍历图像的每一列,接着计算像素索引,通过像素索引去访问每一个像素从而实现颜色通道交换。其中 src.datatar.data 都是一个 uchar* 类型的指针,指向图像数据在内存中的起始位置,通过直接操作这个指针,可以访问和修改图像的原始数据

preprocess_cv_pointer() 方法具体实现如下:

void preprocess_cv_pointer(cv::Mat src, cv::Mat tar){
    for (int i = 0; i < src.rows; i ++) {
        cv::Vec3b* src_ptr = src.ptr<cv::Vec3b>(i);
        cv::Vec3b* tar_ptr = tar.ptr<cv::Vec3b>(i);
        for (int j = 0; j < src.cols; j ++) {
            tar_ptr[j][2] = src_ptr[j][0];
            tar_ptr[j][1] = src_ptr[j][1];
            tar_ptr[j][0] = src_ptr[j][2];
        }
    }
}

该方法通过使用指针来遍历和操作图像的每个像素进行 BGR 到 RGB 的颜色变换,相比直接访问图像数据,这种方法利用了指针的优势,同时保持了相对较好的可读性和操作的灵活性

首先我们逐行指针操作,外层循环遍历图像的每一行,接着进行行指针初始化,然后我们逐列操作,内存循环遍历图像的每一列,此时通过指针 src_ptr[i]tar_ptr[j] 可以直接访问和操作每个像素

我们执行下看下输出如下所示:

在这里插入图片描述

我们从图中可以看到速度最快的是最后两个使用指针操作的方法,相比之下直接使用 cv::cvtColor 方法耗时最严重

Note:博主每次执行的结果都不尽相同,但是总的来说使用指针操作的方法是速度最快的,大家以自己实际的 CPU 执行结果为准就行

下面我们来看 bgr2rgb_norm_hwc2chw 函数的实现,那大部分其实和 bgr2rgb 的方法差不多,只是添加了一个 normalization 以及 hwc2chw 操作,这里我们就简单过一下

preprocess_cv_mat_at() 方法具体实现如下:

void preprocess_cv_mat_at(cv::Mat src, float* tar, float* mean, float* std){
    float* ptar_ch0 = tar + src.rows * src.cols * 0;
    float* ptar_ch1 = tar + src.rows * src.cols * 1;
    float* ptar_ch2 = tar + src.rows * src.cols * 2;

    for (int i = 0; i < src.rows; i++) {
        for (int j = 0; j < src.cols; j++) {
            (*ptar_ch2++) = (src.at<cv::Vec3b>(i, j)[0] / 255.0f - mean[0]) / std[0];
            (*ptar_ch1++) = (src.at<cv::Vec3b>(i, j)[1] / 255.0f - mean[1]) / std[1];
            (*ptar_ch0++) = (src.at<cv::Vec3b>(i, j)[2] / 255.0f - mean[2]) / std[2];
        }
    }
}

该方法首先初始化目标指针,其中 ptar_ch0ptar_ch1ptar_ch2 分别指向目标数组 tar 中 R、G、B 三个通道的起始位置,这种布局方式确保图像数据在内存中按通道顺序存储,而不是按像素顺序存储,即 CHW 格式。接着双重循环遍历图像中的每个像素,外层循环遍历图像的每一行,内存循环遍历图像的每一列,最后进行颜色空间转换和归一化

preprocess_cv_mat_iterator() 方法具体实现如下:

void preprocess_cv_mat_iterator(cv::Mat src, float* tar, float* mean, float* std){
    float* ptar_ch0 = tar + src.rows * src.cols * 0;
    float* ptar_ch1 = tar + src.rows * src.cols * 1;
    float* ptar_ch2 = tar + src.rows * src.cols * 2;
    cv::MatIterator_<cv::Vec3b> it     = src.begin<cv::Vec3b>();
    cv::MatIterator_<cv::Vec3b> end    = src.end<cv::Vec3b>();

    for (; it != end; it++) {
        (*ptar_ch2++) = ((*it)[0] / 255.0f - mean[0]) / std[0];
        (*ptar_ch1++) = ((*it)[1] / 255.0f - mean[1]) / std[1];
        (*ptar_ch0++) = ((*it)[2] / 255.0f - mean[2]) / std[2];
    }
}

该方法使用迭代器 cv::MatIterator_ 来遍历图像数据,并在遍历过程中完成图像数据的 BGR 到 RGB 转换、归一化操作以及 HWC 到 CHW 格式的转换

preprocess_cv_mat_data() 方法具体实现如下:

void preprocess_cv_mat_data(cv::Mat src, float* tar, float* mean, float* std){
    float* ptar_ch0 = tar + src.rows * src.cols * 0;
    float* ptar_ch1 = tar + src.rows * src.cols * 1;
    float* ptar_ch2 = tar + src.rows * src.cols * 2;
    int height      = src.rows;
    int width       = src.cols;
    int channels    = src.channels();

    for (int i = 0; i < height; i ++) {
        for (int j = 0; j < width; j ++) {
            int index = i * width * channels + j * channels;
            (*ptar_ch2++) = (src.data[index + 0] / 255.0f - mean[0]) / std[0];
            (*ptar_ch1++) = (src.data[index + 1] / 255.0f - mean[1]) / std[1];
            (*ptar_ch0++) = (src.data[index + 2] / 255.0f - mean[2]) / std[2];
        }
    }
}

该方法直接操作图像的内存数据,通过计算每个像素在内存中的索引来进行 BGR 到 RGB 的转换、归一化处理以及从 HWC 到 CHW 格式的转换,相比于使用迭代器或 cv::Mat::at 方法,这种方法更加接近底层,通常在性能上更有优势

preprocess_cv_pointer() 方法具体实现如下:

void preprocess_cv_pointer(cv::Mat src, float* tar, float* mean, float* std){
    int area = src.rows * src.cols;
    int offset_ch0 = area * 0;
    int offset_ch1 = area * 1;
    int offset_ch2 = area * 2;

    for (int i = 0; i < src.rows; i ++) {
        cv::Vec3b* src_ptr = src.ptr<cv::Vec3b>(i);
        for (int j = 0; j < src.cols; j ++) {
            tar[offset_ch2++] = (src_ptr[j][0] / 255.0f - mean[0]) / std[0];
            tar[offset_ch1++] = (src_ptr[j][1] / 255.0f - mean[1]) / std[1];
            tar[offset_ch0++] = (src_ptr[j][2] / 255.0f - mean[2]) / std[2];
        }
    }
}

该方法同时使用指针来访问和处理图像数据,完成 BGR 到 RGB 的颜色空间转换、归一化处理以及从 HWC 到 CHW 格式的转换,这种方法结合了指针的高效性,通常在性能上有不错的表现

我们执行下看下输出如下所示:

在这里插入图片描述

我们从图中可以看到速度最快的依旧是指针操作的方法,速度最慢的是逐像素访问的方法

Note:博主每次执行的结果都不尽相同,但是总的来说使用指针操作的方法是速度最快的,大家以自己实际的 CPU 执行结果为准就行

3. 补充说明

值得注意的是这个小节的 Makefile 也有一些改动,主要体现在 bear 工具的使用,如下所示:

ifeq (, $(shell which bear))
BEARCMD       :=
else
ifeq (bear 3.0.18, $(shell bear --version))
BEARCMD       := bear --output config/compile_commands.json --
else
BEARCMD       := bear -o config/compile_commands.json
endif
endif

我们前面有提到过 bear 是一个用于生成 compile_commands.json 文件的工具,这个文件通常用于启用基于 clangd 或其他类似工具的代码分析和自动补全功能。bear 工具会拦截编译命令并记录它们,从而生成描述如何构建项目中每个源文件的 JSON 数据

另外在不同的 Ubuntu 系统中,bear 的指令可能有所不同,这个主要取决于安装的 bear 版本,在最新的 Ubuntu22.04 系统中推荐使用的 bear 版本是 3.0.18,比较老的 Ubuntu18.04 和 Ubuntu20.04 推荐使用的 bear 版本是 2.6 或者 2.7

关于 bear 的安装我们可以直接通过 apt-get 安装,指令如下:

sudo apt-get update
sudo apt-get install bear

当然我们也可以从源码编译安装,指令如下:

git clone https://github.com/rizsotto/Bear.git
cd Bear
git checkout tags/3.0.18
mkdir build && cd build
cmake .. && make
sudo make install

结语

本次课程我们学习了 CPU 端图像预处理的几种方法,包括逐像素方法,迭代器方法以及指针方法,其中指针方法耗时最慢,速度最快。当我们处理的图像较小在 GPU 端计算密度不高时我们可以采用这个小节所说的几种在 CPU 端处理的方法

OK,以上就是 6.0 小节案例的全部内容了,下节我们来学习 6.1 小节分类器的简单部署实现,敬请期待😄

下载链接

  • tensorrt_starter源码

参考

  • Ubuntu20.04软件安装大全
  • https://github.com/kalfazed/tensorrt_starter.git

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

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

相关文章

嵌入式面经篇八——进程线程

文章目录 前言一、进程&线程1、异步 IO 和同步 IO 区别&#xff1f;2、进程间通信方式&#xff1f;3、进程的地址空间模型&#xff1f;4、进程的五种状态分别是?5、子进程从父进程继承的资源有哪些&#xff1f;6、什么是进程上下文、中断上下文&#xff1f;7、如何防止僵尸…

写了一个分页 sql,因为粗心出了 bug 造成了 OOM!

大家好&#xff0c;我是君哥。 最近上完线后&#xff0c;凌晨收到一个生产告警&#xff0c;一个 OOM 异常导致了服务重启。今天来分享一下这个事故。 1.事故现场 事故的代码逻辑并不复杂&#xff0c;从一个大概有 8 万数据的表里面查出数据&#xff0c;汇总后对数据做处理。…

高校宣讲会管理系统--论文pf

TOC springboot370高校宣讲会管理系统--论文pf 第1章 绪论 1.1选题动因 当前的网络技术&#xff0c;软件技术等都具备成熟的理论基础&#xff0c;市场上也出现各种技术开发的软件&#xff0c;这些软件都被用于各个领域&#xff0c;包括生活和工作的领域。随着电脑和笔记本的…

【myz_tools】Python库 myz_tools:Python算法及文档自动化生成工具 - 0.2.0版更新

文章目录 0.2.0 更新内容如下函数generate_2d_combinations_iter函数generate_row_permutations函数calculate_total_permutations函数display_combinations函数evaluate_list_similarity函数check_unique 写在前面关于库库使用库内所有函数目录文件名称: common_maths.py函数部…

ZooKeeper分布式协调系统介绍

1. ZooKeeper概述 1.1 ZooKeeper介绍 ZooKeeper 是 Apache 软件基金会的一个项目&#xff0c;它确实提供了一种非常有用的服务&#xff0c;用于维护分布式系统中的配置信息、命名、提供分布式同步和提供组服务等。它的核心是原子广播和大约一致性模型&#xff0c;这使得它能够…

CCF-GESP五级考级——初等数论,全网最精简的求最大公约数gcd和最小公倍数lcm方法(100%好使)

&#x1f451;一、约数和因数的区别 约数必须在整除的前提下才存在&#xff0c;而因数是从乘积的角度来提出的。如果数与数相乘的积是数&#xff0c;是的因数。 1.约数只能对在整数范围内而言&#xff0c;而因数就不限于整数的范围。 举个栗子&#xff1a;。2和8是16的…

中仕公考:国考往年招录情况对比

2025年国考预计10月中旬启动&#xff0c;11月进行笔试。中仕为大家总结了往年的国考招录情况&#xff0c;希望能给大家一些参考。 2024年计划招录3.96万人。截止到考试结束&#xff0c;共有225.2万人参加了考试&#xff0c;参加考试人数与录用计划数之比约为57:1&#xff0c;2…

CSP-J 2023真题一轮

选择题 阅读题 第1题 第2题 第3题 完善程序 第1题 第2题 答案&#xff1a; 一、单选题 1-5 BDAAC 6-10 BCADA 11-15 ABBAD 二、阅读程序 1&#xff09; 16. √ 17. √ 18. ⅹ 19.A 20.B 2&#xff09; 21. √ 22. ⅹ 23. √ 24. D 25.B 26.D 3&#xff09; 27. √ 28. √ 29…

EasyCVR视频汇聚平台构建远程安防监控:5大亮点解析,助力安防无死角

随着科技的飞速发展&#xff0c;远程安防监控系统已经成为现代社会中不可或缺的一部分&#xff0c;无论是在小区、公共场所还是工业领域&#xff0c;安防监控都发挥着至关重要的作用。而EasyCVR作为一款功能强大的视频监控综合管理平台&#xff0c;其在构建远程安防监控系统方面…

谷歌浏览器下载文件被阻止怎么解除

在工作生活中&#xff0c;我们会使用谷歌浏览器下载各种各样的文件&#xff0c;不过偶尔会遇到文件下载被阻止的情况。为了解决这一问题&#xff0c;本文为大家分享了实用的措施建议&#xff0c;一起来了解一下吧。&#xff08;本文由https://chrome.cmrrs.com/站点的作者进行编…

python爬取豆瓣电影数据

目录 一、背景 二、分析网站 1、ajax请求 三、代码实现 1、导包 2、面向对象实现 3、发送请求 4、解析数据 5、保存数据 6、定义主函数 7、实例化对象运行主函数 8、运行效果 四、以下是全部完整代码 五、报错解决 1、数据库连接报错 2、数据插入报错 一、背景…

136 只出现一次的数字

解题思路&#xff1a; \qquad 这道题目明确要求了时间复杂度为O(N)&#xff0c;空间复杂度为O(1)&#xff0c;不然借助哈希表很容易能够在O(N)的空间复杂度下解决。特殊的要求只能特殊处理&#xff0c;解这道题只能记住异或这种较特殊的运算方式。 \qquad 异或是对二进制数进…

《深入探究 @SpringBootApplication 注解的内部原理》

《深入探究 SpringBootApplication 注解的内部原理》 SpringBootApplication注解涵盖了 Spring Boot 的包扫描原理、自动装配原理等众多重要原理。接下来&#xff0c;我们将对该注解展开深入且详尽的研究。而研究上述原理的关键&#xff0c;在于剖析SpringBootApplication内部…

网上商品订单转手系统bootpf

TOC springboot408网上商品订单转手系统bootpf 第1章 绪论 1.1选题动因 当前的网络技术&#xff0c;软件技术等都具备成熟的理论基础&#xff0c;市场上也出现各种技术开发的软件&#xff0c;这些软件都被用于各个领域&#xff0c;包括生活和工作的领域。随着电脑和笔记本的…

【SpringBoot】SpringBoot框架的整体环境搭建和使用(整合Mybatis,Druid,Junit4,PageHelper,logback等)

目录 1.介绍 1.1 配置文件 1.2 目录结构 2.基于SpringBoot的SpringMVC 4.整合Mybatis 5.整合Druid连接池 6.整合Junit4 7.整合Logback 8.整合PageHelper 9.SpringBoot整合Thymeleaf ​编辑 【附录】springboot的pom.xml 1.介绍 Spring框架的优点是方便解耦、简化开…

openEuler系统安装Visual Studio Code

openEuler系统安装Visual Studio Code 背景安装密钥和存储库更新包缓存并使用dnf安装包Fedora 22及以上版本旧版本使用yum 安装过程截图安装成功看桌面效果 背景 openEuler(openEuler-24.03-LTS)安装了麒麟UKUI桌面但是没有麒麟软件商店想安装Visual Studio Code 安装密钥和…

计算机毕业设计选什么题目好? springboot 大学志愿填报系统

✍✍计算机毕业编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java、…

东晟时尚服饰文化传承与发展研发中心成立

近期&#xff0c;东晟时尚创新科技&#xff08;北京&#xff09;有限公司宣布成立东晟时尚服饰文化传承与发展研发中心&#xff0c;此举标志着公司在促进中国传统文化与现代时尚产业结合方面迈出了关键步伐。 作为一家在时尚科技推广和设计研发应用服务领域具有战略眼光的企业&…

微信云开发云存储 下载全部文件

一、安装 首先按照这个按照好依赖&#xff0c;打开cmd 安装 | 云开发 CloudBase - 一站式后端云服务 npm i -g cloudbase/cli 安装可能遇到的问题 ‘tcb‘ 不是内部或外部命令&#xff0c;也不是可运行的程序或批处理文件。-CSDN博客 二、登录 在cmd输入 tcb login 三、…

导出运营数据Excel报表

文章目录 概要整体架构流程技术细节小结 概要 产品原型 在数据统计页面&#xff0c;有一个数据导出的按钮&#xff0c;点击该按钮时&#xff0c;其实就会下载一个文件。这个文件实际上是一个Excel形式的文件&#xff0c;文件中主要包含最近30日运营相关的数据。表格的形式已经…