文档章节

演讲实录|马晓宇:When TiDB Meets Spark

TiDB
 TiDB
发布于 2017/09/04 18:52
字数 5032
阅读 31
收藏 0
点赞 0
评论 0

本文整理自 TiSpark 项目发起人马晓宇在 Strata Data Conference 上分享的《When TiDB Meets Spark》演讲实录。

先介绍我自己,我是 PingCAP 的马晓宇,是 TiDB OLAP 方向的负责人,也是 TiSpark 项目的发起人,主要是做 OLAP 方面的 Feature 和 Product 相关的工作,之前是网易的 Big Data Infra Team Leader,先前的经验差不多都是在 SQL、Hadoop 和所谓大数据相关的一些东西。

今天主要会讲的议程大概这么几项。

首先稍微介绍一下 TiDB 和 TiKV,因为 TiSpark 这个项目是基于它们的,所以你需要知道一下 TiDB 和 TiKV 分别是什么,才能比较好理解我们做的是什么事情。

另外正题是 TiSpark 是什么,然后 TiSpark 的架构,除了 Raw Spark 之外,我们提供了一些什么样的不一样的东西,再然后是 Use Case,最后是项目现在的状态。

首先说什么是 TiDB。你可以认为 TiDB 是现在比较火的 Spanner 的一个开源实现。它具备在线水平扩展、分布式 ACID Transaction、HA、Auto failover 等特性,是一个 NewSQL 数据库。

然后什么是 TiKV,可能我们今天要说很多次了。TiKV 其实是 TiDB 这个产品底下的数据库存储引擎,更形象,更具体一点,这是一个架构图。

大家可以看到,TiDB 做为一个完整的数据库来说,它是这样的一个架构,上层是 DB 层,DB 层是负责做 DB 相关的东西,比如说一部分的 Transaction,SQL 的解析,然后执行 Query Processing 相关的一些东西。

底下是 KV 层,存储层。存储层就是存储数据,通过 Raft 协议来做 Replica 的,旁边还有 Placement Driver(简称 PD),如果对 Hadoop比较了解,你可以认为它有点像 NameNode,它会存储每一个 Region 分别存了哪些 Key,然后 Key Range 是什么。当然它在需要的时候也会做一些数据搬迁的调度,以及 Leader 的自动负载均衡等。最后 PD 提供了统一中央授时功能。

所有这些组件,都是通过 gRPC 来进行通讯的。

我们回到正题来说,什么叫 TiSpark。TiSpark 就是 Spark SQL on TiKV。为什么说是 on TiKV,而不是 on TiDB,因为我们让 Spark SQL 直接跑在分布式存储层上而绕过了 TiDB。这三个组件,TiDB / TiKV / TiSpark 一起,作为一个完整的平台,提供了 HTAP(Hybrid Transactional/Analytical Processing)的功能。

再具体一点说 TiSpark 实现了什么:首先是比较复杂的计算下推,然后 Key Range Pruning,支持索引(因为它底下是一个真正的分布式数据库引擎,所以它可以支持索引),然后一部分的 Cost Based Optimization 基于代价的优化。

CBO 这里有两部分,一部分是说,因为我们有索引,所以在这种情况下,大家知道会面临一个问题,比如说我有十个不同索引,我现在要选择哪一个索引对应我现在这个查询的谓词条件更有利。选择好的索引,会执行比较快,反之会慢。 另外一个是,刚才大家可能有听华为的 Hu Rong 老师介绍,他们在 Spark 上面做 Join Reorder,对于我们来说,也有类似的东西,需要做 Join Reorder 。这里底下有两个是 Planned 但还没有做。一个是回写,就是说现在 TiSpark 是一个只读的系统。另外我们考虑把常用的一些传统数据库的优化手段,也搬到我们这边来。

现在开始说一下整个架构是什么样的。后面会有一个具体的解说,先看一下架构图。

在 Spark Driver 上,需要接入 TiSpark 的接口,现在 TiSpark 也支持 JDBC。Worker / Executor 那边也需要一个这样的架构。 整个部署,采用 Spark 外接 JAR 的方式,并没有说需要到我整个把 Spark 部署全都换掉属于我们的版本,只需要提交一个 JAR 包就可以。每个 TiSpark 组件会与 TiKV 进行通讯,Driver 这边会和 Placement Driver 进行通讯,然后这边具体干了什么,后面会解释。

