OrangePi AIpro学习3 —— vscode开发昇腾DVPP程序

news2024/11/24 13:30:29

目录

一、VScode配置

1.1 下载和安装

1.2 安装和配置需要的插件

二、构建项目

2.1 项目架构

2.2 解决代码高亮显示

2.3 测试编译

2.4 总结出最简单的代码

2.5 vscode报错找不到头文件解决方法

三、代码简单讲解

3.1 初始化部分

3.2 拷贝数据到NPU显存中

3.3 准备裁剪区域

3.4 准备输入输出描述符

3.5 收尾阶段

3.6 其他历程如何学习


一、VScode配置

1.1 下载和安装

1. 下载地址

Download Visual Studio Code - Mac, Linux, Windows

2. 安装中要注意的内容

1.2 安装和配置需要的插件

1. chinese中文

如果在这里安装不了的话,可以使用离线安装的方式,首先进入官网下载离线插件包:Extensions for Visual Studio family of products | Visual Studio Marketplace

然后在vscode中这么操作,选择刚刚下载的VSIX文件即可完成安装

2. 安装远程工具Remote

设置要连接的主机,然后两次回车

重启vscode

选择linux然后再输入密码

进入文件夹

二、构建项目

2.1 项目架构

data数据是从上面的crop中拿过来的 

cmake_minimum_required(VERSION 3.22)

# 设置变量PROJECT_NAME的值为crop
set(PROJECT_NAME "crop")

# 设置项目名称为crop
project(${PROJECT_NAME})

# 添加dvpp需要的宏定义,相当于在代码中: #define ENABLE_DVPP_INTERFACE
add_definitions(-DENABLE_DVPP_INTERFACE)

# 设置头文件目录
include_directories(
    $ENV{INSTALL_DIR}/runtime/include/
    ./include
)

# 设置动态库目录
link_directories(
    $ENV{INSTALL_DIR}/runtime/lib64/stub
)

