流批一体向量化计算引擎 Flex 在蚂蚁的探索和实践

编者按:Flex是蚂蚁数据部自研的一款流批一体的向量化引擎,Flex是Fink和Velox的全称,也是Flexible的前缀,被赋予了灵活可插拔的寓意。本文将重点从向量化技术背景、Flex架构方案和未来规划三个方面展开论述。

作者介绍刘勇,蚂蚁数据部分布式计算引擎技术专家,Calcite Committer、Flink Contributor。毕业8年一直对分布式计算领域保持着十年如一日的热情与专注。

  

一、背景

1. 什么是向量化计算?

1)并行数据处理SIMD指令

以下面循环代码为例,计算在CPU内完成需经三步:

  • 加载(Load),从内存加载2个源操作数(a[i]和b[i])到2个寄存器。
  • 计算(Compute),执行加法指令,作用于2个寄存器里的源操作数副本,结果产生到目标寄存器。
  • 存储(Store),将目标寄存器的数据存入(拷贝)到目标内存位置(c[i])。
void addArrays(const int* a, const int* b, int* c, int num) {
  for (int i = 0; i < num; ++i) {
    c[i] = a[i] + b[i];
  }
}

该流程即对应传统的计算架构:单指令单数据(SISD)顺序架构,任意时间点只有一条指令作用于一条数据流。如果有更宽的寄存器(超机器字长,比如256位16字节),一次性从源内存同时加载更多的数据到寄存器,一条指令作用于寄存器x和y,在x和y的每个分量(比如32位4字节)上并行进行加,并将结果存入寄存器z的各对应分量,最后一次性将寄存器z里的内容存入目标内存,那么就能实现单指令并行处理数据的效果,这就是单指令多数据(SIMD)。

图片

2)向量化执行框架具有的特性

执行引擎常规按行处理的方式,存在以下问题:

  • CPU Cache命中率差。一行的多列(字段)数据的内存紧挨在一起,哪怕只对其中的一个字段做操作,其他字段所占的内存也需要加载进来,这会抢占稀缺的Cache资源。Cache命中会导致被请求的数据从内存加载进Cache,等待内存操作完成会导致CPU执行指令暂停(Memory Stall),这会增加延时,还可能浪费内存带宽。

  • 变长字段影响计算效率。假设一行包括int、string、int三列,其中int类型是固定长度,而string是变长的(一般表示为int len + bytes content),变长列的存在会导致无法通过行号算offset做快速定位。

  • 虚函数开销。对一行的多列进行处理通常会封装在一个循环里,会抽象出一个类似handle的接口(C++虚函数)用于处理某类型数据,各字段类型会override该handle接口。虚函数的调用多一步查表,且无法被内联,循环内高频调用虚函数的性能影响不可忽。

图片

因此,要让向量化计算发挥威力,只使用SIMD指令还不够,还需要对框架层面进行改造,数据按列组织

  • 数据按列组织将提高数据局部性。参与计算的列的多行数据会内存紧凑的保存在一起,CPU可以通过预取指令将接下来要处理的数据加载进Cache,从而减少Memory Stall。不参与计算的列的数据不会与被处理的列竞争Cache,这种内存交互的隔离能提高Cache亲和性。

  • 同一列数据在循环里被施加相同的计算。批量迭代将减少函数调用次数,通过模版能减少虚函数调用,降低运行时开销。针对固定长度类型的列很容易被并行处理(通过行号offset到数据),这样的执行框架也有利于让编译器做自动向量化代码生成,显著减少分支,减轻预测失败的惩罚。结合模板,编译器会为每个实参生成特定实例化代码,避免运行时查找虚函数表,并且由于编译器知道了具体的类型信息,可以对模板函数进行内联展开。

图片

2. 向量化计算在分布式计算领域的现状

随着技术的发展,向量化技术在硬件、指令集、配套工具、类库等得到多方位协同发展。

  • 在指令集层面,当前大多数机器都支持SIMD指令集,比如x86平台的SSE、AVX指令集,ARM平台的NEON指令集。

  • 在编译器层面,现代编译器如GCC、LLVM可以自动将代码中可向量化的部分转成SIMD指令。比如开源有一个基于arrow的表达式计算框架gandiva就是基于LLVM生成SIMD指令。

  • 在类库层面,有支持跨平台的库XSimd,可同时运行在x86和arm架构。