在 Spark Driver 这边,因为这个架构上没有 TiDB 什么事,所以说 DB 本身干的事情,我们需要再干一遍,比如说 Schema 存在 TiKV 存储引擎里面,然后里面包括 Tables 的元信息,也就是告诉你数据库里面,分别有什么表,每个表里面分别有什么列,这些东西都属于 Schema 信息。因为我们没有直接连接 TiDB,所以说 Schema 信息需要我们自己去解析。

比较重要的功能通过将 Spark SQL 产生的 LogicalPlan,Hook LogicalPlan,然后去做过滤,主要是:

  1. 哪一些谓词可以转化成索引相关的访问;

  2. 哪一些可以转化成 Key Range 相关的,还有哪一些其它计算可以下推,这些 Plan 节点我们会先过滤处理一遍。然后把 TiKV 可以算的部分推下去,TiKV 算不了的反推回 Spark;

  3. 在基于代价的优化部分 Join Reorder 只是在 Plan 状态;

  4. Data Location 是通过 Placement Driver 的交互得到的。Java 这边,会跟 Placement Driver 进行交互,说我要知道的是每个(Task)分别要发哪一台机器,然后分别要知晓哪一块的数据。

之后切分 Partition 的过程就稍微简单一点,按照机器分割区间。之后需要做 Encoding / Decoding:因为还是一样的,抛弃了数据库之后,所有的数据从二进制需要还原成有 Schema 的数据。一个大数据块读上来,我怎么切分 Row,每个 Row 怎么样还原成它对应的数据类型,这个就需要自己来做。

计算下推,我需要把它下推的 Plan 转化成 Coprocessor 可以理解的信息。然后当作 Coprocessor 的一个请求,发送到 Coprocessor,这也是 TiKV-Client 这边做的两个东西。

这些是怎么做的?因为 Spark 提供的两个所谓 Experimental 接口。这两个分别对应的是 Spark Strategy 和 Spark Optimizer,如果做过相关的工作你们可能会知道,你 Hook 了 SQL 引擎的优化器和物理计划生成部分。那两个东西一旦可以改写的话,其实你可以更改数据库的很多很多行为。当然这是有代价的。什么代价?这两个看名字,Experimental Methods,名字提示了什么,也就是在版本和版本之间,比如说 1.6 升到 2.1 不保证里面所有暴露出来的东西都还能工作。可以看到,一个依赖的函数或者类,如果变一些实现,比如说 LogicalPlan 这个类原来是三个参数,现在变成四个参数,那可能就崩了,会有这样的风险。

我们是怎么样做规避的呢?这个项目其实是切成两半的,一半是 TiSpark,另一半是重很多的 TiKV-Client 。TiKV Java Client是负责解析对 TiKV 进行读取和数据解析,谓词处理等等,是一个完整的 TiKV 的独立的 Java 实现的接口。也就是说你有 Java 的系统,你需要操作 TiKV 你可以拿 TiKV Client 去做。底下项目就非常薄,你可以说是主体,就是真的叫 TiSpark 的这个项目,其实也就千多行代码。做的事情就是利用刚才说的两个 Hook 点把 Spark 的 LogicalPlan 导出来,我们自己做一次再变换之后,把剩下的东西交还给 Spark 来做的。这一层非常薄,所以我们不会太担心每个大版本升级的时候,我们需要做很多很多事情,去维护兼容性。

刚才说的有几种可能比较抽象,现在来一个具体的例子,具体看这个东西怎么 Work,可以看一个具体的例子。

这是一个查询,根据所给的学号和学院等条件计算学生平均值。这张表上,有两个索引,一个索引是主键索引,另外一个索引是在 Secondary Index ,建立在 School 上。lottery 是一个用户自定义函数,并不在 TiDB 和 TiKV 的支持范围之内。

首先是说谓词怎么样被处理,这里有几种不同的谓词,比如关于学生 ID 的:大于等于 8000,小于 10100,以及有两个单独学号;然后是一个 school = ‘engineer’,还有一个 UDF 叫 lottery,单独挑选一些运气不好的学生。

