kdb+/q中如何并行实现多延迟aj关联批量新增表列
性能瓶颈分析
原实现速度慢的核心原因有4点:
- 循环内重复读取、排序大表:每处理一个延迟值就对大表做一次全表扫描、排序,延迟越多重复IO和计算浪费越严重,对磁盘分区表来说额外开销尤其大
- Over递归追加列时,每次迭代都会生成全量小表的内存副本,拷贝开销随列数线性增长
- 全流程串行执行,没有利用多核CPU资源
- 没有利用大表按
date+sym分区的特性做数据裁剪,每次关联都扫描全量大表
优化后可运行实现
以下代码和原逻辑完全对齐,支持并行执行,针对磁盘分区表做了适配:
/ 初始化测试数据,和原示例逻辑完全对齐 smallT: ([] sym: (20#`AAPL),(20#`MSFT); time: (asc 00:00:00+20?til 100), (asc 00:00:00+20?til 100)); bigT: ([] sym: (100#`AAPL),(100#`MSFT); time: (asc 00:00:00+til 100), (asc 00:00:00+til 100); price: (til 100),(til 100)); delays: 00:00:00 + (7 * til 5); / 单个sym的处理函数:无跨sym依赖,可独立并行执行 processSingleSym: {[smlSubset;bgSubset;dlys] / 提前取出大表的time、price数组,单sym分区内time天然有序,无需额外排序 bgTime: exec time from bgSubset; bgPrice: exec price from bgSubset; smlTime: exec time from smlSubset; / 批量计算所有延迟对应的匹配值,替代多次循环aj matchRes: value flip {[bt;bp;st;d] pos: bt binr st + d; / binr等价于aj的最近大于等于匹配逻辑 : $[pos=count bt; 0N; (bp pos)%100] / 找不到匹配返回空值,对齐原逻辑的除以100计算 }[bgTime;bgPrice;smlTime] peach dlys; / 生成目标列名,如需对齐原代码的"colnametime_"前缀直接替换即可 colNames: `$"time_",/: string `int$dlys; : smlSubset, colNames!matchRes }; / 内存表并行计算入口 parallelAjMem: {[bigTab;smallTab;dlys] bgGroup: bigTab group `sym; / 大表按sym分组,仅执行一次 smlGroup: smallTab group `sym; / 小表按sym拆分,和大表分组一一对应 / 按sym粒度并行调度任务,无锁无竞争 : raze {[proc;bg;sml;d] processSingleSym[sml;bg;d]}[;bgGroup;;dlys] peach smlGroup }; / 磁盘分区表并行计算入口(适配date+sym分区结构) parallelAjDisk: {[dbRoot;targetDate;smallTab;dlys] needSyms: exec distinct sym from smallTab; / 仅加载小表中存在的sym,跳过无关分区 / 并行加载需要的sym分区,仅读取time、price两个必要列 bgGroup: needSyms! {[dbRoot;dt;s] select time,price from get `$(sv["/";string dbRoot;string dt;string s]) }[dbRoot;targetDate] peach needSyms; smlGroup: smallTab group `sym; : raze {[proc;bg;sml;d] processSingleSym[sml;bg;d]}[;bgGroup;;dlys] peach smlGroup }; / 内存表调用示例 res: parallelAjMem[bigT;smallT;delays]; res / 磁盘分区表调用示例(替换为实际库路径、日期即可) / res: parallelAjDisk[`:path/to/your/db;2024.01.01;smallT;delays];
实现原理说明
- 消除重复开销:大表仅加载、分组一次,利用分区表
sym分区内time天然有序的特性,完全去掉了原循环里重复的xasc排序、全表select操作,IO和排序开销直接降到原实现的1/N(N为延迟值数量)。 - 批量计算替代循环aj:
aj的底层逻辑就是对右表的time列做二分查找匹配,优化后直接对所有延迟批量做偏移、二分查找,省掉了多次调用aj的函数开销,同时去掉了原实现里反复重命名列、删除临时列、递归拷贝表的多余操作。 - 细粒度并行:关联键包含
sym,不同sym之间的计算完全独立没有依赖,按sym拆分任务用peach(并行each)调度到不同CPU核心执行,没有锁竞争,并行效率接近线性提升。 - 分区裁剪优化:针对磁盘分区表,只加载小表中存在的sym分区、只读取需要的
time/price列,无关数据完全不加载,内存占用和IO量比原实现低一个数量级以上。
注意:启动q进程时需要加
-s 核心数参数开启多线程支持,例如8核机器启动命令为q -s 8,否则peach会退化为串行执行。
内容的提问来源于stack exchange,提问作者foam78
相关产品推荐
相关产品推荐