图片

上面讲述了向量化的技术原理和可供使用的方案,我们再看下大数据领域在向量化方向做了哪些探索。首先看下Photon,Photon是Databricks闭源的一个全新的向量化引擎,完全用C++编写。Databricks在过去几年里,一直通过各种技术手段对内部的Spark版本Databricks Runtimes进行优化。

左下图展示了不同DBR版本相对于2.1版本的性能提升情况,可以看到未开启Photon的版本性能提升在1-2倍,自从上了Photon后,性能提升可以达到3-8倍。自从Databricks论文公布其方案和效果后,大数据领域进行了各种尝试和探索。主流分布式计算引擎都在往向量化方向发力且效果显然,Flink作为流批一体实时计算引擎在湖仓一体中起到了关键核心作用,但缺少核心的向量化能力,因此我们蚂蚁在该领域发起了挑战。

图片

由于目前开源社区已经有比较成熟的 Native Engine,如ClickHouse、Velox,具备了优秀的向量化、批处理、列计算等能力,并经过业界广泛验证和实践。因此,我们不打算重头造轮子,而是站在巨人的肩膀上。采用了类似Gluten的实现方式,基于Velox上构建。同时,为了防止闭门造车,在立项之出选择和Gluten社区合作,讨论构建方案以及加强紧密型合作。

自此,该项目在蚂蚁正式落地,内部代号Flex,Flex是Fink和Velox的全称,也是Flexible的前缀,我们希望它能够做到灵活可插拔,像胶水一样扮演其工作。

3. 流批一体向量化引擎在蚂蚁的探索

因此,今年我们在Flink 1.18中首次引入了流批一体向量化计算,通过使用C++开发,利用 SIMD、向量化、列计算等技术,在完全兼容 Flink SQL 语法的基础之上,可以提供比原生的执行引擎更高的吞吐,并可以降低用户 Flink 作业的执行成本。同时,SQL语义对齐,对用户来说,仅仅需要增加配置即可开启,降低存量用户使用成本。该技术具有较强的创新性和技术挑战性,属于自主研发,弥补了业界空白

二、总体方案

Velox算子面向传统批处理场景,算子本身无法处理回撤消息,即天然和流式计算场景相违背,因此无法直接基于gluten的substrain plan方案。该方案实现复杂工作量大,但是也没法支持状态算子。同时,为了复用Velox一些现有的能力,不必重复造轮子,因此我们另辟蹊径采用了新的方案,新方案可以做到真正的流批一体;同时,向量化核心效果在于表达式计算。因此非常适合Calc算子,而且Calc算子不区分流和批,也就是说流任务和批任务都可以享受到向量化带来的技术红利,也可以使用到Velox的表达式计算能力。为此,我们从功能性、正确性、易用性和稳定性四个方面进行全方位建设。

图片

1. 功能性建设

1)Native算子层优化

NativeCalc算子优化
  • 在用户向量化作业验证过程中,发现对于任务中含有很多RexInputRef引用字段,这些字段数据内容长且没有加工逻辑,如果把使用到的Scheme字段全部做攒批和数据转换,转换开销将近11%。我们可以将引用字段单独拆出来,拆成两个Calc算子,在NativeCalc算子层仅仅将表达式中使用的字段进行数据转换,其他字段仅仅forward到原有费Calc codegen计算进行取值,最后再拼接JoinedRowData。为了高效的拼接JoinedRowData,自然需要插入Calc算子对字段重排序,因此需要增加规则支持projection reorder。通过该优化数据转换层开销降低到6.68%(3.96+2.72)

  • 同时对于表达式里面含有udf,复用此方案把udf也拆到引用字段的Calc里面,就不需要整个Calc算子fallback。

图片

支持NativeSource/NativeSink
  • 对于蚂蚁内部有类似kafka的消息队列sls,sls source/sink当前支持Arrow模式,为了避免冗余的数据行转列开销,插件层直接读写Arrow格式数据,从而避免额外的行转列开销。