第一步,整个处理,假设说我们索引选中的是在 studentID 上的聚簇索引。studentID 相关的谓词可以转化为区间 [8000, 10100), 10323, 10327。然后是 school=‘engineer’,因为它没有被任何索引选择,所以是一个独立的条件。这两种不同的条件,一个是跟聚簇索引相关的,可以转化成 Key Range,另外一个是跟索引没有关系的独立的谓词。两者会经过不同的处理,聚簇索引相关的谓词转化成 Key Range,独立的谓词 school=‘engineer’ 会变成 Coprocessor 的 Reqeust,然后进行 gRPC 的编码,最后把请求发过去。聚簇索引相关谓词转化的 Key Range 会通过查询 Placement Driver 取得 Region 的分布信息,进行相应的区间切割。假设说有三个 Region。Region 1 是 [0, 5000),是一个闭开区间,然后 Region 2 是 [5000, 10000)。接着 Region 3 是 [10000, 15000)。对应我们上面的 Request 下推的区间信息你可以看到,谓词区间对应到两个 Region:Region 2 和 Region 3,Region1 的数据根本不用碰,Region 2 的数据会被切成 [8000, 10000),因为对应的数据区间只有 [8000, 10000)。然后剩下的 [10000, 10100) 会单独放到 Region 3 上面,剩下的就是编码 school=‘engineering’ 对应的 Coprocessor Request。最后将编码完成的请求发送到对应的 Region。 上面就是一个谓词处理的逻辑。

多个索引是怎么选择的呢?是通过统计信息。

TiDB 本身是有收集统计信息的, TiSpark 现在正在实现统计信息处理的功能。TiDB 的统计信息是一个等高直方图。例如我们刚才说的两个索引,索引一在 studentId 上,索引二是在 school 上。查询用到了 studentId 和 school 这两个列相关的条件,配合索引,去等高直方图进行估算,直方图可以告诉你,经过谓词过滤大概会有多少条记录返回。假设说使用第一个索引能返回的记录是 1000 条,使用第二个能返回的记录是 800 条,看起来说应该选择 800 条的索引,因为他的选择度可能更好一点。但是实际上,因为聚簇索引访问代价会比较低,因为一次索引访问就能取到数据而 Secondary Index 则需要访问两次才能取到数据,所以实际上,反而可能 1000 条的聚簇索引访问是更好的一个选择。这个只是一个例子,并不是说永远是聚簇索引更好。 然后还有两个优化,一个优化是覆盖索引,也就是说索引是可以建多列的,这个索引不一定是只有 school 这个列,我可以把一个表里面很多列都建成索引,这样有一些查询可以直接用索引本身的信息计算,而不需要回表读取就可以完成。比如,

select count(\*) from student where school=’engineer’

整个一条查询就只用到 school 这个列,如果我的索引键就是 school,此外并不需要其他东西。所以我只要读到索引之后,我就可以得到 count(*) 是多少。类似于这样的覆盖索引的东西,也有优化。TiSpark 比较特殊的是,下层接入的是一个完整的数据库而数据库把控了数据入口,我每个 Update 每个 Insert 都可以看到。这给我们带来什么方便,就是说每个更新带来的历史数据变更可以主动收集。

基于代价优化的其他一些功能例如 Join Reorder 还只是计划中,现在并没有实现。刚才有跟 Hu Rong 老师有讨论,暂时 Spark 2.2 所做的 CBO,并不能接入一个外部的统计信息,我们暂时还没想好,这块应该这么样接。 接下来是聚合下推,聚合下推可能稍微特殊一点,因为一般来说,Spark 下面的数据引擎,就是说现在 Spark 的 Data Source API 并不会做聚合下推这种事情。

还是刚才的 SQL 查询:

这个例子稍微有一点特殊,因为他是计算平均值,为什么特殊,因为没有办法直接在 TiKV 做 AVG 平均值计算,然后直接在 Spark 再做直接聚合计算,因此这种情况会有一个改写,将 AVG 拆解成 SUM 和 COUNT,然后会把他们分别下推到 Coprocessor,最后在 Spark 继续做聚合计算。

TiSpark 项目除了改写 Plan 之外,还要负责结合做类型转换和 Schema 转换。因为 TiKV 这个项目,本身并不是为了 TiSpark 来设计的,所以整个 Schema 和类型转化的规则都是不一样的。Coprocessor 部分聚合 (Partial Aggregation) 的结果,数据的类型和 Spark 是完全不一样的,因此这边还会做一次 Schema 的桥接。之后其他的就是跟前面一样了,会把请求发到对应的 Region。

