HarmonyOS:Neural Network Runtime对接AI推理框架开发指导

news2024/9/25 3:28:12

场景介绍

Neural Network Runtime 作为 AI 推理引擎和加速芯片的桥梁,为 AI 推理引擎提供精简的 Native 接口,满足推理引擎通过加速芯片执行端到端推理的需求。

本文以图 1 展示的 Add 单算子模型为例,介绍 Neural Network Runtime 的开发流程。Add 算子包含两个输入、一个参数和一个输出,其中的 activation 参数用于指定 Add 算子中激活函数的类型。

图 1 Add 单算子网络示意图

环境准备

环境要求

Neural Network Runtime 部件的环境要求如下:

● 开发环境:Ubuntu 18.04 及以上。

● 接入设备:HarmonyOS 定义的标准设备,并且系统中内置的硬件加速器驱动,已通过 HDI 接口对接 Neural Network Runtime。

由于 Neural Network Runtime 通过 Native API 对外开放,需要通过 Native 开发套件编译 Neural Network Runtime 应用。

环境搭建

  1. 打开 Ubuntu 编译服务器的终端。
  2. 把下载好的 Native 开发套件压缩包拷贝至当前用户根目录下。
  3. 执行以下命令解压 Native 开发套件的压缩包。
unzip native-linux-{版本号}.zip

解压缩后的内容如下(随版本迭代,目录下的内容可能发生变化,请以最新版本的 Native API 为准):

native/
├── build // 交叉编译工具链
├── build-tools // 编译构建工具
├── docs
├── llvm
├── nativeapi_syscap_config.json
├── ndk_system_capability.json
├── NOTICE.txt
├── oh-uni-package.json
└── sysroot // Native API头文件和库

接口说明

这里给出 Neural Network Runtime 开发流程中通用的接口,具体请见下列表格。

结构体

模型构造相关接口

模型编译相关接口

执行推理相关接口

设备管理相关接口

开发步骤

Neural Network Runtime 的开发流程主要包含模型构造、模型编译和推理执行三个阶段。以下开发步骤以 Add 单算子模型为例,介绍调用 Neural Network Runtime 接口,开发应用的过程。

  1. 创建应用样例文件。

首先,创建 Neural Network Runtime 应用样例的源文件。在项目目录下执行以下命令,创建 nnrt_example/目录,在目录下创建 nnrt_example.cpp 源文件。

mkdir ~/nnrt_example && cd ~/nnrt_example
touch nnrt_example.cpp
  1. 导入 Neural Network Runtime。

在 nnrt_example.cpp 文件的开头添加以下代码,引入 Neural Network Runtime 模块。

#include <cstdint>
#include <iostream>
#include <vector>

#include "neural_network_runtime/neural_network_runtime.h"

// 常量,用于指定输入、输出数据的字节长度
const size_t DATA_LENGTH = 4 * 12;
  1. 构造模型。

使用 Neural Network Runtime 接口,构造 Add 单算子样例模型。