2)Plan层优化

  • 为了尽可能多的将DAG中各种算子中包含projection和condition的逻辑中包含simd函数的计算逻辑委托给velox进行计算,我们在plan层增加了多种基于规则的Rule对算子进行拆分合并和交互,全方位的发挥性能极致。目前Calc、Join、Correlate算子都已经支持Native执行

  • 由于流式计算场景需要攒批,发挥列计算的优势,但是攒批和转换数据层存在一定开销,因此对于calc算子中整个表达式tree都没有使用到simd函数,将不翻译成native算子

  • 下面的sql RexCall以blink_json_value为例进行阐述优化规则

projection reorder
  • 以下面sql为例,该规则将RexInputRef引用字段排在RexCall前面,通过一个非Native的Cal记录下引用关系。主要用于和算子层优化结合。

SELECT a, blink_json_value(a, c), b FROM MyTable
Calc(select=[a, f0 AS EXPR$1, b])
+- NativeCalc(select=[a, b, blink_json_value(a, c) AS f0])
   +- LegacyTableSourceScan(table=[[default_catalog, default_database, MyTable, source: [TestTableSource(a, b, c, d)]]], fields=[a, b, c, d]
projections包含simd函数
  • 该sql仅仅projections含有simd函数,condition没有使用,因此filter逻辑还是使用非native的算子。

SELECT a, blink_json_value(b, d) FROM MyTable
WHERE concat(a, '1') is not null

经过优化后的执行计划如下所示

NativeCalc(select=[a, blink_json_value(b, d) AS EXPR$1])
+- Calc(select=[a, b, d], where=[CONCAT(a, '1') IS NOT NULL])
   +- LegacyTableSourceScan(table=[[default_catalog, default_database, MyTable, source: [TestTableSource(a, b, c, d)]]], fields=[a, b, c, d])

condition包含simd函数

  • 该sql projections 没有使用simd函数,condition含有,因此condition部分使用native算子执行。

SELECT a, b, concat(a, '1') FROM MyTable
WHERE blink_json_value(a, c) is not null

经过优化后的执行计划如下所示 

Calc(select=[a, b, concat(a, '1') AS EXPR$1], where=[f0])
+- NativeCalc(select=[a, b, blink_json_value(a, c) IS NOT NULL AS f0])
   +- LegacyTableSourceScan(table=[[default_catalog, default_database, MyTable, source: [TestTableSource(a, b, c, d)]]], fields=[a, b, c, d])

projections和condition都包含simd函数

  • 该sql projections和condition都含有simd函数,因此拆成两个Native算子执行。

SELECT blink_json_value(a, b), concat(c, '1') FROM MyTable
WHERE blink_json_value(a, c) is not null

经过优化后的执行计划如下所示 

NativeCalc(select=[blink_json_value(a, b) AS EXPR$0, CONCAT(c, '1') AS EXPR$1])
+- Calc(select=[a, b, c], where=[f0])
   +- NativeCalc(select=[a, b, c, blink_json_value(a, c) IS NOT NULL AS f0])
      +- LegacyTableSourceScan(table=[[default_catalog, default_database, MyTable, source: [TestTableSource(a, b, c, d)]]], fields=[a, b, c, d])

Inner Join的condition中包含simd函数

  • 下面的双流join例子,不仅仅On条件中包含simd函数,后面的Where逻辑也有。

SELECT a, concat(d, '1') FROM(
            SELECT a, d FROM(
            SELECT a, d
                    FROM leftTable JOIN rightTable ON
                    a = d and blink_json_value(a, a) = concat(a, d))
    WHERE blink_json_value(a, d) = concat(a, d))
  • calcite 解析后的AST树如下


LogicalProject(a=[$0], EXPR$1=[CONCAT($1, _UTF-16LE'1')])
+- LogicalProject(a=[$0], d=[$1])
   +- LogicalFilter(condition=[=(blink_json_value($0, $1), CONCAT($0, $1))])
      +- LogicalProject(a=[$0], d=[$3])
         +- LogicalJoin(condition=[AND(=($0, $3), =(blink_json_value($0, $0), CONCAT($0, $3)))], joinType=[inner])
            :- LogicalTableScan(table=[[default_catalog, default_database, leftTable]])
            +- LogicalTableScan(table=[[default_catalog, default_database, rightTable]])
  • 通过logical plan优化,可以看到两个simd函数,已经抽取到单独的Calc算子,跟在Join后面

FlinkLogicalCalc(select=[a, CONCAT(d, '1') AS EXPR$1], where=[f0])
+- FlinkLogicalCalc(select=[a, d, AND(=(blink_json_value(a, a), CONCAT(a, d)), =(blink_json_value(a, d), CONCAT(a, d))) AS f0])
   +- FlinkLogicalJoin(condition=[=($0, $1)], joinType=[inner])
      :- FlinkLogicalCalc(select=[a])
      :  +- FlinkLogicalLegacyTableSourceScan(table=[[default_catalog, default_database, leftTable, source: [TestTableSource(a, b, c)]]], fields=[a, b, c])
      +- FlinkLogicalCalc(select=[d])
         +- FlinkLogicalLegacyTableSourceScan(table=[[default_catalog, default_database, rightTable, source: [TestTableSource(d, e, f)]]], fields=[d, e, f])
  • 如果不开启native能力,翻译后的ExecPlan如下,其中Calc推到了下面的Join算子

Calc(select=[a, CONCAT(d, '1') AS EXPR$1])
+- Join(joinType=[InnerJoin], where=[((a = d) AND (blink_json_value(a, a) = CONCAT(a, d)) AND (blink_json_value(a, d) = CONCAT(a, d)))], select=[a, d], leftInputSpec=[NoUniqueKey], rightInputSpec=[NoUniqueKey])
   :- Exchange(distribution=[hash[a]])
   :  +- Calc(select=[a])
   :     +- TableSourceScan(table=[[default_catalog, default_database, leftTable]], fields=[a, b, c])
   +- Exchange(distribution=[hash[d]])
      +- Calc(select=[d])
         +- TableSourceScan(table=[[default_catalog, default_database, rightTable]], fields=[d, e, f])
  • 开启native能力后,simd表达式是委托给了native计算

Calc(select=[a, CONCAT(d, '1') AS EXPR$1], where=[f0])
+- NativeCalc(select=[a, d, ((blink_json_value(a, a) = CONCAT(a, d)) AND (blink_json_value(a, d) = CONCAT(a, d))) AS f0])
   +- Join(joinType=[InnerJoin], where=[(a = d)], select=[a, d], leftInputSpec=[NoUniqueKey], rightInputSpec=[NoUniqueKey])
      :- Exchange(distribution=[hash[a]])
      :  +- Calc(select=[a])
      :     +- TableSourceScan(table=[[default_catalog, default_database, leftTable, nativeOperator=[false]]], fields=[a, b, c])
      +- Exchange(distribution=[hash[d]])
         +- Calc(select=[d])
            +- TableSourceScan(table=[[default_catalog, default_database, rightTable, nativeOperator=[false]]], fields=[d, e, f])
Correlate节点对应物理算子condition中包含simd函数
  • 同理,对于Flink内置udtf函数物理算子实现,由于包含condition,对于该场景也是可以使用native进行加速

Calc(select=[a, b, c, f0, f1], where=[((CAST(f1 AS BIGINT) = a) AND (c = f0))])
+- Correlate(invocation=[func($cor0.c)], correlate=[table(func($cor0.c))], select=[a,b,c,f0,f1], joinType=[INNER], condition=[AND(=(blink_json_value($0, _UTF-16LE'$.id'), 2), =(+($1, 1), *($1, $1)))])
   +- LegacyTableSourceScan(table=[[default_catalog, default_database, MyTable, source: [TestTableSource(a, b, c)]]], fields=[a, b, c])

经过优化后的执行计划如下所示 

Calc(select=[a, b, c, f0, f1], where=[f00])
+- NativeCalc(select=[a, b, c, f0, f1, ((blink_json_value(f0, '$.id') = 2) AND (CAST(f1 AS BIGINT) = a) AND (c = f0)) AS f00])
   +- Correlate(invocation=[func($cor0.c)], correlate=[table(func($cor0.c))], select=[a,b,c,f0,f1], joinType=[INNER], condition=[=(+($1, 1), *($1, $1))])
      +- LegacyTableSourceScan(table=[[default_catalog, default_database, MyTable, source: [TestTableSource(a, b, c)]]], fields=[a, b, c])

3)Native层

  • 在native层,我们支持了18个simd函数,其中字符串函数15个,数学函数3个,也补齐了大量velox不支持的Flink内置函数。