现在来讲 TiSpark 和 TiDB/TiKV,因为是整个一个产品的不同组件,所以说 TiSpark 的存储,也就是 TiDB 的存储,TiKV 会针对 TiSpark 这个项目来做一些 OLAP 相关定的 Feature。

比如说在 OLTP 的模式下我们使用的是 SI 隔离级别,就是 Snapshot Isolation。在 OLTP 这边,需要面对一个 Lock Resolving 问题和开销。如果要看的话可以看一下 Percolator Transaction 的论文。为了避免 Lock Resolving 带来的开销,我们使用了一个 Read Committed 的模式。如果需要的话,后面再加 SI 也并不是非常难,只是现在这个版本并不会这样做。

之后还有 OLTP 和 OLAP 混跑,大家可能会觉得有很大问题,就是资源怎么样隔离。现在资源隔离是这样的:对于比较大的查询,在 KV 那层会倾向于用更少的线程。当然是说你如果是在空跑,这台机器上没有其他人在跑的话,其实还是会用所有的资源,但如果你有跟其他 OLTP 查询对比的话,你会发现虽然我是请求了很多但你可能未必会拿到很多。用户也可以手动来降低优先级,例如,我明天就要给老板出一个报表,一个小时候之后就要拿结果,我可以手动提高一个分级。 所有刚刚讲的这些,基本上都是 TiSpark 本身提供了一些什么东西。现在说在一个类似于 Big Picture 的语境之下,怎么样去看这个项目。除了 Raw Spark 的功能之外,我们提供了什么多的东西。最不一样的地方就是 SQL-On-Hadoop,基本上来说,你可以认为它并不控制存储,存储并不在你这里,你灌的数据,可能是通过 Flume/Kafka 某一个 Sink 灌进来,或通过 Sqoop 导过来,整个不管是 Hive,还是 Spark SQL,他并不知道数据进来。对于一个数据库来说,每一条数据插入,全是经过数据库本身的,所以每一条数据怎么样进来,怎么样存,整个这个产品是可以知道的。

另外就是说相对于 SQL-On-Hadoop,我们做一个数据库,肯定会提供 Update 和 Delete 这是不用多说的。因为 TiKV 本身会提供一些额外计算的功能,所以我们可以把一些复杂的查询进行下推。现在只是说了两个,一个是谓词相关的 下推,还有一个是刚才说的聚合下推,其他还有 Order,Limit 这些东西,其实也可以往下放。

接下来就属于脑洞阶段了,除了刚才说的已经“高瞻远瞩”的东西之外,脑洞一下,接下来还可以做一些什么(当然现在还没有做),这个已经是 GA 还要再往后的东西了。

首先说存储,TiKV 的存储是可以给你提供一个全局有序,这可能是跟很多的 SQL-On-Hadoop 的存储是不一样的。Global Order 有什么好处,你可以做 Merge Join,一个 Map Stage 可以做完,而不是要做 Shuffle 和 Sort。Index lookup join 是一个可以尝试去做的。

Improved CBO,我们数据库团队现在正在开发实时收集统计信息。 其他一些传统数据库才可能的优化,我们也可以尝试。这里就不展开多说了。 整个系统,一个展望就是 Spark SQL 下层接数据库存储引擎 TiKV ,我可以希望说 Big Data 的那些平台是不是可以和传统的数据库就合在一起。因为本身 TiDB 加 TiKV 就是一个分布式的数据库。然后可以做 Online Transaction,类似于像 Spanner 提供的那些功能之外,我们加上 Spark 之后,是不是可以把一些 Spark 相关的 Workload 也搬上来。

然后是 Use Case:首先一个平台,可以做两种不同的 Workload,Analytical 或者 Transactional 都可以在同一个平台上支持,最大的好处你可以想象:没有 ETL。比如说我现在有一个数据库,我可能通 Sqoop 每小时来同步一次,但是这样有一个延迟。而使用 TiSpark 的话,你查到的数据就是你刚才 Transaction 落地的数据而没有延迟。另外整个东西加在一起的话,就是有一个好处:只需要一套系统。要做数据仓库,或者做一些离线的分析,现在我并不需要把数据从一个平台导入数据分析平台。现在只要一套系统就可以,这样能降低你的维护成本。