OH_NN_ReturnCode BuildModel(OH_NNModel** pModel)
{
    // 创建模型实例,进行模型构造
    OH_NNModel* model = OH_NNModel_Construct();
    if (model == nullptr) {
        std::cout << "Create model failed." << std::endl;
        return OH_NN_MEMORY_ERROR;
    }

    // 添加Add算子的第一个输入Tensor,类型为float32,张量形状为[1, 2, 2, 3]
    int32_t inputDims[4] = {1, 2, 2, 3};
    OH_NN_Tensor input1 = {OH_NN_FLOAT32, 4, inputDims, nullptr, OH_NN_TENSOR};
    OH_NN_ReturnCode ret = OH_NNModel_AddTensor(model, &input1);
    if (ret != OH_NN_SUCCESS) {
        std::cout << "BuildModel failed, add Tensor of first input failed." << std::endl;
        return ret;
    }

    // 添加Add算子的第二个输入Tensor,类型为float32,张量形状为[1, 2, 2, 3]
    OH_NN_Tensor input2 = {OH_NN_FLOAT32, 4, inputDims, nullptr, OH_NN_TENSOR};
    ret = OH_NNModel_AddTensor(model, &input2);
    if (ret != OH_NN_SUCCESS) {
        std::cout << "BuildModel failed, add Tensor of second input failed." << std::endl;
        return ret;
    }

    // 添加Add算子的参数Tensor,该参数Tensor用于指定激活函数的类型,Tensor的数据类型为int8。
    int32_t activationDims = 1;
    int8_t activationValue = OH_NN_FUSED_NONE;
    OH_NN_Tensor activation = {OH_NN_INT8, 1, &activationDims, nullptr, OH_NN_ADD_ACTIVATIONTYPE};
    ret = OH_NNModel_AddTensor(model, &activation);
    if (ret != OH_NN_SUCCESS) {
        std::cout << "BuildModel failed, add Tensor of activation failed." << std::endl;
        return ret;
    }

    // 将激活函数类型设置为OH_NN_FUSED_NONE,表示该算子不添加激活函数。
    ret = OH_NNModel_SetTensorData(model, 2, &activationValue, sizeof(int8_t));
    if (ret != OH_NN_SUCCESS) {
        std::cout << "BuildModel failed, set value of activation failed." << std::endl;
        return ret;
    }

    // 设置Add算子的输出,类型为float32,张量形状为[1, 2, 2, 3]
    OH_NN_Tensor output = {OH_NN_FLOAT32, 4, inputDims, nullptr, OH_NN_TENSOR};
    ret = OH_NNModel_AddTensor(model, &output);
    if (ret != OH_NN_SUCCESS) {
        std::cout << "BuildModel failed, add Tensor of output failed." << std::endl;
        return ret;
    }

    // 指定Add算子的输入、参数和输出索引
    uint32_t inputIndicesValues[2] = {0, 1};
    uint32_t paramIndicesValues = 2;
    uint32_t outputIndicesValues = 3;
    OH_NN_UInt32Array paramIndices = {&paramIndicesValues, 1};
    OH_NN_UInt32Array inputIndices = {inputIndicesValues, 2};
    OH_NN_UInt32Array outputIndices = {&outputIndicesValues, 1};

    // 向模型实例添加Add算子
    ret = OH_NNModel_AddOperation(model, OH_NN_OPS_ADD, &paramIndices, &inputIndices, &outputIndices);
    if (ret != OH_NN_SUCCESS) {
        std::cout << "BuildModel failed, add operation failed." << std::endl;
        return ret;
    }

    // 设置模型实例的输入、输出索引
    ret = OH_NNModel_SpecifyInputsAndOutputs(model, &inputIndices, &outputIndices);
    if (ret != OH_NN_SUCCESS) {
        std::cout << "BuildModel failed, specify inputs and outputs failed." << std::endl;
        return ret;
    }

    // 完成模型实例的构建
    ret = OH_NNModel_Finish(model);
    if (ret != OH_NN_SUCCESS) {
        std::cout << "BuildModel failed, error happened when finishing model construction." << std::endl;
        return ret;
    }

    *pModel = model;
    return OH_NN_SUCCESS;
}
  1. 查询 Neural Network Runtime 已经对接的加速芯片。

Neural Network Runtime 支持通过 HDI 接口,对接多种加速芯片。在执行模型编译前,需要查询当前设备下,Neural Network Runtime 已经对接的加速芯片。每个加速芯片对应唯一的 ID 值,在编译阶段需要通过设备 ID,指定模型编译的芯片。