4)细粒度Fallback机制

  • 支持细粒度的fallback机制,全部做到可配置

    • 仅含有SIMD函数的表达式才翻译成NativeCalc

    • 支持细粒度的函数签名级黑名单机制

    • 对于SQL timestamp/decimal类型fallback机制,对这种容易出现正确性问题的类型先fallback。

5)其他

  • 支持配置化的函数映射机制,函数覆盖优先级机制

    • 支持配置化的函数映射机制对于flink velox spark/presto函数语义一直但是函数名不一样,可以不用改动c++代码,修改配置即可,函数覆盖优先级机制持配置化的函数映射机制。新引入的函数或者修复velox函数bug无需加入velox,导致整个编译时间很久。在Flex内部加入即可,编译时间从2个小时到2分钟。

2. 正确性建设

这个是重中之重。两套引擎函数行为语义无法保证是对齐的,如何通过自动化手段发现二者行为上的差异?因此我们支持了函数级和作业级两套自动化比对框架。

如何复用Flink原有函数单元测试代码,而不需要改动和重写测试逻辑。然后当前Flink内置函数有两套机制oldstack/newstack,测试框架也不同。为此我们改造原有引擎2套测试框架逻辑,通过反射方式将这些单元测试自动注入向量化配置方式进行自动化比对。每天定时跑脚本将语义不一致的函数输出出来。通过这个工具,我们发现了Flink引擎本身函数正确性问题4个,都已经提给社区,Flink和Velox函数语义不对齐问题15个。