另外一个延伸的典型用法是,你可以用 TiDB 作为将多个数据库同步到一起的解决方案。这个方案可以实时接入变更记录,比如 Binlog,实时同步到 TiDB,再使用 TiSpark 做数据分析,也可以用它将 ETL 之后的结果写到 HDFS 数仓进行归档整理。

需要说明的是,由于 TiDB / TiKV 整体是偏重 OLTP,暂时使用的是行存且有一定的事务和版本开销,因此批量读的速度会比 HDFS + 列存如 Parquet 要慢,并不是一个直接替代原本 Hadoop 上基于 Spark SQL / Hive 或者 Impala 这些的数仓解决方案。但是对于大数据场景下,如果你需要一个可变数据的存储,或者需要比较严格的一致性,那么它是一个合适的平台。

后续我们将写一篇文章详细介绍 TiSpark 的 Use Case,对 TiSpark 感兴趣的小伙伴,欢迎发邮件到 info@pingcap.com 与我们交流。

整个这个项目的状态是在 9 月跟整个 TiDB 、TiKV 同步做 release。现在的话,刚刚把 TPC-H 跑通的状态,像刚才说的有些 Feature,例如 CBO 那些还没有完全做完。Index 也只是做了 Index 读取,但是说怎么样选 Index 还没有做,正在 Bug fix 以及 Code Cleanup。在 GA 之前会有一个 Beta 大家可以部署了玩一次。目前 TiSpark Beta 已经发布。

© 著作权归作者所有

共有 人打赏支持
TiDB
粉丝 95
博文 101
码字总数 258813
作品 0
海淀
重磅!开源分布式 NewSQL 数据库 TiDB 2.0 正式发布

去年十月,TiDB 1.0 版本发布,在接下来的六个月中,开发团队一方面在维护 1.0 版本的稳定性并且增加必要的新特性,另一方面马不停蹄的开发 2.0 版本。经过 6 个 RC 版本,TiDB 2.0 GA 版本于...

雨田桑
04/28
0
19
TiDB 在西山居实时舆情监控系统中的应用

公司简介 西山居创建 1995 年初夏,在美丽的海滨小城珠海,西山居工作室孕育而生,一群西山居居士们十年如一日尅勊业业的奋斗。"创造快乐,传递快乐!" 一直是西山居居士们的创作宗旨。西山居...

TiDB
06/11
0
0
易观 OLAP 大赛揭晓 PingCAP 斩获商业组桂冠

28 日,在 2017 易观 A10 大数据应用峰会上,针对“有序漏斗”难题进行行业攻坚的“2017 易观 OLAP 算法大赛”公布了最终结果。PingCAP 参赛组以超过原始基准测试近 30 倍的成绩,获得了商业...

TiDB
2017/10/30
0
0
TIDB集群安装部署方案————————下篇

=================== 下面进入真正的实施部署阶段了=== 概述 Ansible 是一款自动化运维工具,TiDB-Ansible 是 PingCAP 基于 Ansible playbook 功能编写的集群部署工具。使用 TiDB-Ansible 可...

叶海无崖
07/14
0
0
实录 | 黄东旭:开源与基础软件创业在中国

10 月 23 日,EGO 北京分会会员、PingCAP 联合创始人兼 CTO 黄东旭作为 EGO 线上分享第二季嘉宾,与超过 400 位 EGO 会员交流了自己在开源软件和创业方面的感悟。本文为根据口述内容整理的实...

TiDB
2017/11/08
0
0
UCloud 与 PingCAP 达成合作 Cloud TiDB 全球正式发布

2017 年 10 月,国内领先的中立云计算厂商 UCloud 与国内开源分布式 NewSQL 数据库 TiDB 团队 PingCAP 正式达成合作,双方将联手在 UCloud 全球数据中心逐步推出新一代 TiDB 的云端版本——C...

TiDB
2017/10/30
0
0
TiDB 在饿了么归档环境的应用

背景 随着业务增长,公司数据规模不断膨胀,表变多、变大。一方面占用的磁盘、CPU 等物理资源疾速上涨,另一方面大表性能下降且变更困难。实际上,很多大表的数据无需保留很久,比如某些业务...

TiDB
04/26
0
0
TiDB 分布式数据库在转转公司的应用实践

