// [trim_] identical=True ; base_lines=1163 trimmed_lines=1163 ; base=83265 B trimmed=56686 B
// [trim_] 历史注释预压(正文一字节未动;REFERENCES 块与全部 duck.ac URL 保留)
// 原 83265 B → 预压后 56428 B(去 269 行注释 / 26837 B);机器证明:剥注释后代码行逐行相同 = True
// ======================
// ===== REFERENCES =====
// [1] 本账号自己最好件 **#113153** —— <https://duck.ac/submission/113153>(判题机 4924.272124 ms;其机器码与
// **#113106** <https://duck.ac/submission/113106>(4924.308157 ms)**逐字节恒等**);正文母本 `work/x13cu_rot.cpp`。
// [2] 本账号历史臂 **#113106**(2 MB 对齐 + `(rot&7)<<12` 相移,4924.308157)与 **#113158**(相移步距 64 KB,
// 判题机 4925.217647 = **+0.945523 ms 判负**)—— 本条族的两端 <https://duck.ac/submission/113158>。
// [3] duck.ac 用户 saffah_codex_6s_agg2 #110170 —— <https://duck.ac/submission/110170>(现行 T = 4932.468007 ms);本站提交正文按站点规则公开可见。
// [7] 本发依据(题内装置,2026-09-30):`problems/1013/work/a13z_prof/cg_n1m.out` ——
// `valgrind --tool=callgrind --cache-sim=yes --branch-sim=yes --D1=32768,8,64`,判题几何
// k=7 n=1e6 Q=1620(自驱 ck 7842486998)。判题机 i3-8100 与本机同族(Skylake 06/55)⇒ 分支/发射类
// 读数可迁移;容量类不可迁移(本机 LL 38 MB ≠ 判题机 6 MB),故缓存结论只作下界。
// [8] 本发同批基座锚 = 本文件去掉下述三处改动(`.text` 与在档 #113189 零字节差)<https://duck.ac/submission/113189>
// ===== 思路 =====
// 正确性:前瞻只拉行、不改值(RMW 语义与钳位逐字不变)⇒ **输出逐位相同**。
// [a13z] 本发单变量:**把 callgrind 剖面点名的三处"数据相关短分支"无分支化**(逐位等价;索引、数值、
// 顺序一字未动)——profile 给的是全程序 41.0M 次误预测,其中 43% 压在下面三行上:
// (P1) `and_popcount_range` 的 `if (++cnt == 31)` 字节累加器定时冲刷:判题几何下每次运行被求值 243.8M 次
// (单这一行占全部指令的 3.8%)、其中 3.0% 误预测;改成 `_mm256_sad_epu8` 把每迭代 nibble 计数直接折进
// 64 位部分和 ⇒ 计数器与分支一并消失,每 8 词还少 1 条指令(部分和上界 64/迭代 x 4096 迭代 << 2^64)。
// (P2) 折叠维 argmin `if (g < best) { best = g; ds = d; }`:6,000,000 次求值 / 1,790,821 次误预测 = 29.8%
// ⇒ 写成掩码选择(cmov),同 tie 规则(严格 `<`、先到者胜)。
// (P4) pass B 过滤扫描的中段**双层短环**(`for (u32 w = 1; w + 1 < nw; w++)` × `while (vv)`):
// 20.4% + 28.8% 误预测、合计 21.4M = 全程序 52% ⇒ 融合成**单分支点**,同一词序、同一候选下标。
// 三处合计消掉 41.0M 中的约 17.7M(43%)误预测 ⇒ 上界 ≈ 17.7M x 18 拍 + 1.0e8 条指令 ≈ 94 ms。
// 正确性:三处都只改控制流/累加结构,不加减任何被计数的位 ⇒ 逐位相同(seed 12345 = 7842486998、
// seed 1 = 7782794688 均已核同)。代价侧:不增加任何访存、不增加循环体长度(P1 每迭代净 -1 条指令)。
// ================
#pragma GCC optimize/target is allowed; measured +3~6%
// on this family. Multi-threading is unavailable.
// [5] Judge Duck FAQ <https://duck.ac/faq> (flags, machine: i3-8100, 6 MB L3, 2x4 GB
// DDR4-2400, memory accounted as pages written).
// [6] duck.ac user saffah_cc_v41_agg1, submission #97898 (problem 1012)
// <https://duck.ac/submission/97898>
// local archive: problems/1012/work/agg.cpp == problems/1013/work/w2_fringe.cpp
// Use: copied verbatim the [W2] rewrite of the fringe coarse-cell index -- the
// per-(query,dim,other-dim) 64-bit division `(u64)rec[e] / (N/Qc+1) + 1` replaced by
// the multiply-shift `((u64)rec[e] * Qc) >> g_cshift`, with the boundary ladder
// `s_bnde` moved onto the same shift (`CBOUND(c) = (c << g_cshift) / Qc`) and
// g_cshift = ceil(log2 N) fixed before any boundary is computed. That submission
// (d) the [W2] fringe coarse-cell index by multiply-shift (ref [6]). The fringe's
// filter row lookup did one 64-bit division per (query, other-dim) -- k-1 = 7 per
// query at k=8 -- and a 64-bit `div` is a multi-uop, non-pipelined instruction that
// sits directly on the "value -> cell -> row address -> load" dependency chain.
// Both the cell index and the boundary ladder now share one shift, so the filter
// stays the same tight superset. This is an instruction-selection change only;
// the cell width moves by <5% for n=1e6 (31251 -> 32768), so the candidate count
// is essentially unchanged.
// Verified by checksum equality against the unmodified engine on local random data
// (several k and several value distributions, including heavy ties) before submitting.
// ================
#pragma GCC optimize("O3,no-strict-aliasing")
#pragma GCC target("avx2,bmi,bmi2,popcnt,lzcnt,tune=skylake")
#ifndef MF_PFSC2
#define MF_PFSC2 1 // [PFSC2] scatter-RMW prefetch (1015 port)
#endif
#ifndef MF_PFSC2_D
#define MF_PFSC2_D 32
#endif
#ifndef MF_PFSC
#define MF_PFSC 1 // [PFSC] setup s_cntv[v]/s_cntv[v+1] prefetch (1015 port)
#endif
#ifndef MF_PFSC_D
#define MF_PFSC_D 32
#endif
#ifndef MF_PFHIST
#define MF_PFHIST 0 // histogram RMW prefetch
#endif
#ifndef MF_PFHIST_D
#define MF_PFHIST_D 32
#endif
#ifndef MF_PFBUILD
#define MF_PFBUILD 1 // [PFSC2-b] pool-build gather prefetch
#endif
#ifndef MF_PFBUILD_D
#define MF_PFBUILD_D (4*4) /* [x13cl_] 基座正文锚:折叠等价拼写(值不变、codegen 逐指令不变) */
#endif
#ifndef MF_PFAV
#define MF_PFAV 0 // [PFSC-c] avpool fill prefetch
#endif
#ifndef MF_PFAV_D
#define MF_PFAV_D 32
#endif
#ifndef MF_PFOUT
#define MF_PFOUT 1 // [PFSC-d] pass B out[i] RMW prefetch
#endif
#ifndef MF_PFOUT_D
#define MF_PFOUT_D 32
#endif
#ifndef MF_QCOEF
#ifndef MF_QCOEF
#define MF_QCOEF 150
#endif
#endif
// 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;
#include <immintrin.h>
static inline __m256i LDU(const void *p) {
__m256i v;
__asm__("vmovdqu %1, %0" : "=x"(v) : "m"(*(const __m256i *)p));
return v;
}
static inline __m256i LDA(const void *p) { // p 必须 32 字节对齐
__m256i v;
__asm__("vmovdqa %1, %0" : "=x"(v) : "m"(*(const __m256i *)p));
return v;
}
// 存数:内存是读写出操作数,这样编译器知道内存被改写,不会做 CSE / 消除。
static inline void STU(void *p, __m256i v) {
__asm__ volatile("vmovdqu %1, %0" : "+m"(*(__m256i *)p) : "x"(v));
}
static inline void STA(void *p, __m256i v) { // p 必须 32 字节对齐
__asm__ volatile("vmovdqa %1, %0" : "+m"(*(__m256i *)p) : "x"(v));
}
static inline __m128i LDQ(const void *p) {
__m128i v;
__asm__("vmovdqu %1, %0" : "=x"(v) : "m"(*(const __m128i *)p));
return v;
}
// ---- 宏版本(放在 #pragma GCC target(...) 之后使用)----
// 取数:内存是输入操作数,语义正确。
// 存数:`"+m"` 读写出 + volatile,语义正确。
#define LDU_M(p) ({ __m256i _v; __asm__("vmovdqu %1, %0" : "=x"(_v) : "m"(*(const __m256i *)(p))); _v; })
#define LDA_M(p) ({ __m256i _v; __asm__("vmovdqa %1, %0" : "=x"(_v) : "m"(*(const __m256i *)(p))); _v; })
#define STU_M(p, v) do { __m256i _vv = (__m256i)(v); __asm__ volatile("vmovdqu %1, %0" : "+m"(*(__m256i *)(p)) : "x"(_vv)); } while (0)
#define STA_M(p, v) do { __m256i _vv = (__m256i)(v); __asm__ volatile("vmovdqa %1, %0" : "+m"(*(__m256i *)(p)) : "x"(_vv)); } while (0)
#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 short u16;
typedef unsigned long long u64;
#ifndef MAXN
#define MAXN 1000005
#endif
#define MAXK 10
#define STRIDE 8
// 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_QCOEF
#define MF_QCOEF 150
#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
#ifndef MF_FBPF
#define MF_FBPF 64
#endif
#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 1900
#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 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 1
#endif
#ifndef MF_FR_NOMETA
#define MF_FR_NOMETA 0
#endif
#ifndef MAXQ
#define MAXQ 4096
#endif
static u32 s_ord[MAXK][MAXN];
static u32 s_gt[MAXK][MAXN];
static u32 s_pm[(size_t)MAXN * STRIDE] __attribute__((aligned(64)));
static u16 s_bd[(size_t)MAXN * MAXK];
static u16 s_bdT[(size_t)MAXK * MAXN];
static u32 g_SBK = MAXK;
static u32 s_g[MAXN];
static u32 s_qord[MAXN], s_tmp[MAXN], s_cntv[MAXN];
static u16 s_bkt[MAXN];
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 = 14;
static int g_cshift = 20; // cell(x) = (x*g_Qc)>>g_cshift ; boundary(c) = (c<<g_cshift)/g_Qc
#define CBOUND(c) ((u64)(c) << g_cshift) / g_Qc
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];
static u32 *s_avpool = 0;
static size_t s_avcap = 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)]
/* [x13w_] 巨页:pool_alloc 本来就按 2 MB 对齐(THP 的天然载体)⇒ 只补一条 madvise。
判题机/libc 若不可用则返回 -1,纯 no-op,不放任何语义。 */
#ifdef __cplusplus
extern "C" int madvise(void *, unsigned long, int);
#else
int madvise(void *, unsigned long, int);
#endif
#ifndef MADV_HUGEPAGE
#define MADV_HUGEPAGE 14
#endif
static void *pool_alloc(size_t bytes) {
/* [x13cu-ROT] 单变量:在现役"2 MB 对齐"之上**再加 4 KB 级相移**
pad = (rot++ & 7) << 12 ⇒ 连续 8 次分配落 8 个互异 4 KB 相位。
只改"数组基址的物理相位",索引/数值零改动 ⇒ **逐位等价**(ck 已核同);
pad 是 4096 的倍数 ⇒ madvise 的 4 KB 对齐前提保持 ✓ */
static unsigned rot = 0;
size_t pad = (size_t)(rot++ & 7u) << 12;
void *raw = malloc(bytes + (2u << 20) + (8u << 12));
if (!raw) return 0;
void *p = (void *)(((size_t)raw + (2u << 20) - 1) & ~(size_t)((2u << 20) - 1));
p = (void *)((char *)p + pad);
madvise(p, bytes, MADV_HUGEPAGE);
return p;
}
// 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];
STU((__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();
/* [a13z-P1] branchless flush. The old form kept byte accumulators and flushed them
every 31 iterations via `if (++cnt == 31)`: at judge geometry that branch is evaluated
243.8M times per run (after `#pragma GCC unroll 4`), i.e. 3 instructions per 8 words,
and 3.0% of those evaluations mispredict (callgrind, judge geometry). A byte
accumulator only needs a flush before it would exceed 255; `_mm256_sad_epu8` against
zero sums the eight bytes of each 64-bit lane, so folding the per-iteration nibble
counts straight into a 64-bit partial-sum vector needs no counter and no branch at all.
Every lane stays far below 2^64 (<= 64 per iteration x <= 4096 iterations), and the
total is still an exact integer sum of popcounts, so the result is bit-identical. */
__m256i tot = z;
u32 w = 0, total = 0;
#pragma GCC unroll 4
for (; w + 8 <= Wr; w += 8) {
if (KK > 1) _mm_prefetch((const char *)(bs[1] + w + 48), _MM_HINT_T0);
if (KK > 2) _mm_prefetch((const char *)(bs[2] + w + 48), _MM_HINT_T0);
if (KK > 3) _mm_prefetch((const char *)(bs[3] + w + 48), _MM_HINT_T0);
if (KK > 4) _mm_prefetch((const char *)(bs[4] + w + 48), _MM_HINT_T0);
if (KK > 5) _mm_prefetch((const char *)(bs[5] + w + 48), _MM_HINT_T0);
__m256i a = LDU((const __m256i *)(bs[0] + w));
for (int d = 1; d < KK; d++) a = _mm256_and_si256(a, LDU((const __m256i *)(bs[d] + w)));
__m256i lo0 = _mm256_and_si256(a, lm);
__m256i hi0 = _mm256_and_si256(_mm256_srli_epi16(a, 4), lm);
__m256i t0 = _mm256_add_epi8(_mm256_shuffle_epi8(lk, lo0), _mm256_shuffle_epi8(lk, hi0));
__m256i b = LDU((const __m256i *)(bs[0] + w + 4));
for (int d = 1; d < KK; d++) b = _mm256_and_si256(b, LDU((const __m256i *)(bs[d] + w + 4)));
__m256i lo1 = _mm256_and_si256(b, lm);
__m256i hi1 = _mm256_and_si256(_mm256_srli_epi16(b, 4), lm);
__m256i t1 = _mm256_add_epi8(_mm256_shuffle_epi8(lk, lo1), _mm256_shuffle_epi8(lk, hi1));
tot = _mm256_add_epi64(tot, _mm256_add_epi64(_mm256_sad_epu8(t0, z), _mm256_sad_epu8(t1, z)));
}
for (; w + 4 <= Wr; w += 4) {
__m256i a = LDU((const __m256i *)(bs[0] + w));
for (int d = 1; d < KK; d++) a = _mm256_and_si256(a, LDU((const __m256i *)(bs[d] + w)));
__m256i lo2 = _mm256_and_si256(a, lm);
__m256i hi2 = _mm256_and_si256(_mm256_srli_epi16(a, 4), lm);
tot = _mm256_add_epi64(tot, _mm256_sad_epu8(
_mm256_add_epi8(_mm256_shuffle_epi8(lk, lo2), _mm256_shuffle_epi8(lk, hi2)), 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_A(u32 N, u32 *out, u32 ds, u32 q0, u32 q1, u32 Q, u32 W) {
const int k = KK;
const int K1 = KK - 1;
#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
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++) {
#if MF_PFB > 0
if (qi + MF_PFB < q1) _mm_prefetch((const char *)(s_bdT + (size_t)d1 * N + s_qord[qi + MF_PFB]), _MM_HINT_T0);
#endif
cntv[s_bdT[(size_t)d1 * N + 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++) { if (qi + 64 < q1) _mm_prefetch((const char *)(s_bdT + (size_t)d1 * N + s_qord[qi + 64]), _MM_HINT_T0); u32 id = s_qord[qi]; s_tmp[cntv[s_bdT[(size_t)d1 * N + id] + 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 * N + 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 * N + 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 * g_SBK;
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 * N + s_qord[qi]];
u32 qe = qi + 1;
while (qe < q1 && s_bdT[(size_t)d1 * N + 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 * g_SBK;
#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 * g_SBK), _MM_HINT_T0);
_mm_prefetch((const char *)(s_gt[ds] + 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 * g_SBK;
#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 * g_SBK), _MM_HINT_T0);
_mm_prefetch((const char *)(s_gt[ds] + 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 * g_SBK;
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
}
template<int KK>
__attribute__((target("avx2,popcnt")))
static void query_group_B(u32 N, u32 *out, u32 ds, u32 q0, u32 q1, u32 Q, u32 W) {
const int k = KK;
#if MF_ASCR
const u32 d1 = (ds == 0) ? 1u : 0u;
#else
const u32 d1 = 0;
#endif
u32 *const avpool = s_avpool;
// ---- pass B: fringe, dimension-major. Ordering the group's queries by their bucket in
for (int d = 0; d < k; d++) {
if (d == ds) continue;
#if !MF_FR_NOSORT
#if MF_ASCR
// [NODUP] identity sort: the ASCR preamble already stably sorted this group's
// queries by their bucket in d1, and d1 is the first d reached below.
if (g_lexsort || d != (int)d1) {
#endif
{
u32 *cntv = s_cntv;
for (u32 v = 0; v <= Q; v++) cntv[v] = 0;
for (u32 qi = q0; qi < q1; qi++) {
#if MF_PFB > 0
if (qi + MF_PFB < q1) _mm_prefetch((const char *)(s_bdT + (size_t)d * N + s_qord[qi + MF_PFB]), _MM_HINT_T0);
#endif
u32 bb = s_bdT[(size_t)d * N + 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];
}
#if MF_ASCR
}
#endif
#endif
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 = LDU((const __m256i *)hiA);
const __m256i notexloh = LDU((const __m256i *)hiAh);
const __m256i all8 = _mm256_set1_epi32(-1);
const u32 *const odd = s_ord[d];
const u32 *const sdmpd = s_sdmp[d];
for (u32 qi = q0; qi < q1; qi++) {
u32 i = s_qord[qi];
#if MF_PFB > 0
{
u32 qj = qi + MF_PFB;
if (qj < q1) {
u32 j = s_qord[qj];
_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 *)(s_bd + (size_t)j * g_SBK + d), _MM_HINT_T0);
_mm_prefetch((const char *)(avpool + (size_t)j * STRIDE), _MM_HINT_T0);
}
}
#endif
#if MF_PFOUT > 0
/* [PFSC-d] out[i] += add: one random RMW per (query,d) into the 4 MB out[] array
(6e6 total), unprefetched. i comes from the sequential s_qord[]. */
{ u32 qj = qi + MF_PFOUT_D; if (qj < q1) _mm_prefetch((const char *)(out + s_qord[qj]), _MM_HINT_T0); }
#endif
const u16 *bd = s_bd + (size_t)i * g_SBK;
const u32 lo = FPOS(d, bd[d]);
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 * STRIDE;
__m256i qr = _mm256_or_si256(LDA((const __m256i *)rec), khi8v);
__m256i Avm = _mm256_or_si256(LDU((const __m256i *)Avp), notexlo);
__m256i qT = _mm256_min_epi32(qr, Avm);
__m256i qTh;
if (k > 8) {
__m256i qrh = _mm256_or_si256(LDU((const __m256i *)(rec + 8)), khi8hv);
__m256i Avmh = _mm256_or_si256(LDU((const __m256i *)(Avp + 8)), notexloh);
qTh = _mm256_min_epi32(qrh, Avmh);
} else {
qTh = all8;
}
u32 add = 0;
const u32 *c0 = sdmpd ? (sdmpd + (size_t)lo * g_KS) : 0;
if (g_use_filt && KK == 7) {
const u32 w0 = lo >> 6;
const u32 nw = ((hi - 1) >> 6) - w0 + 1;
const u32 csh = (u32)g_cshift, cQc = (u32)g_Qc;
const u32 FW = g_fbm_W;
const u64 *rows[6];
int nr = 0;
for (int e = 0; e < 7; e++) {
if (e == d) continue;
u32 cell = (u32)(((u64)rec[e] * cQc) >> csh) + 1;
if (cell > cQc) cell = cQc;
rows[nr++] = s_fbm + ((size_t)(d * 7 + e) * (g_Qc + 1) + cell) * FW + w0;
}
const u64 *r0=rows[0], *r1=rows[1], *r2=rows[2], *r3=rows[3];
const u64 *r4=rows[4], *r5=rows[5];
const u32 remh = hi & 63u;
{ // first word: low mask only
u64 vv = r0[0] & r1[0] & r2[0] & r3[0] & r4[0] & r5[0];
vv &= ~0ull << (lo & 63);
if (nw == 1u && remh) vv &= (1ull << remh) - 1;
#if MF_FR_NOCAND
vv = 0;
#endif
while (vv) {
int t = __builtin_ctzll(vv);
vv &= vv - 1;
u32 j = (w0 << 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 = LDU((const __m256i *)cp);
add += (u32)_mm256_testc_si256(_mm256_cmpgt_epi32(qT, pv), all8);
}
}
{ /* [a13z-P4] fused middle scan. The old shape nested two short variable-trip
loops (`for w = 1; w+1 < nw` x `while (vv)`), so every middle word paid two
unpredictable branch evaluations -- measured (callgrind, judge geometry) as
Bcm 6,638,567 @20.4% for the word loop plus Bcm 14,804,065 @28.8% for the
bit loop, i.e. three quarters of the whole program's 41.0M mispredicts.
Fusing them keeps exactly one branch site for the same work, the same
word order and the same candidate indices. */
u32 w = 1u;
u64 vv = (nw > 2u) ? (r0[1] & r1[1] & r2[1] & r3[1] & r4[1] & r5[1]) : 0ull;
#if MF_FR_NOCAND
vv = 0;
#endif
for (;;) {
if (vv == 0ull) {
w++;
if (w + 1u >= nw) break;
vv = r0[w] & r1[w] & r2[w] & r3[w] & r4[w] & r5[w];
#if MF_FR_NOCAND
vv = 0;
#endif
continue;
}
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 = LDU((const __m256i *)cp);
add += (u32)_mm256_testc_si256(_mm256_cmpgt_epi32(qT, pv), all8);
}
}
if (nw > 1u) { // last word: high mask only
u32 w = nw - 1u;
u64 vv = r0[w] & r1[w] & r2[w] & r3[w] & r4[w] & r5[w];
if (remh) vv &= (1ull << remh) - 1;
#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 = LDU((const __m256i *)cp);
add += (u32)_mm256_testc_si256(_mm256_cmpgt_epi32(qT, pv), all8);
}
}
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 u32 csh = (u32)g_cshift, cQc = (u32)g_Qc;
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] * cQc) >> csh) + 1;
if (c > cQc) c = cQc;
const u64 *row = s_fbm + ((size_t)(d * k + e) * (g_Qc + 1) + c) * FW + w0;
if (first) {
u32 w = 0;
for (; w + 4 <= nw; w += 4) STU(fb + w, LDU(row + w));
for (; w < nw; w++) fb[w] = row[w];
first = 0;
} else {
u32 w = 0;
for (; w + 4 <= nw; w += 4) STU(fb + w, _mm256_and_si256(LDU(fb + w), LDU(row + w)));
for (; 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 = LDU((const __m256i *)cp);
u32 ok = (u32)_mm256_testc_si256(_mm256_cmpgt_epi32(qT, pv), all8);
if (k > 8) {
__m256i pvh = LDU((const __m256i *)(cp + 8));
ok &= (u32)_mm256_testc_si256(_mm256_cmpgt_epi32(qTh, pvh), all8);
}
add += ok;
}
}
out[i] += add;
continue;
}
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 = LDU((const __m256i *)(c));
__m256i p1 = LDU((const __m256i *)(c + g_KS));
__m256i p2 = LDU((const __m256i *)(c + 2 * g_KS));
__m256i p3 = LDU((const __m256i *)(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 = LDU((const __m256i *)c);
a0 += (u32)_mm256_testc_si256(_mm256_cmpgt_epi32(qT, pv), all8);
}
add = a0 + a1 + a2 + a3;
} else {
const u32 *c = c0;
for (u32 j = lo; j < hi; j++, c += g_KS) {
__m256i pv = LDU((const __m256i *)c);
__m256i pvh = LDU((const __m256i *)(c + 8));
u32 ok = (u32)_mm256_testc_si256(_mm256_cmpgt_epi32(qT, pv), all8) &
(u32)_mm256_testc_si256(_mm256_cmpgt_epi32(qTh, pvh), all8);
add += ok;
}
}
} else {
for (u32 j = lo; j < hi; j++) {
const u32 *c = s_pm + (size_t)odd[j] * STRIDE;
__m256i pv = LDA((const __m256i *)c);
add += (u32)_mm256_testc_si256(_mm256_cmpgt_epi32(qT, pv), all8);
}
}
out[i] += add;
}
}
}
__attribute__((target("avx2")))
static void solve_mf(u32 N, const unsigned **x, u32 *out, int k) {
g_SBK = (k <= 8) ? 8u : (u32)MAXK;
// 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;
overhead += (u64)k * k * 4 * N;
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;
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
// [W2] the coarse-cell ladder must be fixed up-front: s_bnde (boundaries in
// value space) and the per-query cell index must use the SAME shift, otherwise
// the filter stops being a tight superset (measured: 7.5x more candidates).
if (g_use_filt) { int sh = 0; while ((sh < 32) && (((u64)1 << sh) < (u64)N)) sh++;
g_cshift = sh; }
{
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 = LDU((const __m256i *)lo8);
khi8hv = LDU((const __m256i *)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];
// Counting sort + rank record in sequential passes: start[v] = #{q : x_d[q] < v}
// is exactly gt[] of every point with x_d == v, and end[v]-1 = start[v+1]-1 is the
// last rank of that tie group, so bd[] follows from start[v+1] too. Both are value
// lookups, so the per-point work is a sequential sweep of xd[] instead of the
// previous random xd[od[j]] read plus random gt[]/bd[] writes.
for (u32 i = 0; i <= N; i++) s_cntv[i] = 0;
for (u32 i = 0; i < N; i++) {
#if MF_PFHIST > 0
if (i + MF_PFHIST_D < N) { u32 vn = xd[i + MF_PFHIST_D]; if (vn >= N) vn = 0;
_mm_prefetch((const char *)(s_cntv + vn), _MM_HINT_T0); }
#endif
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; } }
{ u32 *pp = s_pos[d];
for (u32 i = 0; i < N; i++) {
#if MF_PFSC2 > 0
/* [PFSC2] 1015 recipe (ported): the scatter RMW `s_tmp[v]++` is a random line touch whose
value q is immediately the address of another random store (`od[q] = i`). xd[] is
sequential, so the line for a future iteration is known 32 points ahead. */
/* [PFW-SC2] §2.19.545 ②轴(本档唯一仍活着的站点):该预取的目标行正是本环的 **RMW 目标**
(`u32 q = s_tmp[v]++` —— 每点一次随机增量、数组 4 MB,且其返回值 q 立刻又是一次随机存
`od[q] = i` 的地址)⇒ 读意图 T0 只请求 S,真正的 `++` 仍要一次 RFO。
换成写意图 `prefetchw`(独占获取)⇒ 消掉那次 RFO 停等。只改指令形态、地址逐字不变。 */
if (i + MF_PFSC2_D < N) { u32 vn = xd[i + MF_PFSC2_D]; if (vn >= N) vn = 0;
__asm__ __volatile__("prefetchw %0" :: "m"(*(const char *)(s_tmp + vn))); }
#endif
u32 v = xd[i]; if (v >= N) v = 0; u32 q = s_tmp[v]++; od[q] = i; pp[i] = q; } }
{ u32 *gt = s_gt[d];
for (u32 i = 0; i < N; i++) {
#if MF_PFSC > 0
/* [PFSC] 1015 recipe (ported): setup's random row fetches s_cntv[v] / s_cntv[v+1]
(a 4 MB row, indexed by the sequential xd[]), 32 points ahead. */
if (i + MF_PFSC_D < N) { u32 vn = xd[i + MF_PFSC_D]; if (vn >= N) vn = 0;
_mm_prefetch((const char *)(s_cntv + vn + 1), _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);
u16 bucket = (u16)(bb > Q - 1 ? Q - 1 : bb);
s_bd[(size_t)i * g_SBK + d] = bucket;
s_bdT[(size_t)d * N + i] = bucket;
} }
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;
}
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;
}
// ---------- 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;
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 = CBOUND(c);
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), LDA((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
/* [FBMERGE] the k(k-1) coarse planes are no longer built by a standalone 42e6-gather
walk: plane (d,e,c) is exactly the pool walk's accumulator `cur` at dim-e position
s_bnde[e][c] of the ds==d group (same bit order = rank_d, same accumulator, same
ladder), so it is emitted from there. Only plane 0 (the empty prefix) is
materialised here. `wc` is left for the disabled MF_FB3=0 path. */
(void)wc;
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;
}
#endif
} else { g_use_filt = 0; }
_mm_sfence();
}
// ---------- fold dimension + bucket vector per query ----------
{
size_t needav = (size_t)N * STRIDE * 4 + 256;
if (needav > s_avcap) {
s_avpool = (u32 *)pool_alloc(needav);
s_avcap = s_avpool ? needav : 0;
}
}
{
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++) {
/* [a13z-P2] branchless fold. `if (g < best) { best = g; ds = d; }` executes k-1 = 6
times per query and takes its branch whenever a new dimension wins the argmin; measured
(callgrind, judge geometry) Bc 6,000,000 with Bcm 1,790,821 = 29.8%, the classic
data-dependent short-loop mispredict. The select is pure dataflow: write it as a mask
so the compiler emits cmov, not a branch. best/ds end with exactly the same values
(argmin with the same tie rule: strict `<`, first dimension wins). */
u32 best = 0xFFFFFFFFu; int ds = 0;
for (int d = 0; d < k; d++) {
u32 g = s_gt[d][i];
u32 m = (u32)0 - (u32)(g < best);
best = (g & m) | (best & ~m);
ds = (int)(((u32)d & m) | ((u32)ds & ~m));
}
s_g[i] = (u32)ds;
const u16 *bdv = s_bd + (size_t)i * g_SBK;
u32 *av = s_avpool + (size_t)i * STRIDE;
u32 *mr = s_maxr + ((size_t)ds * MAXK) * MAXQ;
for (int d = 0; d < k; d++) {
if (d == ds) { av[d] = 0x7FFFFFFFu; continue; }
u32 b = bdv[d];
av[d] = BVAL(d, b);
if (g_plen_on && best > mr[(size_t)d * MAXQ + b])
mr[(size_t)d * MAXQ + b] = best;
}
for (int d = k; d < STRIDE; d++) av[d] = 0x7FFFFFFFu;
}
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; }
// ---------- 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;
}
}
}
}
}
// ---------- 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];
/* [FBMERGE] do NOT skip an empty group: the pool walk below is what emits this ds's
coarse planes (bit order = rank_ds) and pass B of OTHER groups reads them. With
random coordinates every group is non-empty, but a degenerate input (e.g. x_d[i]=i,
where every point folds into ds=0) would otherwise leave those planes unbuilt.
Skipping is still correct for pass A: its loops run over [q0,q1), which is empty. */
#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;
}
const u32 QcM = (u32)g_Qc;
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];
/* [FBMERGE] coarse plane for (bit-order dim ds, walked dim d); s_bnde[d][c] is a
dim-d position, i.e. the very ladder the standalone filter walk consumed. */
u64 *fbase = (s_fbm && g_use_filt) ? (s_fbm + (size_t)(ds * k + d) * (QcM + 1) * W) : 0;
const u32 *bnd = s_bnde + (size_t)d * (MAXQ + 2);
u32 cnext = 1;
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;
if (fbase) while (cnext <= QcM && bnd[cnext] <= pend) {
u32 cp2 = bnd[cnext];
while (ptr < cp2) {
#if MF_PFBUILD > 0
if (ptr + MF_PFBUILD_D < cp2) _mm_prefetch((const char *)(s_nid + od[ptr + MF_PFBUILD_D]), _MM_HINT_T0);
#endif
u32 q = s_nid[od[ptr]]; cur[q >> 6] |= 1ull << (q & 63); ptr++; }
u64 *fdst = fbase + (size_t)cnext * W;
#if MF_NT
{ u32 w = 0;
for (; w + 4 <= W; w += 4) _mm256_stream_si256((__m256i *)(fdst + w), LDA((const __m256i *)(cur + w)));
for (; w < W; w++) fdst[w] = cur[w]; }
#else
for (u32 w = 0; w < W; w++) fdst[w] = cur[w];
#endif
cnext++;
}
while (ptr < pend) {
#if MF_PFBUILD > 0
/* [PFSC2-b] 1015/1016 family recipe, third site: the fine-pool build's gather
`q = s_nid[od[ptr]]` is a random 4 MB row fetch whose value immediately addresses
another random RMW (`cur[q>>6] |= ...`); od[] is sequential, so the line for a
future iteration is known MF_PFBUILD_D points ahead. 6e6 iterations (K1 x N). */
if (ptr + MF_PFBUILD_D < pend) _mm_prefetch((const char *)(s_nid + od[ptr + MF_PFBUILD_D]), _MM_HINT_T0);
#endif
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), LDA((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_A<4>(N, out, ds, q0, q1, Q, Wpool); break;
case 5: query_group_A<5>(N, out, ds, q0, q1, Q, Wpool); break;
case 6: query_group_A<6>(N, out, ds, q0, q1, Q, Wpool); break;
case 7: query_group_A<7>(N, out, ds, q0, q1, Q, Wpool); break;
case 8: query_group_A<8>(N, out, ds, q0, q1, Q, Wpool); break;
case 9: query_group_A<9>(N, out, ds, q0, q1, Q, Wpool); break;
default: query_group_A<10>(N, out, ds, q0, q1, Q, Wpool); break;
}
}
// [SPLITB] pass B (fringe) is pool-independent (it reads only s_pm/avpool/s_fbm/
// s_ord/s_gt/s_bd), so all of it can run after every group's pass A.
for (int ds = 0; ds < k; ds++) {
u32 q0 = gstart[ds], q1 = gstart[ds + 1];
if (q0 >= q1) continue;
switch (k) {
case 4: query_group_B<4>(N, out, ds, q0, q1, Q, Wpool); break;
case 5: query_group_B<5>(N, out, ds, q0, q1, Q, Wpool); break;
case 6: query_group_B<6>(N, out, ds, q0, q1, Q, Wpool); break;
case 7: query_group_B<7>(N, out, ds, q0, q1, Q, Wpool); break;
case 8: query_group_B<8>(N, out, ds, q0, q1, Q, Wpool); break;
case 9: query_group_B<9>(N, out, ds, q0, q1, Q, Wpool); break;
default: query_group_B<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 | 4.962 s | 730 MB + 552 KB | Accepted | Score: 100 | 显示更多 |