# 查找所有CMakeLists.txt所在目录下,src目录中的.c和.cpp文件
file(GLOB_RECURSE CPP_FILES     
    ${PROJECT_SOURCE_DIR}/src/*.c
    ${PROJECT_SOURCE_DIR}/src/*.cpp
)

# 编译输出的文件名为: crop  依赖src目录下的所有.c和.cpp结尾的文件
add_executable(${PROJECT_NAME}
    ${CPP_FILES}
)

target_link_libraries(${PROJECT_NAME}
    ascendcl 
    acl_dvpp
    stdc++
)

2.2 解决代码高亮显示

CMake或者cpp没有高亮显示就上传vsix文件到香橙派

然后在vscode中安装 

2.3 测试编译

提示要增加一行内容,这行内容指定了CMake需要的最小版本

 然后./crop就能运行程程序了

2.4 总结出最简单的代码

#include <iostream>
#include <memory>
#include <dirent.h>
#include <fstream>
#include "acl/acl.h"
#include "acl/ops/acl_dvpp.h"

using namespace std;

typedef struct PicDesc {
    string picName;
    int left;
    int top;
    int width;
    int height;
} PicDesc;


aclrtContext g_context;
aclrtStream g_stream;
aclrtRunMode g_runMode;
acldvppChannelDesc * g_dvppChannelDesc;


char * ReadBinFile(string fileName, uint32_t &fileSize)
{
    ifstream binFile(fileName, ifstream::binary);
    if (binFile.is_open() == false) 
    {
        printf("Open file %s failed.\n", fileName.c_str());
        return nullptr;
    }

    binFile.seekg(0, binFile.end);
    uint32_t binFileBufferLen = binFile.tellg();
    if (binFileBufferLen == 0) 
    {
        printf("Binfile is empty, filename is %s.\n", fileName.c_str());
        binFile.close();
        return nullptr;
    }

    binFile.seekg(0, binFile.beg);

    char * binFileBufferData = new(nothrow) char[binFileBufferLen];
    if (binFileBufferData == nullptr) {
        printf("Malloc binFileBufferData failed.\n");
        binFile.close();
        return nullptr;
    }
    binFile.read(binFileBufferData, binFileBufferLen);
    binFile.close();
    fileSize = binFileBufferLen;
    return binFileBufferData;
}

uint32_t AlignmentHelper(uint32_t origSize, uint32_t alignment)
{
    if (alignment == 0) {
        return 0;
    }
    uint32_t alignmentH = alignment - 1;
    return (origSize + alignmentH) / alignment * alignment;
}

uint32_t SaveDvppOutputData(const char *fileName, const void *devPtr, uint32_t dataSize)
{
    FILE * outFileFp = fopen(fileName, "wb+");
    if (g_runMode == ACL_HOST) {
        void * hostPtr = nullptr;
        aclrtMallocHost(&hostPtr, dataSize);
        aclrtMemcpy(hostPtr, dataSize, devPtr, dataSize, ACL_MEMCPY_DEVICE_TO_HOST);
        fwrite(hostPtr, sizeof(char), dataSize, outFileFp);
        (void)aclrtFreeHost(hostPtr);
    } else {
        fwrite(devPtr, sizeof(char), dataSize, outFileFp);
    }
    fflush(outFileFp);
    fclose(outFileFp);
    return 0;
}

int main()
{
    /* 
     * 初始化device、context、stream和dvppChannelDesc,并获取运行模式
     */
    aclInit("./src/acl.json");
    aclrtSetDevice(0);
    aclrtCreateContext(&g_context, 0);
    aclrtCreateStream(&g_stream);
    g_dvppChannelDesc = acldvppCreateChannelDesc(); // 创建图像数据处理通道时的通道描述信息 g_dvppChannelDesc is acldvppChannelDesc type
    acldvppCreateChannel(g_dvppChannelDesc);        // 创建图像数据处理通道
    aclrtGetRunMode(&g_runMode);


    /* 
     * 读取输入和输出
     */
    // 下面设置输入图片的信息,图片的left和top参数用不到
    PicDesc inPicDesc  = { "./data/wood_rabbit_1024_1068_nv12.yuv", 0, 0, 1024, 1068 };
    // 下面设置输出图片的信息
    PicDesc outPicDesc = { "./output/dvpp_rabbit_224_224_nv12.yuv", 350, 280, 224, 224 };
    

    /* 
     * 根据输入读取文件到NPU显存中
     */
    uint32_t buffSize = 0;
    // inPicDesc.picName:要读取的文件名     buffSize:得到的文件大小    inputBuff:得到的文件的内容
    char * inputBuff = ReadBinFile(inPicDesc.picName, buffSize);

    void * inputBufferDev = nullptr;
    acldvppMalloc(&inputBufferDev, buffSize);     // 在NPU上分配显存
    if (g_runMode == ACL_HOST)  // 如果本段C++代码是运行在CPU上
    {                           // 把数据从内存条拷贝到NPU的显存中
        aclrtMemcpy(inputBufferDev, buffSize, inputBuff, buffSize, ACL_MEMCPY_HOST_TO_DEVICE);
    } 
    else                        // 如果本段C++代码是运行在NPU中的CPU上
    {                           // 把数据从NPU显存拷贝到NPU显存中
        aclrtMemcpy(inputBufferDev, buffSize, inputBuff, buffSize, ACL_MEMCPY_DEVICE_TO_DEVICE);
    }
    delete[] inputBuff;


    /*
     * 设置要裁剪输入图片的区域
     */
    uint32_t outputWidth = AlignmentHelper(outPicDesc.width, 16);   // 输入图片宽度按照16字节对其,例如输入17,得到的outputWidth就是32
    uint32_t outputHeight = AlignmentHelper(outPicDesc.height, 2);  // 输入图片高度按照2字节对其,例如输入13,得到的outputHeight就是2
    uint32_t cropLeftOffset = outPicDesc.left;                      // must 偶数
    uint32_t cropRightOffset = cropLeftOffset + outputWidth - 1;    // must 奇数
    uint32_t cropTopOffset = outPicDesc.top;                        // must 偶数
    uint32_t cropBottomOffset = cropTopOffset + outputHeight - 1;   // must 奇数
    acldvppRoiConfig * cropArea_ = acldvppCreateRoiConfig(cropLeftOffset, cropRightOffset, cropTopOffset, cropBottomOffset);


    /*
     * 创建输入图片描述符,并填写输入图片信息
     */
    uint32_t inputWidthStride = AlignmentHelper(inPicDesc.width, 16);
    uint32_t inputHeightStride = AlignmentHelper(inPicDesc.height, 2);
    uint32_t inputBufferSize = inputWidthStride * inputHeightStride * 3 / 2;

    acldvppPicDesc * vpcInputDesc_ = acldvppCreatePicDesc();            // 创建入参数描述符
    acldvppSetPicDescData(vpcInputDesc_, inputBufferDev);               // 原图片存放位置的指针
    acldvppSetPicDescFormat(vpcInputDesc_, PIXEL_FORMAT_YUV_SEMIPLANAR_420);    // 原图片的格式
    acldvppSetPicDescWidth(vpcInputDesc_, inPicDesc.width);             // 原图片宽度
    acldvppSetPicDescHeight(vpcInputDesc_, inPicDesc.height);           // 原图片高度
    acldvppSetPicDescWidthStride(vpcInputDesc_, inputWidthStride);      // 原图片对齐后的宽度
    acldvppSetPicDescHeightStride(vpcInputDesc_, inputHeightStride);    // 原图片对齐后的高度
    acldvppSetPicDescSize(vpcInputDesc_, inputBufferSize);              // 输入图片的图片大小


    /*
     * 创建输出图片描述符,并填写输出图片信息
     */
    void * outputBufferDev = nullptr;
    uint32_t outputBufferSize = outputWidth * outputHeight * 3 / 2;
    acldvppMalloc(&outputBufferDev, outputBufferSize);                  // 在NPU上给输出图片分配内存

    acldvppPicDesc * vpcOutputDesc_ = acldvppCreatePicDesc();
    acldvppSetPicDescData(vpcOutputDesc_, outputBufferDev);             // 输出图片存放位置指针
    acldvppSetPicDescFormat(vpcOutputDesc_, PIXEL_FORMAT_YUV_SEMIPLANAR_420);   // 输出图片的格式
    cout << outPicDesc.width << " " << outPicDesc.height << " " << outputWidth << " " << outputHeight << endl;
    acldvppSetPicDescWidth(vpcOutputDesc_, outPicDesc.width);           // 输出图片的宽
    acldvppSetPicDescHeight(vpcOutputDesc_, outPicDesc.height);         // 输出图片的高
    acldvppSetPicDescWidthStride(vpcOutputDesc_, outputWidth);          // 输出图片对齐后的宽
    acldvppSetPicDescHeightStride(vpcOutputDesc_, outputHeight);        // 输出图片对齐后的高
    acldvppSetPicDescSize(vpcOutputDesc_, outputBufferSize);

    /*
     * 图片的处理和保存
     */
    // 由g_stream这个流水线来运行处理程序
    acldvppVpcCropAsync(g_dvppChannelDesc, vpcInputDesc_, vpcOutputDesc_, cropArea_, g_stream);
    // 等待g_stream流水线完成
    aclrtSynchronizeStream(g_stream);

    // 保存图片
    SaveDvppOutputData(outPicDesc.picName.c_str(), outputBufferDev, outputBufferSize);
    

    /*
     * 释放占用的资源
     */
    acldvppFree(outputBufferDev);
    acldvppDestroyRoiConfig(cropArea_);
    acldvppDestroyPicDesc(vpcInputDesc_);
    acldvppDestroyPicDesc(vpcOutputDesc_);
    aclrtDestroyStream(g_stream);
    aclrtDestroyContext(g_context);
    aclrtResetDevice(0);
    aclFinalize();

    return 0;
}