void GetAvailableDevices(std::vector<size_t>& availableDevice)
{
    availableDevice.clear();

    // 获取可用的硬件ID
    const size_t* devices = nullptr;
    uint32_t deviceCount = 0;
    OH_NN_ReturnCode ret = OH_NNDevice_GetAllDevicesID(&devices, &deviceCount);
    if (ret != OH_NN_SUCCESS) {
        std::cout << "GetAllDevicesID failed, get no available device." << std::endl;
        return;
    }

    for (uint32_t i = 0; i < deviceCount; i++) {
        availableDevice.emplace_back(devices[i]);
    }
}
  1. 在指定的设备上编译模型。

Neural Network Runtime 使用抽象的模型表达描述 AI 模型的拓扑结构,在加速芯片上执行前,需要通过 Neural Network Runtime 提供的编译模块,将抽象的模型表达下发至芯片驱动层,转换成可以直接推理计算的格式。

OH_NN_ReturnCode CreateCompilation(OH_NNModel* model, const std::vector<size_t>& availableDevice, OH_NNCompilation** pCompilation)
{
    // 创建编译实例,用于将模型传递至底层硬件编译
    OH_NNCompilation* compilation = OH_NNCompilation_Construct(model);
    if (compilation == nullptr) {
        std::cout << "CreateCompilation failed, error happened when creating compilation." << std::endl;
        return OH_NN_MEMORY_ERROR;
    }

    // 设置编译的硬件、缓存路径、性能模式、计算优先级、是否开启float16低精度计算等选项

    // 选择在第一个设备上编译模型
    OH_NN_ReturnCode ret = OH_NNCompilation_SetDevice(compilation, availableDevice[0]);
    if (ret != OH_NN_SUCCESS) {
        std::cout << "CreateCompilation failed, error happened when setting device." << std::endl;
        return ret;
    }

    // 将模型编译结果缓存在/data/local/tmp目录下,版本号指定为1
    ret = OH_NNCompilation_SetCache(compilation, "/data/local/tmp", 1);
    if (ret != OH_NN_SUCCESS) {
        std::cout << "CreateCompilation failed, error happened when setting cache path." << std::endl;
        return ret;
    }

    // 完成编译设置,进行模型编译
    ret = OH_NNCompilation_Build(compilation);
    if (ret != OH_NN_SUCCESS) {
        std::cout << "CreateCompilation failed, error happened when building compilation." << std::endl;
        return ret;
    }

    *pCompilation = compilation;
    return OH_NN_SUCCESS;
}
  1. 创建执行器。

完成模型编译后,需要调用 Neural Network Runtime 的执行模块,创建推理执行器。执行阶段,设置模型输入、获取模型输出和触发推理计算的操作均围绕执行器完成。

OH_NNExecutor* CreateExecutor(OH_NNCompilation* compilation)
{
    // 创建执行实例
    OH_NNExecutor* executor = OH_NNExecutor_Construct(compilation);
    return executor;
}
  1. 执行推理计算,并打印计算结果。

通过执行模块提供的接口,将推理计算所需要的输入数据传递给执行器,触发执行器完成一次推理计算,获取模型的推理计算结果。