也支持了作业级端到端比对框架,本质就是上线前双跑比对。对重要作业mock两个作业消费固定的数据集输出到不同表,每天定时跑并输出比对结果报告到钉钉群。

3. 易用性建设

为了全方位提升易用性,我们在引擎层,尽最大程度将简单交给用户,复杂留给自己

  • 如何提前发现用户作业中使用的函数Velox是否支持,可以翻译成Native算子?我们开发了一套自动化编译工具捞取线上作业自动化执行,将各种函数不支持问题问题提前解决掉,从而减少用户干扰。

  • 如何提前知道开发的SIMD函数效果怎么样,是否有性能回退?因此我们基于JMH框架实现了一套端到端的性能测试框架,因为数据转换层有一定开销,因此需要对比整体性能更合理,目前支持GenericRow和ColumnRow。下图是函数使用SIMD实现端到端的性能效果数据。

图片

  • 我们还搭建了向量化大盘,可以看到作业级别的效果数据

  • 易用的DAG中native calc/source/sink算子展示

4. 稳定性建设

对向量化作业配置监控告警,提前发现问题。同时,由于Native算子需要额外的native内存,我们plan层自动注入额外资源。

效果

我们从线上捞取符合向量化场景的部分作业跑成向量化方式,下面这张图是真实的效果数据,有13%的作业端到端TPS可以提升1倍以上,37%的作业可以提升40%以上,作业平均提升了75%。其中效果最好的作业提升了14倍。

图片

未来规划

  • 全新的数据转换层支持RowData直接转velox RowVector,减少转换层数据拷贝开销

  • 支持更多算子,非状态算子如维表算子,状态算子等

  • 支持更多SIMD,支持SQL全类型,对齐Flink所有内置函数

  • 和Paimon结合,支持Native Parquet/Orc Reader

参考文献

  • https://github.com/apache/incubator-gluten

  • https://github.com/facebookincubator/velox

  • https://tech.meituan.com/2024/06/23/spark-gluten-velox.html