下图代表裁剪成功了

注意:在使用上面代码的时候,我修改了其他的尺寸,输出的图片就是不正确的,所以我现在也没完全搞懂库函数要求传入的尺寸。但是后续我会持续更新,分享我的学习心得

2.5 vscode报错找不到头文件解决方法

按 F1 或 Ctrl+Shift+p 在弹出的备选选项中选择 C/C++:Edit Configurations (JSON),自动打开c_cpp_properties.json配置文件

 在.vscode中添加头文件搜索路径的json,然后配置路径

三、代码简单讲解

下面的内容全是我个人的理解,如果有佬,感谢在评论区指正,我会及时修改文章

3.1 初始化部分

88行:aclInit("./src/acl.json");

● 一个进程内只能调用一次aclInit接口。使用AscendCL接口开发应用时,必须先调用aclInit接口,否则可能会导致后续系统内部资源初始化出错,进而导致其它业务异常。

● 输入参数是一个json格式的文件。json里面可以写啥,这个可以看官方文档:

aclInit-系统配置-AscendCL API(C&C++)-应用开发接口-CANN社区版8.0.RC2.alpha001开发文档-昇腾社区 (hiascend.com)

目前我是在json里面就写了一对花括号:{}

● 在进程的最后,要成对的使用 aclFinalize(); 函数,做收尾工作

89行:aclrtSetDevice(0);

● 我的理解就是类似于下图(下图是我的猜想,并不来自于官方),一个板子上面有3块昇腾310B的芯片。上面的代码就是设置本线程(一个进程有一个主线程)使用索引为0的昇腾芯片

