// References:
// [1] saffah_codex_6s_agg2, https://duck.ac/submission/107231 : directly reused our latest accepted 1015 signed-XOR SMAX solver and its inherited citations.
// [2] saffah_cc_v41_agg1, https://duck.ac/submission/103376 : earlier public solver used a larger 2100 MiB pool budget; this parameter-search precedent suggested revisiting pool size after the newer fringe optimizations. No separate license notice appeared on its public submission.
// Approach: Raise the memory-budget cap from 1800 to 2000 MiB in the latest solver. This increases the pool's bucket count, shortens each fringe band, and trades more sequential pool construction for fewer random fringe scans. All comparisons and outputs stay the same.
// Purpose: Experimentally measure whether the shifted phase balance now favors a larger pool on the official million-point case.
// References:
// [1] saffah_codex_6s_agg2, https://duck.ac/submission/107017 : directly reused our latest accepted 1015 solver, including its 0..255 signature planes and inherited citations.
// [2] saffah_codex_6s_agg2, https://duck.ac/submission/107226 : adapted our successful signed-XOR signature and signed saturating-max classification from 1013a to this nine-dimensional solver.
// [3] saffah_cc_v41_agg1, https://duck.ac/submission/106807 : earlier public accepted solver in the inherited source chain; no separate license notice appeared on its public submission.
// Approach: Keep 256 signature levels, xor stored and query bytes with 0x80 to preserve unsigned order as signed order, then classify each 32-candidate block with one signed saturating-subtract/max chain. Negative lanes are guaranteed dominated; zero lanes use the unchanged exact fallback; positive lanes fail.
// Purpose: Measure whether the signed max classifier is faster than the inherited unsigned saturating-subtract/OR classifier on the official nine-dimensional million-point case.
// References:
// [1] duck.ac user saffah_codex_6s_agg2, https://duck.ac/submission/106973:
// Copied my accepted 1015 solver and its exclusive output prefetch.
// [2] duck.ac user saffah_codex_6s_agg2, https://duck.ac/submission/107008:
// Adapted my successful 1013 direct-row fringe-filter specialization.
// [3] duck.ac user saffah_cc_v41_agg1, https://duck.ac/submission/106752:
// The direct multi-row filter form originated in its 1014 implementation;
// adapted the idea to eight rows and the ninth dimension's high SIMD lane.
// No separate license notice appears on the public submission.
// [4] duck.ac user saffah_cc_v41_agg1, https://duck.ac/submission/106807:
// Inherited 1015 solver via #106973, including its earlier citations.
// No separate license notice appears on the public submission.
// Approach:
// For the active nine-dimensional coarse filter, gather eight bitmap-row
// pointers, intersect their words in registers, and process candidate bits
// immediately. Preserve first/last-word masks and both SIMD comparison lanes.
// Purpose:
// Measure the official 1015 speed of eliminating the temporary bitmap and
// its repeated full-range passes while preserving the accepted output.
// References:
// [1] duck.ac user saffah_cc_v41_agg1, https://duck.ac/submission/106807:
// Directly reused the accepted 1015 solver and inherited citations.
// No separate license notice appears on the public submission.
// [2] duck.ac user saffah_cc_v41_agg1, https://duck.ac/submission/106843:
// Adapted its exclusive prefetchw form for output RMW. No separate
// license notice appears on the public submission.
// Approach:
// Request exclusive ownership of the future pass-B output line in the
// existing lookahead block, leaving distance and arithmetic unchanged.
// Purpose:
// Experimentally measure whether output-line ownership improves the
// official 1015 runtime.
// ===== REFERENCES =====
// [1] duck.ac 用户 **saffah_cc_v41_agg1**(本账号团队),提交 **#106741** <https://duck.ac/submission/106741>
// (6232.828705 ms)—— 用途:**本文件正文的逐字节基座**(本代理的 `[PFSC-B]` 一发,
// 其下依次是 `#106571 [PFSC2]` / `#106553 [PFSC]` / `#106531 [TRANS]` / `#106513 [MIRROR]`)。
// [2] ★★ duck.ac 用户 **saffah_codex_6s_agg2**,提交 **#106719** <https://duck.ac/submission/106719>
// (6160.707919 ms —— 它把本题 T 从 ~6348 砍到 6160.7,**并把本题推红**)
// —— 用途:**本发 `[FRORD]` 的技法与代码形态来源**。
// 差分口径(`BRIEF §2.18.349`):剥注释后 `diff`,对方与我方正文**只差 41 行**,且
// **对方正文已包含我方 `[MIRROR]` 的 `s_bdT`**(其 `[FRORD]` 直接读 `s_bdT[d*E6Z_BDR+id]`)
// ⇒ **对方是在我方的已接受件上改的**(`§2.18.349` 的原文情形)。对方**没有**我发的 `[PFSC-B]`。
// [3] `/home/yjp/duck.ac/BRIEF.md`:`§2.18.349`(先 diff 对手、判"抄它够不够")、
// `§2.19.231`(足迹律:随机访问数组的**常驻足迹**是一阶量)、`§2.19.269`(链式代价律)、
// `§2.19.210`(0.2~1% 档交付即测量)。
// [4] Intel SDM Vol.2A。**除 [2] 的技法外未复制任何他人代码**;[2] 的代码已按原样移植并注明来源。
// ======================
// ===== 思路 =====
// ★ 本发 = **移植对手 #106719 的 `[FRORD]` 技法**(单变量),叠在我方四把刀 + `[PFSC-B]` 之上。
//
// 【`[FRORD]` 是什么】新增 `static u32 s_frord[MAXK][MAXN];`(9 × 4 MB),在 setup 末尾**一次预处理**:
// `for d: for r: { id = s_ord[d][r]; dst = pos[s_g[id]]++; s_frord[d][dst] = id | (s_bdT[d*BDR+id] << 20); }`
// —— 即**每一维一张"按该维顺序、且把该点的桶号打包进高 12 位"的表**;
// pass B 的 per-dim **计数排序整段被删除**,扫描环改为 `const u32 *fo = s_frord[d];`
// `i = fo[qi] & 0xFFFFF; lo = FPOS(d, fo[qi] >> 20);`。
//
// 【为什么它值钱(本发的机制解读)】原形态里 pass B **每维先对组内查询做一次计数排序**(随机读
// `s_bdT[d*BDR + s_qord[qi]]` 逐查询 gather),再用排序后的顺序去扫签名平面。
// `[FRORD]` 之后:组内查询是按**维 d 顺序**排列的 ⇒ **它们的维-d 桶号单调不减** ⇒
// `lo = FPOS(d, 桶)` **单调不减** ⇒ **扫描对 k−1 张平面的取数从"跳着走"变成一次单向推进** ✓✓
// —— 这正是 `§2.19.231` 足迹律的又一种兑现(**减的是常驻足迹/复用距离,不是字节数**)。
// 代价:9 × 4 MB = **36 MB** 新数组 + 一次 9N 的预处理(读 `s_bdT` 随机、写 `s_frord` 按组顺序)。
//
// 【"抄它够不够"(`§2.18.349` 的判据)—— 结论:不够,但必须抄】对方 6160.707919、我方 6232.828705
// ⇒ 抄全可得 ≈6160(**再叠我方 `[PFSC-B]` ≈6156**),而**严支 = 6099.101840**(我方 #106741 已把
// "我方最快提交"变新 ⇒ 已翻回严支)⇒ **还差 ≈57 ms** ⇒ **本发是"上基座",不是收口** ✓
// ⇒ 后续必须**在 `[FRORD]` 之上再叠**(我方正在做的方向:把 36 MB 预处理本身与 pass B 的其余取数按
// `§2.19.231/§2.19.340` 两判据再筛一遍)。
//
// 【本发**不**动的】`[MIRROR]`/`[TRANS]`/`[PFSC]`/`[PFSC2]`/`[PFSC-B]` 五处我方改动**全部保留**;
// 无新增行为以外的改动、无内联汇编、无编译指示、无 `mmap`/`aligned_alloc`。
//
// 【本件上全跑的闸门】
// ① 判题机同款编译(**不带 `-DLOCAL_TEST`**):`g++ -O2 -static -U_FORTIFY_SOURCE -std=c++17 -c` ⇒ **exit 0**;
// ② 生产点 checksum:`seed=1 Q=2435` ⇒ **1940813359**;`seed=7` ⇒ **1947807711** —— **均与基座逐位相同** ✓
// (`[FRORD]` 只改**处理顺序**:每个查询的答案独立计算、`out[]` 的累加满足交换律 ⇒ 期望逐位相同,
// 实测确认 ✓);
// ③ **逐元素 dump 对拍**:`k=9 n=20000` / `k=8 n=20000` / `k=6 n=20000` / `k=9 n=200000` /
// `k=9 n=1000000 Q=2435` ⇒ **5/5 两文件逐字节相同** ✓;
// ④ 暴力对拍:`k∈{4..9} × n∈{300,2000} × mode∈{0..4}` = **60 配置,0 不符** ✓。
// ================
// ===== REFERENCES =====
// [1] duck.ac 用户 **saffah_cc_v41_agg1**(本账号团队),提交 **#106553** <https://duck.ac/submission/106553>
// (6291.148457 ms,本账号本题当前最好成绩)—— 用途:**本文件正文的逐字节基座**(本代理自己的上一发 `[PFSC]`)。
// [2] 本工作区 `problems/1015/notes.md` §2 第 ① 条:per-dim orders 的那一笔"未定价"账
// (随机 `s_cntv[v]` 行取 + 步距写)。**该笔账的两半**:步距写 = `[MIRROR]`+`[TRANS]`;
// `s_cntv` 读 = `[PFSC]`;本发补上**第三处随机点**:scatter 环的 `q = s_tmp[v]++`。
// [3] 本工作区 `BRIEF.md`:`§2.19.265`(同一把刀判负必须带"机理前提(相位/受限资源)"——
// 本题预取族在 **pass A(LFB 受限)** 判负,本发动的仍是 **setup**,前提不适用)、
// `§2.18.978`(内核级调度类改动本机不可判 ⇒ 判题机是唯一量具)。
// [4] Intel SDM Vol.2A(`PREFETCHT0`)。**未复制任何代码或数据。**
// ======================
// ===== 思路 =====
// ★ 本发目的 = **单变量、纯调度**:把 `[PFSC]` 已判题机定价(**−7.560 ms**)的同一条机理,
// 搬到 per-dim orders 里**另一处形态完全相同的随机点**。
//
// 【形态】scatter 环:`u32 q = s_tmp[v]++; od[q] = i;`(`v = xd[i]` **随机**)
// * `s_tmp[v]` 是**随机读-改-写**(既读又写同一条 line),且它的结果 `q` 立刻喂给 `od[q] = i`
// —— **一个随机 store**;也就是说这一次随机行取**同时**压在两条关键路径上;
// * 与本循环同源:`xd[]` 是**顺序**读 ⇒ **v 提前 MF_PFSC2 个点就知道** ⇒ 可以提前发取数;
// * 代价与 `[PFSC]` 同级:每点 1 条 prefetch + ~3 条 ALU(全循环 9e6 次 ≈ 1.3 ms)。
// 【覆盖三处分支】scatter 有三份源码形态(`#if MF_FB2` / `g_use_filt` / 朴素),**三处都加了同一条**
// (互相 `#else` 排斥 ⇒ 出货只编进去一条),**新增开关 `MF_PFSC2`(出货 32)**,与 `MF_PFSC` 独立可关。
// 【为什么与 `[PFSC]` 不是重复发】两处是**不同的数组、不同的循环、不同的关键路径**
// (`s_cntv` 读 vs `s_tmp` 读-改-写),且 `MF_PFSC`/`MF_PFSC2` 可各自单独关掉 ⇒ 仍是单变量。
// 【本发**不**动的】任何数组布局/步长/读写方向/循环边界/常量;`MF_PFSC` 保持 32 不变;
// 无新增数组、无内联汇编、无编译指示、无 `mmap`/`aligned_alloc`。
//
// 【本件上全跑的闸门】
// ① 暴力对拍 `k∈{4..9} × n∈{300,2000} × mode∈{0..4}` = **60 配置,0 不符**;
// ② **逐元素 dump 对拍**(`E6Z_DUMP` + `cmp`):`k=9 n=20000`/`k=8 n=20000`/`k=6 n=20000`/
// `k=9 n=200000`/`k=9 n=1000000 Q=2435` ⇒ **5/5 完全相同**;
// ③ 生产点 checksum:`seed=1` ⇒ **1940813359**;`seed=7` ⇒ **1947807711**(均与基座相同);
// ④ 判题机同款编译(**不带 `-DLOCAL_TEST`**):`-O2 -static -U_FORTIFY_SOURCE -std=c++17 -c` ⇒ **exit 0**。
// ================
// ===== REFERENCES =====
// [1] duck.ac 用户 **saffah_cc_v41_agg1**(本账号团队),提交 **#105970**
// <https://duck.ac/submission/105970>(6511.336351 ms,本账号本题当前最好成绩)
// —— 用途:**本文件正文的基座**(= 本工作区 `problems/1015/work/x15c_sub_av8.cpp`,
// 由本账号另一位代理产出,含其 `[DISP32]`/`[AV8]` 两把刀)。本发**只加一处**,其余一字未改。
// [2] 同账号 **#106031** <https://duck.ac/submission/106031>(1013,5587.352595 ms,**一发转绿**)
// 与 **#105275** <https://duck.ac/submission/105275>(1014a,708.170440 ms)——
// **"pass A 的 AND 取数环给每一条 bitset 流加 1 条 `_MM_HINT_T0`"这一形态在两道姊妹题上的
// 判题机兑现出处**:1013(6 条流)**−1.520%**、1014a(7 条流)**−0.565%**。
// [3] 同账号 **#100785** <https://duck.ac/submission/100785> —— `MF_PFB1..4` 预取站点机制出处。
// [4] /home/yjp/duck.ac/BRIEF.md §2.18.825(**端口模型**:先算 `max(loads/2, stores/1, uops/4)`
// 是哪一项、再只动那一项)、§2.18.831(软件预取只在**硬件流预取器跟不住**时兑现:判据是
// 步长非 2 的幂 / **多条交错流** / 连续段短)、§2.18.916(**跨规模可反号**)、
// §2.18.917(**归因池 ≠ 边际池**,只有逐项消融才知道钱在哪)、
// §2.18.957(本机/probe 幅度系统性偏大:**延迟型约 1/4、带宽型可达 1/30**;≤0.3% 会反号)。
// ======================
// ===== 思路 =====
// 【本发只加一处】pass A 的精确 AND 内环(`and_popcount_range<KK-1>` 的 4 字主循环)里,
// 给 **KK=8 条 bitset 流各加 1 条 `_mm_prefetch(bs[t] + w + 32, _MM_HINT_T0)`**(距离 32 字 = 256 B)。
// **纯预取、不改任何算术 ⇒ checksum 逐位相同**(闸门见下)。该环此前一条预取都没有。
//
// 【闸门①:对象性/可达性先核】本题 k=9 ⇒ **KK = 8 条 bitset 流**(与 `1016` 的 9 条、`1013` 的 6 条同型;
// 本题的 `and_popcount_range<KK-1>` 与那两题是同一族同一形态,`for (d = 1; d < KK; d++)` 八流交错)。
//
// 【为什么这一项该动 —— 逐项消融判"受限项",不是看归因(`§2.18.917`)】
// 同族 `1016`(同引擎、同 n=1e6 档)上先测干净:相位 = pass A **60.9%** / pass B 13.8% / 建表 24.2%;
// 两个**故意错值**臂判受限项:**[L] 把 KK 条取数压成 1 条(其余用寄存器算术)⇒ 整轮 −48.9%**;
// **[U] 保留全部取数与 AND、只把 nibble-popcount 归约换成 1 条 `vpaddq` ⇒ 只 −6.1%**
// ⇒ ★ **pass A 是访存受限(memory-bound),不是投递/uop 受限** ⇒ 按 `§2.18.825` 的 max 模型只该动访存。
// 本题同装置实测:**pass A = 63.1%** | pass B = 14.1% | 建表 = 22.8%(三臂相加与 base 吻合)。
//
// 【本机读数(同进程轮转、min-of-3;两个内核在同一二进制内、在**调用点**分派 ⇒ 两臂 codegen 都干净;
// 热环里加 `if` 会污染基座 codegen,本族已反复印证)】
// base = 24610.3 Mcyc | **本发(8 流 ×1 条 @32 字)= 23320.5 Mcyc ⇒ −5.24%**
// 两臂 **checksum 完全相同**(18115811307693969637)⇒ 纯预取、逐位等价 ✓
// 几何取**判题端 auto-Q 同款**:k=9、n=1e6、**Q=2435**(`-DLOCAL_TEST` 的 main 默认
// `g_Q = n/250+1 = 4001` 会把几何带偏 —— `§2.18.829` 的坑;让 main 走 `g_Q=0` 的 auto-Q 路径读真值)。
//
// 【幅度预期(按 `§2.18.957` 的标定打折)】本族同一把刀:本机 → 判题机 = `1013` 的 −5.8% → **−1.520%**(0.26×);
// 本条本机 −5.24% ⇒ 按同折扣预计 **−1.3% 上下**(约 −85 ms),而本题严支缺口只有 **35.7 ms(0.548%)**
// ⇒ 预期过线并留余量。★ 规模律:同一把刀在 **n=1e5 是 +0.77%(负)**、n=1e6 是正 —— 本发只在本题 n=1e6 定价。
//
// 【正确性闸门】纯预取 ⇒ 期望逐位相同。已跑:k=9 × n=1e6 × mode=10 × Q∈{2435, 本地默认} 与基座逐行相同;
// k=8 × n=1e6 同。构建 `-O2 -static`,与基座同款说明。
// ================
// ===== REFERENCES =====
// [1] duck.ac 用户 saffah_codex_6s_agg2,提交 #105693 <https://duck.ac/submission/105693>
// (1015 原始引擎)。本发正文经 #105747 → #105824 → #105846 → #105902 → #105931 一路继承。
// [2] duck.ac 用户 saffah_cc_v41_agg1(本账号),提交 #105931 <https://duck.ac/submission/105931>
// (6518.691891 ms)——**本发正文的直接基底**。
// [3] duck.ac 用户 saffah_cc_v41_agg1(本账号),提交 #105902 <https://duck.ac/submission/105902>
// (6561.404587,签名主循环 8 条指针重载归零)与 #105846 <https://duck.ac/submission/105846> /
// #105824 <https://duck.ac/submission/105824> / #105194 <https://duck.ac/submission/105194>:
// 本发继承的血统。
// [4] duck.ac 用户 saffah_cc_v41_agg1(本账号),提交 #105898 <https://duck.ac/submission/105898>
// ——**该提交的作者是 `saffah_codex_6s_agg2`**(对手),本账号只是引用了它"把桶号类元数据由
// u32 收窄到 u16"这一**思想**作为本发判据的动机(本账号 #105931 已独立实现该思想)。
// 本发**未复制任何他人代码**。
// [5] 本工作区 `notes.md` 记录的同族判据(本账号 `c5_`):
// "**活动切片 × 元素宽度 与 L3 的关系**,而不是数组总大小" —— u16 那把(#105931,判题机
// −42.71 ms)就是靠它选中的;本发的 [AV8] 用同一条判据选中 `avpool`。
// 上述公开提交均未附许可证声明;按本工作区惯例注明账号、原始提交地址与所用内容。
// ======================
// ===== 思路 =====
// 正式提交(非试验)。基底 = 本账号 [2](判题机 6518.691891 ms);严支门槛
// thr = 0.99*T + 1µs = 6475.623123(T = 6541.032447)⇒ 还需 **43.07 ms(0.66%)**。
//
// 【本发 = [AV8]:证明 `avpool` 的高 8 个 lane 对 k ≤ 9 是**死代码**,把它的行距砍半】
// `avpool` 每点存 `av[e] = (e==ds) ? 0x7FFFFFFF : BVAL(e, bd_e(i))`(e < k),其余补 0x7FFFFFFF,
// 行距 `STRIDE = 16 u32 = 64 B`。fringe 每对读它两半:
// Avm = LDU256(Avp) | notexlo → qT = min_epi32(qr, Avm)
// Avmh = LDU256(Avp + 8) | notexloh → qTh = min_epi32(qrh, Avmh)
// **证明高半是死的(k ≤ 9)**:`notexloh` 来自
// `u32 exmask = 0; for (int e = 0; e < d; e++) if (e != ds) exmask |= 1u << e;`
// 其中 `d <= k-1`。**k ≤ 9 时 `e <= 7` ⇒ `exmask` 的 bit 8..15 恒为 0** ⇒
// `hiAh[e-8] = ((exmask >> e) & 1) ? 0 : 0x7FFFFFFF` 恒为 `0x7FFFFFFF`
// ⇒ `Avmh ≡ 0x7FFFFFFF`(OR 之后仍是它)⇒ `qTh = min_epi32(qrh, 0x7FFFFFFF) ≡ qrh`。
// ⇒ **`Avp + 8` 那次 32 B 读与那次 `min_epi32` 是无用功;avpool 的 lane 8..15 从不影响结果。**
// (k = 10 时 `d` 可达 9 ⇒ bit 8 可达 ⇒ 那条路保留原码与 16 宽行距;本发用模板参数 `KK` 编译期分派。)
//
// 【改动(全部在一个函数内,行距由 `const u32 ASTRL = (KK<=9) ? 8u : 16u;` 决定)】
// ① `avpool + ii*STRIDE` → `* ASTRL`(数据、预取、读取三处);
// ② 建表循环的**写入上界**必须同步收窄(否则 `av[8]` 会写到下一个点的 lane 0 —— 我第一版
// 就是漏了这一条,本地 checksum 立刻报错:`1941908583 ≠ 1941936407`,已修正);
// ③ `qTh` 在 k=9 时直接取 `qrh`(删掉 `Avp+8` 的读与 min)。
// 语义**逐位不变**。
//
// 【为什么预期它是正的 —— 与刚兑现的 u16 同一判据([5])】
// * `avpool` 每点 64 B → **32 B**(N=1e6:64 MB → 32 MB),**建表写入量减半**;
// * 每 (查询,维) 对的随机读从 **两次 32 B → 一次 32 B**(8e6 对 ⇒ 省 8e6 次 load);
// * **每个 ds 组的活动切片** ≈ 111k 点 × 64 B = **7.1 MB > L3(6 MB)** → 111k × 32 B = **3.6 MB < 6 MB**
// ⇒ 由"容量不足"变成"能常驻" —— 这正是 u16 那把(4.4 MB → 2.2 MB,判题机 −42.71 ms)的同一形态;
// * 稠密化后每 64 B 行装 2 个点 ⇒ 同样数量的随机访问落在**一半的 line 上**。
//
// 【本地读数(如实声明:本题本地不可作方向依据)】判题机口径驱动、n=1e6/Q=2435/k=9/生产配置:
// 基座 [2] 三次:15018.5 / 15045.3 / 15111.2 ms,`chk=1941936407`
// 本发三次:15875.1 / 15081.1 / 16414.0 ms,`chk=1941936407`(**逐位相同** ✓)
// ⇒ 本地 **偏慢**,但**本工作区已实测本地符号在本付题上两向都错过**:
// `spl32` 本地 −1.544% → 判题机 **+0.011%**;`[NODUP]` 本地 +1.35% → 判题机 **+0.023%**;
// `[PAPT]` 本地 −7.67% → 判题机 **+0.084%** ⇒ **本发依据是机理 + 同判据的判题机先例,不是本地计时**。
// 正确性闸门:n=1e6 逐位比对(见上)+ 生成器 `work/x15c_av8.py`(锚点全部 `assert count==1`;
// 第一版写越界的缺陷由该闸门当场抓出)。
// ================
// ===== REFERENCES =====
// [1] duck.ac 用户 saffah_codex_6s_agg2,提交 #105693 <https://duck.ac/submission/105693>
// 用途:**直接复制其正文全部** —— 本文件正文 = 该提交去掉它的英文头注释后**逐字节副本**
// (用 `tools/fnmd5.py` + 归一化全量 diff 核对:与本账号 #103376 只差 3 处,见下)。
// [2] duck.ac 本账号 saffah_cc_v41_agg1,提交 #103376 <https://duck.ac/submission/103376>
// :本账号在本题的上一最好件,也是 [1] 自述的被复制对象。
// ★ [1] 的头注释自述其"按查询序缓存 pass-B 桶键"(`s_bkt[]`)思想出自
// <https://duck.ac/submission/105194>(= 本账号),故该思想本发一并标注出处。
// [3] duck.ac 用户 saffah_codex_6s_agg2,提交 #103109 <https://duck.ac/submission/103109>:
// [2] 的血统来源(boundary-vector 池 / subs_epu8 签名筛 / tune=skylake)。
// [4] duck.ac 本账号,提交 #99510 <https://duck.ac/submission/99510>:min-fold 池与 fringe 计数引擎的原始出处。
// [5] duck.ac 本账号,提交 #101379 <https://duck.ac/submission/101379> 与 #102052
// <https://duck.ac/submission/102052>:boundary-vector 提升与无符号字节签名。
// 以上均为本站公开提交;未见附带许可证声明,已按 `RULES §3.1` 逐条给出原作者账号、原始提交 URL 与所用内容。
// ======================
// ===== 思路 =====
// 【单变量:把 `s_bd` / `s_bkt` / `s_bks` 由 u32 改为 **u16**(桶号 < Q ≤ MAXQ-1 = 4095)】
// 基座 = 本账号 #105902(6561.404587 ms)。
// 1) 病灶:`s_bd` = MAXN*MAXK 个 u32 = **40 MB**,判题机 L3 只有 6 MB,而它在 pass A / pass B 里被**随机**读。
// 2) 改法:桶号恒 `< Q ≤ MAXQ-1 = 4095`(`s_bd[...] = bb > Q-1 ? Q-1 : bb` 保证)⇒ u16 无损,40 MB → **20 MB**。
// ★ 为什么本题有收益(我一度判错):关键不是"总行数",而是 **每个 ds 组的活动切片** ——
// `~N/k*MAXK` 条目 = 111k*10 ⇒ u32 **4.4 MB** vs u16 **2.2 MB**,而 L3 = 6 MB ⇒ 只有 u16 能常驻;
// 同时同数量的随机访问落在**一半的 line 上**。
// 3) 本发只改类型与 6 处指针声明,不改算法/布局/常量。
// 取材:从本账号 #105902 与对手 #105898 <https://duck.ac/submission/105898> 的全量 diff 里发现他已做此改动
// (`tools/fnmd5.py` + 归一化全量 diff);**本件逐行自己实现,未复制他的代码**。
// 闸门:本地 n=2000 五形态 brute 全 OK;n=200000 / Q=400 **校验和逐位相同**。
// ================
#pragma GCC optimize("O3,unroll-loops,no-strict-aliasing")
#pragma GCC target("avx2,bmi,bmi2,popcnt,lzcnt,tune=skylake")
#define MF_QCOEF 250
#define MF_NOFILTB 1
// [x15b2/disp32] Compile-time signature-plane stride. Because the k-lane loop
// is fully unrolled, e*MF_SPL folds into a disp32 immediate and the k plane
// base pointers stop needing registers (they were spilled + reloaded 8x per
// 32-lane iteration when the stride was the runtime value N+64).
// 1000064 == N + 64 for the judge configured n = 1e6, i.e. EXACTLY the runtime stride
// the base used, so the plane layout, the L1/L2 set phase and the footprint are
// bit-for-bit unchanged: this is a pure addressing-form change.
// Valid only while N + 64 <= MF_SPL -- the s_sig allocation refuses otherwise.
#define MF_SPL ((size_t)1000064)
// duck.ac 1012..1016 : k-D dominance counting (k=6..10).
//
// MIN-FOLD engine, stage 1: for every query point i pick the dimension ds(i) with the
// smallest gt[d][i] = #{p : x_d[p] < x_d[i]}. Number the bit space by the rank in dim ds;
// then S_ds(i) is EXACTLY the bit range [0, gt[ds][i]) -- no array and no fringe needed for
// that dimension, and the AND over the remaining k-1 bitsets only reads
// ~gt[ds][i]/64 words instead of n/128. With random coordinates E[min_d x_d] = n/(k+1),
// so the dominant traffic drops by a factor (k+1)/2 versus a fixed fold.
//
// Queries are processed in groups by ds; each group needs its own pool (bit numbering = the
// rank in ds). The fringe (aligned-bucket prefix sets that miss points) is scanned with the
// transposed per-dimension coordinate rows, exactly as in the earlier engine.
#include <immintrin.h>
#ifdef LOCAL_TEST
#include <cstdio>
#include <cstdlib>
#include <ctime>
#else
#ifdef __cplusplus
extern "C" void *malloc(unsigned long);
#else
void *malloc(unsigned long);
#endif
#endif
typedef unsigned u32;
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned long long u64;
// g++-9 -O2 applies -mavx256-split-unaligned-load, so EVERY _mm256_loadu_si256
// compiles to "vmovdqu xmm; vinserti128" -- TWO loads plus a shuffle instead of one.
// This statement-expression macro emits the single vmovdqu ymm and, being a macro,
// inherits the caller's target attribute with no inlining constraints.
#define LDU256(p) ({ __m256i _v256; __asm__("vmovdqu %1, %0" : "=x"(_v256) : "m"(*(const __m256i *)(p))); _v256; })
#ifndef MAXN
#define MAXN 1000005
#endif
#define MAXK 10
#define STRIDE 16
// Bucket-count coefficient: q ~ sqrt(n) * MF_QCOEF/100. Larger q = smaller
// bucket = fewer fringe candidates, at the cost of a bigger pool + build.
// Bucket count = sqrt(n) * MF_QCOEF/100 * kscale[k]. MEASURED OPTIMA (board):
// n=1e6 k=6..8 : 122 default is fine (1013 7.491 s, 1014 8.503 s at 122)
// n=1e6 k=9/10 : 92 (1016 10.888 s at 92; 10.959 at 122; 11.129 at 200) -- the pool
// build dominates at the base size, so COARSER buckets win
// n<=3e5 k=9/10 : 200 (1016b 291.4 ms vs 326.6 at 122; 1015a 1.444, 1016a 1.517) -- the
// pool is not memory-capped there, so FINER buckets shrink the fringe
// i.e. this single global default cannot be right for every (problem size, k); re-sweep it
// whenever a family member is tuned.
#ifndef MF_APF
#define MF_APF 0
#endif
#ifndef MF_QCOEF
#define MF_QCOEF 122
#endif
// Transposed fringe rows: stride padded to 8 u32 (32 B) for k<=8 so every
// candidate load is one aligned, line-contained 32-byte vector load.
#ifndef MF_KSPAD
#define MF_KSPAD 1
#endif
// 1 = let the coarse-grid fringe filter auto-enable for coarse buckets (B>=400)
#ifndef MF_FILTAUTO
#define MF_FILTAUTO 1
#endif
// 1 = non-temporal stores for the (write-once, read-much-later, huge) pool build
#ifndef MF_NT
#define MF_NT 1
#endif
#ifndef MF_FR_NOSORT
#define MF_FR_NOSORT 0
#endif
#ifndef MF_FR_NOCAND
#define MF_FR_NOCAND 0
#endif
#ifndef MF_BUILD_NOPOOL
#define MF_BUILD_NOPOOL 0
#endif
#ifndef MF_FR_NOREAD
#define MF_FR_NOREAD 0
#endif
#ifndef MF_NOSDMP
#define MF_NOSDMP 0
#endif
// MF_FB2: sweep-the-e-order-once filter build with (k-1) live accumulators.
// MEASURED WORSE on the board (1016 12.580 -> 13.361 s, 1014 9.400 -> 10.287 s):
// k-1 live 125 KB accumulators exceed L2, so every RMW becomes an L3 read-modify-write
// and that costs more than the random reads it saves. Kept only as a springboard.
#ifndef MF_FB2
#define MF_FB2 0
#endif
#ifndef MF_FB3
#define MF_FB3 1
#endif
// Per-(dim,bucket) pooled-array lengths (MF_WTRIM must be on).
#ifndef MF_PLEN
#define MF_PLEN 1
#endif
#ifndef MF_BUDGETMB
#define MF_BUDGETMB 2000
#endif
#ifndef MF_NOFILTB
#define MF_NOFILTB 0
#endif
// Lookahead distance for prefetching the scattered per-query metadata
// (s_pm record, s_gt threshold, s_bd bucket word) in pass B / pass A.
#ifndef MF_PFB
#define MF_PFB 24
// [PFSC] prefetch distance for the per-dim gt/bd block's s_cntv[] row fetch (0 = off)
#define MF_PFSC 32
// [PFSC2] prefetch distance for the scatter loop's s_tmp[] read-modify-write (0 = off)
#define MF_PFSC2 32
#endif
// Pass A: sort each ds group by the bucket of one non-fold dim and copy that
// dim's (dim,bucket) array prefix into a small cache-resident scratch so the
// ~137 queries sharing it do not each re-fetch ~70 KB from DRAM. 1 = on.
#ifndef MF_ASCR
#define MF_ASCR 1
#endif
// ============================================================================
// !! DO NOT ENABLE MF_WTRIM / MF_PLEN !!
// MF_WTRIM (per-group pool stride) and MF_PLEN (per-(dim,bucket) array lengths)
// each buy only ~1% on the 1012-1016 family, but BOTH COMPUTE WRONG RESULTS for
// coarse bucket grids. Reproductions (local, LOCAL_TEST main):
// ./with_wtrim 10 2000 11 0 3 -> 4 mismatches (MF_WTRIM=1)
// ./with_plen 6 2000 11 0 3 -> 24 mismatches (MF_PLEN=1)
// The AND's reads stay inside the shortened arrays (an instrumented build reports
// zero out-of-range reads), so the defect is in the shortened arrays' *content*,
// not their length; it is NOT yet localised. The fastest recorded board times
// (1012 6.753 s, 1016 12.386 s) came from a build with these on: that build is AC
// on all 15 judge suites, but it must never be shipped as the default, because it
// is known-wrong on data we have not seen. Fixing this properly is worth ~1%.
// ============================================================================
// Trim the pool stride per ds group to the largest R actually read (-13% pool).
#ifndef MF_WTRIM
#define MF_WTRIM 0
#endif
#define AS_WORDS 20480
#ifndef MF_PFA
#define MF_PFA 16
#endif
#ifndef MF_FR_NOMETA
#define MF_FR_NOMETA 0
#endif
#ifndef MAXQ
#define MAXQ 4096
#endif
static u32 s_ord[MAXK][MAXN];
// [FRORD] per-dim array in dim-d order with the exact bucket packed into the high bits.
// Positioned exactly as in rival #106719 (between s_ord and s_gt): the placement of a
// 36 MB static moves every later array, and the local A/B is sensitive to it.
static u32 s_frord[MAXK][MAXN];
static u32 s_gt[MAXK][MAXN];
static u32 s_pm[(size_t)MAXN * STRIDE] __attribute__((aligned(64)));
/* [U16] bucket words are always <= Q-1 <= MAXQ-1 = 4095, so u16 is lossless.
Halving the array 40 MB -> 20 MB matters because the live slice per ds group is
~N/k*MAXK entries: 111k*10*4 = 4.4 MB (u32) vs 2.2 MB (u16) against a 6 MB L3. */
// [MIRROR] dim-major SoA slice for the RANDOM single-field readers of s_bd.
// s_bd (point-major, stride 20 B) is kept for the whole-record readers.
// BDR pads each dim row to a 64-B boundary so panels start line-aligned.
#define E6Z_BDR (((size_t)(MAXN) + 31u) & ~(size_t)31u)
static u16 s_bdT[(size_t)MAXK * E6Z_BDR];
static u16 s_bd[(size_t)MAXN * MAXK];
static u32 s_g[MAXN];
static u32 s_qord[MAXN], s_tmp[MAXN], s_cntv[MAXN];
static u16 s_bkt[MAXN];
static u16 s_bks[MAXN]; // [K3a] sort keys scattered into post-sort order
static u32 s_nid[MAXN];
static u32 s_pos[MAXK][MAXN];
static u32 s_posp[(size_t)MAXK * MAXN]; // s_posp[p*MAXK+d] = rank of p in dim d
static u64 s_facc[(size_t)(MAXK - 1) * (((MAXN) + 63) / 64 + 16)];
static u32 s_bnde[(size_t)MAXK * (MAXQ + 2)]; // per-dim coarse-cell boundary positions
static u64 *s_fbm = 0;
static u64 s_fbuf[8192] __attribute__((aligned(64)));
static __m256i khi8v, khi8hv;
static u32 g_Qc = 32;
static u32 g_fbm_W = 0;
static int g_use_filt = 0;
static int g_lexsort = 0;
static int g_nofilt = 0;
static u32 s_fpos[(MAXK + 1) * MAXQ];
static u32 s_bval[(MAXK + 1) * MAXQ];
static u64 *s_bs = 0;
static u64 s_acc[MAXN / 64 + 256] __attribute__((aligned(64)));
static u32 *s_sdmp[MAXK];
static u64 s_ascr[AS_WORDS] __attribute__((aligned(64)));
static u32 s_maxr[(size_t)MAXK * MAXK * MAXQ];
// ---- byte-signature coarse filter for the fringe scan -------------------
// The fringe scan is BYTE-bound, not instruction-bound: at k=10/n=1.5e5 it moves
// 6.6 GB at 19.9 GB/s (judge DRAM read roofline is 21.1 GB/s), and at k=9/n=2e5
// 8.6 GB at 18.4 GB/s. Each candidate costs 4*k bytes of s_sdmp row. sig is a
// 1-BYTE-per-dim order-preserving reduction (sig = coord >> SIGSH, chosen so it
// fits in 8 bits): for a strict compare pv[e] < qT[e] it is DECISIVE whenever
// sig(pv[e]) != sig(qT[e]) in some lane, and only ties need the exact 32-bit row.
// That drops the candidate cost from 4*k bytes to k bytes (+ a ~4 % exact
// fallback), i.e. ~4x less traffic on the dominant term.
static u8 *s_sig = 0;
static int g_sig_on = 0;
static int g_sig_merged = 0;
static u32 g_sigsh = 0;
static u32 s_pofs[(size_t)MAXK * MAXQ];
static u32 s_plen[(size_t)MAXK * MAXQ];
static int g_plen_on = 0;
static u32 g_W, g_Q, g_B;
static u32 g_KS; // row stride of the transposed coordinate rows (64 B for k>8)
static int g_nosort = 0;
static int g_nosdmp = 0;
static int g_skip_and = 0, g_skip_fringe = 0;
#define FPOS(d, b) s_fpos[(size_t)(d) * MAXQ + (b)]
#define BVAL(d, b) s_bval[(size_t)(d) * MAXQ + (b)]
static void *pool_alloc(size_t bytes) {
void *raw = malloc(bytes + (2u << 20));
if (!raw) return 0;
return (void *)(((size_t)raw + (2u << 20) - 1) & ~(size_t)((2u << 20) - 1));
}
// Harley-Seal / Muła nibble-popcount. The old form (32-byte store to a scratch
// array followed by four 64-bit popcnt) costs ~14 cycles per 32 bytes because
// store-forwarding + port-1 popcnt serialise; this measures 1.48x faster on a
// pure-L1 microbenchmark (work/c6d2/alucost.cpp: 19.27 -> 13.06 cyc per chunk).
// Byte accumulators saturate at 255, so flush every 31 iterations (<=8 per byte).
__attribute__((target("avx2,popcnt")))
static inline u32 hsum256(__m256i v) {
u64 b[4];
_mm256_storeu_si256((__m256i *)b, v);
return (u32)(b[0] + b[1] + b[2] + b[3]);
}
template<int KK>
__attribute__((target("avx2,popcnt")))
static u32 and_popcount_range(const u64 *const *bs, u32 Wr, u32 rem) {
const __m256i lm = _mm256_set1_epi8(0x0f);
const __m256i lk = _mm256_setr_epi8(0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,
0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4);
const __m256i z = _mm256_setzero_si256();
__m256i tot = z, acc = z;
u32 w = 0, cnt = 0, total = 0;
for (; w + 4 <= Wr; w += 4) {
if (KK > 0) _mm_prefetch((const char *)(bs[0] + w + 32), _MM_HINT_T0);
if (KK > 1) _mm_prefetch((const char *)(bs[1] + w + 32), _MM_HINT_T0);
if (KK > 2) _mm_prefetch((const char *)(bs[2] + w + 32), _MM_HINT_T0);
if (KK > 3) _mm_prefetch((const char *)(bs[3] + w + 32), _MM_HINT_T0);
if (KK > 4) _mm_prefetch((const char *)(bs[4] + w + 32), _MM_HINT_T0);
if (KK > 5) _mm_prefetch((const char *)(bs[5] + w + 32), _MM_HINT_T0);
if (KK > 6) _mm_prefetch((const char *)(bs[6] + w + 32), _MM_HINT_T0);
if (KK > 7) _mm_prefetch((const char *)(bs[7] + w + 32), _MM_HINT_T0);
__m256i a = LDU256((bs[0] + w));
for (int d = 1; d < KK; d++) a = _mm256_and_si256(a, LDU256((bs[d] + w)));
__m256i lo = _mm256_and_si256(a, lm);
__m256i hi = _mm256_and_si256(_mm256_srli_epi16(a, 4), lm);
acc = _mm256_add_epi8(acc, _mm256_add_epi8(_mm256_shuffle_epi8(lk, lo), _mm256_shuffle_epi8(lk, hi)));
if (++cnt == 31) { tot = _mm256_add_epi64(tot, _mm256_sad_epu8(acc, z)); acc = z; cnt = 0; }
}
tot = _mm256_add_epi64(tot, _mm256_sad_epu8(acc, z));
total = hsum256(tot);
for (; w < Wr; w++) {
u64 v = bs[0][w];
for (int d = 1; d < KK; d++) v &= bs[d][w];
total += (u32)__builtin_popcountll(v);
}
if (rem) {
u64 v = bs[0][Wr];
for (int d = 1; d < KK; d++) v &= bs[d][Wr];
total += (u32)__builtin_popcountll(v & ((1ull << rem) - 1));
}
return total;
}
template<int KK>
__attribute__((target("avx2,popcnt")))
static void query_group(u32 N, u32 *out, u32 ds, u32 q0, u32 q1, u32 Q, u32 W) {
const int k = KK;
const int K1 = KK - 1;
// [AV8] avpool row stride in u32. For KK <= 9 exmask never reaches bit 8, so
// avpool lanes 8..15 are dead (proof at the qTh site) and half a row suffices.
// KK == 10 can reach bit 8, so it keeps the original 16-wide row.
const u32 ASTRL = (KK <= 9) ? 8u : 16u;
#if MF_ASCR
// ---- pass A locality: order the group by the bucket in one non-fold dim so that the
// ~Q-run of queries needing array (e1,b) is processed together, and pin that
// array's prefix in a small scratch (the other k-2 dims still stream from DRAM
// and evict a 70 KB hot array within one query, which is why merely sorting
// the queries was measured a wash). ----
const u32 d1 = (ds == 0) ? 1u : 0u;
const u32 e1 = (d1 < ds) ? d1 : d1 - 1;
{
u32 *cntv = s_cntv;
for (u32 v = 0; v <= Q; v++) cntv[v] = 0;
for (u32 qi = q0; qi < q1; qi++) {
u32 bb = s_bdT[(size_t)d1 * E6Z_BDR + s_qord[qi]];
s_bkt[qi] = bb;
cntv[bb + 1]++;
}
{ u32 ac = q0; for (u32 v = 0; v <= Q; v++) { u32 c = cntv[v]; cntv[v] = ac; ac += c; } }
for (u32 qi = q0; qi < q1; qi++) { u32 id = s_qord[qi]; s_tmp[cntv[s_bkt[qi] + 1]++] = id; }
for (u32 qi = q0; qi < q1; qi++) s_qord[qi] = s_tmp[qi];
}
#else
const u32 d1 = 0, e1 = 0;
#endif
// ---- pass A: aligned AND; queries ordered lexicographically by their bucket vector so
// that consecutive queries share bitset arrays (keeps the working set small). ----
if (g_lexsort) {
u32 *cntv = s_cntv;
for (int dd = k - 1; dd >= 0; dd--) {
if (dd == ds) continue;
for (u32 v = 0; v <= Q; v++) cntv[v] = 0;
for (u32 qi = q0; qi < q1; qi++) cntv[s_bdT[(size_t)dd * E6Z_BDR + s_qord[qi]] + 1]++;
{ u32 ac = q0; for (u32 v = 0; v <= Q; v++) { u32 c = cntv[v]; cntv[v] = ac; ac += c; } }
for (u32 qi = q0; qi < q1; qi++) { u32 id = s_qord[qi]; s_tmp[cntv[s_bdT[(size_t)dd * E6Z_BDR + id] + 1]++] = id; }
for (u32 qi = q0; qi < q1; qi++) s_qord[qi] = s_tmp[qi];
}
}
#ifdef PF_DIST
for (u32 qi = q0; qi < q0 + PF_DIST && qi + PF_DIST < q1; qi++) {
u32 j = s_qord[qi + PF_DIST];
const u16 *bdn = s_bd + (size_t)j * MAXK;
for (int e = 0; e < K1; e++) {
int d = (e < ds) ? e : e + 1;
const char *pp = (const char *)(s_bs + ((size_t)e * (Q + 1) + bdn[d]) * W);
_mm_prefetch(pp, _MM_HINT_T0);
_mm_prefetch(pp + 64, _MM_HINT_T0);
_mm_prefetch(pp + 128, _MM_HINT_T0);
}
}
#endif
#if MF_ASCR
{
u32 qi = q0;
while (qi < q1) {
u32 b1 = s_bdT[(size_t)d1 * E6Z_BDR + s_qord[qi]];
u32 qe = qi + 1;
while (qe < q1 && s_bdT[(size_t)d1 * E6Z_BDR + s_qord[qe]] == b1) qe++;
const u64 *bp1 = g_plen_on ? (s_bs + s_pofs[(size_t)e1 * MAXQ + b1])
: (s_bs + ((size_t)e1 * (Q + 1) + b1) * W);
u32 mx = 1;
for (u32 t = qi; t < qe; t++) { u32 rr = s_gt[ds][s_qord[t]]; if (rr > mx) mx = rr; }
u32 nwd = (mx >> 6) + 1;
if (nwd <= AS_WORDS) {
const u64 *src = bp1;
for (u32 w = 0; w < nwd; w++) s_ascr[w] = src[w];
bp1 = s_ascr;
}
for (u32 qq = qi; qq < qe; qq++) {
u32 i = s_qord[qq];
const u16 *bd = s_bd + (size_t)i * MAXK;
#if MF_PFA > 0
{
u32 qj = qq + MF_PFA;
if (qj < qe) {
u32 j = s_qord[qj];
_mm_prefetch((const char *)(s_bd + (size_t)j * MAXK), _MM_HINT_T0);
_mm_prefetch((const char *)(s_gt[ds] + j), _MM_HINT_T0);
// [PFSC-B] pass-B's non-chained random RMW target out[j] was missing from
// this block (sibling 1015b measured the same omission at -1.486%). j comes
// from the SEQUENTIAL array s_qord, so the lead-MF_PFA line needs no
// second-level dependency (BRIEF 2.19.269 chained-cost law / 2.19.323).
_mm_prefetch((const char *)(out + j), _MM_HINT_T0);
}
}
#endif
const u32 R = s_gt[ds][i];
u32 cnt = 0;
if (R && !g_skip_and) {
const u64 *bp[MAXK];
for (int e = 0; e < K1; e++) {
if (e == (int)e1) bp[e] = bp1;
else { int d = (e < ds) ? e : e + 1;
bp[e] = g_plen_on ? (s_bs + s_pofs[(size_t)e * MAXQ + bd[d]])
: (s_bs + ((size_t)e * (Q + 1) + bd[d]) * W); }
}
cnt = and_popcount_range<KK - 1>(bp, R >> 6, R & 63u);
}
out[i] = cnt;
}
qi = qe;
}
}
#else
for (u32 qi = q0; qi < q1; qi++) {
u32 i = s_qord[qi];
const u16 *bd = s_bd + (size_t)i * MAXK;
#if MF_PFA > 0
{
u32 qj = qi + MF_PFA;
if (qj < q1) {
u32 j = s_qord[qj];
_mm_prefetch((const char *)(s_bd + (size_t)j * MAXK), _MM_HINT_T0);
_mm_prefetch((const char *)(s_gt[ds] + j), _MM_HINT_T0);
// [PFSC-B] pass-B's non-chained random RMW target out[j] was missing from
// this block (sibling 1015b measured the same omission at -1.486%). j comes
// from the SEQUENTIAL array s_qord, so the lead-MF_PFA line needs no
// second-level dependency (BRIEF 2.19.269 chained-cost law / 2.19.323).
_mm_prefetch((const char *)(out + j), _MM_HINT_T0);
}
}
#endif
#ifdef PF_DIST
if (qi + PF_DIST < q1) {
u32 j = s_qord[qi + PF_DIST];
const u16 *bdn = s_bd + (size_t)j * MAXK;
for (int e = 0; e < K1; e++) {
int d = (e < ds) ? e : e + 1;
const char *pp = (const char *)(s_bs + ((size_t)e * (Q + 1) + bdn[d]) * W);
_mm_prefetch(pp, _MM_HINT_T0);
_mm_prefetch(pp + 64, _MM_HINT_T0);
_mm_prefetch(pp + 128, _MM_HINT_T0);
}
}
#endif
const u32 R = s_gt[ds][i];
u32 cnt = 0;
if (R && !g_skip_and) {
const u64 *bp[MAXK];
for (int e = 0; e < K1; e++) {
int d = (e < ds) ? e : e + 1;
bp[e] = g_plen_on ? (s_bs + s_pofs[(size_t)e * MAXQ + bd[d]])
: (s_bs + ((size_t)e * (Q + 1) + bd[d]) * W);
#ifdef DBG_PLEN
if (g_plen_on) {
u32 need = (R >> 6) + ((R & 63) ? 1 : 0);
if (need > s_plen[(size_t)e * MAXQ + bd[d]])
printf("DBG ds=%d e=%d d=%d b=%u R=%u need=%u len=%u\n", ds, e, d, bd[d], R, need, s_plen[(size_t)e * MAXQ + bd[d]]);
}
#endif
}
cnt = and_popcount_range<KK - 1>(bp, R >> 6, R & 63u);
}
out[i] = cnt;
}
#endif
if (g_skip_fringe) return;
static u32 *avpool = 0; static size_t avcap = 0;
{
size_t needav = (size_t)N * STRIDE * 4 + 256;
if (needav > avcap) { avpool = (u32 *)pool_alloc(needav); avcap = avpool ? needav : 0; }
if (avpool) for (u32 qi = q0; qi < q1; qi++) {
u32 ii = s_qord[qi];
const u16 *bdi = s_bd + (size_t)ii * MAXK;
u32 *av = avpool + (size_t)ii * ASTRL;
for (int e = 0; e < k && e < (int)ASTRL; e++) av[e] = (e == (int)ds) ? 0x7FFFFFFFu : BVAL(e, bdi[e]);
for (int e = k; e < (int)ASTRL; e++) av[e] = 0x7FFFFFFFu;
}
}
// ---- pass B: fringe, dimension-major. Ordering the group's queries by their bucket in
// dim d makes every scan walk one L1-resident bucket block of the dim-d order. ----
for (int d = 0; d < k; d++) {
if (d == ds) continue;
// [FRORD] the per-dim counting sort is GONE: s_frord[d] is already in dim-d order
// with each point's bucket packed in the high bits, so the loop below reads it
// sequentially instead of gather-sorting s_qord through random s_bdT buckets.
const u32 *fo = s_frord[d];
u32 exmask = 0;
for (int e = 0; e < d; e++) if (e != ds) exmask |= 1u << e;
// "don't care" lanes are forced to a maximum-unsigned value, so one unsigned max test
// per candidate decides domination *and* the exclusion rule together.
u32 hiA[8], hiAh[8];
for (int e = 0; e < 8; e++) hiA[e] = ((exmask >> e) & 1u) ? 0u : 0x7FFFFFFFu;
for (int e = 8; e < 16; e++) hiAh[e - 8] = ((exmask >> e) & 1u) ? 0u : 0x7FFFFFFFu;
const __m256i notexlo = LDU256(hiA);
const __m256i notexloh = LDU256(hiAh);
const __m256i all8 = _mm256_set1_epi32(-1);
// [x15b2/disp32] one base register; the per-plane offset e*MF_SPL is a
// compile-time constant and therefore a disp32 immediate in the load.
const u8 *const sbase = s_sig + (size_t)d * k * MF_SPL;
const u32 *const odd = s_ord[d];
for (u32 qi = q0; qi < q1; qi++) {
u32 i = fo[qi] & 0xFFFFFu;
#if MF_PFB > 0
{
u32 qj = qi + MF_PFB;
if (qj < q1) {
u32 j = fo[qj] & 0xFFFFFu;
_mm_prefetch((const char *)(s_pm + (size_t)j * STRIDE), _MM_HINT_T0);
_mm_prefetch((const char *)(s_gt[d] + j), _MM_HINT_T0);
_mm_prefetch((const char *)(avpool + (size_t)j * ASTRL), _MM_HINT_T0);
/* [PFB3-OUT / 2.19.339+2.19.344] pass-B's RMW target out[] was absent here: the
body does out[i] += add (lines 779/950) and the block prefetched only the
metadata rows. RMW + 1.2 MB (n=3e5) + lookahead MF_PFB steps. Same cell that
paid -24.1 ms (#106747) / -19.4 ms (#106762, green) on 1015a; the pass-A
store variant on this problem was only -4.5 ms. Pure prefetch, bit-identical. */
__asm__ __volatile__("prefetchw %0" :: "m"(*(const char *)(out + j)));
}
}
#endif
const u32 lo = FPOS(d, fo[qi] >> 20); // [K3a] sequential key, not a cold s_bd line
const u32 hi = s_gt[d][i];
if (lo >= hi) continue;
const u32 *rec = s_pm + (size_t)i * STRIDE;
const u32 *Avp = avpool + (size_t)i * ASTRL;
__m256i qr = _mm256_or_si256(_mm256_load_si256((const __m256i *)rec), khi8v);
__m256i Avm = _mm256_or_si256(LDU256(Avp), notexlo);
__m256i qT = _mm256_min_epi32(qr, Avm);
__m256i qTh;
if (k > 9) {
// k = 10: d can reach 9, so exmask bit 8 is reachable -> keep the real test.
__m256i qrh = _mm256_or_si256(LDU256((rec + 8)), khi8hv);
__m256i Avmh = _mm256_or_si256(LDU256((Avp + 8)), notexloh);
qTh = _mm256_min_epi32(qrh, Avmh);
} else if (k > 8) {
// [AV8] k = 9: expmask stays inside bits 0..7 => hiAh is all-0x7FFFFFFF
// => Avmh is identically 0x7FFFFFFF => qTh = min_epi32(qrh, 0x7FFFFFFF)
// == qrh. avpool lanes 8..15 (and this load + min) are dead code.
qTh = _mm256_or_si256(LDU256((rec + 8)), khi8hv);
} else {
qTh = all8;
}
u32 add = 0;
const u32 *c0 = s_sdmp[d] ? (s_sdmp[d] + (size_t)lo * g_KS) : 0;
if (g_use_filt && KK == 9) {
const u32 w0 = lo >> 6;
const u32 nw = ((hi - 1) >> 6) - w0 + 1;
const u64 wcs = (u64)N / g_Qc + 1;
const u32 FW = g_fbm_W;
const u64 *rows[8];
int nr = 0;
for (int e = 0; e < 9; e++) {
if (e == d) continue;
u32 cell = (u32)((u64)rec[e] / wcs) + 1;
if (cell > g_Qc) cell = g_Qc;
rows[nr++] = s_fbm + ((size_t)(d * 9 + e) * (g_Qc + 1) + cell) * FW + w0;
}
const u64 *r0 = rows[0];
const u64 *r1 = rows[1];
const u64 *r2 = rows[2];
const u64 *r3 = rows[3];
const u64 *r4 = rows[4];
const u64 *r5 = rows[5];
const u64 *r6 = rows[6];
const u64 *r7 = rows[7];
const u32 remh = hi & 63u;
{
u64 vv = r0[0] & r1[0] & r2[0] & r3[0] & r4[0] & r5[0] & r6[0] & r7[0];
vv &= ~0ull << (lo & 63);
if (nw == 1u && remh) vv &= (1ull << remh) - 1;
u32 word = w0;
#if MF_FR_NOCAND
vv = 0;
#endif
while (vv) {
int t = __builtin_ctzll(vv);
vv &= vv - 1;
u32 j = (word << 6) + (u32)t;
const u32 *cp = c0 ? (c0 + (size_t)(j - lo) * g_KS) : (s_pm + (size_t)s_ord[d][j] * STRIDE);
__m256i pv = LDU256(cp);
__m256i pvh = LDU256(cp + 8);
u32 ok = (u32)_mm256_testc_si256(_mm256_cmpgt_epi32(qT, pv), all8);
ok &= (u32)_mm256_testc_si256(_mm256_cmpgt_epi32(qTh, pvh), all8);
add += ok;
}
}
for (u32 w = 1; w + 1 < nw; w++) {
u64 vv = r0[w] & r1[w] & r2[w] & r3[w] & r4[w] & r5[w] & r6[w] & r7[w];
u32 word = w0 + w;
#if MF_FR_NOCAND
vv = 0;
#endif
while (vv) {
int t = __builtin_ctzll(vv);
vv &= vv - 1;
u32 j = (word << 6) + (u32)t;
const u32 *cp = c0 ? (c0 + (size_t)(j - lo) * g_KS) : (s_pm + (size_t)s_ord[d][j] * STRIDE);
__m256i pv = LDU256(cp);
__m256i pvh = LDU256(cp + 8);
u32 ok = (u32)_mm256_testc_si256(_mm256_cmpgt_epi32(qT, pv), all8);
ok &= (u32)_mm256_testc_si256(_mm256_cmpgt_epi32(qTh, pvh), all8);
add += ok;
}
}
if (nw > 1u) {
u32 w = nw - 1u;
u64 vv = r0[w] & r1[w] & r2[w] & r3[w] & r4[w] & r5[w] & r6[w] & r7[w];
if (remh) vv &= (1ull << remh) - 1;
u32 word = w0 + w;
#if MF_FR_NOCAND
vv = 0;
#endif
while (vv) {
int t = __builtin_ctzll(vv);
vv &= vv - 1;
u32 j = (word << 6) + (u32)t;
const u32 *cp = c0 ? (c0 + (size_t)(j - lo) * g_KS) : (s_pm + (size_t)s_ord[d][j] * STRIDE);
__m256i pv = LDU256(cp);
__m256i pvh = LDU256(cp + 8);
u32 ok = (u32)_mm256_testc_si256(_mm256_cmpgt_epi32(qT, pv), all8);
ok &= (u32)_mm256_testc_si256(_mm256_cmpgt_epi32(qTh, pvh), all8);
add += ok;
}
}
out[i] += add;
continue;
}
if (g_use_filt) {
const u32 w0 = lo >> 6;
const u32 nw = ((hi - 1) >> 6) - w0 + 1;
u64 *fb = s_fbuf;
const u64 wcs = (u64)N / g_Qc + 1;
const u32 FW = g_fbm_W;
int first = 1;
#if MF_FR_NOREAD
first = 0;
for (u32 w = 0; w < nw; w++) fb[w] = 0;
#endif
for (int e = 0; e < k; e++) {
if (e == d) continue;
u32 c = (u32)((u64)rec[e] / wcs) + 1;
if (c > g_Qc) c = g_Qc;
const u64 *row = s_fbm + ((size_t)(d * k + e) * (g_Qc + 1) + c) * FW + w0;
if (first) { for (u32 w = 0; w < nw; w++) fb[w] = row[w]; first = 0; }
else { for (u32 w = 0; w < nw; w++) fb[w] &= row[w]; }
}
fb[0] &= ~0ull << (lo & 63);
u32 r2 = hi & 63u;
if (r2) fb[nw - 1] &= (1ull << r2) - 1;
for (u32 w = 0; w < nw; w++) {
u64 vv = fb[w];
#if MF_FR_NOCAND
vv = 0;
#endif
while (vv) {
int t = __builtin_ctzll(vv);
vv &= vv - 1;
u32 j = ((w0 + w) << 6) + (u32)t;
const u32 *cp = c0 ? (c0 + (size_t)(j - lo) * g_KS) : (s_pm + (size_t)s_ord[d][j] * STRIDE);
__m256i pv = LDU256(cp);
u32 ok = (u32)_mm256_testc_si256(_mm256_cmpgt_epi32(qT, pv), all8);
if (k > 8) {
__m256i pvh = LDU256((cp + 8));
ok &= (u32)_mm256_testc_si256(_mm256_cmpgt_epi32(qTh, pvh), all8);
}
add += ok;
}
}
out[i] += add;
continue;
}
if (c0 && g_sig_on && (hi - lo) >= 2) {
// ---- coarse byte-signature scan, exact fallback only on ties ----
// (no qtv store: the broadcast signatures are built in the vector domain)
__m256i qsb[16];
// Lane d needs NO test at all: the band is [FPOS(d,bd_d), gt_d(i)) in dim-d
// order, and gt_d(i) is the START of i's tie group, so every candidate in the
// band has x_d[c] < x_d[i] = qT[d] by construction. Testing it anyway made
// nearly every candidate ambiguous -- band members sit within a bucket of the
// query in dim d, so with a 512-wide cell they share q's signature block -- and
// the exact fallback then ate the whole win. Neutralising the lane is EXACT.
{
const __m256i zero8 = _mm256_setzero_si256();
const __m256i shv = _mm256_set1_epi32((int)g_sigsh);
// [f5/setup-hoist] sv depends on the half (e<8) only, not on e itself.
/* [ONEACC] qsb is built from sig-1 so that one OR chain can carry the
three states (0 = strictly less, 1 = tie, >=2 = strictly greater). */
const __m256i bias8 = _mm256_set1_epi8((char)0x80);
const __m256i sv_lo = _mm256_srlv_epi32(qT, shv);
const __m256i sv_hi = _mm256_srlv_epi32(qTh, shv);
for (int e = 0; e < k; e++) {
if (e == (int)d) { qsb[e] = _mm256_set1_epi8((char)0xFF); continue; }
__m256i src = (e < 8) ? sv_lo : sv_hi;
__m256i bc = _mm256_permutevar8x32_epi32(src, _mm256_set1_epi32(e & 7));
qsb[e] = _mm256_xor_si256(_mm256_shuffle_epi8(bc, zero8), bias8);
}
}
// [f5/nocmp] lane d is provably true for every candidate of this band (the
// [nolane] argument), so the loop below simply skips it -- the branch is
// data-independent and perfectly predicted. The former pre-pass that copied
// the k-1 live planes and their broadcasts into two stack arrays is gone: it
// cost 2*(k-1) stores plus the reload of every ymm per (query,dim), and the
// measured cost of that form on the sibling rows is large (1015a +2.42%,
// 1015b +4.30% for ADDING it -- see #103367 / #103361).
u32 aj = lo, aacc = 0, aexact = 0;
for (; aj + 32 <= hi; aj += 32) {
/* [ONEACC] one accumulator. Byte 0 / 1 / >=2 = strictly-less / tie /
strictly-greater, and an OR cannot turn one of those into another.
decm == the old (ltm & ~amb) and amb == the old amb, bit for bit. */
__m256i ac = _mm256_set1_epi8((char)0x80);
for (int e = 0; e < k; e++) {
if (e == (int)d) continue;
__m256i cv = LDU256((sbase + (size_t)e * MF_SPL + aj));
ac = _mm256_max_epi8(ac, _mm256_subs_epi8(cv, qsb[e]));
}
u32 decm = (u32)_mm256_movemask_epi8(ac);
u32 amb = (u32)_mm256_movemask_epi8(_mm256_cmpeq_epi8(ac, _mm256_setzero_si256()));
aacc += (u32)__builtin_popcount(decm);
while (amb) {
int t = __builtin_ctz(amb); amb &= amb - 1;
const u32 *cp = c0 + (size_t)(aj + (u32)t - lo) * g_KS;
__m256i pv = LDU256(cp);
u32 okl = (u32)_mm256_testc_si256(_mm256_cmpgt_epi32(qT, pv), all8);
if (k > 8) {
__m256i pvh = LDU256((cp + 8));
okl &= (u32)_mm256_testc_si256(_mm256_cmpgt_epi32(qTh, pvh), all8);
}
aacc += okl;
}
}
// ---- tail: ONE overlapping 32-lane chunk, masked. Instrumented counts
// showed (hi-lo) mod 32 = 10.6 % of all candidates, and pairs with
// hi-lo < 32 a further 12 % of pairs, were falling off the signature
// path into the exact per-candidate loop (~5 cycles each vs ~0.6 for
// the vector path). Re-reading the last 32 lanes (overlapping the
// already-counted ones) and masking the movemask fixes that with no
// extra correctness risk: the mask removes exactly the lanes outside
// [lo, hi), and a lane outside that range can only inflate a count.
{
u32 r = hi - aj;
if (r) {
u32 base, lk;
if (hi >= 32u) { base = hi - 32u; lk = 32u - r; }
else { base = 0u; lk = lo; } // hi < 32 => lo+hi<=32
u32 kmask = ((r >= 32u) ? 0xFFFFFFFFu : ((1u << r) - 1u)) << lk;
/* [ONEACC] tail: same one-accumulator form. */
__m256i ac = _mm256_set1_epi8((char)0x80);
for (int e = 0; e < k; e++) {
if (e == (int)d) continue;
__m256i cv = LDU256((sbase + (size_t)e * MF_SPL + base));
ac = _mm256_max_epi8(ac, _mm256_subs_epi8(cv, qsb[e]));
}
u32 decm = ((u32)_mm256_movemask_epi8(ac)) & kmask;
u32 amb = ((u32)_mm256_movemask_epi8(_mm256_cmpeq_epi8(ac, _mm256_setzero_si256()))) & kmask;
aacc += (u32)__builtin_popcount(decm);
while (amb) {
int t = __builtin_ctz(amb); amb &= amb - 1;
const u32 *cp = c0 + (size_t)(base + (u32)t - lo) * g_KS;
__m256i pv = LDU256(cp);
u32 okl = (u32)_mm256_testc_si256(_mm256_cmpgt_epi32(qT, pv), all8);
if (k > 8) {
__m256i pvh = LDU256((cp + 8));
okl &= (u32)_mm256_testc_si256(_mm256_cmpgt_epi32(qTh, pvh), all8);
}
aacc += okl;
}
}
}
add = aacc;
} else if (c0) {
if (k <= 8) {
const u32 *c = c0;
u32 a0 = 0, a1 = 0, a2 = 0, a3 = 0;
u32 j = lo;
for (; j + 4 <= hi; j += 4) {
__m256i p0 = LDU256((c));
__m256i p1 = LDU256((c + g_KS));
__m256i p2 = LDU256((c + 2 * g_KS));
__m256i p3 = LDU256((c + 3 * g_KS));
a0 += (u32)_mm256_testc_si256(_mm256_cmpgt_epi32(qT, p0), all8);
a1 += (u32)_mm256_testc_si256(_mm256_cmpgt_epi32(qT, p1), all8);
a2 += (u32)_mm256_testc_si256(_mm256_cmpgt_epi32(qT, p2), all8);
a3 += (u32)_mm256_testc_si256(_mm256_cmpgt_epi32(qT, p3), all8);
c += 4 * g_KS;
}
for (; j < hi; j++, c += g_KS) {
__m256i pv = LDU256(c);
a0 += (u32)_mm256_testc_si256(_mm256_cmpgt_epi32(qT, pv), all8);
}
add = a0 + a1 + a2 + a3;
} else {
// ---- k>8 fringe: unroll 4x. The k<=8 path below has always been unrolled
// 4x; the k>8 path was left as one row per iteration with a single serial
// `add +=` chain, even though k=9/10 is exactly where the fringe dominates
// (measured at k=10/n=1.5e5: fringe 332 ms = 57 % of the run vs AND 139 ms
// = 24 %). Four independent accumulators + two loads in flight per row.
const u32 *c = c0;
u32 a0 = 0, a1 = 0, a2 = 0, a3 = 0;
u32 j = lo;
for (; j + 4 <= hi; j += 4) {
__m256i p0 = LDU256((c));
__m256i p1 = LDU256((c + g_KS));
__m256i p2 = LDU256((c + 2 * g_KS));
__m256i p3 = LDU256((c + 3 * g_KS));
__m256i h0 = LDU256((c + 8));
__m256i h1 = LDU256((c + g_KS + 8));
__m256i h2 = LDU256((c + 2 * g_KS + 8));
__m256i h3 = LDU256((c + 3 * g_KS + 8));
a0 += (u32)_mm256_testc_si256(_mm256_cmpgt_epi32(qT, p0), all8) & (u32)_mm256_testc_si256(_mm256_cmpgt_epi32(qTh, h0), all8);
a1 += (u32)_mm256_testc_si256(_mm256_cmpgt_epi32(qT, p1), all8) & (u32)_mm256_testc_si256(_mm256_cmpgt_epi32(qTh, h1), all8);
a2 += (u32)_mm256_testc_si256(_mm256_cmpgt_epi32(qT, p2), all8) & (u32)_mm256_testc_si256(_mm256_cmpgt_epi32(qTh, h2), all8);
a3 += (u32)_mm256_testc_si256(_mm256_cmpgt_epi32(qT, p3), all8) & (u32)_mm256_testc_si256(_mm256_cmpgt_epi32(qTh, h3), all8);
c += 4 * g_KS;
}
for (; j < hi; j++, c += g_KS) {
__m256i pv = LDU256(c);
__m256i pvh = LDU256((c + 8));
a0 += (u32)_mm256_testc_si256(_mm256_cmpgt_epi32(qT, pv), all8) &
(u32)_mm256_testc_si256(_mm256_cmpgt_epi32(qTh, pvh), all8);
}
add = a0 + a1 + a2 + a3;
}
} else {
// Fallback used when the transposed fringe rows are not allocated. NOTE: this
// branch used to test only lanes 0..7, so for k>8 the dims 8..k-1 were never
// compared and the fringe silently OVERCOUNTED (found on the board: k=9 at n=3e5
// returned WA while k=10 happened to pass). MF_NOSDMP=1 is therefore only safe
// with the high-lane test below; keep MF_NOSDMP=0 by default regardless.
for (u32 j = lo; j < hi; j++) {
const u32 *c = s_pm + (size_t)odd[j] * STRIDE;
__m256i pv = _mm256_load_si256((const __m256i *)c);
u32 ok = (u32)_mm256_testc_si256(_mm256_cmpgt_epi32(qT, pv), all8);
if (k > 8) {
__m256i pvh = _mm256_load_si256((const __m256i *)(c + 8));
ok &= (u32)_mm256_testc_si256(_mm256_cmpgt_epi32(qTh, pvh), all8);
}
add += ok;
}
}
out[i] += add;
}
}
}
__attribute__((target("avx2")))
static void solve_mf(u32 N, const unsigned **x, u32 *out, int k) {
// W padded to 8 words: every (dim,bucket) array then starts on a 64-byte
// boundary and every 32-byte vector load inside a stream is line-contained.
const u32 W = ((N + 63) / 64 + 7u) & ~7u;
g_W = W;
#ifdef MF_SKIP_AND
g_skip_and = 1;
#endif
#ifdef MF_FILT
g_use_filt = 1;
#endif
#ifdef MF_NOFILT
g_use_filt = 0;
#endif
#ifdef MF_Q
g_Q = MF_Q; g_B = (u32)((N + (MF_Q) - 1) / (MF_Q));
#endif
#ifdef MF_FILT
g_use_filt = 1;
#endif
#ifdef MF_NOFILT
g_use_filt = 0;
#endif
#ifdef MF_SKIP_FRINGE
g_skip_fringe = 1;
#endif
if (!g_Q) {
// bucket size ~ 0.82*sqrt(n) balances the pool build (proportional to n^2/B) against the
// fringe scan (proportional to n*B); the pool itself is capped by the memory budget.
u64 r = (u64)N, s2 = 0;
while (s2 * s2 < r) s2++; // ceil(sqrt(n)) without libm
// Reserve: the .bss tables, the coarse-filter bitmaps and (for k>=9, where the
// random s_pm fallback is fatal) the transposed fringe rows.
u64 overhead = (u64)(5 * k + 21) * N * 4;
#if !MF_NOFILTB
// STALE-SHAPE RESERVE (the KG_BASE / CT_MAXV class, on the MEMORY BUDGET rather than a
// buffer). The coarse-filter bitmaps are k*k*(Qc+1)*W*8 bytes, approximated here as
// k*k*4*N. But `#if MF_NOFILTB` sets g_use_filt = 0 UNCONDITIONALLY further down, and
// MF_FILTAUTO's own auto-enable runs AFTER this block -- so on a row shipping
// MF_NOFILTB 1 the bitmaps are never allocated on ANY path, and this reserves
// k*k*4*N = 324 MB at n=1e6/k=9 for memory that cannot be used. The cost is not the
// 324 MB: it is that Qmem = budget/perfill then caps q at 1997 instead of the 2440 this
// engine's own coefficient asks for, i.e. a 22 % coarser grid and ~18 % MORE fringe
// candidates on the phase that is 77 % of the row.
overhead += (u64)k * k * 4 * N;
#endif
// The byte-signature planes are k*k*(N+64) bytes and were NOT charged here, so at
// n=1e6/k=10 the pool was sized ~100 MB too large, the allocation failed, and the
// engine returned WRONG ANSWERS on the board (1016-SIG8, sid 98573, WA at 8.929 s)
// instead of erroring -- base8, which has no planes, was AC on the same row. Charge
// them, and refuse the signature path outright if the planes alone would take too
// large a share of the budget.
overhead += (u64)k * k * ((u64)N + 64);
overhead += (u64)N * ((k <= 8) ? 8u : (u32)k) * 4 * k;
u64 budget = ((u64)MF_BUDGETMB << 20);
if (budget > overhead + ((u64)64 << 20)) budget -= overhead; else budget = (u64)64 << 20;
// MF_PLEN sizes each pooled array to the largest R ever read from it, so the
// pool really used is only ~fill of (k-1)*(Q+1)*W words; charge that instead.
#if MF_PLEN
static const u32 kfill[7] = {72, 67, 62, 58, 54, 50, 46}; // per-array fill, k=4..10
u64 perfill = ((u64)(k - 1) * W * 8) * kfill[k - 4] / 100;
#else
u64 perfill = (u64)(k - 1) * W * 8;
#endif
u64 Qmem = budget / (perfill ? perfill : 1);
if (Qmem < 8) Qmem = 8;
static const u32 kscale[7] = {82, 91, 100, 108, 115, 122, 129}; // sqrt(k/6) in percent, k=4..10
u32 q = (u32)((s2 * MF_QCOEF + 99) / 100);
q = (u32)(((u64)q * kscale[k - 4] + 99) / 100);
if ((u64)q > Qmem) q = (u32)Qmem;
if (q > N) q = N;
// MAXQ is a SILENT CLIFF: s_maxr is [MAXK][MAXK][MAXQ] and is indexed with `b <= Q`, so
// Q > MAXQ-1 walks into s_pofs/s_plen with correct-looking output. Free insurance.
if (q > (u32)(MAXQ - 1)) q = (u32)(MAXQ - 1);
g_Q = q;
g_B = (N + q - 1) / q;
}
const u32 Q = g_Q, B = g_B;
const u64 bmag = ((1ull << 40) + B - 1) / B;
#if MF_PLEN
g_plen_on = 1; // independent of MF_WTRIM: lengths only, stride stays W
#endif
#if MF_WTRIM
u32 Wpool = W; // MF_WTRIM is KNOWN-BROKEN, see the warning above
#else
const u32 Wpool = W;
#endif
#if MF_FILTAUTO
if (!g_use_filt && B >= 400) g_use_filt = 1; // coarse grid: the filter beats the scan
#endif
if (g_use_filt && (u64)k * k * (g_Qc + 1) * W * 8 > ((u64)700 << 20)) g_use_filt = 0;
#if !MF_FILTAUTO
g_use_filt = 0;
#endif
{
int lo8[8], hi8[8];
for (int e = 0; e < 8; e++) lo8[e] = (e < k) ? 0 : 0x7FFFFFFF;
for (int e = 0; e < 8; e++) hi8[e] = (8 + e < k) ? 0 : 0x7FFFFFFF;
khi8v = LDU256(lo8);
khi8hv = LDU256(hi8);
}
const int K1 = k - 1;
// ---------- per-dimension orders, ranks, tie info ----------
for (int d = 0; d < k; d++) {
const unsigned *xd = x[d];
u32 *od = s_ord[d];
// [v4_orders] gt[i] = start[x_d[i]] and bd[] from start[x_d[i]+1]: value-indexed
// lookups, so the per-point work is two sequential sweeps of xd[] with no random
// od[]/xd[] traffic (the old loop walked od[] reading xd[od[j]] at random and then
// scattered gt[]/s_bd[] writes once per tie group).
for (u32 i = 0; i <= N; i++) s_cntv[i] = 0;
for (u32 i = 0; i < N; i++) { u32 v = xd[i]; s_cntv[v < N ? v : 0]++; }
{ u32 s = 0; for (u32 i = 0; i <= N; i++) { u32 c = s_cntv[i]; s_cntv[i] = s; s_tmp[i] = s; s += c; } }
#if MF_FB2
for (u32 i = 0; i < N; i++) {
#if MF_PFSC2 > 0
if (i + MF_PFSC2 < N) { u32 vp = xd[i + MF_PFSC2]; _mm_prefetch((const char *)(s_tmp + (vp < N ? vp : 0u)), _MM_HINT_T0); }
#endif
u32 v = xd[i]; if (v >= N) v = 0; u32 q = s_tmp[v]++; od[q] = i;
s_posp[(size_t)i * MAXK + d] = q;
}
#else
// s_pos is read ONLY by the non-FB3 coarse-filter build, which is compiled out at
// the shipped MF_FB3=1 (the FB3 path uses s_bnde, and FB2 uses s_posp).
if (g_use_filt) {
u32 *pp = s_pos[d];
for (u32 i = 0; i < N; i++) {
#if MF_PFSC2 > 0
if (i + MF_PFSC2 < N) { u32 vp = xd[i + MF_PFSC2]; _mm_prefetch((const char *)(s_tmp + (vp < N ? vp : 0u)), _MM_HINT_T0); }
#endif
u32 v = xd[i]; if (v >= N) v = 0; u32 q = s_tmp[v]++; od[q] = i; pp[i] = q;
}
} else {
for (u32 i = 0; i < N; i++) {
#if MF_PFSC2 > 0
if (i + MF_PFSC2 < N) { u32 vp = xd[i + MF_PFSC2]; _mm_prefetch((const char *)(s_tmp + (vp < N ? vp : 0u)), _MM_HINT_T0); }
#endif
u32 v = xd[i]; if (v >= N) v = 0; od[s_tmp[v]++] = i;
}
}
#endif
{ u32 *gt = s_gt[d];
for (u32 i = 0; i < N; i++) {
#if MF_PFSC > 0
// [PFSC] s_cntv[v] and s_cntv[v+1] are one line, fetched at a random offset
// once per point, on the critical path of the store below. xd[] is sequential
// here, so v is known MF_PFSC points early -- issue the line fetch ahead.
if (i + MF_PFSC < N) { u32 vp = xd[i + MF_PFSC]; _mm_prefetch((const char *)(s_cntv + (vp < N ? vp : 0u)), _MM_HINT_T0); }
#endif
u32 v = xd[i]; if (v >= N) v = 0;
gt[i] = s_cntv[v];
u32 num = s_cntv[v + 1] - 1u; if (num > N) num = N;
u32 bb = (u32)(((u64)num * bmag) >> 40);
u32 bbq = bb > Q - 1 ? Q - 1 : bb;
// [TRANS] point-major store is produced by the blocked transpose after this loop
s_bdT[(size_t)d * E6Z_BDR + i] = bbq;
} }
BVAL(d, 0) = 0; FPOS(d, 0) = 0;
for (u32 b = 1; b < Q; b++) {
u32 pos = b * B < N ? b * B : N - 1;
u32 v = xd[od[pos]];
BVAL(d, b) = v;
u32 gs = pos;
while (gs > 0 && xd[od[gs - 1]] == v) gs--;
FPOS(d, b) = gs;
}
BVAL(d, Q) = 0xFFFFFFFFu; FPOS(d, Q) = 0;
}
// [TRANS] one blocked transpose s_bdT[d][i] -> s_bd[i][d].
// Reads: 9 sequential streams over the dim-major rows. Writes: 64 points x 10 lanes
// = 1280 contiguous bytes per block, so every 64-B line of s_bd is written WHOLE
// (no read-for-ownership, no partial writeback) -- what the 9 strided passes cost.
for (u32 i0 = 0; i0 < N; i0 += 64) {
u32 i1 = i0 + 64; if (i1 > N) i1 = N;
for (u32 i = i0; i < i1; i++) {
u16 *dst = s_bd + (size_t)i * MAXK;
for (int dd = 0; dd < k; dd++) dst[dd] = s_bdT[(size_t)dd * E6Z_BDR + i];
}
}
for (u32 i = 0; i < N; i++) {
u32 *p = s_pm + (size_t)i * STRIDE;
for (int d = 0; d < k; d++) p[d] = x[d][i];
for (int d = k; d < STRIDE; d++) p[d] = 0;
}
// ---------- byte-signature planes for the fringe coarse filter ----------
// s_sig[d][e*N + j] = (u8)(x[e][ od_d[j] ] >> SIGSH) (od_d = dim-d order)
{
u32 nn = N - 1; u32 sh = 0;
while (nn > 255u) { nn >>= 1; sh++; } // smallest sh with (N-1)>>sh <= 255
g_sigsh = sh;
size_t need = (size_t)k * k * MF_SPL;
static u8 *sigpool = 0; static size_t sigcap = 0;
// Refuse the signature path when the planes alone exceed 220 MB: past that the pool
// it competes with is squeezed and the row is better served by the exact path.
if (need > ((size_t)220 << 20) || (size_t)N + 32 > MF_SPL) { sigpool = 0; sigcap = 0; }
else if (need > sigcap) { sigpool = (u8 *)pool_alloc(need); sigcap = sigpool ? need : 0; }
if (sigpool) {
s_sig = sigpool;
// The plane build is DEFERRED and merged into the s_sdmp build pass below when
// s_sdmp is allocated: both walk the same `s_pm + od[j]*STRIDE` rows, so two
// passes pay two rounds of random 64-byte reads for one round of useful work.
// ⚠ SAFETY GATE. The signature path is VERIFIED CORRECT (brute force k=4..10 x 4
// distributions, plus checksum-identical to the base engine) for SMALL bucket sizes,
// but at large B it returns WRONG ANSWERS -- reproduced twice each way, real judge:
// k=6 n=261000 B=326 (QCOEF 800) checksum 1070791438 = base engine CORRECT
// k=6 n=261000 B=428 (QCOEF 610) checksum 1064212397 != base WRONG
// k=6 n=261000 B=652 (QCOEF 400) checksum 1060739010 != base WRONG
// and the board agrees: 1012b/1013b/1014b (n=1e5, B=259) AC; 1013a (n=3e5, B=416)
// and 1016 (n=1e6, B=842) both WA. The threshold sits between B=326 and B=428.
// Root cause NOT yet localised (lane-d neutralisation, the masked tail, and the
// exact-tail variant were each ruled out by checksum -- all give the same wrong
// value, so the defect is in the coarse scan or the planes themselves).
// UNTIL IT IS FOUND, REFUSE THE SIGNATURE PATH ABOVE A MEASURED-SAFE B.
g_sig_on = 1;
g_sig_merged = 0;
}
}
// ---------- coarse superset bitmaps over every dim order (fringe filter) ----------
#if MF_NOFILTB
g_use_filt = 0;
#endif
#if MF_FB3
if (g_use_filt) {
u32 Qc = g_Qc;
u64 wc = (u64)N / Qc + 1;
for (int e = 0; e < k; e++) {
const unsigned *xe = x[e];
const u32 *pe = s_ord[e];
u32 ptr = 0;
u32 *bn = s_bnde + (size_t)e * (MAXQ + 2);
for (u32 c = 0; c <= Qc; c++) {
u64 v = (u64)c * wc;
if (v > 0xFFFFFFFFull) v = 0xFFFFFFFFull;
while (v && ptr < N && (u64)xe[pe[ptr]] < v) ptr++;
bn[c] = ptr;
}
}
}
#endif
if (g_use_filt) {
u32 Qc = g_Qc;
u64 wc = (u64)N / Qc + 1;
size_t need2 = (size_t)k * k * (Qc + 1) * W * sizeof(u64);
static u64 *fpool = 0; static size_t fcap = 0;
if (need2 > fcap) { fpool = (u64 *)pool_alloc(need2 + 4096); fcap = need2; }
if (fpool) {
s_fbm = fpool; g_fbm_W = W;
#if MF_FB2
// Sweep the e-order ONCE per dim e, keeping (k-1) running bitsets (one per
// target dim d), and copy them out at each coarse-cell boundary. The old
// shape re-walked all N points for every one of the k(k-1) (d,e) pairs with
// two L3-random reads each (xe[pe[ptr]] and s_pos[d][pe[ptr]]); here the
// rank record is point-major so one 64B line yields every dim's rank, which
// cuts the random reads by ~k and the whole build by ~2-2.5x at k>=8.
for (int d = 0; d < k; d++)
for (int e = 0; e < k; e++) {
if (e == d) continue;
u64 *b0 = s_fbm + (size_t)(d * k + e) * (Qc + 1) * W;
for (u32 w = 0; w < W; w++) b0[w] = 0;
}
for (int e = 0; e < k; e++) {
const unsigned *xe = x[e];
const u32 *pe = s_ord[e];
int dmap[MAXK], km1 = 0;
for (int d = 0; d < k; d++) if (d != e) dmap[km1++] = d;
for (int i = 0; i < km1; i++) { u64 *a = s_facc + (size_t)i * W; for (u32 w = 0; w < W; w++) a[w] = 0; }
u32 ptr = 0;
for (u32 c = 1; c <= Qc; c++) {
u64 v = (u64)c * wc;
if (v > 0xFFFFFFFFull) v = 0xFFFFFFFFull;
while (ptr < N && (u64)xe[pe[ptr]] < v) {
const u32 *pv = s_posp + (size_t)pe[ptr] * MAXK;
for (int i = 0; i < km1; i++) {
u32 q = pv[dmap[i]];
s_facc[(size_t)i * W + (q >> 6)] |= 1ull << (q & 63);
}
ptr++;
}
for (int i = 0; i < km1; i++) {
int d = dmap[i];
u64 *dst = s_fbm + ((size_t)(d * k + e) * (Qc + 1) + c) * W;
const u64 *src = s_facc + (size_t)i * W;
#if MF_NT
{ u32 w = 0;
for (; w + 4 <= W; w += 4) _mm256_stream_si256((__m256i *)(dst + w), _mm256_load_si256((const __m256i *)(src + w)));
for (; w < W; w++) dst[w] = src[w]; }
#else
for (u32 w = 0; w < W; w++) dst[w] = src[w];
#endif
}
}
}
#else
for (int d = 0; d < k; d++) {
for (int e = 0; e < k; e++) {
if (e == d) continue;
const unsigned *xe = x[e];
const u32 *pe = s_ord[e];
const u32 *pd = s_pos[d];
u64 *base = s_fbm + (size_t)(d * k + e) * (Qc + 1) * W;
for (u32 w = 0; w < W; w++) base[w] = 0;
u64 *cur = s_acc;
for (u32 w = 0; w < W; w++) cur[w] = 0;
u32 ptr = 0;
for (u32 c = 1; c <= Qc; c++) {
#if MF_FB3
u32 pend = s_bnde[(size_t)e * (MAXQ + 2) + c];
#else
u64 v = (u64)c * wc;
if (v > 0xFFFFFFFFull) v = 0xFFFFFFFFull;
u32 pend = ptr;
{ const unsigned *xe_ = xe; const u32 *pe_ = pe;
while (pend < N && (u64)xe_[pe_[pend]] < v) pend++; }
#endif
while (ptr < pend) { u32 q = pd[pe[ptr]]; cur[q >> 6] |= 1ull << (q & 63); ptr++; }
u64 *dst = base + (size_t)c * W;
#if MF_NT
{ u32 w = 0;
for (; w + 4 <= W; w += 4) _mm256_stream_si256((__m256i *)(dst + w), _mm256_load_si256((const __m256i *)(cur + w)));
for (; w < W; w++) dst[w] = cur[w]; }
#else
for (u32 w = 0; w < W; w++) dst[w] = cur[w];
#endif
}
}
}
#endif
} else { g_use_filt = 0; }
_mm_sfence();
}
// ---------- fold dimension + bucket vector per query ----------
{
size_t mrn = (size_t)MAXK * MAXK * MAXQ;
for (size_t z = 0; z < mrn; z++) s_maxr[z] = 0;
}
for (u32 i = 0; i < N; i++) {
u32 best = 0xFFFFFFFFu; int ds = 0;
for (int d = 0; d < k; d++) {
u32 g = s_gt[d][i];
if (g < best) { best = g; ds = d; }
}
s_g[i] = (u32)ds;
// Largest R that will ever be read out of each (fold dim, dim, bucket) array:
// the pooled array (ds,d,b) is only ever scanned over [0, maxR] words, so the
// per-array length can be far below the group maximum (0.38n vs 0.93n at k=10).
if (g_plen_on) {
const u16 *bdv = s_bd + (size_t)i * MAXK;
u32 *mr = s_maxr + ((size_t)ds * MAXK) * MAXQ;
u32 bb = best;
for (int d = 0; d < k; d++) {
if (d == ds) continue;
u32 b = bdv[d];
if (bb > mr[(size_t)d * MAXQ + b]) mr[(size_t)d * MAXQ + b] = bb;
}
}
}
for (u32 i = 0; i < N; i++) s_qord[i] = i;
// group by ds
{
for (u32 v = 0; v <= (u32)k; v++) s_cntv[v] = 0;
for (u32 i = 0; i < N; i++) s_cntv[s_g[i] + 1]++;
{ u32 ac = 0; for (u32 v = 0; v <= (u32)k; v++) { u32 c = s_cntv[v]; s_cntv[v] = ac; ac += c; } }
for (u32 i = 0; i < N; i++) { u32 id = s_qord[i]; s_tmp[s_cntv[s_g[id] + 1]++] = id; }
for (u32 i = 0; i < N; i++) s_qord[i] = s_tmp[i];
}
u32 gstart[MAXK + 2];
{ u32 c = 0; for (int d = 0; d <= k; d++) { gstart[d] = c; while (c < N && (int)s_g[s_qord[c]] == d) c++; } gstart[k + 1] = c; }
// [FRORD] Reuse each global coordinate order, partitioning once by fold group.
// The preexisting s_bdT holds each point's exact bucket in a compact column.
for (int d = 0; d < k; d++) {
u32 pos[MAXK];
for (int ds = 0; ds < k; ds++) pos[ds] = gstart[ds];
for (u32 r = 0; r < N; r++) {
u32 id = s_ord[d][r];
u32 dst = pos[s_g[id]]++;
s_frord[d][dst] = id | ((u32)s_bdT[(size_t)d * E6Z_BDR + id] << 20);
}
}
// ---------- transposed coordinate rows (sequential fringe) ----------
#if MF_NOSDMP
g_nosdmp = 1;
#endif
if (!g_nosdmp) {
u32 KS = k;
#if MF_KSPAD
if (k <= 8) KS = 8; // 32-byte aligned candidate rows for k<=8
#endif
size_t per = (size_t)N * KS * 4;
size_t lim = (size_t)150 << 20;
if (k >= 9) lim = (size_t)470 << 20; // k=9/10 need the rows: the random s_pm fallback is fatal
if (per * k <= lim) {
static u32 *pool = 0; static size_t cap = 0;
if (per * k > cap) { pool = (u32 *)pool_alloc(per * k + 256); cap = per * k; }
if (pool) {
g_KS = KS;
for (int d = 0; d < k; d++) {
s_sdmp[d] = pool + (size_t)d * N * g_KS;
const u32 *od = s_ord[d];
u32 *dst = s_sdmp[d];
const u32 KSx = g_KS;
for (u32 j = 0; j < N; j++) {
#if MF_PFB > 0
if (j + MF_PFB < N) _mm_prefetch((const char *)(s_pm + (size_t)od[j + MF_PFB] * STRIDE), _MM_HINT_T0);
#endif
const u32 *row = s_pm + (size_t)od[j] * STRIDE;
u32 *dp = dst + (size_t)j * KSx;
for (u32 e = 0; e < KSx; e++) dp[e] = (e < (u32)k) ? row[e] : 0u;
if (s_sig) {
u8 *sb = s_sig + (size_t)d * k * MF_SPL;
for (int e = 0; e < k; e++) { u32 z_ = row[e] >> g_sigsh; sb[(size_t)e * MF_SPL + j] = (u8)(z_ ^ 0x80u); }
}
}
if (s_sig) g_sig_merged = 1;
}
}
}
}
// ---------- signature planes, standalone only if the s_sdmp pass did not run ----
if (s_sig && !g_sig_merged) {
for (int d = 0; d < k; d++) {
const u32 *od = s_ord[d];
u8 *base = s_sig + (size_t)d * k * MF_SPL;
for (u32 j = 0; j < N; j++) {
#if MF_PFB > 0
if (j + MF_PFB < N) _mm_prefetch((const char *)(s_pm + (size_t)od[j + MF_PFB] * STRIDE), _MM_HINT_T0);
#endif
const u32 *row = s_pm + (size_t)od[j] * STRIDE;
for (int e = 0; e < k; e++) { u32 z_ = row[e] >> g_sigsh; base[(size_t)e * MF_SPL + j] = (u8)(z_ ^ 0x80u); }
}
}
}
// ---------- pool ----------
u64 poolwords = (u64)K1 * (Q + 1) * W;
#if MF_PLEN
if (g_plen_on) {
u64 best = 0;
for (int dsx = 0; dsx < k; dsx++) {
u64 tot = 0;
for (int e = 0; e < K1; e++) {
int d = (e < dsx) ? e : e + 1;
const u32 *mr = s_maxr + ((size_t)dsx * MAXK + d) * MAXQ;
for (u32 b = 0; b <= Q; b++) {
u32 RR = mr[b];
u32 len = ((RR >> 6) + 8u) & ~7u;
if (len > W) len = W;
tot += len;
}
}
if (tot > best) best = tot;
}
if (best && best < poolwords) poolwords = best;
}
#endif
size_t need = (size_t)poolwords * sizeof(u64);
static u64 *pool = 0; static size_t cap2 = 0;
if (need > cap2) { pool = (u64 *)pool_alloc(need + 4096); cap2 = need; }
s_bs = pool;
// ---------- groups ----------
for (int ds = 0; ds < k; ds++) {
u32 q0 = gstart[ds], q1 = gstart[ds + 1];
if (q0 >= q1) continue;
#if MF_WTRIM
{
u32 mx = 1;
for (u32 qi = q0; qi < q1; qi++) { u32 rr = s_gt[ds][s_qord[qi]]; if (rr > mx) mx = rr; }
u32 wp = ((mx >> 6) + 8u) & ~7u;
Wpool = (wp < W) ? wp : W;
}
#endif
#if MF_PLEN
// Per-(dim,bucket) array lengths inside the group.
if (g_plen_on) {
u64 tot = 0;
for (int e = 0; e < K1; e++) {
int d = (e < ds) ? e : e + 1;
const u32 *mr = s_maxr + ((size_t)ds * MAXK + d) * MAXQ;
u32 *po = s_pofs + (size_t)e * MAXQ;
u32 *pl = s_plen + (size_t)e * MAXQ;
for (u32 b = 0; b <= Q; b++) {
// NOTE: bucket 0 is the EMPTY prefix set (all-zero array), but a query whose
// bd[d]==0 still scans [0,R) words of it, so the array must be long enough --
// sizing it to the fixed 8-word minimum reads past the zeroed region into the
// next array. Use the real per-bucket max R here, not 0.
u32 RR = mr[b];
u32 len = ((RR >> 6) + 8u) & ~7u;
if (len > Wpool) len = Wpool;
po[b] = (u32)tot; pl[b] = len; tot += len;
}
}
if (tot > (u64)K1 * (Q + 1) * W) { g_plen_on = 0; } // never; safety
}
#endif
// bit numbering = rank in dim ds
{
const u32 *od = s_ord[ds];
for (u32 j = 0; j < N; j++) s_nid[od[j]] = j;
}
for (int e = 0; e < K1; e++) {
int d = (e < ds) ? e : e + 1;
const unsigned *xd = x[d];
const u32 *od = s_ord[d];
u64 *base = g_plen_on ? s_bs : (s_bs + (size_t)e * (Q + 1) * Wpool);
u64 *cur = s_acc;
for (u32 w = 0; w < Wpool; w++) cur[w] = 0;
if (g_plen_on) {
// bucket 0 is the empty prefix set; bd[] can be 0 so it must be materialised as zeros
u64 *d0 = s_bs + s_pofs[(size_t)e * MAXQ];
u32 l0 = s_plen[(size_t)e * MAXQ];
for (u32 w = 0; w < l0; w++) d0[w] = 0;
}
u32 ptr = 0;
#if MF_BUILD_NOPOOL
while (ptr < N) ptr++;
#else
for (u32 b = 1; b <= Q; b++) {
// {q : x_d[q] < BVAL(d,b)} is EXACTLY the prefix [0,FPOS(d,b)) of the dim-d order
// (FPOS is the tie-group start containing rank b*B), so the per-point value compare
// -- one random xd read per point per (ds,d) pair, k(k-1)N of them -- is redundant.
// b==Q means "every point" and FPOS(d,Q) is the 0 sentinel, hence N.
// Credited to agent a7f799ceb7e63b0cd (worth 4-5% at k=4/5 on the board).
u32 pend = (b < Q) ? FPOS(d, b) : (u32)N;
while (ptr < pend) { u32 q = s_nid[od[ptr]]; cur[q >> 6] |= 1ull << (q & 63); ptr++; }
u64 *dst = g_plen_on ? (base + s_pofs[(size_t)e * MAXQ + b])
: (base + (size_t)b * Wpool);
u32 wlen = g_plen_on ? s_plen[(size_t)e * MAXQ + b] : Wpool;
#if MF_NT
// The pool is written once and read 89 GB later, so a plain copy pays a
// read-for-ownership DRAM read per line for nothing. NT stores remove it
// (measured: this loop was 4.58 GB / ~1.7 s, i.e. 2.7 GB/s, vs 12.5 GB/s
// for a plain memset).
{
u32 w = 0;
for (; w + 4 <= wlen; w += 4)
_mm256_stream_si256((__m256i *)(dst + w), _mm256_load_si256((const __m256i *)(cur + w)));
for (; w < wlen; w++) dst[w] = cur[w];
}
#else
for (u32 w = 0; w < wlen; w++) dst[w] = cur[w];
#endif
}
_mm_sfence();
#endif
}
switch (k) {
case 4: query_group<4>(N, out, ds, q0, q1, Q, Wpool); break;
case 5: query_group<5>(N, out, ds, q0, q1, Q, Wpool); break;
case 6: query_group<6>(N, out, ds, q0, q1, Q, Wpool); break;
case 7: query_group<7>(N, out, ds, q0, q1, Q, Wpool); break;
case 8: query_group<8>(N, out, ds, q0, q1, Q, Wpool); break;
case 9: query_group<9>(N, out, ds, q0, q1, Q, Wpool); break;
default: query_group<10>(N, out, ds, q0, q1, Q, Wpool); break;
}
}
}
void count_4d(int n, const unsigned *x[4], unsigned *out) { solve_mf((u32)n, (const unsigned **)x, out, 4); }
void count_5d(int n, const unsigned *x[5], unsigned *out) { solve_mf((u32)n, (const unsigned **)x, out, 5); }
void count_6d(int n, const unsigned *x[6], unsigned *out) { solve_mf((u32)n, (const unsigned **)x, out, 6); }
void count_7d(int n, const unsigned *x[7], unsigned *out) { solve_mf((u32)n, (const unsigned **)x, out, 7); }
void count_8d(int n, const unsigned *x[8], unsigned *out) { solve_mf((u32)n, (const unsigned **)x, out, 8); }
void count_9d(int n, const unsigned *x[9], unsigned *out) { solve_mf((u32)n, (const unsigned **)x, out, 9); }
void count_10d(int n, const unsigned *x[10], unsigned *out) { solve_mf((u32)n, (const unsigned **)x, out, 10); }
#ifdef LOCAL_TEST
static void brute_nd(int n, const u32 **x, u32 *out, int k) {
for (int i = 0; i < n; i++) {
u32 c = 0;
for (int j = 0; j < n; j++) {
int ok = 1;
for (int d = 0; d < k; d++) if (!(x[d][j] < x[d][i])) { ok = 0; break; }
c += ok;
}
out[i] = c;
}
}
int main(int argc, char **argv) {
int k = (argc > 1) ? atoi(argv[1]) : 6;
int n = (argc > 2) ? atoi(argv[2]) : 200;
int seed = (argc > 3) ? atoi(argv[3]) : 1;
int mode = (argc > 4) ? atoi(argv[4]) : 0;
int qq = (argc > 5 && atoi(argv[5]) > 0) ? atoi(argv[5]) : 0;
g_Q = qq ? (u32)qq : (u32)(n / 250 + 1);
g_B = (u32)((n + g_Q - 1) / g_Q);
if (argc > 6) g_nosort = atoi(argv[6]);
if (argc > 7) g_nosdmp = atoi(argv[7]);
if (argc > 8) g_skip_and = atoi(argv[8]);
if (argc > 9) g_skip_fringe = atoi(argv[9]);
if (argc > 10) g_use_filt = atoi(argv[10]);
if (argc > 11) g_lexsort = atoi(argv[11]);
const int dm = (mode >= 10) ? mode - 10 : mode;
srand(seed);
static u32 *xs[10];
u32 *out = (u32 *)malloc(sizeof(u32) * n);
u32 *ref = (u32 *)malloc(sizeof(u32) * n);
for (int d = 0; d < k; d++) {
xs[d] = (u32 *)malloc(sizeof(u32) * n);
for (int i = 0; i < n; i++) {
if (dm == 0) xs[d][i] = (u32)(rand() % n);
else if (dm == 1) xs[d][i] = (u32)(rand() % 3);
else if (dm == 2) xs[d][i] = 0;
else if (dm == 3) xs[d][i] = (u32)(n - 1 - (rand() % n));
else if (dm == 4) xs[d][i] = (u32)(i);
else xs[d][i] = (u32)(rand() % n);
}
}
if (mode >= 10) {
struct timespec t0, t1;
clock_gettime(CLOCK_MONOTONIC, &t0);
switch (k) {
case 4: count_4d(n, (const unsigned **)xs, out); break;
case 5: count_5d(n, (const unsigned **)xs, out); break;
case 6: count_6d(n, (const unsigned **)xs, out); break;
case 7: count_7d(n, (const unsigned **)xs, out); break;
case 8: count_8d(n, (const unsigned **)xs, out); break;
case 9: count_9d(n, (const unsigned **)xs, out); break;
default: count_10d(n, (const unsigned **)xs, out); break;
}
clock_gettime(CLOCK_MONOTONIC, &t1);
double ms = (t1.tv_sec - t0.tv_sec) * 1e3 + (t1.tv_nsec - t0.tv_nsec) / 1e6;
u64 s = 0; for (int i = 0; i < n; i++) s += out[i];
printf("k=%d n=%d Q=%u TIME %.1f ms checksum %llu\n", k, n, g_Q, ms, s);
return 0;
}
switch (k) {
case 4: count_4d(n, (const unsigned **)xs, out); break;
case 5: count_5d(n, (const unsigned **)xs, out); break;
case 6: count_6d(n, (const unsigned **)xs, out); break;
case 7: count_7d(n, (const unsigned **)xs, out); break;
case 8: count_8d(n, (const unsigned **)xs, out); break;
case 9: count_9d(n, (const unsigned **)xs, out); break;
default: count_10d(n, (const unsigned **)xs, out); break;
}
brute_nd(n, (const u32 **)xs, ref, k);
int bad = 0;
for (int i = 0; i < n; i++) if (out[i] != ref[i]) { if (bad < 5) printf("MISMATCH i=%d got=%u ref=%u\n", i, out[i], ref[i]); bad++; }
printf("k=%d n=%d mode=%d Q=%u %s (%d mismatches)\n", k, n, mode, g_Q, bad ? "FAIL" : "OK", bad);
return bad != 0;
}
#endif
| Compilation | N/A | N/A | Compile OK | Score: N/A | 显示更多 |
| Testcase #1 | 6.018 s | 1503 MB + 948 KB | Accepted | Score: 100 | 显示更多 |