欢迎关注「蚂蚁数据AntData」CSDN 官方博客,跟技术爱好者一起学习Data+AI前沿技术知识~

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Pytorch | 利用I-FGSSM针对CIFAR10上的ResNet分类器进行对抗攻击

Pytorch | 利用I-FGSSM针对CIFAR10上的ResNet分类器进行对抗攻击 CIFAR数据集I-FGSSM介绍I-FGSSM代码实现I-FGSSM算法实现攻击效果 代码汇总ifgssm.pytrain.pyadvtest.py 之前已经针对CIFAR10训练了多种分类器&#xff1a; Pytorch | 从零构建AlexNet对CIFAR10进行分类 Pytorch…

全面Kafka监控方案:从配置到指标

文章目录 1.1.监控配置1.2.监控工具1.3.性能指标系统相关指标GC相关指标JVM相关指标Topic相关指标Broker相关指标 1.4.性能指标说明1.5.重要指标说明 1.1.监控配置 开启JMX服务端口&#xff1a;kafka基本分为broker、producer、consumer三个子项&#xff0c;每一项的启动都需要…

HTML制作一个普通的背景换肤案例2024版

一&#xff0c;完整的代码&#xff1a; <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>换肤</t…

《计算机组成及汇编语言原理》阅读笔记:p86-p115

《计算机组成及汇编语言原理》学习第 6 天&#xff0c;p86-p115 总结&#xff0c;总计 20 页。 一、技术总结 1.if statement 2.loop 在许多编程语言中&#xff0c;有类种循环&#xff1a;一种是在程序开头检测条件(test the condition),另一种是在程序末尾检测条件。 3.C…

CSS(三)盒子模型

目录 Content Padding Border Margin 盒子模型计算方式 使用 box-sizing 属性控制盒子模型的计算 所有的HTML元素都可以看作像下图这样一个矩形盒子&#xff1a; 这个模型包括了四个区域&#xff1a;content&#xff08;内容区域&#xff09;、padding&#xff08;内边距…

MySQL外键类型与应用场景总结:优缺点一目了然

前言&#xff1a; MySQL的外键简介&#xff1a;在 MySQL 中&#xff0c;外键 (Foreign Key) 用于建立和强制表之间的关联&#xff0c;确保数据的一致性和完整性。外键的作用主要是限制和维护引用完整性 (Referential Integrity)。 主要体现在引用操作发生变化时的处理方式&…

MySQL从入门到入土---MySQL表的约束 (内含实践)---详细版

目录 引入&#xff1a; null 与not null default&#xff1a; comment列描述 &#xff1a; not null 和 default&#xff1a; zerofill &#xff1a; 主键&#xff1a;primary key 复合主键&#xff1a; 自增长:auto_increment 唯一键&#xff1a;unique key 外键&a…

基于NodeMCU的物联网窗帘控制系统设计

最终效果 基于NodeMCU的物联网窗帘控制系统设计 项目介绍 该项目是“物联网实验室监测控制系统设计&#xff08;仿智能家居&#xff09;”项目中的“家电控制设计”中的“窗帘控制”子项目&#xff0c;最前者还包括“物联网设计”、“环境监测设计”、“门禁系统设计计”和“小…

Webpack在Vue CLI中的应用

webpack 作为目前最流行的项目打包工具&#xff0c;被广泛使用于项目的构建和开发过程中&#xff0c;其实说它是打包工具有点大材小用了&#xff0c;我个人认为它是一个集前端自动化、模块化、组件化于一体的可拓展系统&#xff0c;你可以根据自己的需要来进行一系列的配置和安…

java日志框架:slf4j、jul(java.util.logging)、 log4j、 logback

SLF4J--抽象接口 SLF4J (Simple Logging Facade for Java) 是一个为各种 Java 日志框架提供简单统一接口的库。它的主要目的是将应用程序代码与具体的日志实现解耦&#xff0c;使得在不修改应用程序代码的情况下&#xff0c;可以轻松地切换不同的日志框架。 jul-to-slft4j.ja…