● 查看官方的device操作发现,aclrtGetDeviceCount函数可以获取Device的数量,我觉得如果有3个昇腾芯片,可能返回的是3。目前香橙派上面只有一个昇腾310B芯片,返回的就是1

hiascend.com/doc_center/source/zh/canncommercial/601/inferapplicationdev/aclcppdevg/aclcppdevg_03_0019.html

● 在线程的最后,要成对的使用aclrtResetDevice(0);函数,做收尾工作

90行:aclrtCreateContext(&g_context, 0);

● 香橙派上是昇腾310B芯片,昇腾310B芯片device中默认存在1个context,本程序中可创建也可不创建context

● 猜测context可能不是一个实体的内容,而是stream集合的一个概念,几个stream组成一个context

● 本程序就是在昇腾设备0上创建了一个context。创建好了以后,本线程就默认使用新创建的context。当前线程在同一时刻内只能使用其中一个Context

●  在线程的最后,要成对的使用aclrtDestroyContext(g_context);函数,做收尾工作

91行:aclrtCreateStream(&g_stream);

● 用于给当前context创建一个stream。上图昇腾310B芯片硬件资源最多支持1024个stream

● 一个stream可以用于执行一个任务,本案例中使用这个stream执行了图片裁剪指定区域的任务。多个stream可以用于同时执行多个任务

● 在线程的最后,要成对的使用aclrtDestroyStream(g_stream);函数,做收尾工作

92、93行:g_dvppChannelDesc = acldvppCreateChannelDesc();
acldvppCreateChannel(g_dvppChannelDesc);

● 这个是图片处理函数必须要的一个参数,在这里提前准备一下
acldvppVpcCropAsync(g_dvppChannelDesc, vpcInputDesc_, vpcOutputDesc_, cropArea_, g_stream);

94行:aclrtGetRunMode(&g_runMode);

● 获取当前程序的运行模式

ACL_DEVICE:昇腾AI软件栈运行在Device的Control CPU或板端环境上
ACL_HOST:昇腾AI软件栈运行在Host CPU上

如果是ACL_DEVICE,就代表当前程序是在AI CPU上运行的;如果是ACL_HOST,就代表当前程序是在CPU上运行的,如下图红色圆圈中写的那样

3.2 拷贝数据到NPU显存中

● 111行,通过ReadBinFile函数读取数据到内存中。如果程序是运行在CPU上,那么读取到的数据就放在运行内存上;如果程序是运行在昇腾芯片的AI CPU上,那么读取到的数据就放在昇腾芯片的内存,也就是显存上

● 114行,在显存上分配一段文件大小的内存。

● 115行,做判断。如果程序是运行在CPU上,就要把数据从运行内存上搬运到显存上;如果程序是运行在昇腾芯片的AI CPU上,那么就把数据从显存搬运到显存上(这里也可以不搬运,主要是为了后面写库的时候使用)

在运行内存上对应的就是HOST,到显存上对应的就是TO_DEVICE;在显存上对应的就是DEVICE,到显存上对应的就是TO_DEVICE。同理也可以HOST_TO_HOST,DEVICE_TO_HOST。

补充一下:如果程序运行在昇腾AI CPU上,想要在运行内存中分配内存,就需要使用aclrtMallocHost来申请内存

3.3 准备裁剪区域

上面分别生成了cropArea_,用于告诉下面的函数,输入图片要裁剪的范围

acldvppVpcCropAsync(g_dvppChannelDesc, vpcInputDesc_, vpcOutputDesc_, cropArea_, g_stream);

因为我对芯片对齐等不是很了解,也不清楚这里到底怎么实现随便裁剪,这里我就不分析了,等后面学习完了再来补充,有知道怎么使用的佬也可以在评论区帮助我一下

3.4 准备输入输出描述符

● 143行,为什么输入的图片大小是这么计算的呢

首先是对齐后图片的宽高如下图,前面这部分很好理解:

● 后面的*3,是因为存储图片的是YUV 3个通道,这样就需要 对齐后的宽高*3得到需要的字节数,

● 那么为什么要除2呢?这是因为YUV本来YUV三个通道都应该有4个信息,也就是8bit来表示Y,8bit来表示U,8bit来表示V。YUV420图片,有8bit来表示Y,只有4bit来表述U,0bit来表示V,字节数自然就少了一半,所以要/2

3.5 收尾阶段

