/*
References:
- saffah_codex_6a_agg3, https://duck.ac/submission/121146: complete radix/SIMD
pipeline and guarded uniform-byte counting, original citations retained.
- saffah_cc_v41_agg1, https://duck.ac/submission/107172: inherited sorting
network and buffer layout. Public sources supplied no separate license.
Idea:
- Dispatch on the already-computed maximum byte1 bucket count. If every
bucket holds at most sixteen bytes, use a compact 16-byte bucket stride
and initialize its contiguous 4KiB sentinel region with memcpy-family code.
Otherwise retain the 32-byte stride. Templates keep both address strides
constant throughout scatter and packed SIMD output.
- Validated bucket bounds also make the old scatter-index mask unnecessary.
Purpose:
- Evaluate a smaller active third-level workspace without changing any sort
or overflow decision. Existing assembly helpers remain unchanged; no new
assembly or system calls are introduced.
*/
/*
References:
- saffah_codex_6a_agg3, https://duck.ac/submission/121146: complete sorting
pipeline, pre-scatter histogram guard and uniform-byte fused count.
- saffah_cc_v41_agg1, https://duck.ac/submission/107172: inherited radix/SIMD
sorter with its attribution below; no separate license was supplied.
Idea:
- Delete the byte-scatter address mask after the histogram guard. This path
runs only when every bucket count is at most 32; cursors start at 32*b,
so every written index is already in [0,8191]. The mask is redundant.
Purpose:
- Evaluate removal of one scalar operation per ordinary third-level record.
Skewed buckets still bypass scatter and use counting sort. No new assembly
or system-call instructions are introduced.
*/
/*
References:
- saffah_codex_6a_agg3, https://duck.ac/submission/120693: retained cursor-copy
pipeline and its corrected overflow handling.
- saffah_cc_v41_agg1, https://duck.ac/submission/107172: inherited byte-radix
sorting and guarded uniform-byte special case, with its attributions below.
No separate source license was supplied with that public submission.
Idea:
- Fuse the uniform byte2 check and low-word histogram in sort_255_one_b2.
Successful uniform-byte regions now consume their packed triples once.
On a mismatch, discard the partial histogram and use the unchanged general
sorter, so the special case remains fully guarded for arbitrary input.
Purpose:
- Evaluate the removed input pass on the official special-distribution test.
All new logic is ordinary C++; inherited assembly is left unchanged.
*/
/*
References:
- saffah_cc_v41_agg1, https://duck.ac/submission/107172: inherited sorting
pipeline, source attribution and public-source license notes retained below.
- saffah_codex_6a_agg3, https://duck.ac/submission/120596:
retained the corrected all-lane u16 maximum and pre-scatter histogram reuse.
Idea:
- Materialize the invariant 256 bucket starting offsets at compile time and
restore the cursor array with one C++ memcpy expression. This removes all
runtime offset arithmetic and permits a compact copy instead of an expanded
vector-construction loop. Use the same table for initial cursor setup.
Purpose:
- Test whether compact cursor reset improves the official runtime while
retaining robust handling of skewed odd buckets. No new assembly or system
calls are added; local runs verify complete output against std::sort.
*/
/*
References:
- saffah_cc_v41_agg1, https://duck.ac/submission/107172: complete sorting
pipeline, all original inherited source attribution retained below.
Idea: Reuse the byte1 histogram computed before scatter instead of recomputing
it from cursors. Correct the inherited u16 maximum reduction: its original
32-bit shuffles omitted every odd u16 lane, so a skewed odd bucket could
overflow undetected until the second histogram pass. A minpos reduction of
complemented u16 lanes now covers every bucket before scatter; only cursor
reset is needed afterward. This also avoids the unnecessary overflowing
scatter for skewed inputs.
Experiment purpose: Validate odd-bucket skew and full-size sorting, then measure
the reduction of repeated histogram reconstruction in the official judge.
*/
// [CONTROL] 环境对照发(非新刀):正文 = #107002 的逐字节基座 work/c1w_base.cpp,一字未改。
// 目的:本刀 #107146/#107170 在 19:21/19:32 测得 640.57/640.50,而 #107002 在 18:22 测得 623.72。
// 判题机日志显示同一题在数小时内漂移 623.7~640.5(±2.7%),同窗口内散布仅 0.08%(1014 的 8 次重复 5614.4~5618.9)。
// 故必须在同一窗口内测基座,才能把刀与机时态分开。
// ===== REFERENCES =====
// [1] duck.ac 用户 saffah_cc_v41_agg1(本账号),提交 #106928 <https://duck.ac/submission/106928>
// 用途:**本文件正文 = #106928 的完整源码**(本地副本 `work/c1y_pf6.cpp`),只把 L2 分块预取的
// 条数由 6 改成 3(见思路段)。#106928 自述的上游引用一并转述:
// 1.1 <https://duck.ac/submission/106129> 本账号 —— 其直接上游(当代最好件)。
// 1.2 <https://duck.ac/submission/106060> duck.ac 用户 saffah_codex_6s_agg2(当代 T)—— #106129 = 该提交正文逐字节复刻。
// 1.3 <https://duck.ac/submission/105905> / <https://duck.ac/submission/105851> 本账号 —— level-3 环 unroll 16 两把刀。
// 1.4 <https://duck.ac/submission/102619> duck.ac 用户 saffah_codex_6s_agg2 —— 同族管线/SIMD emit 血统。
// 1.5 <https://duck.ac/submission/105161> 本账号(1001,已达标)—— 预取距离/条数参数来源。
// 1.6 <https://duck.ac/problem/1001c> 题目页(sort 2^27 个 u32,特殊构造数据,单测试点)。
// [2] duck.ac 用户 saffah_cc_v41_agg1(本账号),提交 #106993 <https://duck.ac/submission/106993>
// 用途:**参考了思想**(不是复制代码):该提交(1002,同族)把"预取轮次频率"这条不变量
// 在 4 处守卫上从 `& 31` 改成 `& 16`,判题机兑现 **−241.136 µs(−1.454%)**并转绿 ⇒
// 本发按同一不变量**反向**检查本题唯一的预取站点(见思路段)。
// ======================
// ===== 思路 =====
// **单变量(一处):L2 分块预取的条数 6 → 3(其余一字不动)。**
// * 入场口径(本席自核 `tools/exact.py 1001c`):`mine #106928 = 624.918451` · `T #106060 = 626.471492`
// (早于我方 ⇒ 严支)· 严支 `620.207777` ⇒ **缺口 4.710674 ms(0.754%)**。
// * **§2.19.455 不变量("每消费一条 64 B line,发了几轮?必须是 1")的审计结果**:
// 本题全文只有 **一处** 数据预取站点(本席上一发加的 L2 分块预取)。该块消费 **64 记录 = 192 B
// = 3 条 line**,原形态发 **6 条**(覆盖 [sp+512, sp+896))= **2x 覆盖、每 line 被覆盖 2 次** ✓
// —— **没有裸 line**(即不存在 `§2.19.455` 病灶"一半 line 完全裸读"✗),但**轮次 ≠ 1**。
// 本发把条数收到 **3**(覆盖 [sp+512, sp+704))= **恰 1x 覆盖、每 line 恰 1 轮** ✓ 按不变量该形态是正解。
// * 反向证据(为何这不是显然的):1001 上同一构型的台架读数是"3 条 = 84.01 M、4 = 83.75、5 = 83.57、
// 6 = 83.55(饱和)" ⇒ 在 1001 上 6 条略优于 3 条(−0.55%)。本题是否同向**未知** ⇒ 本发就是定价那一格 ✓
// (判负不伤 best:板只留最好件 ✓ `§2.19.457`:严支上发改善件永不推远靶线 ⇒ 该发就发 ✓)
// * 输出逐位不变:只改提示指令的条数 ✓ 仍按纪律跑生产 n(2^27)逐元素 + 8 模式 FNV 闸门。
// 提交目的 = **定价**(把 `§2.19.455` 的"轮次 = 1"落在本题唯一的预取站点上)。判据 = 与 `#106928` 比。
// ★ 声明:只改预取条数;失败只关掉"1x 覆盖优于 2x"这一条。
// ================
// ===== REFERENCES =====
// [1] duck.ac 用户 saffah_cc_v41_agg1(本账号),提交 #106129 <https://duck.ac/submission/106129>
// 用途:**本文件正文 = #106129 的完整源码**(本地副本 `work/sm7_pad2560.cpp`),只改
// `sort_region` 里 L2(byte2 散列趟)的循环结构 + 预取条数(见思路段)。
// #106129 自述的上游引用一并转述:
// 1.1 <https://duck.ac/submission/106060> duck.ac 用户 saffah_codex_6s_agg2(当代 T)—— #106129 = 该提交正文逐字节复刻。
// 1.2 <https://duck.ac/submission/105905> / <https://duck.ac/submission/105851> 本账号 —— level-3 环的
// `#pragma GCC unroll 16` 两把刀。
// 1.3 <https://duck.ac/submission/102619> duck.ac 用户 saffah_codex_6s_agg2 —— 同族管线/SIMD emit 血统。
// 1.4 <https://duck.ac/problem/1001c> 题目页(sort 2^27 个 u32,特殊构造数据,单测试点)。
// [2] duck.ac 用户 saffah_cc_v41_agg1(本账号),提交 #105161 <https://duck.ac/submission/105161>
// 用途:**直接复制**该提交(1001,n=1e8,已达标)里 byte2 记录流预取的参数形状
// —— 「64 记录(192 B)定长分块 + 块首 6 条 `__builtin_prefetch(r+512 .. r+832, 0, 0)`」。
// 该参数在 1001 上由本账号逐发判题机定价:0→1 条 = −0.4838 ms、1→3 条 = −1.330252 ms、3→6 条 = −0.420711 ms。
// [3] duck.ac 用户 saffah_cc_v41_agg1(本账号),提交 #106840 <https://duck.ac/submission/106840>
// 用途:**直接复制**该提交(1001b,已达标)的**同一形态**(同一把刀在 1001b 上 = **−3.010 ms**,
// 兑现率 ≈1.0),并沿用其"外层定长分块 + 内层定长展开"的结构纪律。
// [4] Intel 64 与 IA-32 架构软件开发手册,PREFETCHh 语义 <https://www.intel.com/sdm>
// 用途:仅用于确认 `PREFETCHh` 对任意地址(含映射之外)不产生异常、不影响架构状态。
// ======================
// ===== 思路 =====
// **单变量(一处):`sort_region` 的 L2 趟(三字节记录 → u16 T 的散列)改写为分块 + 6 条预取。**
// * 入场口径(`tools/exact.py 1001c`,本席自核):`mine #106129 = 626.522589` · `T #106060 = 626.471492`
// (**早于**我方最快件 ⇒ 严支)· 严支 `620.207777` ⇒ **缺口 6.314812 ms(1.008%)**。
// * 原形态:`#pragma GCC unroll 8` 的循环体内每元素带守卫 `if((i&15u)==0u) prefetchnta(sp+512)`
// ⇒ 每元素 2 条 uop 花在守卫上(prefetch 只每 16 元素发 1 条,覆盖率 64 B/48 B = 1.33x)。
// * 本形态:外层 64 记录(= 192 B)定长分块,块首发 6 条 `__builtin_prefetch(sp+512..+832)`
// (覆盖 384 B ⇒ 覆盖率 2x,与 1001/1001b 上被定价的最优点同形),内层定长 64(= 4x16,无余数)
// ⇒ 守卫从"每元素 2 条 uop"变成"每 64 元素 6 条 prefetch"(每元素 0.09 条)。
// * 机理(本题相位账,notes 已由两发独立消融钉住):L2 = 136 ms(20.6%),每元素 2 store + 3~4 load
// ≈ 9~11 uop,实测 3.65 cycle/元素 vs store 端口硬下界 2.0 ⇒ **L2 是 uop/前端受限**,且本档实测
// "每元素增减 uop 会迁移并放大"(+2 uop/元素 ≈ +44 ms)⇒ 省掉每元素 2 条守卫 uop 是本发的主要收益,
// 预取参数升级为次要收益。两题同族同形态(3 字节记录、同一递增下标、402 MB 冷 DRAM 流)。
// * 输出逐位不变:只是把同一标量体搬进"分块 + 内层"两层循环,记录处理顺序逐位相同;预取是提示指令,
// `PREFETCHh` 不触发缺页(引用 [4]),`sp+832` 仍在 `g_S8`(约 474 MB)映射内。
// 提交目的 = **定价 + 兑现**(缺口 6.314812 ms)。判据 = 与 `#106129`(626.522589 ms)单变量比。
// ★ 声明:只改这一趟的循环写法与预取参数,不改算法/布局/表;失败只关掉这一条路。
// ================
// ===== REFERENCES =====
// [1] duck.ac 用户 saffah_codex_6s_agg2,提交 #106060 <https://duck.ac/submission/106060>(626.471492 ms,当代 T)。
// 用途:**本文件正文 = 该提交正文逐字节复制**(RULES §2.16 的第一步"纯复刻")。它自己写明其父本是
// 本账号 #105905 <https://duck.ac/submission/105905>(630.401385 ms)= 我方最好件 ⇒ 本次复刻是"追平对手"。
// [2] 本账号 #105905 <https://duck.ac/submission/105905>(`work/c1x_p2.cpp`)—— 对手文件里已含我方那两把
// level-3 环 `#pragma GCC unroll 16` 刀(#105851 −0.73 ms / #105905 −0.66 ms)⇒ 复刻件自动继承 ✓。
// [3] duck.ac 用户 saffah_codex_6s_agg2,提交 #102619 <https://duck.ac/submission/102619>(634.780 ms)——
// [1] 的父本;本文件的 radix 管线、SIMD emit、robust 路与其一致。
// ======================
// ===== 思路 =====
// 入场:`mine = 630.401385`(#105905)/ `T = 626.471492`(#106060)/ 宽支线 `1.005*T = 629.604849`
// ⇒ 红 0.796536 ms;严支(交新最好后)`0.99*T+1us = 620.207777` ⇒ 差 10.193608 ms(1.617%)。
// ★ 本发 = **纯复刻**(不是新机理):先用 `tools/subhtis`/`fetch_rival.py` 取证,`ref2/rival_102619.cpp → rival_106060.cpp`
// 逐行 diff 得到对手当代的全部增量(相对其父本 #102619;其文件里已含我方两把 unroll 刀):
// ① `BLKST 320 → 384`(L1 staging 行距);② `g_stg` 尾部 **+2048 B**(只加长度、不动索引 ⇒ 把其后
// BSS 数组(g_T/g_sc/g_sc2/g_U)相对 msc 源 `g_S8` 整体平移 2048 B,打断 mod-4096 相位关系);
// ③ 新增 `sort_255_one_b2`(top-byte=255 区且全体 byte2 相同时走 64K 桶直填 + `std::fill_n`)及其守卫;
// ④ `L2BODY` 的 byte3 取值简化(`d = v>>24`)+ 该环 8→16 展开;⑤ `pospad` `.fill 139 → 155`(代码位置 pad)。
// ⇒ 本发**只测这条复刻链**(单变量 = "是否复刻对手当代件")。方向判据(`§2.18.973`):我方 630.401 > T 626.471
// ⇒ **我方比 T 慢** ⇒ 复刻有效。
// ★ 正确性:对手件在判题机上 **Accepted** ✓;本发另跑本机"输出逐位相同"闸门(对拍我方最好件)与 `gcc93check.py`。
// ★ 定价预期:复刻通常还能反超原件(本族已 3~4 次:+47~125 µs),且我方 #105905 的两把 unroll 刀已在其中。
// 若落在 ≈625 ms ⇒ 会把被动窗口重新武装(`mine/1.005 ≈ 621.9 ≤ T'`:对手任何 ≤0.73% 的新最好即翻绿)。
// ================
#pragma GCC optimize("O3","unroll-loops","schedule-insns","sched-stalled-insns=2","live-range-shrinkage")
#pragma GCC optimize("-fno-crossjumping")
#pragma GCC target("avx2,bmi,bmi2,popcnt,lzcnt,sse4.1,ssse3")
#include <string.h>
#include <algorithm>
#include <stdlib.h>
#include <immintrin.h>
#include <stdint.h>
// lane p1001_dead: THE STORE BARRIER WAS TOO WIDE. The shipped st256 is an
// `asm volatile` with a **"memory" clobber**, i.e. it tells GCC-9 that the store
// may have modified ALL of memory. The emit executes ~21 M of these (4 per
// group always + 8 in the merge arm), so the clobber invalidates every load in
// the loop body on every iteration: the four `cnt3[b1..b1+3]` scalars, the
// `BM(g_pb1+8*b1)` prefix vectors, and -- crucially -- it is the reason the
// seven loop-invariant constant tables were re-loaded per iteration at all
// (work/e1001o (D) recorded that "constexpr changes NOTHING"; this clobber is
// why). Declaring the EXACT bytes written as a memory output operand keeps the
// asm `volatile` (so the store still executes, is never deleted, and the nine
// sites keep their RELATIVE ORDER, which the overlapping-store chain relies on)
// while letting GCC keep unrelated loads in registers across it. Same
// instruction, same address, same value, same order.
static inline void st256(void *p,__m256i v){ // GCC-9 splits storeu into 2x128B + vextracti128
asm("vmovdqu %1, %0" : "=m"(*(__m256i*)p) : "x"(v));
}
typedef unsigned u32; typedef unsigned short u16; typedef unsigned char u8;
#define SLK1 4352u
#define DIT 256u
#define NMAX 134217728u
#define CAP1M (NMAX/256u + SLK1)
// region base must be 64-byte aligned: base = 3*(b*STR1M + 64*k), and 3*x is
// 64-aligned iff x is. Otherwise the 192-byte NT flush straddles line boundaries
// and each destination is a PARTIAL line -- the exact case where NT pays nothing.
#define STR1M (((CAP1M + DIT + 64u) + 63u) & ~63u)
#define REC3 3u
// Guard region: an adversarially skewed input (one byte3 bucket holding far more
// than CAP1M elements) can push a region cursor past its own region; this slack
// absorbs that so the scatter can never write outside g_S8. In normal operation
// these pages are never written, and the judge charges only WRITTEN pages.
#define GUARD8 (64u<<20)
static u8 g_S8[REC3*(256u*STR1M + CAP1M + 8192u) + 64u + GUARD8];
// L1 staging: one BLK-byte block per bucket, L2-resident. A block holds
// BLK/3 = 85 records + the trailing 4-byte store; it is flushed to the real
// destination as whole cache lines with non-temporal stores (full-line NT is
// what the judge rewards: 2.6-4.5x over a strided regular scatter).
#define BLK 192u
#define BLKREC 64u
#define BLKST 384u // staging stride: 192B payload + pad, multiple of 32
// lane p1001_gstg: THE PRICING INSTRUMENT. `volatile` so gcc-9 cannot fold it:
// `off[b]=b*g_blkst` is the ONLY use in the L2 loop's data flow, and the flush
// test forces `o+3 == 0 (mod 64)` while BLK is 192, so the flush source `SG+(o+3-BLK)`
// is 64-byte aligned for EVERY value below => the loop's .text is IDENTICAL.
static volatile u32 g_blkst = 192u;
/* [B1PAD2K] 单变量:L1 staging 数组尾部 +2048 B(**只改数组长度、不改任何索引**)。
目的:把 g_stg 之后的所有 BSS 数组(g_T / g_sc(在 g_T 内) / g_sc2 / g_U)
相对 **g_S8**(msc 的源)整体平移 2048 B ⇒ 打断 g_T 与 g_S8 的 mod-4096 相位关系。
依据:本代理在 1001 上做的**判题机侧同二进制多臂扫描**(`work/b1_al_gen.py`,
把散射表相对源流平移 0/512/1024/1536/2048/3072 B)实测 **+2048 比 +0 快 0.18%**
(单调趋势、超出同码噪声 0.03~0.06%)⇒ "散射表 vs 源流的相对页相位"是一个真实维度。
本发的 pad 段**从不被访问**(索引上界 255*BLKST+BLKST-1 < 256*BLKST)⇒ 输出逐位不变。 */
static u8 g_stg[256u*BLKST + 2560u] __attribute__((aligned(256)));
#define SLK2 256u
#define RS2MAX ((((CAP1M>>8) + SLK2 + 63u) >> 5) + 2u) * 32u
static u16 g_T[256u*RS2MAX + CAP1M + 8192u];
#define TSZ (256u*RS2MAX + CAP1M + 8192u)
#define SCZ (CAP1M + 8192u)
static u16 * const g_sc = g_T + (256u*RS2MAX);
alignas(64) static u32 g_sc2[SCZ+16u];
#define SLK3 18u
#define RS3MAX 32u
// e1001o: with RS3 == 32 every bucket starts at U + 32*b, so a 32-byte-aligned
// g_U makes EVERY bucket load 32-byte aligned (no split 16-byte loads). The
// fleet's earlier alignas(64) test on g_U was NULL, but that was with the old
// non-power-of-two RS3 = 23, where the bucket addresses were arbitrary anyway.
alignas(32) static u8 g_U[256u*RS3MAX + RS2MAX + 256u];
static u32 g_off[257];
static inline void sink64(unsigned long long v){ __asm__ volatile("" :: "r"(v) : "memory"); }
int g_noavx_dbg=-1; static int g_noavx=0;
static inline void pretouch_a(unsigned *p,int n){
if(n<=0) return;
for(int i=0;i<n;i+=1024)
__asm__ volatile("orl $0, %0" : "+m"(p[i]) : : "memory");
__asm__ volatile("orl $0, %0" : "+m"(p[n-1]) : : "memory");
}
static inline __m256i net8_simd(__m256i v){
#define BD(P,IMM) { __m256i t=P; __m256i lo=_mm256_min_epu32(v,t),hi=_mm256_max_epu32(v,t); v=_mm256_blend_epi32(hi,lo,IMM); }
BD(_mm256_shuffle_epi32(v,_MM_SHUFFLE(2,3,0,1)),0x99)
BD(_mm256_shuffle_epi32(v,_MM_SHUFFLE(1,0,3,2)),0xC3)
BD(_mm256_shuffle_epi32(v,_MM_SHUFFLE(2,3,0,1)),0xA5)
BD(_mm256_permute2x128_si256(v,v,1),0x0F)
BD(_mm256_shuffle_epi32(v,_MM_SHUFFLE(1,0,3,2)),0x33)
BD(_mm256_shuffle_epi32(v,_MM_SHUFFLE(2,3,0,1)),0x55)
return v;
#undef BD
}
static inline __m256i ld8s(const u8 *g,u32 prefix){
return _mm256_or_si256(_mm256_cvtepu8_epi32(_mm_loadl_epi64((const __m128i*)g)),
_mm256_set1_epi32((int)prefix));
}
static inline __m256i ld8p(const u8 *g,u32 cc,u32 prefix){
const __m256i idx=_mm256_setr_epi32(0,1,2,3,4,5,6,7);
__m256i msk=_mm256_cmpgt_epi32(_mm256_set1_epi32((int)cc),idx);
__m256i x=_mm256_cvtepu8_epi32(_mm_loadl_epi64((const __m128i*)g));
x=_mm256_or_si256(x,_mm256_set1_epi32((int)prefix));
return _mm256_blendv_epi8(_mm256_set1_epi32(-1),x,msk);
}
static void emit_avx2(const u8 *g,u32 c,u32 prefix,u32 *o){
if(c<=8){
st256(o,net8_simd(ld8p(g,c,prefix)));
} else {
__m256i A=net8_simd(ld8p(g,8u,prefix));
__m256i B=net8_simd(ld8p(g+8,c-8u,prefix));
__m256i BR=_mm256_permutevar8x32_epi32(B,_mm256_setr_epi32(7,6,5,4,3,2,1,0));
st256(o,net8_simd(_mm256_min_epu32(A,BR)));
st256(o+8,net8_simd(_mm256_max_epu32(A,BR)));
}
}
static inline __m256i bmerge8(__m256i v){
#define BS(P,IMM) { __m256i t=P; __m256i lo=_mm256_min_epu32(v,t),hi=_mm256_max_epu32(v,t); v=_mm256_blend_epi32(hi,lo,IMM); }
BS(_mm256_permute2x128_si256(v,v,1),0x0F)
BS(_mm256_shuffle_epi32(v,_MM_SHUFFLE(1,0,3,2)),0x33)
BS(_mm256_shuffle_epi32(v,_MM_SHUFFLE(2,3,0,1)),0x55)
return v;
#undef BS
}
static inline void emitv(const u8 *g,u32 c,u32 prefix,__m256i *A,__m256i *B){
if(c<=8){ *A=net8_simd(ld8s(g,prefix)); }
else {
const __m256i REV=_mm256_setr_epi32(7,6,5,4,3,2,1,0);
__m256i X=net8_simd(ld8s(g,prefix));
__m256i Y=net8_simd(ld8s(g+8,prefix));
__m256i YR=_mm256_permutevar8x32_epi32(Y,REV);
__m256i MN=_mm256_min_epu32(X,YR), MX=_mm256_max_epu32(X,YR);
*A=bmerge8(MN); *B=bmerge8(MX);
}
}
static void emit_scalar(const u8 *g,u32 c,u32 prefix,u32 *o){
for(u32 i=0;i<c;i++){
u32 v=prefix|g[i]; u32 j=i;
while(j&&o[j-1]>v){ o[j]=o[j-1]; j--; }
o[j]=v;
}
}
static inline void emit(const u8 *g,u32 c,u32 prefix,u32 *o){
if(!g_noavx && c<=16) emit_avx2(g,c,prefix,o);
else emit_scalar(g,c,prefix,o);
}
// ---- packed emit: 4 buckets x 8 bytes in one ymm (byte-lane network) ----
static u32 g_pb1[256*8] __attribute__((aligned(32))); // PB1[b] = 8 x (b<<8)
static void init_pb1(void){
for(u32 b=0;b<256;b++){ u32 v=b<<8; for(int k=0;k<8;k++) g_pb1[8*b+k]=v; }
}
alignas(32) static u8 g_sh1[32],g_sh2[32],g_sh4[32],g_selb[6][32],g_shrev[32];
static u8 g_mp0[32],g_mp2[32],g_mp2r[32],g_mp0s[32],g_mp2s[32];
static u8 g_k0s2[32],g_k2s4[32];
static void init_bmasks(void){
for(int g=0;g<4;g++){ u8*p=g_shrev+8*g; int b=(g&1)*8; for(int k=0;k<8;k++) p[k]=b+7-k; }
static const u8 P1[8]={1,0,3,2,5,4,7,6};
static const u8 P2[8]={2,3,0,1,6,7,4,5};
static const u8 P4[8]={4,5,6,7,0,1,2,3};
for(int g=0;g<4;g++){
int b=(g&1)*8; // vpshufb byte indices are absolute within the 128-bit lane
u8*p=g_sh1+8*g; u8*q=g_sh2+8*g; u8*r=g_sh4+8*g;
for(int k=0;k<8;k++){ p[k]=P1[k]+b; q[k]=P2[k]+b; r[k]=P4[k]+b; }
}
const u32 S[6]={0x99,0xC3,0xA5,0x0F,0x33,0x55};
for(int s=0;s<6;s++) for(int g=0;g<4;g++) for(int e=0;e<8;e++)
g_selb[s][8*g+e]=((S[s]>>e)&1)?0xFF:0x00;
// ---- lane p1001_bi: PATTERN-SWAP masks for the E-form of the d=1 K0/K2 stages.
// blend_epi16's imm bit i selects lane-local WORD i (lane-local bytes 2i,2i+1); the
// 8-byte group g&1 inside a 128-bit lane owns imm bits 4*(g&1)+{0..3}. Mapping derived
// EMPIRICALLY (work/p1001_bi/immmap.cpp), never from the intrinsic's name.
for(int g=0;g<4;g++){
int gb=g&1, base=8*gb;
for(int w=0;w<4;w++){
int i0=base+2*w, i1=base+2*w+1;
int b0=(0x55u>>(4*gb+w))&1, b2=(0x33u>>(4*gb+w))&1;
g_mp0[8*g+2*w]=b0?i1:i0; g_mp0[8*g+2*w+1]=b0?i0:i1;
g_mp2[8*g+2*w]=b2?i1:i0; g_mp2[8*g+2*w+1]=b2?i0:i1;
}
}
for(int i=0;i<32;i++){ int off=(i>=16)?16:0;
g_mp2r[i]=g_mp2[g_shrev[i]+off];
g_mp0s[i]=g_mp0[g_sh1[i]+off]; g_mp2s[i]=g_mp2[g_sh1[i]+off];
g_k0s2[i]=g_mp0[g_sh2[i]+off]; g_k2s4[i]=g_mp2[g_sh4[i]+off]; }
}
#define BM(a) _mm256_load_si256((const __m256i*)(a))
// v95: identical networks, but VPBLENDD (1 uop, lat 1) replaces VPBLENDVB
// (2 uops, lat 2) in the three stages whose constant selection mask is
// dword- or WORD-granular, and VPBLENDW likewise in three more. The mask has
// PERIOD 8 over the 32-byte register, so e.g. S=0x0F selects lane dwords 0 AND
// 2 -- the lane mapping was derived by direct measurement (work/e1001g/u3.cpp),
// not from the intrinsic's name.
#define BSTW(M,IMM) { __m256i t=_mm256_shuffle_epi8(v,BM(M)); v=_mm256_blend_epi16(_mm256_max_epu8(v,t),_mm256_min_epu8(v,t),IMM); }
#define BSTD(M,IMM) { __m256i t=_mm256_shuffle_epi8(v,BM(M)); v=_mm256_blend_epi32(_mm256_max_epu8(v,t),_mm256_min_epu8(v,t),IMM); }
static inline __m256i bsort8(__m256i v){
#define BST(M,S) { __m256i t=_mm256_shuffle_epi8(v,BM(M)); v=_mm256_blendv_epi8(_mm256_max_epu8(v,t),_mm256_min_epu8(v,t),BM(S)); }
BST(g_sh1,g_selb[0]) BSTW(g_sh2,0x99) BST(g_sh1,g_selb[2])
BSTD(g_sh4,0x55) BSTW(g_sh2,0x55) BST(g_sh1,g_selb[5])
#undef BST
return v;
}
// ---- lane p1001_dead: TABLE-HOISTED networks ---------------------------------
// The five shuffle/select tables are LOOP-INVARIANT: bsort8/bsort8x3/bmerge8b use
// the fixed indices g_sh1, g_sh2, g_sh4, g_selb[0], g_selb[2], g_selb[5] and
// g_shrev. The shipped forms re-load each of them from memory on EVERY call
// (GCC cannot hoist a load from a non-const static that stores in the loop might
// alias). These forms take the values as ARGUMENTS, so the caller can load them
// ONCE per b2 and keep them in registers across the whole b1 loop.
#define BH_SEL(S) __m256i S
static inline __m256i bsort8h(__m256i v,__m256i S1,__m256i S2,__m256i S4,
__m256i K0,__m256i K2,__m256i K5,__m256i K0S2,__m256i K2S4){
#define BSW(M,IMM) { __m256i t=_mm256_shuffle_epi8(v,M); v=_mm256_blend_epi16(_mm256_min_epu8(v,t),_mm256_max_epu8(v,t),IMM); }
#define BSD(M,IMM) { __m256i t=_mm256_shuffle_epi8(v,M); v=_mm256_blend_epi32(_mm256_min_epu8(v,t),_mm256_max_epu8(v,t),IMM); }
{ __m256i t0=_mm256_shuffle_epi8(v,S1); __m256i m0=_mm256_min_epu16(v,t0);
__m256i t2=_mm256_shuffle_epi8(m0,K0S2); v=_mm256_shuffle_epi8(m0,K0);
v=_mm256_blend_epi16(_mm256_min_epu8(v,t2),_mm256_max_epu8(v,t2),0x66); }
{ __m256i t3=_mm256_shuffle_epi8(v,S1); __m256i m3=_mm256_min_epu16(v,t3);
__m256i t4=_mm256_shuffle_epi8(m3,K2S4); v=_mm256_shuffle_epi8(m3,K2);
v=_mm256_blend_epi32(_mm256_min_epu8(v,t4),_mm256_max_epu8(v,t4),0xAA); }
BSW(S2,0xAA)
{ __m256i t=_mm256_shuffle_epi8(v,S1); v=_mm256_max_epu16(v,t); }
#undef BSH
#undef BSW
#undef BSD
return v;
}
static inline __m256i bsort8x3hR(__m256i v,__m256i S1,__m256i S2,__m256i K0,__m256i K5r){
#define BSY(M,IMM) { __m256i t=_mm256_shuffle_epi8(v,M); v=_mm256_blend_epi16(_mm256_min_epu8(v,t),_mm256_max_epu8(v,t),IMM); }
{ __m256i t=_mm256_shuffle_epi8(v,S1); v=_mm256_shuffle_epi8(_mm256_min_epu16(v,t),K0); }
BSY(S2,0x66)
{ __m256i t=_mm256_shuffle_epi8(v,S1); v=_mm256_shuffle_epi8(_mm256_min_epu16(v,t),K5r); }
#undef BSY
return v;
}
static inline __m256i bsort8x3h(__m256i v,__m256i S1,__m256i S2,__m256i K0,__m256i K2){
#define BSY(M,IMM) { __m256i t=_mm256_shuffle_epi8(v,M); v=_mm256_blend_epi16(_mm256_min_epu8(v,t),_mm256_max_epu8(v,t),IMM); }
{ __m256i t=_mm256_shuffle_epi8(v,S1); v=_mm256_shuffle_epi8(_mm256_min_epu16(v,t),K0); }
BSY(S2,0x66)
{ __m256i t=_mm256_shuffle_epi8(v,S1); v=_mm256_shuffle_epi8(_mm256_min_epu16(v,t),K2); }
#undef BSY
#undef BSZ
return v;
}
static inline __m256i bmerge8bhMX(__m256i v,__m256i S1,__m256i S2,__m256i S4,__m256i K5){
/* MX12 form: positions 0..3 of every 8-byte group are 0xFF, so the merge's d=4 stage
reduces to a dword swap (min(FF,x)=x to the low dword, max(FF,x)=FF to the high one). */
#define BMX(M,IMM) { __m256i t=_mm256_shuffle_epi8(v,M); v=_mm256_blend_epi32(_mm256_min_epu8(v,t),_mm256_max_epu8(v,t),IMM); }
#define BMY(M,IMM) { __m256i t=_mm256_shuffle_epi8(v,M); v=_mm256_blend_epi16(_mm256_min_epu8(v,t),_mm256_max_epu8(v,t),IMM); }
v=_mm256_shuffle_epi8(v,S4);
BMY(S2,0xAA)
{ __m256i t=_mm256_shuffle_epi8(v,S1); v=_mm256_max_epu16(v,t); }
#undef BMX
#undef BMY
return v;
}
static inline __m256i bmerge8bh(__m256i v,__m256i S1,__m256i S2,__m256i S4,__m256i K5){
#define BMD(M,IMM) { __m256i t=_mm256_shuffle_epi8(v,M); v=_mm256_blend_epi32(_mm256_min_epu8(v,t),_mm256_max_epu8(v,t),IMM); }
#define BMW(M,IMM) { __m256i t=_mm256_shuffle_epi8(v,M); v=_mm256_blend_epi16(_mm256_min_epu8(v,t),_mm256_max_epu8(v,t),IMM); }
BMD(S4,0xAA)
BMW(S2,0xAA)
{ __m256i t=_mm256_shuffle_epi8(v,S1); v=_mm256_max_epu16(v,t); }
#undef BMH
#undef BMD
#undef BMW
return v;
}
// v99: bsort8 truncated to its first 3 stages -- fully sorts bytes 0..3 of each
// 8-byte group and leaves bytes 4..7 untouched. Bit-identical to the full
// bsort8 in those positions whenever bytes 4..7 are 0xFF (verified over 4.8e6
// random cases, work/e1001g/u5.cpp). The merge's B-half holds bytes 8..16 of a
// bucket, so this is valid exactly when c <= 12.
static inline __m256i bsort8x3(__m256i v){
#define BSTY(M,IMM) { __m256i t=_mm256_shuffle_epi8(v,BM(M)); v=_mm256_blend_epi16(_mm256_max_epu8(v,t),_mm256_min_epu8(v,t),IMM); }
#define BSTZ(M,S) { __m256i t=_mm256_shuffle_epi8(v,BM(M)); v=_mm256_blendv_epi8(_mm256_max_epu8(v,t),_mm256_min_epu8(v,t),BM(S)); }
BSTZ(g_sh1,g_selb[0]) BSTY(g_sh2,0x99) BSTZ(g_sh1,g_selb[2])
#undef BSTY
#undef BSTZ
return v;
}
// sorts each of 4 buckets' first 8 bytes; bytes[c..8) must be 0xFF (U is pre-filled)
static inline void emit4b(const u8*g0,const u8*g1,const u8*g2,const u8*g3,__m256i O[4]){
__m128i a=_mm_loadl_epi64((const __m128i*)g0);
__m128i b=_mm_loadl_epi64((const __m128i*)g1);
__m128i c=_mm_loadl_epi64((const __m128i*)g2);
__m128i d=_mm_loadl_epi64((const __m128i*)g3);
__m256i v=_mm256_set_m128i(_mm_unpacklo_epi64(c,d),_mm_unpacklo_epi64(a,b));
v=bsort8(v);
__m128i lo=_mm256_castsi256_si128(v), hi=_mm256_extracti128_si256(v,1);
O[0]=_mm256_cvtepu8_epi32(lo);
O[1]=_mm256_cvtepu8_epi32(_mm_srli_si128(lo,8));
O[2]=_mm256_cvtepu8_epi32(hi);
O[3]=_mm256_cvtepu8_epi32(_mm_srli_si128(hi,8));
}
static inline __m256i bmerge8b(__m256i v){
#define BMG(M,S) { __m256i t=_mm256_shuffle_epi8(v,BM(M)); v=_mm256_blendv_epi8(_mm256_max_epu8(v,t),_mm256_min_epu8(v,t),BM(S)); }
BSTD(g_sh4,0x55) BSTW(g_sh2,0x55) BMG(g_sh1,g_selb[5])
#undef BMG
return v;
}
// packed8 that also returns the packed sorted first-8 (A) and the raw bytes 8..16 (B)
static inline void emit4b2(const u8*g0,const u8*g1,const u8*g2,const u8*g3,__m256i*A,__m256i*B,__m256i O[4]){
__m128i x0=_mm_loadu_si128((const __m128i*)g0);
__m128i x1=_mm_loadu_si128((const __m128i*)g1);
__m128i x2=_mm_loadu_si128((const __m128i*)g2);
__m128i x3=_mm_loadu_si128((const __m128i*)g3);
__m256i a=_mm256_set_m128i(_mm_unpacklo_epi64(x2,x3),_mm_unpacklo_epi64(x0,x1));
*B=_mm256_set_m128i(_mm_unpackhi_epi64(x2,x3),_mm_unpackhi_epi64(x0,x1));
a=bsort8(a); *A=a;
__m128i lo=_mm256_castsi256_si128(a), hi=_mm256_extracti128_si256(a,1);
O[0]=_mm256_cvtepu8_epi32(lo);
O[1]=_mm256_cvtepu8_epi32(_mm_srli_si128(lo,8));
O[2]=_mm256_cvtepu8_epi32(hi);
O[3]=_mm256_cvtepu8_epi32(_mm_srli_si128(hi,8));
}
// ---- xc3 v20 (L3): the byte0 scatter cursor and the byte1 emit cursor hold values
// in [0,m2]; xc3_h0u is used only when m2 < 65536 (else the original u32 code runs), so
// both tables can be u16. Bit-identical: same values, same order. Halves the L1
// footprint of the two hottest RMW cursors and narrows their stores to 2 bytes.
static inline void xc3_z256u16(u16 *p){ const __m256i z=_mm256_setzero_si256();
for(u32 k=0;k<256u;k+=16) _mm256_storeu_si256((__m256i*)(p+k),z); }
static inline void xc3_pfx256u16(u16 *p){ __m256i c=_mm256_setzero_si256();
for(u32 k=0;k<256u;k+=16){
__m256i v=_mm256_loadu_si256((const __m256i*)(p+k));
__m256i in=_mm256_add_epi16(v,_mm256_slli_si256(v,2));
in=_mm256_add_epi16(in,_mm256_slli_si256(in,4));
in=_mm256_add_epi16(in,_mm256_slli_si256(in,8));
in=_mm256_add_epi16(in,_mm256_blend_epi32(_mm256_setzero_si256(),
_mm256_set1_epi16((short)(u16)_mm_extract_epi16(_mm256_castsi256_si128(in),7)),0xF0));
_mm256_storeu_si256((__m256i*)(p+k),_mm256_add_epi16(_mm256_sub_epi16(in,v),c));
c=_mm256_add_epi16(c,_mm256_set1_epi16((short)(u16)_mm256_extract_epi16(in,15))); } }
static u16 xc3_h0u[256];
// ---- xc3 v21 (L3): the byte1 histogram (cnt3) is the last u32 table on the robust
// path although its values are <= m2; xc3_cnt3u is used only when m2 < 65536, and if the
// SIMD path is selected instead, cnt3 is materialised in u32 from it before that path
// runs. The u32 code stays as the else branch. Bit-identical.
static inline void xc3_pfx256u16_to(const u16 * __restrict src,u16 * __restrict dst){
__m256i c=_mm256_setzero_si256();
for(u32 k=0;k<256u;k+=16){
__m256i v=_mm256_loadu_si256((const __m256i*)(src+k));
__m256i in=_mm256_add_epi16(v,_mm256_slli_si256(v,2));
in=_mm256_add_epi16(in,_mm256_slli_si256(in,4));
in=_mm256_add_epi16(in,_mm256_slli_si256(in,8));
in=_mm256_add_epi16(in,_mm256_blend_epi32(_mm256_setzero_si256(),
_mm256_set1_epi16((short)(u16)_mm_extract_epi16(_mm256_castsi256_si128(in),7)),0xF0));
_mm256_storeu_si256((__m256i*)(dst+k),_mm256_add_epi16(_mm256_sub_epi16(in,v),c));
c=_mm256_add_epi16(c,_mm256_set1_epi16((short)(u16)_mm256_extract_epi16(in,15))); } }
struct CursorOffsets { u32 v[256]; };
constexpr CursorOffsets make_cursor_offsets() {
CursorOffsets result{};
for (u32 i=0;i<256;++i) result.v[i]=i<<5;
return result;
}
alignas(64) static constexpr CursorOffsets cursor_offsets=make_cursor_offsets();
static u16 xc3_cnt3u[256];
static inline u32 xc3_max256u16(const u16 *p){ __m256i m=_mm256_setzero_si256();
for(u32 k=0;k<256u;k+=16) m=_mm256_max_epu16(m,_mm256_loadu_si256((const __m256i*)(p+k)));
__m128i v=_mm_max_epu16(_mm256_castsi256_si128(m),_mm256_extracti128_si256(m,1));
v=_mm_minpos_epu16(_mm_xor_si128(v,_mm_set1_epi16(-1)));
return 65535u-(u32)_mm_extract_epi16(v,0); }
static __attribute__((noinline)) u32 *emit_b1range(u32 b1,const u16 *p,const u32 *cnt3,u32 hi24,u32 *o){
for(u32 b=b1;b<b1+4;b++){ u32 c=cnt3[b]; if(!c) continue;
emit(g_U+(b*RS3MAX),c,hi24|(b<<8),o); o+=c; }
return o;
}
static inline void flush_partial(u32 *out,u32 pos,u32 partial[16],u32 &used){
if(!used) return;
u32 *dest=out+pos-used;
for(u32 j=0;j<used;j++) dest[j]=partial[j];
used=0;
}
static inline void emit_coalesced(u32 *out,u32 pos,const u32 *src,u32 n,
u32 partial[16],u32 &used){
u32 *dest=out+pos;
u32 i=0;
if(used){
u32 take=16u-used;
if(take>n) take=n;
for(u32 j=0;j<take;j++) partial[used+j]=src[j];
used+=take;i=take;
if(used==16u){
u32 *line=dest+i-16u;
__m256i lo=_mm256_load_si256((const __m256i*)partial);
__m256i hi=_mm256_load_si256((const __m256i*)(partial+8));
_mm256_stream_si256((__m256i*)line,lo);
_mm256_stream_si256((__m256i*)(line+8),hi);
used=0;
}
if(i==n) return;
}
while(i<n && ((uintptr_t)(dest+i)&63u)) {dest[i]=src[i];i++;}
for(;i+16u<=n;i+=16u){
__m256i lo=_mm256_load_si256((const __m256i*)(src+i));
__m256i hi=_mm256_load_si256((const __m256i*)(src+i+8));
_mm256_stream_si256((__m256i*)(dest+i),lo);
_mm256_stream_si256((__m256i*)(dest+i+8),hi);
}
used=n-i;
for(u32 j=0;j<used;j++) partial[j]=src[i+j];
}
// src: 24-bit records packed 3 bytes; m elements
static __attribute__((noinline)) bool sort_255_one_b2(const u8 *src,u32 m,u32 *out);
template<unsigned STRIDE> constexpr CursorOffsets make_strided_cursor_offsets(){
CursorOffsets result{};for(unsigned i=0;i<256;++i)result.v[i]=i*STRIDE;return result;
}
template<unsigned STRIDE>
static __attribute__((noinline)) void scatter_emit_small(const u16*p,u32 m2,const u32*cnt3,
u32*out,u32 m,u32 d3,u32 hi24,u32&pos){
constexpr u32 RS3=STRIDE;
u8 * __restrict U=g_U;
alignas(64) static constexpr CursorOffsets offsets=make_strided_cursor_offsets<STRIDE>();
alignas(32) u32 st3[256];memcpy(st3,offsets.v,sizeof(st3));
if constexpr(STRIDE==16)memset(U,255,4096);
else{
const __m128i ff=_mm_set1_epi8(-1);
for(u32 b=0;b<256;++b)_mm_store_si128((__m128i*)(U+b*STRIDE),ff);
}
#pragma GCC unroll 8
for(u32 i=0;i<m2;++i){const u32 r=p[i],b=r>>8,c=st3[b];U[c]=(u8)r;st3[b]=c+1;}
if(d3!=255u){
u32 *o=out+pos;
const __m256i HIV=_mm256_set1_epi32((int)hi24);
// ---- lane p1001_dead: HOIST THE SEVEN LOOP-INVARIANT TABLES OUT OF THE
// b1 LOOP. bsort8/bsort8x3/bmerge8b reference g_sh1, g_sh2, g_sh4,
// g_selb[0], g_selb[2], g_selb[5] and g_shrev at FIXED indices, so every
// one of those loads is loop-invariant -- but they are non-const statics,
// so GCC must re-issue them on every iteration. Loading them once here
// is bit-identical (same values, same instruction sequence otherwise).
const __m256i SH1=BM(g_sh1),SH2=BM(g_sh2),SH4=BM(g_sh4);
const __m256i K0S2=BM(g_k0s2),K2S4=BM(g_k2s4);
const __m256i K0=BM(g_mp0),K2=BM(g_mp2),K5=BM(g_mp2r);
__m256i Pn=_mm256_set_m128i(_mm_loadu_si128((const __m128i*)(U+(2*RS3))),_mm_loadu_si128((const __m128i*)(U)));
__m256i Qn=_mm256_set_m128i(_mm_loadu_si128((const __m128i*)(U+(3*RS3))),_mm_loadu_si128((const __m128i*)(U+RS3)));
for(u32 b1=0;b1<256;b1+=4){
u32 c0=cnt3[b1],c1=cnt3[b1+1],c2=cnt3[b1+2],c3=cnt3[b1+3];
u32 mx=c0; if(c1>mx)mx=c1; if(c2>mx)mx=c2; if(c3>mx)mx=c3;
// ---- lane p1001_ports: THE A HALF IS COMMON TO BOTH PATHS, SO SORT IT
// BEFORE THE BRANCH. The 8 low bytes of each of the 4 buckets form `AP`,
// and `bsort8(AP)` is EXACTLY what the old emit4b (packed path) and the old
// emit4b2 (merge arm) each computed SEPARATELY, once per taken branch. The
// packed path therefore pays NOTHING (same loads, same unpack, same network)
// while the arm stops paying a second A assembly + a second 6-stage network
// inside an unpredictable (15.3 % taken) branch, where its ~18 cycles of
// latency are largely EXPOSED rather than overlapped.
__m256i P=Pn,Q=Qn;
{ const u8 *nb=U+((b1+4)*RS3);
Pn=_mm256_set_m128i(_mm_loadu_si128((const __m128i*)(nb+(2*RS3))),_mm_loadu_si128((const __m128i*)(nb)));
Qn=_mm256_set_m128i(_mm_loadu_si128((const __m128i*)(nb+(3*RS3))),_mm_loadu_si128((const __m128i*)(nb+RS3))); }
__m256i AP=_mm256_unpacklo_epi64(P,Q);
__m256i A=bsort8h(AP,SH1,SH2,SH4,K0,K2,K5,K0S2,K2S4);
if(__builtin_expect(mx<=8u,1)){
__m128i lo=_mm256_castsi256_si128(A), hi=_mm256_extracti128_si256(A,1);
__m256i O0=_mm256_cvtepu8_epi32(lo), O1=_mm256_cvtepu8_epi32(_mm_srli_si128(lo,8));
__m256i O2=_mm256_cvtepu8_epi32(hi), O3=_mm256_cvtepu8_epi32(_mm_srli_si128(hi,8));
st256(o,_mm256_or_si256(_mm256_or_si256(O0,BM(g_pb1+8*b1)), HIV)); o+=c0;
st256(o,_mm256_or_si256(_mm256_or_si256(O1,BM(g_pb1+8*(b1+1))), HIV)); o+=c1;
st256(o,_mm256_or_si256(_mm256_or_si256(O2,BM(g_pb1+8*(b1+2))), HIV)); o+=c2;
st256(o,_mm256_or_si256(_mm256_or_si256(O3,BM(g_pb1+8*(b1+3))), HIV)); o+=c3;
continue;
}
// lane p1001_dead: TWO REMOVALS FROM THE ARM'S CRITICAL CHAIN.
// (1) The B half is NOT used by the >16 fallback (that path re-reads U), so
// its 3-instruction assembly (2 vpunpckhqdq + vinserti128) is moved
// BELOW the test instead of being computed on 100 % of arm entries.
// (2) `(c0|c1|c2|c3)>16u` is a CONSERVATIVE over-approximation of "some
// bucket exceeds 16" -- it fires for (16,1,1,1) too. `mx` (already
// computed 7 instructions earlier, for the mx<=8 test) is the EXACT
// test and costs nothing: it deletes `movl+3x orl` from every arm
// entry. The scalar path stays correct for every c, and a bucket with
// c>16 always sets mx>16, so the routing can only get tighter.
if(__builtin_expect(mx>16u,0)){
o=emit_b1range(b1,p,cnt3,hi24,o);
continue;
}
__m256i B=_mm256_unpackhi_epi64(P,Q);
// 9..16 elements in at least one bucket: one packed bitonic merge of (sorted first 8)
// with (sorted bytes 8..16); stores stay in bucket order so each 32B tail is
// overwritten by the next bucket's store (c>=9 for the buckets that get a tail).
// mx<=12 <=> all four buckets have c<=12 <=> every B half holds <=4 real
// values in its low 4 positions plus 0xFF padding, so 3 of bsort8's 6
// stages are provably dead. P(mx<=12 | mx>8) = 0.909.
__m256i MN,MX;
if(__builtin_expect(mx<=12u,1)){
__m256i BR=bsort8x3hR(B,SH1,SH2,K0,K5);
MN=bmerge8bh(_mm256_min_epu8(A,BR),SH1,SH2,SH4,K5);
MX=bmerge8bhMX(_mm256_max_epu8(A,BR),SH1,SH2,SH4,K5);
} else {
__m256i BR=_mm256_shuffle_epi8(bsort8h(B,SH1,SH2,SH4,K0,K2,K5,K0S2,K2S4),BM(g_shrev));
MN=bmerge8bh(_mm256_min_epu8(A,BR),SH1,SH2,SH4,K5);
MX=bmerge8bh(_mm256_max_epu8(A,BR),SH1,SH2,SH4,K5);
}
__m128i lo,hi;
lo=_mm256_castsi256_si128(MN); hi=_mm256_extracti128_si256(MN,1);
__m256i M0=_mm256_cvtepu8_epi32(lo), M1=_mm256_cvtepu8_epi32(_mm_srli_si128(lo,8));
__m256i M2=_mm256_cvtepu8_epi32(hi), M3=_mm256_cvtepu8_epi32(_mm_srli_si128(hi,8));
lo=_mm256_castsi256_si128(MX); hi=_mm256_extracti128_si256(MX,1);
__m256i X0=_mm256_cvtepu8_epi32(lo), X1=_mm256_cvtepu8_epi32(_mm_srli_si128(lo,8));
__m256i X2=_mm256_cvtepu8_epi32(hi), X3=_mm256_cvtepu8_epi32(_mm_srli_si128(hi,8));
u32 *p=o;
// branchless: MV==OO and XV==0xFF|HIV when CC<=8 (B is all-0xFF there, so
// min(A,BR)=A, bmerge8b(A)=A; max(A,BR)=bmerge8b(0xFF)=0xFF). The extra p+8
// store is garbage for CC<=8 but the NEXT bucket's store starts at p+CC and
// covers [p+CC, p+CC+64) â [p+CC, p+64) for CC<8, so it is always
// overwritten -- the same store-overlap chain the CC<=8 path already relies on.
// lane p1001_dead: `BM(PB)` was evaluated TWICE per invocation (once per store),
// so each of the four prefix vectors g_pb1[b1..b1+3] was loaded from memory twice
// per arm group -- 8 loads where 4 suffice -- and the whole `or(PB,HIV)` product
// was rebuilt for the second store. Named operands (an unused macro parameter is
// EXACTLY how a duplicate like this hides: OO looked like the dead one) turn it
// into one load + three ORs per bucket instead of two loads + four.
#define STK(CC,MV,XV,OO,PB) { __m256i PV=_mm256_or_si256(BM(PB),HIV); \
st256(p,_mm256_or_si256(MV,PV)); \
st256(p+8,_mm256_or_si256(XV,PV)); p+=CC; }
STK(c0,M0,X0,O[0],g_pb1+8*b1)
STK(c1,M1,X1,O[1],g_pb1+8*(b1+1))
STK(c2,M2,X2,O[2],g_pb1+8*(b1+2))
STK(c3,M3,X3,O[3],g_pb1+8*(b1+3))
#undef STK
o=p;
}
pos=(u32)(o-out);
} else {
for(u32 b1=0;b1<256;b1++){
u32 c=cnt3[b1];
if(!c) continue;
const u8 *g=U+(b1*RS3);
u32 prefix=hi24|(b1<<8);
u32 *o=out+pos;
u32 w=(c<=8)?8u:16u;
if(pos+w<=m) emit(g,c,prefix,o);
else emit_scalar(g,c,prefix,o);
pos+=c;
}
}
}
static void sort_region(const u8 *src,u32 m,u32 *out,u32 d3){
if(m<=64){
const u32 hi=(u32)d3<<24;
for(u32 i=0;i<m;i++){
u32 v=hi|((*(const u32*)(src+REC3*i))&0xFFFFFFu); u32 j=i;
while(j&&out[j-1]>v){ out[j]=out[j-1]; j--; }
out[j]=v;
}
return;
}
u32 RS2=(m>>8)+SLK2;
{ u32 k=(RS2+31u)>>5; if(!(k&1u)) k++; RS2=k<<5; } // odd # of 64B lines -> no L1 set aliasing
if(256u*RS2+m>TSZ || m>SCZ){ // pathological size: no scratch fits
const u32 hi=(u32)d3<<24;
for(u32 i=0;i<m;i++) out[i]=hi|((*(const u32*)(src+REC3*i))&0xFFFFFFu);
std::sort(out,out+m); return;
}
u16 * __restrict T=g_T;
u32 cb2[256]; u32 bs2[256];
if (d3 == 255u) {
u32 cs[256] = {};
{ const u8 *sp=src;
for (u32 i=0;i<m;i++) { ++cs[sp[2]]; sp+=REC3; }
}
u32 sum=0;
for (u32 b=0;b<256;b++) { u32 c=cs[b];cb2[b]=c;bs2[b]=sum;cs[b]=sum;sum+=c; }
{ const u8 *sp=src;
for (u32 i=0;i<m;i++) { u32 lo=*(const u16*)sp;u32 b2=sp[2];sp+=REC3;T[cs[b2]++]=lo; }
}
} else {
u32 st2[256];
for(u32 b=0;b<256;b++) st2[b]=b*RS2;
{
const u8 * __restrict sp=src;
/* [PFSRC6] 见文件头思路段:原形态每 16 元素一条 `prefetchnta sp+512`(覆盖率 64/48 = 1.33x),
且守卫 `(i&15u)==0` 是每元素 2 条 uop。本形态 = 外层 64 记录(192 B)定长分块 + 块首 6 条预取
(覆盖 [sp+512, sp+896) = 384 B,覆盖率 2x)+ 内层定长 64(= 4x16,无余数,保住展开)。 */
u32 i=0;
for(;i+64u<=m;i+=64u){
/* [PF-R1] 见文件头思路段:§2.19.455 —— 预取轮次频率 = 64 B line 的消费频率(必须 = 1)。
本块消费 64 记录 = 192 B = 3 line ⇒ 轮次必须是 3 条(覆盖 [sp+512, sp+704),1x 覆盖、
无裸 line、每 line 恰被覆盖一次)。原 6 条 = 2x 覆盖(冗余一半),本发只改条数这一项。 */
__builtin_prefetch((const void*)(sp+512),0,0);
__builtin_prefetch((const void*)(sp+576),0,0);
__builtin_prefetch((const void*)(sp+640),0,0);
#pragma GCC unroll 16
for(u32 j=0;j<64u;j++){
u32 lo=*(const u16*)sp; u32 b2=sp[2]; sp+=REC3;
T[st2[b2]++]=lo;
}
}
#pragma GCC unroll 8
for(;i<m;i++){
u32 lo=*(const u16*)sp; u32 b2=sp[2]; sp+=REC3;
T[st2[b2]++]=lo;
}
}
// ---- ROBUST L2: if any byte2 sub-bucket overflows its fixed region, redo the
// scatter as a compact counting sort instead of falling back to std::sort.
// This fires on the judge's 1001c data (one region), where the old path cost ~35 ms.
{ int ovf=0;
for(u32 b=0;b<256;b++){ u32 c=st2[b]-b*RS2; cb2[b]=c; if(c>RS2) ovf=1; }
if(!ovf){ for(u32 b=0;b<256;b++) bs2[b]=b*RS2; }
else{
u32 cs[256];
for(u32 b=0;b<256;b++) cs[b]=0;
{ const u8 * __restrict sp=src;
for(u32 i=0;i<m;i++){ cs[sp[2]]++; sp+=REC3; } }
u32 s=0;
for(u32 b=0;b<256;b++){ u32 c=cs[b]; cb2[b]=c; bs2[b]=s; cs[b]=s; s+=c; }
{ const u8 * __restrict sp=src;
for(u32 i=0;i<m;i++){ u32 lo=*(const u16*)sp; u32 b2=sp[2]; sp+=REC3; T[cs[b2]++]=lo; } }
}
}
}
u32 pos=0;
alignas(64) u32 partial[16]; u32 partial_used=0;
for(u32 b2=0;b2<256;b2++){
u32 m2=cb2[b2];
if(!m2) continue;
const u16 *p=T+bs2[b2];
u32 hi24=(b2<<16)|(d3<<24);
if(m2<=8){
flush_partial(out,pos,partial,partial_used);
u32 buf[8];
for(u32 i=0;i<m2;i++){ u32 v=(u32)p[i]|hi24; u32 j=i;
while(j&&buf[j-1]>v){ buf[j]=buf[j-1]; j--; } buf[j]=v; }
for(u32 i=0;i<m2;i++) out[pos+i]=buf[i];
pos+=m2; continue;
}
// ---- e1001o: RS3 pinned to RS3MAX = 32 = 2^5 (see sol_v103_rs3.cpp header) --
const u32 RS3=RS3MAX;
const u32 RSH=5u; // log2(RS3); b*RS3 is a shift everywhere
u8 * __restrict U=g_U;
alignas(32) u32 cnt3[256];
u32 bad=0,dense16=0;
static u32 h0[256];
u16 *h0u=xc3_h0u; u32 use16=(m2<65536u);
// ---- change (this submission): build the byte1 histogram (cnt3) AND the byte0
// histogram (h0) in ONE cheap scalar pass BEFORE the U scatter. When `bad` fires the
// U scatter's only remaining job is to produce cnt3, so it is skipped outright
// (7 uop/element + an 8 KB table write); cnt3's values are identical, so the decision
// is unchanged. The robust branch then reuses h0 and loses a whole read pass over p[].
{ if(use16){
xc3_z256u16(xc3_cnt3u); xc3_z256u16(h0u);
#pragma GCC unroll 16
for(u32 i=0;i<m2;i++){ u32 r=p[i]; xc3_cnt3u[r>>8]++; h0u[r&255u]++;
} }
else{
for(u32 k=0;k<256u;k++) cnt3[k]=0;
for(u32 k=0;k<256u;k++) h0[k]=0;
#pragma GCC unroll 8
for(u32 i=0;i<m2;i++){ u32 r=p[i]; cnt3[r>>8]++; h0[r&255u]++; } }
u32 mx=0;
if(use16) mx=xc3_max256u16(xc3_cnt3u);
else for(u32 k=0;k<256u;k++) if(cnt3[k]>mx) mx=cnt3[k];
if(mx>RS3) bad=1;
dense16=(mx<=16u);
if(use16 && !bad){ /* materialise cnt3 (u32) for the SIMD path below */
for(u32 k=0;k<256u;k+=16){ __m256i v=_mm256_loadu_si256((const __m256i*)(xc3_cnt3u+k));
_mm256_store_si256((__m256i*)(cnt3+k), _mm256_cvtepu16_epi32(_mm256_castsi256_si128(v)));
_mm256_store_si256((__m256i*)(cnt3+k+8), _mm256_cvtepu16_epi32(_mm256_extracti128_si256(v,1))); } } }
if(bad){
// ---- ROBUST L3: byte0 counting pass + scatter into a scratch, then a
// single emit pass bucketed by BYTE1 -- and cnt3 IS ALREADY the byte1
// histogram of p[], so that pass is not repeated. All tables are 256
// entries (L1) and the byte1 bucket sizes come from a 4-bank histogram, so
// no step has a serial RMW chain. (Two earlier forms were board-priced:
// an O(m2^2) insertion sort -> 27.4 s, and a 65536-entry single counting
// sort -> 2.203 s. This is the third.)
u32 *o=out+pos;
u16 *sc=g_sc;
if(use16){ /* xc3 v20: same tables, u16 (values <= m2 < 65536) */
u16 off1[256], *h16=h0u;
xc3_pfx256u16_to(xc3_cnt3u,off1);
xc3_pfx256u16(h16);
for(u32 i=0;i<m2;i++){ u32 v=p[i]; sc[h16[v&255u]++] = (u16)v; }
u32 pad=((u32)((uintptr_t)o>>2))&15u;
u32 *sc2=g_sc2+pad;
{ u32 HI=hi24; __asm__("" : "+r"(HI)); /* [HIREG] 把循环不变的 hi24 钉在寄存器里 */
#pragma GCC unroll 16
for(u32 i=0;i<m2;i++){ u32 v=sc[i]; sc2[off1[v>>8]++] = HI|v; } }
emit_coalesced(out,pos,sc2,m2,partial,partial_used);
pos+=m2; continue; }
flush_partial(out,pos,partial,partial_used);
u32 *h = h0; /* byte0 histogram already built by the pass above */
u32 off1[256];
{ u32 s=0;
for(u32 k=0;k<256u;k++){ off1[k]=s; s+=cnt3[k]; } }
{ u32 s=0; for(u32 k=0;k<256u;k++){ u32 c=h[k]; h[k]=s; s+=c; } }
for(u32 i=0;i<m2;i++){ u32 v=p[i]; sc[h[v&255u]++] = (u16)v; }
{ u32 HI=hi24; __asm__("" : "+r"(HI));
for(u32 i=0;i<m2;i++){ u32 v=sc[i]; o[off1[v>>8]++] = HI | v; } }
pos+=m2; continue;
}
flush_partial(out,pos,partial,partial_used);
if(dense16)scatter_emit_small<16>(p,m2,cnt3,out,m,d3,hi24,pos);
else scatter_emit_small<32>(p,m2,cnt3,out,m,d3,hi24,pos);
}
flush_partial(out,pos,partial,partial_used);
}
__attribute__((noinline)) void pospad(void) {
__asm__ __volatile__(".fill 155,1,0x90 ; ret");
}
__attribute__((noinline)) void pospadB(void) {
__asm__ __volatile__(".fill 139,1,0x90 ; ret");
}
void sort(unsigned *a,int n){
static int initt=0;
if(!initt){ initt=1; init_pb1(); init_bmasks(); }
static int init=0;
if(g_noavx_dbg>=0) g_noavx=g_noavx_dbg; else if(!init){ init=1; g_noavx = __builtin_cpu_supports("avx2")?0:1; }
{
// ---- v102 (e1001n cursor x e1001m unroll) ---------------------------------
// (a) e1001n: the L1 cursor is a 4-BYTE BYTE-OFFSET (u32 off[256], 1 KB)
// instead of 8-byte pointers (cur[256] + ecur[256], 4 KB); the block base
// is COMPUTED from d and the bound test is against the CONSTANT BLK-3, so
// the ecur[] array and its load do not exist. Harness ct_cur2.cpp #26:
// 3.9820 vs controls 4.1660/4.1621 = -0.184 cyc/el, 3x reproduced.
// BOARD-ALONE: 390.873 (sid 91469) vs controls 395.123 / 395.121.
// (b) e1001m: the same loop hand-unrolled 8x (their -0.25 ms on this base).
// --------------------------------------------------------------------------
u16 off[256]; u32 base[256], ob[256]; // xc4_e7: 游标改 u16(值域 49152 < 65536 ⇒ 逐位不变),数组 1KB->512B
for(u32 b=0;b<256;b++){ u32 bs=REC3*(b*STR1M+64u*((b*1237u)&7u)); base[b]=bs; ob[b]=bs; off[b]=b*(u32)g_blkst; }
const u32 * __restrict aa=a; u8 * __restrict S=g_S8; u8 * __restrict SG=g_stg;
// lane p1001_dead: THE BOUND TEST USES THE **NEW** CURSOR, SO NO VALUE NEEDS TO
// SURVIVE THE BRANCH. The shipped body loads o, stores the payload, and then
// needs the SAME o again for the bound test and the flush -- but o is already
// advanced, so GCC-9 keeps TWO extra register copies per element
// (`movl %r11d,%ebx` and `movq %r11,%rcx`: 2 of the loop's 11 instructions, and
// they exist ONLY to survive the branch). Since 3 and 64 are coprime and the
// block stride is a multiple of 64, (o_old&63)==61 is EXACTLY (o_old+3&63)==0,
// so the test can run on the post-increment value: 9 instructions/element
// (movzbl,movl,movl,leal,movl store,movq,testb,je,movl store) against 11.
#define L2BODY(K) { u32 v=aa[i+(K)], d=v>>24; u32 o=off[d]; u32 _n=o+REC3; off[d]=_n; if(__builtin_expect((_n&63u)==0u,0)){ off[d]=_n-BLK; *(u16*)(SG+_n-3)=(u16)v; SG[_n-1]=(u8)(v>>16); u32 f=ob[d]; ob[d]=f+BLK; u8*_fq=SG+(_n-BLK); u8*_fr=S+f; for(u32 _fk=0;_fk<BLK;_fk+=32) _mm256_stream_si256((__m256i*)(_fr+_fk),_mm256_load_si256((const __m256i*)(_fq+_fk))); } else *(u32*)(SG+o)=v; }
{ int i=0;
for(;i+16<=n;i+=16){
// lane p1001_lock PROBE P2b: the WRITE touch of `a`'s page p+1, issued at the
// FIRST iteration of page p and placed BEFORE that iteration's loads. Element
// i+1024 is the first element of the NEXT page and is read by no statement until
// iteration i+1024 -- a full page (128 iterations, ~11 000 instructions) later,
// i.e. ~50x the ROB -- so the write-touch ALWAYS retires (and its fault is
// handled, mapping the page writable+dirty) LONG before that page is read.
// P2a put the touch one iteration ahead of the read and LOST THE RACE (measured:
// the process's own minor-fault counter doubles, 268 561 vs 170 905 -- see
// FINDINGS.txt sec. 3), because the scheduler hoists the next iteration's loads
// above it. A write touch that is not the page's FIRST access is worse than none.
if(__builtin_expect((i&1023u)==0u && i+8208<(int)n,0))
__asm__ volatile("orl $0, %0" : "+m"(((u32*)aa)[i+8208]));
L2BODY(0) L2BODY(1) L2BODY(2) L2BODY(3)
L2BODY(4) L2BODY(5) L2BODY(6) L2BODY(7)
L2BODY(8) L2BODY(9) L2BODY(10) L2BODY(11)
L2BODY(12) L2BODY(13) L2BODY(14) L2BODY(15) }
for(;i<n;i++){ L2BODY(0) }
}
#undef L2BODY
_mm_sfence();
int bad=0; u32 tot=0;
for(u32 b=0;b<256;b++){
u32 rem=off[b]%(u32)g_blkst; // off[] is absolute: off[b] = b*BLKST + slot(0..191)
u32 cnt=(ob[b]-base[b])/BLK*BLKREC+rem/REC3;
if(cnt>CAP1M) bad=1;
g_off[b]=tot; tot+=cnt;
}
g_off[256]=tot;
if(bad || tot!=(u32)n){
// ---- EXACT L1 FALLBACK: a counting-sort pass by byte3 instead of
// std::sort on the whole array (which cost ~15 s on any skewed pattern).
// No fixed-capacity assumption; correct for every distribution.
u32 c3[256];
for(u32 b=0;b<256;b++) c3[b]=0;
{ const u8 * __restrict q=(const u8*)a;
for(int i=0;i<n;i++) c3[q[4*(size_t)i+3]]++; }
u32 o3[257]; u32 s3=0;
for(u32 b=0;b<256;b++){ o3[b]=s3; s3+=c3[b]; }
o3[256]=s3;
u32 w3[256];
for(u32 b=0;b<256;b++) w3[b]=o3[b]*REC3;
for(int i=0;i<n;i++){
u32 v=a[i]; u32 d=v>>24; u32 pp=w3[d]; w3[d]=pp+REC3;
u8*q2=g_S8+pp; *(u16*)q2=(u16)v; q2[2]=(u8)(v>>16);
}
for(u32 b=0;b<256;b++){
u32 m=c3[b];
if(!m) continue;
sort_region(g_S8+o3[b]*REC3, m, a+o3[b], b);
}
_mm_sfence(); return;
}
for(u32 b=0;b<256;b++){
u32 m=g_off[b+1]-g_off[b];
if(!m) continue;
u32 rem=off[b]%(u32)g_blkst;
if(rem){ // flush the partial staging block
u8*q=SG+(off[b]-rem); u8*r=g_S8+ob[b];
for(u32 k=0;k<rem;k++) r[k]=q[k];
}
// re-anchor: region data now starts at base[b] and is contiguous? No -
// blocks were flushed at ob[] which is base[b]+BLK*k, and the partial tail
// sits right after the last full block, so base[b] is still the start.
if(b==255u && m>65535u && sort_255_one_b2(g_S8+base[b],m,a+g_off[b])) continue;
sort_region(g_S8+base[b], m, a+g_off[b], b);
}
_mm_sfence();
volatile u8 l_pad[1920u]; l_pad[0]=1;
sink64(l_pad[0]);
}
}
static __attribute__((noinline)) bool sort_255_one_b2(const u8 *src,u32 m,u32 *out){
u32 b2=src[2];
static u32 hist[65536];
memset(hist,0,sizeof(hist));
// Checking the uniform high byte and counting the low word consume one stream.
const u8 *p=src;
for(u32 i=0;i<m;i++,p+=REC3){
if(p[2]!=b2) return false;
++hist[*(const u16*)p];
}
u32 *dst=out,high=(255u<<24)|(b2<<16);
for(u32 v=0;v<65536u;v++){u32 c=hist[v];if(c){std::fill_n(dst,c,high|v);dst+=c;}}
return true;
}
| Compilation | N/A | N/A | Compile OK | Score: N/A | 显示更多 |
| Testcase #1 | 635.983 ms | 898 MB + 480 KB | Accepted | Score: 100 | 显示更多 |