基于准静态自适应环型缓存器(QSARC)的taskBus万兆吞吐实现
文章目录
- 概要
- 整体架构流程
- 技术名词解释
- 技术细节
- 1. 数据结构
- 2. 自适应计算队列大小
- 3. 生产者拼接缓存
- 4. 高效地通知消费者
- 小结
- 1. 性能表现情况
- 2. 主要改进和局限
- 3. 源码和发行版
概要
准静态自适应环形缓存器(Quasi-Static Adaptive Ring Cache)是taskBus用于数据吞吐的软件结构。
- 准静态:缓存器的大小并不是静态分配,而是随着吞吐需求的提高,缓慢增长,并最终适应最高峰时的内存消耗。当缓存器已经达峰后,不再有堆内存分配。
- 自适应:根据包大小的不同,缓存器在恒定的最大峰值内存容积下,根据统计获得包的大小数据,决定环状缓存队列元素的个数、每个元素的大小。
- 环形: 队列收尾相接形成环状,生产者、消费者使用两个时钟前后相随,时钟本身采用atomic保护,无需额外的锁。
使用该缓存器,基于增强管道数据流转技术(EPDR)的业余软线无线电平台taskBus可在Linux 系统 i7 6700K 主频 4GHz下达到3GBps(24Gbps)的总交换能力。该交换能力被各个通道均分,共同支撑taskBus平台按照工程的连接关系,把各个生产者产出的数据包及时、完整、有序地输送给消费者。尽管采用了本技术,但与采用内核层面的zero-copy函数进行管道直接交换的性能极限还差了20倍。
整体架构流程
taskBus的整体架构是一种多进程的合作机制,平台管理N个子进程,各个进程可以充当消费者和生产者的角色。平台收集各个子进程的stdout输出,并按照用户给定的消费关系,送入消费者的stdout.在这样的整体架构下,数据流转流程如下:
在202409版本之前,taskBus使用的是消费者队列。由1个生产者产出的数据,会复制N份进入各个消费者的队列。这种情况下,平台既要为生产者部位保留一个用于包拼接的缓存,又要做N次memcpy。在消费者显著多于生产者时,吞吐能力下降的很厉害。
202409版本之后,设计成每份包只有唯一的1个副本,存储在生产者队列。消费者根据索引去拉取:
- 平台为每个生产者维护1个环状缓存队列。
- 平台按照消费关系,把每个包的队列位置索引播发给各个消费者。
- 播发时,会维护一个待消费计数器,将值加1
- 消费者消费包后,会将计数器的值减1
- 如果生产者队列绕了一圈,发现下一个位置的计数器不为0,说明消费的速度赶不上生产速度,此时平台不再接受生产者的数据,等待。
整个流程如下图所示(动图):
注意的是,上图中队列的大小是4个包,即使包消费完毕,可用的容积也不会释放,这样,随着程序的运行,渐渐地动态内存分配会越来越少,速度会越来越快。当然,如果包长是固定的,则不存在此问题,可以提前全部分配。
值得注意的是,在时钟11时,因为下一个位置依旧有消费者在驻留,因此生产被暂停等待。这种架构的缺点是整体的吞吐能力存在“木桶效应”,即由消费最慢的消费者决定1秒内能够流转多少包。
技术名词解释
- 包:用于一次功能操作的数据单位,可以理解为1段连续内存的数据。包由包头、数据段组成。包头含有长度指示,正常的数据包之间是紧密衔接的。
- 时钟:用于控制生产、消费的整形变量,从0开始无限增长。每处理1个包,时钟会加1。
- 当前位置:生产者、消费者操作队列的当前位置,数值= 时钟 % 队列大小。
技术细节
1. 数据结构
生产者为taskNode类型,拥有如下成员变量维护自己的生产队列:
QVector<int> m_status_stdin;
QVector<QByteArray> m_array_stdin;
QVector<qsizetype> m_size_stdin;
QVector<QAtomicInteger<qsizetype> > m_cnt_stdin;
变量1 控制生产的状态。管道到来的数据,是一个无限有序无损流,类似TCP。但是,每次流可能会被切断,导致包头、数据块可能被切割到多次调用里。这个变量用于在各次调用中记忆上次的生产状态。
变量2 就是缓存的内存本身。这个列表的QByteArray元素个数是缓存的包个数,每个QByteArray会保持在历史最大包的高位。这样,只有到来的包大于当前体积时,才会重新分配内存。
变量3 存储缓存内当前的写入位置。如果包已经写完了,则等于包长。如果是空闲,则为0。
变量4 就是待消费计数器,每次生产完毕1个包,会根据消费者等待队列广播包的索引(时钟),并把计数设置为消费者个数。消费者消费完,会减一。
2. 自适应计算队列大小
这种队列存在1个重大的问题,就是很难控制内存的用量。由于各个QByteArray都只增加不释放,因此期望的用量是:
C = M ∗ N C = M * N C=M∗N
M是最大包大小,N是队列包个数。
由于taskBus设计时,建议包的大小小于64KB,在假设包的种类小于K时,可以预先统计前K个包的最大长度M,从而确定按照最大内存门限,如C=128MB,要设置的N。
N = C / M N = C/M N=C/M
当然,这种算法是极为简陋的。这是建立在taskBus的具体应用场景上的。对包变化幅度很大、无法简单统计的情况,要考虑一定的shink策略,如在每次消费指针归0时,裁剪队列的大小,并释放尾部的内存。
//Adjust buf sizeif (m_pos_stdin==8){qsizetype sza = 0;for (int ia = 0;ia<8;++ia)if (sza < m_size_stdin[ia])sza = m_size_stdin[ia];if (sza < 32)sza = 32;m_bufsize_adjust = taskCell::default_ringcache * 1024 * 1024 / sza;if (m_bufsize_adjust > m_bufsize_stdin)m_bufsize_adjust = m_bufsize_stdin;if (m_bufsize_adjust < 8)m_bufsize_adjust = 8;emit_message(QString("Adjusted buffer ring size : %1 MB / %2 Bytes = %3 frames.").arg(taskCell::default_ringcache).arg(sza).arg(m_bufsize_adjust).toUtf8());}
3. 生产者拼接缓存
在当前生产位置上,平台为生产者拼接一个完整的包。这里用到了状态机。状态机拥有如下状态:
状态名 | 取值 | 意义 |
---|---|---|
头部捕获中 | 0 | 正在捕获头部 |
数据缓存中 | 1 | 正在根据头部指示的状态,缓存数据 |
数据缓存完毕 | 2 | 包接收完整,触发消费。 |
数据缓存完毕 | 3 | 触发消费完毕,待回收。 |
在每个状态上,都会进行进程管道读操作,不断的从stdout获取数据。直到状态2,会触发消费,并在全部消费通知播发后,进入状态3。当下一次生产者访问状态3的队列成员时,如果没有消费者还在消费这个包,则会把包位置归零,状态归零。否则,会阻塞生产者,直到消费者消费完毕为止。
void taskNode::slot_readyReadStandardOutput()
{LOG_PROFILE("IO","Start Recieving packs.");qsizetype total_sz = m_process->size();int badHeader = 0;while (total_sz){const qsizetype pos = m_pos_stdin % m_bufsize_adjust;QByteArray & curr_array = m_array_stdin[pos];qsizetype & readBufMax = m_size_stdin[pos];QAtomicInteger<qsizetype> & cnt = m_cnt_stdin[pos];int & stat = m_status_stdin[pos];//生产者被阻塞了,因为下一个缓存位置依旧有消费者在消费数据。if (cnt>0)break;//Old dataif (stat==3){readBufMax = 0;stat = 0;}auto * header = reinterpret_cast<const TASKBUS::subject_package_header *> (curr_array.data());//Headerif (stat==0){if (total_sz<sizeof(TASKBUS::subject_package_header))break;auto needRead = sizeof(TASKBUS::subject_package_header) - readBufMax ;auto red = m_process->read(curr_array.data()+readBufMax,needRead);readBufMax += red;if (readBufMax == sizeof(TASKBUS::subject_package_header)){if (header->prefix[0]==0x3C && header->prefix[1]==0x5A && header->prefix[2]==0x7E && header->prefix[3]==0x69){stat = 1;}else{++badHeader;readBufMax = 0;}}Q_ASSERT(readBufMax <= sizeof(TASKBUS::subject_package_header));}//dataif (stat==1){const qsizetype packAllSize = sizeof(TASKBUS::subject_package_header)+header->data_length;if (curr_array.size()<packAllSize){curr_array.resize(packAllSize);header = reinterpret_cast<const TASKBUS::subject_package_header *> (curr_array.data());}auto needRead = packAllSize - readBufMax ;auto red = m_process->read(curr_array.data()+readBufMax,needRead);readBufMax += red;if (readBufMax==packAllSize){stat = 2;}Q_ASSERT(readBufMax <= packAllSize);}//Sendif (stat==2){const qsizetype pack_size = sizeof(TASKBUS::subject_package_header)+header->data_length;extern QAtomicInteger<quint64> g_totalrev;g_totalrev += readBufMax;++m_spackage_sent;m_sbytes_sent += sizeof(TASKBUS::subject_package_header)+header->data_length;if (header->subject_id == TB_SUBJECT_CMD){//Command must endwith \0const char * pCmd = (const char *)header+sizeof(TASKBUS::subject_package_header);QString cmd = QString::fromUtf8(pCmd,header->data_length);QMap<QString, QVariant> map_z = taskCell::string_to_map(cmd);//remember uuidif (map_z.contains("source")){if(m_uuid.size()==0 )m_uuid = map_z["source"].toString();if (map_z.contains("destin"))emit sig_new_command(map_z);}}else if (m_currPrj)m_currPrj->routing_new_package(this,pos);if (m_bDebug)log_package(true,(char *)header,pack_size);stat = 3;++m_pos_stdin;//Adjust buf sizeif (m_pos_stdin==8){qsizetype sza = 0;for (int ia = 0;ia<8;++ia)if (sza < m_size_stdin[ia])sza = m_size_stdin[ia];if (sza < 32)sza = 32;m_bufsize_adjust = taskCell::default_ringcache * 1024 * 1024 / sza;if (m_bufsize_adjust > m_bufsize_stdin)m_bufsize_adjust = m_bufsize_stdin;if (m_bufsize_adjust < 8)m_bufsize_adjust = 8;emit_message(QString("Adjusted buffer ring size : %1 MB / %2 Bytes = %3 frames.").arg(taskCell::default_ringcache).arg(sza).arg(m_bufsize_adjust).toUtf8());}}total_sz = m_process->size();}if (badHeader)emit_message(QByteArray("Error header recieved. ""Header must be 0x3C, 0x5A, 0x7E,"" 0x69. Aborting."));LOG_PROFILE("IO","End Recieving packs.");
}
4. 高效地通知消费者
如果每个包很小,则QEvent触发的密度会很大,开销很大。我们设置一个消费者的索引队列,存储待消费的生产者队列、索引:
QMutex m_mtx_queue;QList<taskNode *> m_write_queue;QList<qsizetype> m_write_pos;
而后,只在队列为0时,触发Event。
bool taskNode::enqueue_write(taskNode * node, qsizetype pos)
{m_mtx_queue.lock();int z = m_write_queue.size() + m_write_cmd.size();m_write_queue.push_back( node);m_write_pos.push_back(pos);m_mtx_queue.unlock();if (!z){QCoreApplication::postEvent(this,new QEvent(m_nPackEvent));}return true;
}
同时,在消费时,一次性获取队列,并清空。这样减少锁的碰撞。
void taskNode::flush_write()
{m_mtx_queue.lock();QList<taskNode *> write_queue = m_write_queue;QList<qsizetype> write_pos = m_write_pos;QByteArrayList write_cmd = m_write_cmd;m_write_queue.clear();m_write_pos.clear();m_write_cmd.clear();//qDebug()<<write_queue.size();m_mtx_queue.unlock();while (write_queue.size()){taskNode * node = write_queue.first();qsizetype pos = write_pos.first();write_queue.pop_front();write_pos.pop_front();QByteArray & arr = node->get_stdin_array(pos);m_process->write(arr.constData(),sz);--cnt;}
小结
尽管采用了环形队列,由于在QProcess上还是存在mem-alloc,这使得峰值状态下CPU占用还是很大的。整体速率距离PCI总线和DDR4的能力还相去甚远,即使和bash直接管道连接相比,也有不小的差距。不过,为了构造灵活的管道吞吐能力,允许数据被多对多流转和反馈回环,损失一些性能也差强人意。
1. 性能表现情况
通过上述操作,taskBus的吞吐能力得到了保证,在只使用用户态的内存操作情况下,缓存64MB时,可以获得10Gbps以上的性能。在Linux下,可达 24Gbps,即3GBps1。
平台 | 系统 | 峰值吞吐 | 单路流量 | 平均来回延迟 |
---|---|---|---|---|
i7-10700U1 | Linux x64 | 3354MBps | 1340MBps | 1ms |
i7-6700K | Linux x64 | 2844MBps | 1050MBps | 2.2ms |
i7-10700U2 | win10 home x64 | 1561MBps | 400MBps | 22ms |
i7-6700K | win10 home x64 | 1345MBps | 340MBps | 40ms |
RaspberryPi 4(8GB) | Rasbain 64 | 263MBps | 102MBps | 6ms |
上表是运行双进程点对点双向PING的状态下达到的。多进程下,总速率会稍高。可以发现,同样的硬件配置下:
- windows下taskBus的吞吐能力要比Linux少1倍
- windows下taskBus的带宽利用率要低于Linux,总速率/单路速率Linux更优。
- windows下的延迟更大。
这是非常奇怪的现象,讲起来windows应该更快才对。对于里面的细节原因,只有后面慢慢研究了。
2. 主要改进和局限
与2024年8月版本相比,少了一层生产者–>消费者的memcpy,转而只是传递int类型的索引,在生产者:消费者个数=1:N的情况下,吞吐能力会得到较大提高。这种memcpy次数的降低,对于老旧CPU影响更大,即使在2进程互PING(1:1)的测试中,也能达到 20-30%的提速。
同时要注意到,3GBps已经很接近用户态内存的吞吐极限。若要追求极为苛刻的传输,需要按照文章开始的链接里的vmsplice zero-copy来定制,取得额外10-20倍的性能提升。
3. 源码和发行版
源码和发行版参考
GitCode.net
或者
GitCode.com
i7-10700U是一个笔记本上的低功耗cpu,在最大睿频4GHz下的Manjaro Linux系统上,3GBps持续了5秒。由于温度上升,温度墙导致频率下降到1.8GHz,速度降低1倍。 ↩︎ ↩︎
i7-10700U是一个笔记本上的低功耗cpu,通过在windows-10下去除温度墙,可以在97摄氏度的高温状态下,保持在4GHz,维持1.5GBps的流量。 ↩︎
相关文章:

基于准静态自适应环型缓存器(QSARC)的taskBus万兆吞吐实现
文章目录 概要整体架构流程技术名词解释技术细节1. 数据结构2. 自适应计算队列大小3. 生产者拼接缓存4. 高效地通知消费者 小结1. 性能表现情况2. 主要改进和局限3. 源码和发行版 概要 准静态自适应环形缓存器(Quasi-Static Adaptive Ring Cache)是task…...
C++笔记---指针常量和常量指针
巧记方法(方法来自于网络出处忘记了):const读作常量,*读作指针,按顺序读即可。例如: const int * ptr; //const在前*在后读作常量指针 const * int ptr; //const在前*在后读作常量指针 int * const prt; /…...
Python习题 177:设计银行账户类并实现存取款功能
(编码题)Python 实现一个简单的银行账户类 BankAccount,包含初始化方法、存款、取款、获取余额等功能。 参考答案 分析需求如下。 Python 类 BankAccount,用于模拟银行账户的基本功能。该类应包含以下方法: 初始化方法: 接受两个参数:account_holder(账户持有人的姓…...

IPhone 16:它的 “苹果智能 “包括哪些内容?
IPhone 16 的发布让科技界看到了该公司的人工智能产品 “苹果智能”(Apple Intelligence)究竟能做些什么。 苹果公司发布了拥有人工智能硬件升级的最新款 iPhone 16,进一步进军人工智能领域。苹果公司首席执行官蒂姆-库克(Tim Coo…...

【中国国际航空-注册/登录安全分析报告】
前言 由于网站注册入口容易被黑客攻击,存在如下安全问题: 1. 暴力破解密码,造成用户信息泄露 2. 短信盗刷的安全问题,影响业务及导致用户投诉 3. 带来经济损失,尤其是后付费客户,风险巨大,造…...

【ArcGIS】栅格计算器原理及案例介绍
ArcGIS:栅格计算器原理及案例介绍 栅格计算器(Raster Calculator)原理介绍案例案例1:计算栅格数据平均值 参考 栅格计算器(Raster Calculator)原理介绍 描述:在类似计算器的界面中,…...

LOOKUP函数和VLOOKUP函数知识讲解与案例演示
〇、需求 在 Excel 文档中,根据查找值从查找域和结果域构成的数组中,找到对应的结果值。 一、知识点讲解 LOOKUP函数(比较常用,推荐)和VLOOKUP函数 两个公式都可以实现上述需求。 1. LOOKUP 函数 1.1 单个查询条件…...
Java技术深度探索:高并发场景下的线程安全与性能优化
Java技术深度探索:高并发场景下的线程安全与性能优化 在当今的软件开发领域,随着互联网应用的日益复杂和用户量的激增,高并发成为了一个不可忽视的技术挑战。Java,作为一门广泛应用于企业级开发的编程语言,其内置的并发支持机制如线程(Thread)、锁(Lock)、并发集合(…...

Vulnhub-RickdiculouslyEasy靶场(9个flag)
flag1 端口9090有一个flag flag2 13337端口 flag3 使用dirb进行扫描网站的80端口,发现一些敏感文件 访问80端口,没有发现有效信息 访问passwords目录 访问FLAG.txt 再返回访问passwords.html文件 查看页面源代码发现一个密码 flag4 之前扫描到了robo…...

Android Studio Menu制作
文章目录 在Activity上新建onCreateOptionsMenu新建menu目录及资源文件新建Menu一级菜单在Activity上加载Menu 在Activity上新建onCreateOptionsMenu Overridepublic boolean onCreateOptionsMenu(Menu menu) {return super.onCreateOptionsMenu(menu);}新建menu目录及资源文件…...

【mybatis】使用模糊查询时报错:Encountered unexpected token: “?“ “?“
报错信息如下: Mapper.xml报错代码: AND HILIST_NAME like %#{hilistName}% 解决方案: 把模糊查询的 sql 语句改为使用 CONCAT 命令拼接, 就不会报错了。 AND HILIST_NAME like CONCAT(%, #{hilistName},%)...

【Linux】文件权限与类型全解:你的文件安全指南
欢迎来到 CILMY23 的博客 🏆本篇主题为:文件权限与类型全解:你的文件安全指南 🏆个人主页:CILMY23-CSDN博客 🏆系列专栏:Python | C | C语言 | 数据结构与算法 | 贪心算法 | Linux | 算法专题…...

解析DNS查询报文,探索DNS工作原理
目录 1. 用 tcpdump工具监听抓包 2. 用 host 工具获取域名对应的IP地址 3. 分析DNS以太网查询数据帧 3.1 linux下查询DNS服务器IP地址 3.2 DNS以太网查询数据帧 (1)数据链路层 (2)网络层 (3)传输层…...

Unity让摄像机跟随物体的方法(不借助父子关系)
在Unity中,不使用子对象的方式让相机跟随物体移动,我们通过编写脚本来实现。下面放一个从工程中摘出来的的C#脚本示例,用于将相机绑定到一个Target对象上并跟随其移动: using UnityEngine; public class FollowCamera : MonoBeh…...

misc音频隐写
一、MP3隐写 (1)题解:下载附件之后是一个mp3的音频文件;并且题目提示keysyclovergeek;所以直接使用MP3stego对音频文件进行解密;mp3stego工具是音频数据分析与隐写工具 (2)mp3stego工具的使用:…...

如何启动网络安全计划:首先要做的事情
目录 数据分类:网络安全的基石 为什么它很重要? 如何对数据进行分类? 风险分析 什么是风险分析? 如何进行风险分析? 业务影响分析 (BIA) BIA 的用途是什么? BIA 是如何进行的? 安全解…...
Java零基础-三维数组详解!
哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云/阿里云/华为云/51CTO;欢迎大家常来逛逛 今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互…...
数据分析-20-时间序列预测之基于PyTorch的LSTM数据准备及模型训练流程
文章目录 1 数据加载2 去除异常值3 数据归一化4 切分窗口5 制作数据集加载器6 定义模型7 训练模型8 模型评估9 参考附录1 数据加载 参考数据集kaggle下载DailyDelhiClimate import pandas as pd import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] = SimHei # 设…...
vue2中使用web worker启动定时器
vue2中使用web worker启动定时器,避免浏览器最小化或切换标签页时定时器不能按设定周期执行【一般是周期小于60s时,大于60s一般可正常执行】 1、添加worker-loader2、修改vue.config.js3、创建timer.worker.js4、创建TimerWorker.js5、使用TimerWorker启…...

【Python 学习】Numpy的基础和应用
目录 1 数组基础1.1 Numpy简介1.2 Numpy数组基础1.3 创建数组1.3.1 使用np.array()函数生成数组1.3.2 利用内置函数产生特定形式的数组1.3.2.1 简单内置函数1.3.2.2 特殊内置函数 1.3.3 生成随机数组 1.4 数组的数据类型1.5 数组的迭代1.6数组的索引和切片1.6.1 一维数组的索引…...
Linux云原生安全:零信任架构与机密计算
Linux云原生安全:零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言:云原生安全的范式革命 随着云原生技术的普及,安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测,到2025年,零信任架构将成为超…...

12.找到字符串中所有字母异位词
🧠 题目解析 题目描述: 给定两个字符串 s 和 p,找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义: 若两个字符串包含的字符种类和出现次数完全相同,顺序无所谓,则互为…...

dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...
站群服务器的应用场景都有哪些?
站群服务器主要是为了多个网站的托管和管理所设计的,可以通过集中管理和高效资源的分配,来支持多个独立的网站同时运行,让每一个网站都可以分配到独立的IP地址,避免出现IP关联的风险,用户还可以通过控制面板进行管理功…...

解读《网络安全法》最新修订,把握网络安全新趋势
《网络安全法》自2017年施行以来,在维护网络空间安全方面发挥了重要作用。但随着网络环境的日益复杂,网络攻击、数据泄露等事件频发,现行法律已难以完全适应新的风险挑战。 2025年3月28日,国家网信办会同相关部门起草了《网络安全…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
「全栈技术解析」推客小程序系统开发:从架构设计到裂变增长的完整解决方案
在移动互联网营销竞争白热化的当下,推客小程序系统凭借其裂变传播、精准营销等特性,成为企业抢占市场的利器。本文将深度解析推客小程序系统开发的核心技术与实现路径,助力开发者打造具有市场竞争力的营销工具。 一、系统核心功能架构&…...

Linux操作系统共享Windows操作系统的文件
目录 一、共享文件 二、挂载 一、共享文件 点击虚拟机选项-设置 点击选项,设置文件夹共享为总是启用,点击添加,可添加需要共享的文件夹 查询是否共享成功 ls /mnt/hgfs 如果显示Download(这是我共享的文件夹)&…...

【版本控制】GitHub Desktop 入门教程与开源协作全流程解析
目录 0 引言1 GitHub Desktop 入门教程1.1 安装与基础配置1.2 核心功能使用指南仓库管理日常开发流程分支管理 2 GitHub 开源协作流程详解2.1 Fork & Pull Request 模型2.2 完整协作流程步骤步骤 1: Fork(创建个人副本)步骤 2: Clone(克隆…...
StarRocks 全面向量化执行引擎深度解析
StarRocks 全面向量化执行引擎深度解析 StarRocks 的向量化执行引擎是其高性能的核心设计,相比传统行式处理引擎(如MySQL),性能可提升 5-10倍。以下是分层拆解: 1. 向量化 vs 传统行式处理 维度行式处理向量化处理数…...