在图片处理的过程中,程序可以异步的去执行了,但是176行,我们任然阻塞的去等待图片处理结果。等到图片处理结束,就将输出的图片指针和大小传入181行的函数中,函数会将图片保存到picName指定的位置去

做完上面这些任务,就释放掉占用的资源

3.6 其他历程如何学习

把其他历程中的关键内容抽取出来,写成像上面那种形式的程序,多动手练习。自己依次分析函数的功能等。

上面代码过程分为这几步:初始化->读取要处理的图片->创建输入图片描述符->创建输出图片描述符->acl处理图片->把输出的图片保存->释放占用的资源

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

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

相关文章

Leetcode每日一题之仅仅反转字母(C++)

在学习之余对于知识的巩固也尤为重要&#xff0c;不论难度高低&#xff0c;都会对代码的理解有所加深&#xff0c;下面我们开始练习 思路解析 关于本题的核心思路就是如何判断字符串中元素是否为字母以及如何遍历字符串以达到仅反转的目的&#xff0c;这里用到的知识就是关于 s…

【数据结构与算法 | 二叉树篇】AVL树

1. 前言 AVL树是一种自平衡的二叉搜索树。为什么会出现AVL树。众所周知&#xff0c;虽然普通的二叉搜索树的平均时间复杂度为O(logn)&#xff0c;但最差的情况的时间复杂度为O(n)。为了避免最差的这种情况&#xff0c;出现了AVL树。 我们规定&#xff1a;如果树有个节点它的左…

大模型LLM——微调的七种方法

大模型的七种微调方法 文章目录 大模型的七种微调方法1. LoRA2. QLoRA3. 适配器调整4. 前缀调整5. 提示调整6. P-Tuning7. P-Tuning v2 1. LoRA LoRA的主要步骤包括&#xff1a; 选择微调目标权重矩阵&#xff1a;首先在大型模型&#xff08;如GPT&#xff09;中识别需要微调…

学习vue3 五,传送,缓存组件以及过渡和过渡列表

目录 Teleport传送组件 keep-alive缓存组件 transition动画组件 1. 过渡的类名 2. 自定义过渡class名 3. transition的生命周期 4.appear transition-group 1. 过渡列表 2. 列表的移动过渡 3. 状态过渡 Teleport传送组件 Teleport Vue 3.0新特性之一。 Teleport 是一…

AI技术如何重塑企业EHS安全健康环保体系,附实践案例

随着人工智能技术的快速发展&#xff0c;其在环境、健康和安全&#xff08;EHS&#xff09;管理领域的应用日益广泛。AI技术通过大数据分析、模式识别和预测建模等手段&#xff0c;为EHS管理提供了新的视角和工具。这一变革不仅提升了风险评估和事故预防的效率&#xff0c;同时…

Can‘t use Subversion command line client:svn不能使用Subversion命令行客户端:svn

1、导入idea中会报Can’t use Subversion command line client… 2、在提交svn代码的时候&#xff0c;出现这样的错误&#xff1a;Can’t use Subversion command line client: svn Probably the path to Subversion executable is wrong. Fix it. 问题原因&#xff1a;在安…

实验8-1-4 拆分实数的整数与小数部分

本题要求实现一个拆分实数的整数与小数部分的简单函数。 函数接口定义&#xff1a; void splitfloat( float x, int *intpart, float *fracpart );其中x是被拆分的实数&#xff08;0≤x<10000&#xff09;&#xff0c;intpart和fracpart分别是将实数x拆分出来的整数部分与…

sqli-labs1-24通关教程

目录 前置知识 第一关 1、第一关是单引号字符型注入,输入id1‘会报错 2、输入注释符正常显示 3、爆出列数为联合做准备 4、使用联合查询爆出数据库名 5、使用information_schema爆出表名 6、猜测用户名再users表中&#xff0c;爆出列名 ​编辑 7、利用查出来的表名数据…

【第2期】2024 搜索客 Meetup | Elasticsearch 的代码结构和写入查询流程的解读

本次活动由 搜索客社区、极限科技&#xff08;INFINI Labs&#xff09;联合举办&#xff0c;活动主题将深入探讨 Elasticsearch 的两个核心方面&#xff1a;代码结构以及写入和查询的关键流程。本次活动将为 Elasticsearch 初学者和有经验的用户提供宝贵的见解&#xff0c;欢迎…

叉车(工业车辆)安全监控管理系统,叉车安全方案