OH_NN_ReturnCode Run(OH_NNExecutor* executor)
{
    // 构造示例数据
    float input1[12] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
    float input2[12] = {11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22};

    int32_t inputDims[4] = {1, 2, 2, 3};
    OH_NN_Tensor inputTensor1 = {OH_NN_FLOAT32, 4, inputDims, nullptr, OH_NN_TENSOR};
    OH_NN_Tensor inputTensor2 = {OH_NN_FLOAT32, 4, inputDims, nullptr, OH_NN_TENSOR};

    // 设置执行的输入

    // 设置执行的第一个输入,输入数据由input1指定
    OH_NN_ReturnCode ret = OH_NNExecutor_SetInput(executor, 0, &inputTensor1, input1, DATA_LENGTH);
    if (ret != OH_NN_SUCCESS) {
        std::cout << "Run failed, error happened when setting first input." << std::endl;
        return ret;
    }

    // 设置执行的第二个输入,输入数据由input2指定
    ret = OH_NNExecutor_SetInput(executor, 1, &inputTensor2, input2, DATA_LENGTH);
    if (ret != OH_NN_SUCCESS) {
        std::cout << "Run failed, error happened when setting second input." << std::endl;
        return ret;
    }

    // 设置输出的数据缓冲区,OH_NNExecutor_Run执行计算后,输出结果将保留在output中
    float output[12];
    ret = OH_NNExecutor_SetOutput(executor, 0, output, DATA_LENGTH);
    if (ret != OH_NN_SUCCESS) {
        std::cout << "Run failed, error happened when setting output buffer." << std::endl;
        return ret;
    }

    // 执行计算
    ret = OH_NNExecutor_Run(executor);
    if (ret != OH_NN_SUCCESS) {
        std::cout << "Run failed, error doing execution." << std::endl;
        return ret;
    }

    // 打印输出结果
    for (uint32_t i = 0; i < 12; i++) {
        std::cout << "Output index: " << i << ", value is: " << output[i] << "." << std::endl;
    }

    return OH_NN_SUCCESS;
}
  1. 构建端到端模型构造-编译-执行流程。

步骤 3-步骤 7 实现了模型的模型构造、编译和执行流程,并封装成 4 个函数,便于模块化开发。以下示例代码将 4 个函数串联成完整的 Neural Network Runtime 开发流程。

int main()
{
    OH_NNModel* model = nullptr;
    OH_NNCompilation* compilation = nullptr;
    OH_NNExecutor* executor = nullptr;
    std::vector<size_t> availableDevices;

    // 模型构造阶段
    OH_NN_ReturnCode ret = BuildModel(&model);
    if (ret != OH_NN_SUCCESS) {
        std::cout << "BuildModel failed." << std::endl;
        OH_NNModel_Destroy(&model);
        return -1;
    }

    // 获取可执行的设备
    GetAvailableDevices(availableDevices);
    if (availableDevices.empty()) {
        std::cout << "No available device." << std::endl;
        OH_NNModel_Destroy(&model);
        return -1;
    }

    // 模型编译阶段
    ret = CreateCompilation(model, availableDevices, &compilation);
    if (ret != OH_NN_SUCCESS) {
        std::cout << "CreateCompilation failed." << std::endl;
        OH_NNModel_Destroy(&model);
        OH_NNCompilation_Destroy(&compilation);
        return -1;
    }

    // 创建模型的推理执行器
    executor = CreateExecutor(compilation);
    if (executor == nullptr) {
        std::cout << "CreateExecutor failed, no executor is created." << std::endl;
        OH_NNModel_Destroy(&model);
        OH_NNCompilation_Destroy(&compilation);
        return -1;
    }

    // 使用上一步创建的执行器,执行单步推理计算
    ret = Run(executor);
    if (ret != OH_NN_SUCCESS) {
        std::cout << "Run failed." << std::endl;
        OH_NNModel_Destroy(&model);
        OH_NNCompilation_Destroy(&compilation);
        OH_NNExecutor_Destroy(&executor);
        return -1;
    }

    // 释放申请的资源
    OH_NNModel_Destroy(&model);
    OH_NNCompilation_Destroy(&compilation);
    OH_NNExecutor_Destroy(&executor);

    return 0;
}

调测验证

  1. 准备应用样例的编译配置文件。

新建一个 CMakeLists.txt 文件,为开发步骤中的应用样例文件 nnrt_example.cpp 添加编译配置。以下提供简单的 CMakeLists.txt 示例:

cmake_minimum_required(VERSION 3.16)
project(nnrt_example C CXX)

add_executable(nnrt_example
    ./nnrt_example.cpp
)

target_link_libraries(nnrt_example
    neural_network_runtime.z
)
  1. 编译应用样例。

执行以下命令,在当前目录下新建 build/目录,在 build/目录下编译 nnrt_example.cpp,得到二进制文件 nnrt_example。

