从零构建深度学习推理框架-9 再探Tensor类,算子输入输出的分配
再探Tensor类:
第二节中我们编写的Tensor
类其实并不能满足我们的使用需要,我们将在这一节以代码阅读的方式来看看一个完全版本的Tensor
应该具备怎样的要素,同时我们对Tensor
类的分析来看看在C++中一个设计好的类应该是怎么样的。
Tensor<float>::Tensor(uint32_t channels, uint32_t rows, uint32_t cols) {data_ = arma::fcube(rows, cols, channels);if (channels == 1 && rows == 1) {this->raw_shapes_ = std::vector<uint32_t>{cols};} else if (channels == 1) {this->raw_shapes_ = std::vector<uint32_t>{rows, cols};} else {this->raw_shapes_ = std::vector<uint32_t>{channels, rows, cols};}
}
在这里,raw_shape
记录的是另外一个方面的形状信息,主要用于review
和flatten
层中。
举一个简单的例子,当Tensor
将一个大小为(2,16,1)的Tensor
reshape到(32,1,1)的大小时,raw_shapes
变量会被记录成(32). 将一个大小为(2,16, 2)的Tensor
reshape到(2, 64)的大小时,raw_shapes
会被记录成(2,64).
那这样做的目的是什么呢?原来的
Tensor
不能在逻辑上区分当前的张量是三维的、二维的还是一维的,因为实际的数据存储类arma::fcube
总是一个三维数据。所以我们要区分他的逻辑结构,就需要这么一个raw_shape
列优先的Reshape
void Tensor<float>::ReRawshape(const std::vector<uint32_t>& shapes) {CHECK(!this->data_.empty());CHECK(!shapes.empty());const uint32_t origin_size = this->size();uint32_t current_size = 1;for (uint32_t s : shapes) {current_size *= s;}CHECK(shapes.size() <= 3);CHECK(current_size == origin_size);if (shapes.size() == 3) {this->data_.reshape(shapes.at(1), shapes.at(2), shapes.at(0));this->raw_shapes_ = {shapes.at(0), shapes.at(1), shapes.at(2)};} else if (shapes.size() == 2) {this->data_.reshape(shapes.at(0), shapes.at(1), 1);this->raw_shapes_ = {shapes.at(0), shapes.at(1)};} else {this->data_.reshape(shapes.at(0), 1, 1);this->raw_shapes_ = {shapes.at(0)};}
}
我们再来分析一下这个函数,如果传入的shapes
是1维的,就相当于将数据展开为(elem_size,1,1)
,并将逻辑维度赋值为1. 如果传入的shapes
,相当于将数据展开为(shapes.at(0), shapes.at(1), 1)
. 我们来看看下面的这个图例:
如果把上面的(2,2,3)展平为一维的,那就应该是如下图所示:
而且这也是arma:cube的默认排序(列排序)
行优先的Reshape
那如果我们在某些情况下需要行优先的Reshape
呢?
void Tensor<float>::ReView(const std::vector<uint32_t>& shapes) {CHECK(!this->data_.empty());const uint32_t target_channels = shapes.at(0);const uint32_t target_rows = shapes.at(1);const uint32_t target_cols = shapes.at(2);arma::fcube new_data(target_rows, target_cols, target_channels);const uint32_t plane_size = target_rows * target_cols;for (uint32_t c = 0; c < this->data_.n_slices; ++c) {const arma::fmat& channel = this->data_.slice(c);for (uint32_t c_ = 0; c_ < this->data_.n_cols; ++c_) {const float* colptr = channel.colptr(c_);for (uint32_t r = 0; r < this->data_.n_rows; ++r) {const uint32_t pos_index =c * data_.n_rows * data_.n_cols + r * data_.n_cols + c_;const uint32_t ch = pos_index / plane_size;const uint32_t row = (pos_index - ch * plane_size) / target_cols;const uint32_t col = (pos_index - ch * plane_size - row * target_cols);new_data.at(row, col, ch) = *(colptr + r);}}}this->data_ = new_data;
}
我们只能通过位置计算的方式来对逐个元素进行搬运,const uint32_t plane_size = target_rows * target_cols;
来计算行数和列数相乘的积。
const uint32_t pos_index = c * data_.n_rows * data_.n_cols + r * data_.n_cols + c_; 得 到调整前的元素下标,随后我们计算调整后的通道下标位置:ch = pos_index / plane_size
,plane_size就是和一面,一行乘一列。同理计算row
,col
等调整位置后的行、列坐标。
计算图关系
内容回顾
我们在回顾一下之前的内容,我们根据pnnx
计算图得到了我们的计算图,我们的计算图由两部分组成,分别是kuiper_infer::RuntimeOperator
和kuier_infer::RuntimeOperand
.
但是作为一个计算图,计算节点之间往往是有连接的,包括从input operator
到第一个计算节点再到第二个计算节点,直到最后的输出节点output operator
,我们再来回顾一下这两个数据结构的具体定义:
struct RuntimeOperator {int32_t meet_num = 0; /// 计算节点被相连接节点访问到的次数~RuntimeOperator() {for (auto ¶m : this->params) {if (param.second != nullptr) {delete param.second;param.second = nullptr;}}}std::string name; /// 计算节点的名称std::string type; /// 计算节点的类型std::shared_ptr<Layer> layer; /// 节点对应的计算Layerstd::vector<std::string> output_names; /// 节点的输出节点名称std::shared_ptr<RuntimeOperand> output_operands; /// 节点的输出操作数std::map<std::string, std::shared_ptr<RuntimeOperand>> input_operands; /// 节点的输入操作数std::vector<std::shared_ptr<RuntimeOperand>> input_operands_seq; /// 节点的输入操作数,顺序排列std::map<std::string, std::shared_ptr<RuntimeOperator>> output_operators; /// 输出节点的名字和节点对应std::map<std::string, RuntimeParameter *> params; /// 算子的参数信息std::map<std::string, std::shared_ptr<RuntimeAttribute> > attribute; /// 算子的属性信息,内含权重信息
};
- std::map<:string std::shared_ptr>> output_operators; 我们重点来看这个定义,它是当前这个计算节点的下一个计算节点,当数据在当前
RuntimeOperator
上计算完成之后,系统会读取output_operators
中准备就绪的算子并开始执行。 - std::map<:string std::shared_ptr>> input_operands; 是当前计算节点所需要的输入,它往往来自于上一个
RuntimeOperator
的输入。 - std::shared_ptr output_operands; 是当前节点计算得到的输出,它是通过当前的
op
计算得到的。
具体的流程是这样的,假设我们在系统中有三个RuntimeOperators
,分别为op1
,op2
和op3
. 这三个算子的顺序是依次执行的,分别是op1
-->op2
-->op3
.
- 当我们执行第一个算子
op1
的时候,需要将来自于图像的输入填充到op1->input_operands
中。 - 第一个算子
op1
开始执行,执行的过程中读取op1->input_operands
并计算得到相关的输出,放入到op1->output_operands
中 - 从
op1
的output_operators
中读取到ready的op2
- 第二个算子
op2
开始执行,执行的过程读取op1->output_operands
并拷贝op2->input_operands
中,随后op2
算子开始执行并计算得到相关的输出,放入到op2->output_operands
中。
所以我们可以看到者之间是有一个图关系的,那我们来看一下他是怎么构建这样一个图关系的
怎样构建图关系:
/ 构建图关系for (const auto ¤t_op : this->operators_) {const std::vector<std::string> &output_names = current_op->output_names;for (const auto &next_op : this->operators_) {if (next_op == current_op) {continue;}if (std::find(output_names.begin(), output_names.end(), next_op->name) !=output_names.end()) {current_op->output_operators.insert({next_op->name, next_op});}}}```- **const std::vector\<std::string> &output_names = current_op->output_names;** 存放的是当前`op`的`output_names`,`output_names`也就是当前算子的后一层算子的名字。对于`op1`,它的`output_names`就是`op2`的name.- **const auto &next_op : this->operators_** 我们遍历整个图中的`RuntimeOperators`,如果遇到`next_op`的name和当前`current_op->output_name`是一致的,那么我们就可以认为`next_op`是当前`op`的下一个节点之一。- **current_op->output_operators.insert({next_op->name, next_op});** 将`next_op`插入到`current_op`的下一个节点当中。- 这样一来,当`current_op`执行完成之后就取出`next_op`,并将当前`current_op`的输出`output_opends`(输出)拷贝到`next_op`的`input_operands`(输入)中。
因为在初始化的时候就已经约定好了op1的输出是op2,所以只要在接下来的点中不停地寻找op2就好了,找到了之后就把它insert到output_operators里面.这个output_operators是一个map,就可以让输出节点的名字和节点对应。
这么一个计算图也得有输入输出节点吧
作者:傅大狗
链接:https://zhuanlan.zhihu.com/p/604613883
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
this->input_operators_maps_.clear();this->output_operators_maps_.clear();for (const auto &kOperator : this->operators_) {if (kOperator->type == "pnnx.Input") {this->input_operators_maps_.insert({kOperator->name, kOperator});} else if (kOperator->type == "pnnx.Output") {if (kOperator->name == output_name) {this->output_operators_maps_.insert({kOperator->name, kOperator});} else {LOG(FATAL) << "The graph has two output operator!";}} else {std::shared_ptr<Layer> layer = RuntimeGraph::CreateLayer(kOperator);CHECK(layer != nullptr) << "Layer create failed!";if (layer) {kOperator->layer = layer;}}}
- kOperator->type == "pnnx.Output" 找到
this->operators
中的输出节点,但是目前Kuiperinfer
只支持一个输出节点,其实也可以多输出,作为一个教学框架我实在不想支持这种corner case
- 同理: kOperator->type == "pnnx.Input" 来找到图中,也就是op list中的输入节点
就是在op3结束之后,我们还要把op3的output_operand复制到输出节点的input_operand里面
初始化输入
struct RuntimeOperand {std::string name; /// 操作数的名称std::vector<int32_t> shapes; /// 操作数的形状std::vector<std::shared_ptr<Tensor<float>>> datas; /// 存储操作数 为什么是vector,因为是一个batch,如果batch是2的话,那就存储的是两个RuntimeDataType type = RuntimeDataType::kTypeUnknown; /// 操作数的类型,一般是float
};
可以看到这里的RuntimeOperand::datas
就是存储具体数据的地方,我们初始化输入输出的空间也就是要在推理之前先根据shapes
来初始化好这里datas
的空间
代码位于runtime_ir.cpp
的InitOperatorInputTensor
中 RuntimeGraphShape::InitOperatorInputTensor(operators_) 这个函数的输入是operator list
, 所以将在这个函数中对所有的op
进行输入和输出空间的初始化。
- 得到一个
op
的输入空间input_operands
const std::map<std::string, std::shared_ptr<RuntimeOperand>> &input_operands_map = op->input_operands;
- 如果初始的是空就continue
for (const auto &op : operators) {if (op->input_operands.empty()) {continue;}
- 得到
input_operands
中记录的数据应有大小input_operand_shape
和存储数据的变量input_datas
auto &input_datas = input_operand->datas;CHECK(!input_operand_shape.empty());
const int32_t batch = input_operand_shape.at(0);
CHECK(batch >= 0) << "Dynamic batch size is not supported!";
CHECK(input_operand_shape.size() == 2 ||input_operand_shape.size() == 4 ||input_operand_shape.size() == 3)
- 我们需要根据
input_operand_shape
中记录的大小去初始化input_datas
. 而input_operand_shape
可能是三维的,二维的以及一维的,如下方所示 - input_operand_shape : (batch, elemsize) 一维的
- input_operand_shape : (batch, rows,cols) 二维的
- input_operand_shape : (batch, rows,cols, channels) 三维的
如果当前input_operand_shape
是二维的数据,也就是说输入维度是(batch,rows,cols)的. 我们首先对batch
进行遍历,对一个batch的中的数据input_datas= op->input_operand
(输入)进行初始化。
input_datas.resize(batch);
for (int32_t i = 0; i < batch; ++i) {
}
在for
循环内,它会调用如下的方法去初始化一个二维的张量:
input_datas.at(i) = std::make_shared<Tensor<float>>(1, input_operand_shape.at(1), input_operand_shape.at(2));
这一块不太清楚,我们实际代码看一遍:
for (int32_t i = 0; i < batch; ++i) {if (input_operand_shape.size() == 4) {input_datas.at(i) = std::make_shared<Tensor<float>>(input_operand_shape.at(1), input_operand_shape.at(2),input_operand_shape.at(3));
也就是如果是shape == 4 , 那就是三维的,那么1就是channel,2就是row,3就是col
那么如果输入的channel == 1,或者row == 1
Tensor<float>::Tensor(uint32_t channels, uint32_t rows, uint32_t cols) {data_ = arma::fcube(rows, cols, channels);if (channels == 1 && rows == 1) {this->raw_shapes_ = std::vector<uint32_t>{cols};} else if (channels == 1) {this->raw_shapes_ = std::vector<uint32_t>{rows, cols};} else {this->raw_shapes_ = std::vector<uint32_t>{channels, rows, cols};}
}
那就正好被初始化为了我们之前的raw_shape 这样的一个逻辑维度
这就和我们上面的课程内容对应上了,Tensor<float>
原本是一个三维数据,我们怎么在逻辑上给他表现成一个二维的张量呢?这就要用到我们上面说到的raw_shapes
了。
- 调用并初始化一维的数据也同理, 在初始化的过程中会调用(channels==1&&rows==1) 这个条件判断,并将
raw_shapes
这个维度定义成一维。
input_datas.at(i) = std::make_shared<Tensor<float>>(1, input_operand_shape.at(1), 1)
避免第二次初始化
那么在计算的过程中,我们只需要一次初始化就可以。
所以在第二次遇到她的时候,只需要去检查空间是否发生改变就可以啦
if (!input_datas.empty()) {CHECK(input_datas.size() == batch) << "Batch size is wrong!";for (int32_t i = 0; i < batch; ++i) {const std::vector<uint32_t> &input_data_shape =input_datas.at(i)->shapes();CHECK(input_data_shape.size() == 3)<< "THe origin shape size of operator input data do not equals ""to three";if (input_operand_shape.size() == 4) {CHECK(input_data_shape.at(0) == input_operand_shape.at(1) &&input_data_shape.at(1) == input_operand_shape.at(2) &&input_data_shape.at(2) == input_operand_shape.at(3));} else if (input_operand_shape.size() == 2) {CHECK(input_data_shape.at(1) == input_operand_shape.at(1) &&input_data_shape.at(0) == 1 && input_data_shape.at(2) == 1);} else {// current shape size = 3CHECK(input_data_shape.at(1) == input_operand_shape.at(1) &&input_data_shape.at(0) == 1 &&input_data_shape.at(2) == input_operand_shape.at(2));}}}
CHECK(input_data_shape.at(0) == input_operand_shape.at(1) &&input_data_shape.at(1) == input_operand_shape.at(2) &&input_data_shape.at(2) == input_operand_shape.at(3));
上面这一部分,左边是我们实际有的shape,也就是我们第一次初始化的shape,而右边的是我们再次遇到的时候应该具有的shape,所以这一次就是check这两个shape是否一致,如果check不通过,那就代表输入空间的大小被改变了。那这样的话就会报错,退出这个程序
示例:
这里的conv的输出分别是1和2
expression就接受了1和2为输入
最近在忙老师布置的任务,就耽误了这方面的进度,慢慢补把
相关文章:
从零构建深度学习推理框架-9 再探Tensor类,算子输入输出的分配
再探Tensor类: 第二节中我们编写的Tensor类其实并不能满足我们的使用需要,我们将在这一节以代码阅读的方式来看看一个完全版本的Tensor应该具备怎样的要素,同时我们对Tensor类的分析来看看在C中一个设计好的类应该是怎么样的。 Tensor<fl…...
Vue使用element-ui
main.js配置 //引入Vue import Vue from vue //引入App import App from ./App.vue//完整引入 //引入ElementUI组件库 // import ElementUI from element-ui; //引入ElementUI全部样式 // import element-ui/lib/theme-chalk/index.css;//按需引入 import { Button,Row,DatePi…...
使用ApplicationRunner简化Spring Boot应用程序的初始化和启动
ApplicationRunner这个接口,我们一起来了解这个组件,并简单使用它吧。🤭 引言 在开发Spring Boot应用程序时,应用程序的初始化和启动是一个重要的环节。ApplicationRunner是Spring Boot提供的一个有用的接口,可以帮助…...
Vue 2.x 项目升级到 Vue 3详细指南【修改清单】
文章目录 前言0.迁移过程1. 安装 Vue 32. 逐一处理迁移中的警告3. 迁移全局和内部 API4. 迁移 Vue Router 和 Vuex5. 处理其他的不兼容变更 1. Vue3特性1. Composition API2. 更好的性能3. 更好的 TypeScript 支持4. 多个根元素5. Suspense 组件6. Teleport 组件7. 全局 API 的…...
【算法日志】贪心算法刷题:重叠区问题(day31)
代码随想录刷题60Day 目录 前言 无重叠区间(筛选区间) 划分字母区间(切割区间) 合并区间 前言 今日的重点是掌握重叠区问题。 无重叠区间(筛选区间) int eraseOverlapIntervals(vector<vector<in…...
基于Jenkins构建生产CICD环境、jenkins安装
目录 Jenkins简介 安装配置Jenkins Jenkins简介 Jenkins是一个用Java编写的开源的持续集成工具。在与Oracle发生争执后,项目从Hudson项目独立。官方网站:https://jenkins.io/。 Jenkins提供了软件开发的持续集成服务。它运行在Servlet容器中ÿ…...
基于Java SpringBoot+vue+html 的地方美食系统(2.0版本)
博主介绍:✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W,csdn、博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 文章目录 1 简介2 技术栈3 系统流程的分析3.1 用户管理的流程3.2个人中心管理流程3.3登录流程 4系统设计…...
opencv-gpu版本编译(添加java支持,可选)实现硬解码
目录 opencv gpu版本编译,实现硬解码,加速rtsp视频流读取1、准备文件2、复制 NVCUVID 头文件到 cuda 安装目录 include3、安装相关依赖4、 执行cmake5、编译安装6、测试 opencv gpu版本编译,实现硬解码,加速rtsp视频流读取 前置条…...
数据分析问答总结
一、SQL窗口函数 1.是什么 OLAP(Online Anallytical Processing联机分析处理),对数据库数据进行实时分析处理。 2.基本语法: <窗口函数>OVER (PARTITION BY <用于分组的列名> ORDER BY <用于排序的…...
Python学习笔记_实战篇(二)_django多条件筛选搜索
多条件搜索在很多网站上都有用到,比如京东,淘宝,51cto,等等好多购物教育网站上都有,当然网上也有很多开源的比楼主写的好的多了去了,仅供参考,哈哈 先来一张效果图吧,不然幻想不出来…...
【生态经济学】利用R语言进行经济学研究技术——从数据的收集与清洗、综合建模评价、数据的分析与可视化、因果推断等方面入手
查看原文>>>如何快速掌握利用R语言进行经济学研究技术——从数据的收集与清洗、综合建模评价、数据的分析与可视化、因果推断等方面入手 近年来,人工智能领域已经取得突破性进展,对经济社会各个领域都产生了重大影响,结合了统计学、…...
xml中的vo是干什么用的
在Java中,VO(Value Object)是一种常见的设计模式,用于表示纯粹的数据对象。VO 通常用于在不同层或模块之间传递数据,并且它们的主要目的是封装和组织数据,而不包含业务逻辑。 VO 在Java中的具体作用有以下…...
现代企业数据泄露的原因分析与建议
近年来,随着信息技术的飞速发展,数据已经成为现代企业不可或缺的发展资源。然而,随之而来的数据泄露危机,给个人、企业甚至整个社会带来了巨大的风险与威胁。本文将综合探讨企业数据泄露的主要途径和原因,并提出防护建…...
飞天使-kubeadm安装一主一从集群
文章目录 安装前准备安装前准备配置yum源等安装前准备docker安装 安装kubeadm配置kubeadm验证集群 参考链接 安装前准备 cat >> /etc/hosts <<EOF 192.168.100.30 k8s-01 192.168.100.31 k8s-02 EOF hostnamectl set-hostname k8s-01 #所有机器按照要求修改 ho…...
string类写时拷贝
文章目录 1.string类拷贝构造函数的现代写法2.string类写时拷贝vs和g下string结构的不同vs下string的结构:g下string的结构 3.总结 1.string类拷贝构造函数的现代写法 string类拷贝构造函数的传统写法: string(const string& s){if (this ! &s)…...
QT VS编译环境无法打开包括文件type_traits
这问题,别人给的处理方法都是: 添加环境变量执行vsvars32.bat/vcvarsall.bat/vsdevcmd.bat重新安装QT项目:执行qmake。。。。 个人不推荐配置环境编译,除非你非常熟,因为配置环境变量需要你知道有哪些路径需要添加&a…...
深入浅出 TCP/IP 协议栈
TCP/IP 协议栈是一系列网络协议的总和,是构成网络通信的核心骨架,它定义了电子设备如何连入因特网,以及数据如何在它们之间进行传输。TCP/IP 协议采用4层结构,分别是应用层、传输层、网络层和链路层,每一层都呼叫它的下…...
Servlet+JDBC实战开发书店项目讲解第13讲:库存管理功能
ServletJDBC实战开发书店项目讲解第13讲:库存管理功能 在第13讲中,我们将讲解如何实现书店项目中的库存管理功能。该功能包括图书的添加、编辑、删除和查询等核心功能。下面是实现该功能的主要思路: 显示库存列表: 创建一个管理页…...
Shepherd: A Critic for Language Model Generation
本文是LLM系列的相关文章,针对《Shepherd: A Critic for Language Model Generation》的翻译。 Shepherd:语言模型生成的评价 摘要1 引言2 数据收集3 Shepherd模型4 评估反馈5 结果6 相关工作7 结论不足 摘要 随着大型语言模型的改进,人们对…...
【Python爬虫案例】爬取大麦网任意城市的近期演出!
老规矩,先上结果: 含10个字段: 页码,演出标题,链接地址,演出时间,演出城市,演出地点,售价,演出类别,演出子类别,售票状态。 代码演示…...
【框架】SpringBoot数组传参问题
方式一 前端以字符串形式传递idList,采用逗号拼接,后端直接使用list接收 // 前端代码 form: {otherParam: ,idList: [id1,id2].join(,) }//后端代码 // 在后端接收idList时,直接使用List<T> 就可以接收前端字符串(默认使用…...
四川天蝶电子商务:2023短视频运营分析
短视频运营分析是指通过对短视频平台上的各种数据进行收集、整理和分析,以寻找出视频内容、用户活跃度、用户行为等方面的规律和问题,从而为短视频平台的运营决策提供依据。下面将从几个方面具体介绍短视频运营分析的重要性和方法。 首先,短…...
Git(5)已有项目连接远端git仓库
文章目录 初始化git连接远程仓库拉下仓库代码添加代码到本地仓库删除idea配置的git本地缓存提交代码推上去 初始化git git init连接远程仓库 git remote add origin 你的仓库地址拉下仓库代码 git pull --rebase origin master添加代码到本地仓库 git add .删除idea配置的g…...
Datawhale Django 后端开发入门 Task05 DefaultRouter、自定义函数
一、DefaultRouter是Django REST framework中提供的一个路由器类,用于自动生成URL路由。路由器是将URL与视图函数或视图集关联起来的一种机制。Django REST framework的路由器通过简单的配置可以自动生成标准的URL路由,从而减少了手动编写URL路由的工作量…...
JVM的元空间了解吗?
笔者近期在面试的时候被问到了这个问题,元空间也是Java8当时的一大重大革新,之前暑期实习求职的时候有专门看过,但是近期秋招的时候JVM相关的内容确实有点生疏了,故在此进行回顾。 结构 首先,我们应了解JVM的堆结构&a…...
WPS中的表格错乱少行
用Office word编辑的文档里面包含表格是正常的,但用WPS打开里面的表格就是错乱的,比如表格位置不对,或者是表格的前几行无法显示、丢失了。 有一种可能的原因是: 表格属性里面的文字环绕选成了“环绕”而非“无”,改…...
Pytorch-day09-模型微调-checkpoint
模型微调(fine-tune)-迁移学习 torchvision微调timm微调半精度训练 起源: 1、随着深度学习的发展,模型的参数越来越大,许多开源模型都是在较大数据集上进行训练的,比如Imagenet-1k,Imagenet-11k等2、如果…...
leetcode304. 二维区域和检索 - 矩阵不可变(java)
前缀和数组 二维区域和检索 - 矩阵不可变题目描述前缀和代码演示 一维数组前缀和 二维区域和检索 - 矩阵不可变 难度 - 中等 原题链接 - 二维区域和检索 - 矩阵不可变 题目描述 给定一个二维矩阵 matrix,以下类型的多个请求: 计算其子矩形范围内元素的总…...
记一次oracle数据库迁移至mysql数据库(表同步)
目录 一、利用Navicat将oracle迁移至mysql数据库 1、建立数据传输 2、选择需要迁移的数据库跟目标库 3、数据传输选项 4、选择需要迁移表信息 二、迁移之后遇到的一些问题 1、大小写问题 2、数据库函数问题 3、sql语句是否使用空格隔开问题 4、关于子查询别命名问题 …...
打怪升级之从零开始的网络协议
序言 三个多月过去了,我又来写博客了,这一次从零开始学习网络协议。 总的来说,计算机网络很像现实生活中的快递网络,其最核心的目标,就是把一个包裹(信息)从A点发送到B点去。下面是一些共同的…...
萍乡做网站/如何在各种网站投放广告
德勤于近日发布了《2019科技、传媒和电信行业预测》报告,报告着眼于全球科技、传媒和电信行业在未来5年的关键趋势,分析颠覆性技术变革及未来可能会影响行业内企业的因素。一5G,新型网络时代来临据德勤全球预测,第五代广域无线网络…...
自学建立网站/花钱推广的网络平台
一、new只是隐藏父类中的同名方法。基类和父类中都存在这个方法。 namespace ConsoleApplication1 {class Program{static void Main(string[] args){BaseClass bcdc new DerivedClass();bcdc.Method2(); //结果:Base - Method2Console.Read();}public class BaseC…...
公众号开发是前端还是后端/北京seo结算
实验内容: 测试典型运算符的使用。将程序补充完整。获取实数的整数部分,并求出实数与整数部分的差。输入摄氏温度,计算华氏温度并输出。输入圆柱体的高和半径,求其体积。输入一个梯形的上底、下底、高&#x…...
生活门户网站开发方案/网站建设规划要点详解
写在最前:只有A1/A2版的K2P才能照本文编译固件并刷入,B1/B2版切莫作死。一、简述写在最前,只有A1/A2版本的K2P能照本文编译固件并刷入,B1版本切莫作死。虽然斐讯迟早翻车 现在已经翻车了,不过K2P这款路由器依然声名在外。 CPU MT7…...
b2b电子商务网站调研报告国家开放大学/网站制作需要多少钱
{if(xif(xa[mid]) printf(\ else printf(\} 三、数值计算常用经典算法: 1.级数计算级数计算的关键是“描述出通项”,而通项的描述法有两种:一为直接法、二为间接法又称递推法。直接法的要领是:利用项次直接写出通项式&…...
建设工程项目查询网站/如何关闭2345网址导航
BadImageFormatException,未能加载正确的程序集XXX的解决办法 IDE:VS2010 语言:C# 异常:System.BadImageFormatException,未能加载正确的程序集XXX或其某一依赖项。。。 一般是由于目标程序的目标平台与其某一依赖项的…...