叉车作为特种设备的一种&#xff0c;存在一定的危险性&#xff0c;操作过程出现意外的情况可谓是不胜枚举&#xff0c;轻则伤财、受些皮外伤&#xff0c;重则直接致人死亡。为加强叉车操作的安全管理&#xff0c;从2023年12月1日起实施的TSG 81-2022《场&#xff08;厂&#xf…

【网络安全学习】SQL注入02:使用sqlmap进行注入

1.sqlmap的基本功能 sqlmap的基本功能是对Web应用中的数据库进行自动化的检测、利用和攻击。 使用pikachu靶场进行sqlmap的基础功能使用。 1️⃣ 第一步&#xff1a;先检查是否有注入点&#xff1a; # -u : 指定目标url&#xff0c;也就是指定注入点 sqlmap -u "http:…

终端命令行|CLI工具|CMD|PowerShell

基本概念 终端是一个文本界面&#xff0c;用于执行基于文本的程序。 果你正在运行任何用于 web 开发的工具&#xff0c;你一定需要打开命令行并运行一些命令来使用你所选择的工具 (这样的工具被称为CLI 工具也就是命令行接口工具)。 命令行接口工具 (CLI 工具) 是什么&#xf…

延伸--人工鱼群算法

目录 基本原理 改进策略 代码示例 应用实例 总结 人工鱼群算法在解决多峰函数问题中的具体应用案例和效果如何&#xff1f; 双群人工鱼群算法与传统人工鱼群算法相比&#xff0c;有哪些具体的改进和优势&#xff1f; 步长自适应调整在人工鱼群算法中的实现机制是什么&a…

vue3踩坑问题记录

//vue3element-plus //1、placeholder换行显示 const startTxt ref() const contentText ref<any>() startTxt.value "请描述问题内容、例如&#xff1a;" historyData.prompt.forEach((el:any)>{contentText.value \n${el.question}}) <ElInputv-mo…

点赞收藏测试文章(让我看看有多少机器人在刷互动量)

前言 这里有一些看似合理但实际毫无意义的句子&#xff0c;我需要用它来看看跟我互动的有多少机器人 如果你是人类用户&#xff0c;可以用评论来代替点赞&收藏&#xff0c;爱你~ 目录 前言 正文 1. 紫色的大象在夜空中游泳。 2. 月亮上的饼干师烤出了一片海洋。 3. 时…

数据结构(邓俊辉)学习笔记】词典 03—— 排解冲突(1)

文章目录 1. 一山二虎2. 泾渭分明3. 开放定址4. 线性试探5. 赖惰删除 1. 一山二虎 此前我们已经多次指出&#xff0c;对于需要动态维护的散列表冲突是不可避免的&#xff0c;无论你的散列函数设计的有多么精妙&#xff0c;因此我们不得不回答的第二个重要问题就是一旦发生冲突&…

苹果电脑维护工具:CleanMyMac X让你的Mac焕发新生!

在我们的数字生活中&#xff0c;苹果电脑&#xff08;Mac&#xff09;已成为不可或缺的一部分&#xff0c;无论是为工作披星戴月&#xff0c;还是为娱乐畅游云端。但是&#xff0c;就像任何长时间运行的机器一样&#xff0c;Mac也可能会因为积累的文件和不必要的数据而开始变慢…

DAMA学习笔记(十一)-元数据管理

1.引言 元数据最常见的定义是“关于数据的数据”。它描述了数据本身&#xff08;如数据库、数据元素、数据模型&#xff09;&#xff0c;数据表示的概念&#xff08;如业务流程、应用系统、软件代码、技术基础设施&#xff09;&#xff0c;数据与概念之间的联系&#xff08;关系…

60页PPT数据湖 + 数据中台实施方案

关注智慧方案文库&#xff0c;学习8700多份智慧城市&#xff0c;智慧医院&#xff0c;智能制造&#xff0c;数字化转型&#xff0c;新质生产力&#xff0c;算力&#xff0c;大模型&#xff0c;AIGC&#xff0c;工业互联网&#xff0c;数字孪生......持续更新热点行业解决方案。…

.NET C# Dictionary Hashtable

.NET C# Dictionary & Hashtable 文章目录 .NET C# Dictionary & Hashtable1 Dictionary1.1 底层实现1.2 优点1.3 缺点 2 Hashtable2.1 底层实现2.2 优点2.3 缺点 3 对比总结4 遍历方式&#xff0c;与耗时对比foreach遍历Keys遍历IDictionaryEnumerator遍历耗时对比 1 …