作者:孙玄,转转公司首席架构师;陈东,转转公司资深工程师;冀浩东,转转公司资深 DBA。 公司及业务架构介绍 转转二手交易网 —— 把家里不用的东西卖了变成钱,一个帮你赚钱的网站。由腾讯...

TiDB
05/30
0
0
开源分布式 NewSQL 关系型数据库 - TiDB

TiDB 是国内 PingCAP 团队开发的一个分布式 SQL 数据库。其灵感来自于 Google 的 F1 和 Google spanner, TiDB 支持包括传统 RDBMS 和 NoSQL 的特性。 TiDB 的源码已经托管在 Git@OSC 上,详情...

goroutine
2015/09/06
0
37
TiDB 源码阅读系列文章(十)Chunk 和执行框架简介

什么是 Chunk TiDB 2.0 中,我们引入了一个叫 Chunk 的数据结构用来在内存中存储内部数据,用于减小内存分配开销、降低内存占用以及实现内存使用量统计/控制,其特点如下: 只读 不支持随机写...

TiDB
06/19
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

全新内存布局Android5 for one x

众所周知Android5.0默认ART模式,运行速度加倍,软件占用内存也加倍,我们one x这种元老机采用旧的内存布局,data空间2g ART模式下安装几个软件也就不够用了。最近逛国外的xda论坛,发现有大...

CrazyManDF
3分钟前
0
0
web3j转账

 web3 转账功能   为了完成以太坊交易,必须有几个先决条件   1、对方的以太坊地址   2、确定要转账的金额   3、自己地址的转账权限   4、大于转账金额的以太币,以太币转账其实就...

智能合约
4分钟前
0
0
10.28 rsync工具介绍 , rsync常用选项, rsync通过ssh同步

rsync远程同步 重点!重点!!重点!!! 例子 • rsync -av /etc/passwd /tmp/1.txt • rsync -av /tmp/1.txt 192.168.188.128:/tmp/2.txt rsync格式 • rsync [OPTION] … SRC DEST • rs......

Linux_老吴
18分钟前
0
0
iis php 环境搭建,非常详细的教程

准备篇 一、环境说明: 操作系统:Windows Server 2016 PHP版本:php 7.1.0 MySQL版本:MySQL 5.7.17.0 二、相关软件下载: 1、PHP下载地址: http://windows.php.net/downloads/releases/ph...

T_star
20分钟前
0
0
Day35 rsync通过服务同步

rsync通过服务同步 rsyncd.conf配置文件详解 port:指定在哪个端口启动rsyncd服务,默认是873端口。 log file:指定日志文件。 pid file:指定pid文件,这个文件的作用涉及服务的启动、停止等...

杉下
25分钟前
1
0
【最新最全】为 iOS 和 Android 的真机和模拟器编译 Luajit 库

编译 Luajit 库,的确是一个挑战。因为官网的教程,在当前版本的 Xcode 和 NDK 环境中,已经不适用了。以前只是编译了适用于真机的 Luajit 库。最近在尝试编译模拟器 Luajit 库,就顺便梳理了...

ios122
26分钟前
0
0
rsync至ssh同步

rsync: 文件同步工具,可实现“增量拷贝”;使用yum安装rsync包 常用选项:-a=-rtplgoD (-r同步目录,-t保持文件的时间属性,-p保持文件的权限属性,-l保持软连接,-g保持文件的属组,-o保持...

ZHENG-JY
31分钟前
0
0
TradingView 学习笔记

#前言 公司最后需要使用TradingView展示K线图走势。由于之前没接触过,拿到文档时一脸蒙逼。还好找到二篇文章+Demo代码,直接改改就行了。 #被批 由于上面的懵懂,有个问题困扰4个小时没解决...

hihubs
31分钟前
0
0
10.28 rsync工具介绍~10.31 rsync通过ssh同步

rsync命令是一个远程数据同步工具,可通过LAN/WAN快速同步多台主机间的文件。rsync使用所谓的“rsync算法”来使本地和远程两个主机之间的文件达到同步,这个算法只传送两个文件的不同部分,而...

洗香香
34分钟前
1
0
卷积为什么要旋转180度

参考《最容易理解的对卷积(convolution)的解释》 https://blog.csdn.net/bitcarmanlee/article/details/54729807 这篇博客详细讲解了“卷积”,提及了为什么要反转180度,我简述下。 1.卷积的...

datadev_sh
43分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部