mkdir build && cd build
cmake -DCMAKE_TOOLCHAIN_FILE={交叉编译工具链的路径}/build/cmake/ohos.toolchain.cmake -DOHOS_ARCH=arm64-v8a -DOHOS_PLATFORM=OHOS -DOHOS_STL=c++_static ..
make
  1. 执行以下代码,将样例推送到设备上执行。
# 将编译得到的 `nnrt_example` 推送到设备上,执行样例。
hdc_std file send ./nnrt_example /data/local/tmp/.

# 给测试用例可执行文件加上权限。
hdc_std shell "chmod +x /data/local/tmp/nnrt_example"

# 执行测试用例
hdc_std shell "/data/local/tmp/nnrt_example"

如果样例执行正常,应该得到以下输出。

Output index: 0, value is: 11.000000.
Output index: 1, value is: 13.000000.
Output index: 2, value is: 15.000000.
Output index: 3, value is: 17.000000.
Output index: 4, value is: 19.000000.
Output index: 5, value is: 21.000000.
Output index: 6, value is: 23.000000.
Output index: 7, value is: 25.000000.
Output index: 8, value is: 27.000000.
Output index: 9, value is: 29.000000.
Output index: 10, value is: 31.000000.
Output index: 11, value is: 33.000000.
  1. 检查模型缓存(可选)。

如果在调测环境下,Neural Network Runtime 对接的 HDI 服务支持模型缓存功能,执行完 nnrt_example, 可以在 /data/local/tmp 目录下找到生成的缓存文件。

说明

模型的 IR 需要传递到硬件驱动层,由 HDI 服务将统一的 IR 图,编译成硬件专用的计算图,编译的过程非常耗时。Neural Network Runtime 支持计算图缓存的特性,可以将 HDI 服务编译生成的计算图,缓存到设备存储中。当下一次在同一个加速芯片上编译同一个模型时,通过指定缓存的路径,Neural Network Runtime 可以直接加载缓存文件中的计算图,减少编译消耗的时间。

检查缓存目录下的缓存文件:

ls /data/local/tmp

以下为打印结果:

# 0.nncache  cache_info.nncache

如果缓存不再使用,需要手动删除缓存,可以参考以下命令,删除缓存文件。