命令行之巅:Linux Shell编程的至高艺术(中)

文章一览 前言一、输入/输出及重定向命令1.1 输入/输出命令1.1.1 read命令1.1.2 echo命令 1.2 输入/输出重定向1.3 重定向深入讲解1.4 Here Document1.4.1 /dev/null 文件 二、shell特殊字符和命令语法2.1 引号2.1.1 双引号2.1.2 单引号2.1.3 倒引号 2.2 注释、管道线和后台命令…

一文理解机器学习中二分类任务的评价指标 AUPRC 和 AUROC

&#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 在机器学习的二分类任务中&#xff0c;评估模型性能是至关重要的一步。两种常用的评价指标是 Precision-Recall Curve 下的面积 (AUPRC) 和 Receiver Operating Characteristic Curve 下的面积 (AUROC)…

Visual Studio Code(VS Code)配置C/C++环境

一、Visual Studio Code安装 Visual Studio Code&#xff0c;下文中简称为VS Code的详细安装方法请参考VSCode安装教程&#xff08;超详细&#xff09;-CSDN博客 二、MinGW编译器下载与配置 1、MinGW介绍 MinGW(Minimalist GNU for Windows)是一款用于Windows 平台的轻…

少儿编程在线培训系统:客户服务与学习支持

2.1 VUE技术 VUE它是由HTML代码&#xff0c;配上嵌入在HTML代码里面的Java代码组成的应用于服务器端的语言&#xff0c;使用VUE进行开发能够更加容易区分网页逻辑以及网页设计内容&#xff0c;让程序员开发思路更加清晰化&#xff0c;VUE在设计组件时&#xff0c;它是可以重用的…

uniapp Native.js原生arr插件服务发送广播到uniapp页面中

前言 最近搞了个设备&#xff0c;需求是读取m1卡&#xff0c;厂家给了个安卓原生demo&#xff0c;接入arr插件如下&#xff0c;接入后发现还是少了一部分代码&#xff0c;设备服务调起后触发刷卡无法发送到uniapp里。 中间是一些踩坑记录&#xff0c;最后面是解决办法&#xf…

C项目 天天酷跑(下篇)

上篇再博客里面有&#xff0c;接下来我们实现我们剩下要实现的功能 文章目录 碰撞检测 血条的实现 积分计数器 前言 我们现在要继续优化我们的程序才可以使这个程序更加的全面 碰撞的检测 定义全局变量 实现全局变量 void checkHit() {for (int i 0; i < OBSTACLE_C…

HarmonyOS NEXT 实战之元服务:静态案例效果--航空出行

背景&#xff1a; 前几篇学习了元服务&#xff0c;后面几期就让我们开发简单的元服务吧&#xff0c;里面丰富的内容大家自己加&#xff0c;本期案例 仅供参考 先上本期效果图 &#xff0c;里面图片自行替换 效果图1完整代码案例如下&#xff1a; import { authentication } …

福特汽车物流仓储系统WMS:开源了,可直接下载

导语 大家好&#xff0c;我是社长&#xff0c;老K。专注分享智能制造和智能仓储物流等内容。欢迎大家到本文底部评论区留言。 近日&#xff0c;福特汽车公司推出了其广受好评的仓库管理系统GreaterWMS&#xff08;更大仓库管理系统&#xff09;的开源版本&#xff0c;意味着各行…

STM32完全学习——FLASH上FATFS文件管理系统

一、需要移植的接口 我们通过看官网的手册&#xff0c;可以看到我们只要完成下面函数的实现&#xff0c;就可以完成移植。我们这里只移植前5个函数&#xff0c;获取时间的函数我们不在这里移植。 二、移植接口函数 DSTATUS disk_status (BYTE pdrv /* Physical drive nmuber…

pyqt5冻结+分页表

逻辑代码 # -*- coding: utf-8 -*- import sys,time,copy from PyQt5.QtWidgets import QWidget,QApplication, QDesktopWidget,QTableWidgetItem from QhTableWidgetQGN import Ui_QhTableWidgetQGN from PyQt5.QtCore import Qt from PyQt5 import QtCore, QtGui, QtWidgets…