rm /data/local/tmp/*nncache

为了能让大家更好的学习鸿蒙 (Harmony OS) 开发技术,这边特意整理了《鸿蒙 (Harmony OS)开发学习手册》(共计890页),希望对大家有所帮助:https://qr21.cn/FV7h05

《鸿蒙 (Harmony OS)开发学习手册》

入门必看:https://qr21.cn/FV7h05

  1. 应用开发导读(ArkTS)
  2. 应用开发导读(Java)

HarmonyOS 概念:https://qr21.cn/FV7h05

  1. 系统定义
  2. 技术架构
  3. 技术特性
  4. 系统安全

如何快速入门:https://qr21.cn/FV7h05

  1. 基本概念
  2. 构建第一个ArkTS应用
  3. 构建第一个JS应用
  4. ……

开发基础知识:https://qr21.cn/FV7h05

  1. 应用基础知识
  2. 配置文件
  3. 应用数据管理
  4. 应用安全管理
  5. 应用隐私保护
  6. 三方应用调用管控机制
  7. 资源分类与访问
  8. 学习ArkTS语言
  9. ……

基于ArkTS 开发:https://qr21.cn/FV7h05

  1. Ability开发
  2. UI开发
  3. 公共事件与通知
  4. 窗口管理
  5. 媒体
  6. 安全
  7. 网络与链接
  8. 电话服务
  9. 数据管理
  10. 后台任务(Background Task)管理
  11. 设备管理
  12. 设备使用信息统计
  13. DFX
  14. 国际化开发
  15. 折叠屏系列
  16. ……

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

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

相关文章

Linux线程——死锁

什么是死锁 死锁是一组相互竞争资源的线程因为他们之间得到互相等待导致“永久“阻塞的现象&#xff1b;&#xff08;你等我 我等你 你不放我也不放 就导致“永久“阻塞的现象&#xff09; 死锁是指两个或两个以上的进程在执行过程中&#xff0c;由于竞争资源或者由于彼此通信…

【AntDesign】如何设置Form表单初始值以及会出现的问题

方法一&#xff1a;使用 setFieldsValue() 方法&#xff08;推荐&#xff09; 首先&#xff0c;解构出form实例 const [form] Form.useForm()然后&#xff0c;将该实例与Form绑定 <Form form{form} ...>...</Form>恰当时机&#xff0c;调用setFieldsValue()方法…

Python (十二) NumPy操作

程序员的公众号&#xff1a;源1024&#xff0c;获取更多资料&#xff0c;无加密无套路&#xff01; 最近整理了一波电子书籍资料&#xff0c;包含《Effective Java中文版 第2版》《深入JAVA虚拟机》&#xff0c;《重构改善既有代码设计》&#xff0c;《MySQL高性能-第3版》&…

IDEA中也能用Postman了,这款插件平替

Postman是大家最常用的API调试工具&#xff0c;那么有没有一种方法可以不用手动写入接口到Postman&#xff0c;即可进行接口调试操作&#xff1f;今天给大家推荐一款IDEA插件&#xff1a;Apipost Helper&#xff0c;写完代码就可以调试接口并一键生成接口文档&#xff01;而且还…

基于Tkinter制作简易的CAN bootloader上位机

文章目录 1.前言2.测试设备3.上位机3.1 参考资料3.2 上位机主要功能3.3 上位机发送流程 升级测试例程分享 1.前言 之前基于S32K144EVB和Tkinter编写了一个简易的串口bootloader上位机&#xff0c;链接如下&#xff1a; 基于Tkinter制作简易的串口bootloader上位机 (qq.com) …

Python之json模块和pickle模块详解

json模块和pickle模块的用法 在python中&#xff0c;可以使用pickle和json两个模块对数据进行序列化操作。 其中&#xff1a; json可以用于字符串或者字典等与python数据类型之间的序列化与反序列化操作。 pickle可以用于python特有类型与python数据类型之间的序列化与反序…

【软件工程】软件工程复习题库2023

&#x1f40c;个人主页&#xff1a; &#x1f40c; 叶落闲庭 &#x1f4a8;我的专栏&#xff1a;&#x1f4a8; SpringCloud MybatisPlus JVM 石可破也&#xff0c;而不可夺坚&#xff1b;丹可磨也&#xff0c;而不可夺赤。 软件工程复习题库 一、选择题二、填空题三、判断题四…

E : DS查找—二叉树平衡因子

Description 二叉树用数组存储&#xff0c;将二叉树的结点数据依次自上而下,自左至右存储到数组中&#xff0c;一般二叉树与完全二叉树对比&#xff0c;比完全二叉树缺少的结点在数组中用0来表示。 计算二叉树每个结点的平衡因子&#xff0c;并按后序遍历的顺序输出结点的平衡…

SpringBoot基于gRPC进行RPC调用

SpringBoot基于gRPC进行RPC调用 一、gRPC1.1 什么是gRPC&#xff1f;1.2 如何编写proto1.3 数据类型及对应关系1.4 枚举1.5 数组1.6 map类型1.7 嵌套对象 二、SpringBoot gRPC2.1 工程目录2.2 jrpc-api2.2.1 引入gRPC依赖2.2.2 编写 .proto 文件2.2.3 使用插件机制生产proto相关…

延迟消息队列的几种实现方案,哪种更适合业务,要看具体情况分析

延迟消息队列的几种实现方案&#xff0c;延迟消息怎么实现&#xff0c;很多人可能一想到的是rabbitmq的死信队列来实现&#xff0c;但是一旦引入mq的话&#xff0c;就依赖这个中间件&#xff0c;另外维护成本&#xff0c;开发成本都很大&#xff0c;那有么有简单点的实现方式呢…

ubuntu推送本地仓库到coding

本教程提供在ubuntu系统下推送本地仓库到coding的指令&#xff0c;用于查阅 一、主要步骤有&#xff1a; 0.初始化仓库 git init 1.添加远程仓库 git remote add origin https://coding.git #修改自己仓库链接 &#xff08;命名仓库别名为origin&#xff09; 2.提交代码…

服务器解析漏洞有哪些?IIS\APACHE\NGINX解析漏洞利用

解析漏洞是指在Web服务器处理用户请求时&#xff0c;对输入数据&#xff08;如文件名、参数等&#xff09;进行解析时产生的漏洞。这种漏洞可能导致服务器对用户提供的数据进行错误解析&#xff0c;使攻击者能够执行未经授权的操作。解析漏洞通常涉及到对用户输入的信任不足&am…

那些令人惊叹的awk简略写法

​​​​​​​awk是一门美妙的语言&#xff0c;被称为unix命令行工具皇冠上的明珠。它有很多简略写法&#xff0c;用好了可以用极少的代码快速解决问题。 下面就列举一些令人惊叹的awk简略写法&#xff1a; awk {sub(/pattern/, "foobar")} 1 # 无论替换是否成功&…

一步步教你创建酒店预订小程序

如果你想为你的酒店或旅馆创建一个预订小程序&#xff0c;这篇文章将为你提供详细的步骤和指南。我们将按照以下顺序进行&#xff1a; 一、进入乔拓云网后台 首先&#xff0c;打开乔拓云网的官方网站&#xff0c;点击右上角的“登录”按钮&#xff0c;登录成功后&#xff0c;点…

基于vue与three.js,监听FPX(Stats类使用)

第一步&#xff0c;引入stats类并new出来 import Stats from three/examples/jsm/libs/stats.module.js; data(){return {stats : new Stats(),} } 第二步&#xff0c;添加dom mounted() {this.init3D();this.animate();window.addEventListener("keydown", this.…

【大数据面试】Flink面试题附答案

目录 ✅Flink介绍、特点、应用场景 ✅Flink与Spark Streaming的区别 ✅Flink有哪些部署模式 ✅Flink架构 ✅怎么设置并行度&#xff1f; ✅什么是算子链&#xff1f; ✅什么是任务槽&#xff08;Task Slots&#xff09;&#xff1f; ✅任务槽和并行度的关系 ✅Flink作…

easyexcel常见注解

easyexcel常见注解 一、依赖 <!--阿里巴巴EasyExcel依赖--><dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.2.10</version></dependency>二、常见注解 ExcelProperty 注解中…

使用Shell脚本监控主机

实验环境 某公司随着业务的不断发展&#xff0c;所使用的Linux服务器也越来越多&#xff0c;管理员希望编写一个简单的性 能监控脚本&#xff0c;放到各服务器中&#xff0c;当监控指标出现异常时发送告警邮件。 需求描述 > 编写名为sysmon.sh的 Shell 监控脚本。 > 监…

《数据结构、算法与应用C++语言描述》- 最小输者树模板的C++实现

输者树 完整可编译运行代码见&#xff1a;Github::Data-Structures-Algorithms-and-Applications/_31loserTree 输者树&#xff1a;每一个内部节点所记录的都是比赛的输者&#xff0c;晋级的节点记录在边上。本文中&#xff0c;赢者是分数较低的那个&#xff0c;输者是分数高…

安捷伦Agilent 34970A数据采集

易学易用 从34972A简化的配置到内置的图形Web界面&#xff0c;我们都投入了非常多的时间和精力&#xff0c;以帮助您节约宝贵的时间。一些非常简单的东西,例如模块上螺旋型端子连接器内置热电偶参考结、包括众多实例和提示的完整用户文档&#xff0c;以及使您能够在开机数分钟后…