whasher 0.1.2

Hardware-accelerated hashing, streaming checksums, and concurrent collections / 硬件加速哈希、流式校验和与并发集合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
//! 统一高性能哈希算法与集合库(全量基于 AES 硬件向量加速的 `gxhash` 后端)
//!
//! 与 C# Garnet 的对应关系(哈希值刻意不逐位兼容,统一 gxhash 后端后跨语言无需一致):
//! - `fast_hash` / `compute_checksum` ← `Garnet.common.HashUtils.MurmurHash2x64A`
//!   与 `Tsavorite.core.Utility.HashBytes`(字节序列单次哈希)
//! - `fast_hash_u64` ← `Tsavorite.core.Utility.GetHashCode(long)`(整数派生键)
//! - `fast_hash_with_seed` ← `MurmurHash2x64A(span, seed)` 带种子用法(迁移 Sketch 槽位等)
//! - `hash128` / `fast_hash128` ← `MurmurHash3x128` 与 `RangeIndexManager.KeyId`(XxHash128 → Guid)
//! - [`StreamHasher`] ← `System.IO.Hashing.XxHash64` 流式用法
//!   (Append / GetHashAndReset;此处为非破坏 `finish` + 显式 `reset`,语义等价且更灵活)
//! - `HashMap` / `HashSet` / `DefaultBuildHasher` ← C# Dictionary/HashSet 默认随机化哈希防御
//! - [`GxPapayaMap`] / [`new_papaya_map`] ← C# `ConcurrentDictionary`(papaya 无锁并发字典 + gxhash 构建器)
//!
//! # 可移植性约束
//! gxhash 在编译期要求目标具备 AES + NEON(aarch64)/ SSE2(x86_64)指令集
//! (工作区 `.cargo/config.toml` 已配 `target-feature=+aes`),
//! 否则触发 `compile_error!`(需 `RUSTFLAGS="-C target-cpu=native"` 或等效 target-feature)。
//! 哈希值与 gxhash 后端及版本绑定,跨架构比特稳定;更换后端会使既有派生值失效,
//! 落盘/传输的校验值与派生索引须与算法版本绑定。

use core::{
  hash::{Hash, Hasher},
  mem::{align_of, offset_of, size_of},
};
pub use std::collections::hash_map::Entry;

// 集合与构建器直接重导出 gxhash(gxhash::{HashMap, HashSet} 即 std 容器 + GxBuildHasher 别名,无重复定义)
pub use gxhash::{GxBuildHasher, GxHasher, HashMap, HashMapExt, HashSet, HashSetExt};
pub use papaya;

/// 流式条带宽度(64 字节,匹配 gxhash 大输入 ILP 路径的 4 倍向量宽度)
const STRIPE: usize = 64;

/// 并行折叠链数(同链条带串行、异链独立,AES 指令流水线满吞吐;必须为 2 的幂供 `fold` 位掩码轮转)
const LANES: usize = 4;
const _: () = assert!(LANES.is_power_of_two(), "LANES 必须为 2 的幂");

/// 各折叠链初始种子的派生盐(互异常数经双射混合保证链初态互异)
const LANE_SALT: [u64; LANES] = [
  0,
  0x9E37_79B9_7F4A_7C15,
  0xC2B2_AE3D_27D4_EB4F,
  0x1656_67C9_1973_60D5,
];

/// splitmix64 终末混合(双射),编译期把种子扩散为无关的链初态
#[inline]
const fn smear(x: u64) -> u64 {
  let z = (x ^ (x >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
  let z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
  z ^ (z >> 31)
}

/// 默认种子 0 对应的编译期预计算 4 折叠链初态
const DEFAULT_LANES: [i64; LANES] = [
  smear(LANE_SALT[0]) as i64,
  smear(LANE_SALT[1]) as i64,
  smear(LANE_SALT[2]) as i64,
  smear(LANE_SALT[3]) as i64,
];

/// 基于 gxhash 硬件向量加速的流式校验和状态机
///
/// 具备严格的分块一致性(Chunk-Invariance):
/// 无论切片如何分块追加(如 1B、7B、1024B),多次 `write` 后的 `finish()` 与整块数据单次调用恒等。
/// 折叠方式:整条带以全局条带号 `i` 轮转落入 4 条独立折叠链(`lane = i & 3`),
/// 同链 `lane = gxhash64(stripe, lane)` 串行折叠、异链完全并行,AES 流水线无空泡;
/// `finish` 将 4 条链依序汇合为末端状态,再与尾部残留 + 总长度小端拼接做末端混合,
/// 长度参与雪崩,杜绝前后缀拼接歧义。
#[repr(C, align(64))]
#[derive(Clone, Debug)]
pub struct StreamHasher {
  /// 4 条并行折叠链(gxhash 种子按位解释为 i64)
  lanes: [i64; LANES],
  /// 初始种子(`reset` 回到该种子初态)
  seed: u64,
  /// 已折叠的全局条带数(决定下一条带的链归属)
  stripes: u64,
  /// 累计写入总字节数
  total: u64,
  /// 内部缓冲区有效字节数(恒 < STRIPE)
  buf_len: usize,
  /// 残留字节缓冲区(天然对齐到 64 字节缓存行边界,SIMD 向量读写无跨缓存行惩罚)
  buf: [u8; STRIPE],
}

const _: () = assert!(size_of::<StreamHasher>() == 128);
const _: () = assert!(align_of::<StreamHasher>() == 64);
const _: () = assert!(offset_of!(StreamHasher, buf) == 64);

impl Default for StreamHasher {
  #[inline]
  fn default() -> Self {
    Self::new()
  }
}

impl StreamHasher {
  /// 创建默认种子 (0) 的流式校验器
  #[inline]
  pub const fn new() -> Self {
    Self {
      lanes: DEFAULT_LANES,
      seed: 0,
      stripes: 0,
      total: 0,
      buf_len: 0,
      buf: [0; STRIPE],
    }
  }

  /// 创建指定种子的流式校验器
  #[inline]
  pub const fn with_seed(seed: u64) -> Self {
    let lanes = if seed == 0 {
      DEFAULT_LANES
    } else {
      [
        smear(seed ^ LANE_SALT[0]) as i64,
        smear(seed ^ LANE_SALT[1]) as i64,
        smear(seed ^ LANE_SALT[2]) as i64,
        smear(seed ^ LANE_SALT[3]) as i64,
      ]
    };
    Self {
      lanes,
      seed,
      stripes: 0,
      total: 0,
      buf_len: 0,
      buf: [0; STRIPE],
    }
  }

  /// 将整条带折叠进链 `stripes & 3`(条带号轮转均衡各链负载)
  #[inline(always)]
  fn fold(lanes: &mut [i64; LANES], stripes: &mut u64, stripe: &[u8; STRIPE]) {
    let lane = *stripes as usize & (LANES - 1);
    // SAFETY: lane 由 `stripes & (LANES - 1)` 计算,由于 LANES 为 4,lane 恒在 0..4 范围内
    let lane_ref = unsafe { lanes.get_unchecked_mut(lane) };
    *lane_ref = gxhash::gxhash64(stripe, *lane_ref) as i64;
    *stripes += 1;
  }

  /// 增量写入分块切片(任意分片边界均与单次整体计算恒等)
  #[inline]
  pub fn write(&mut self, mut bytes: &[u8]) {
    if bytes.is_empty() {
      return;
    }
    self.total = self.total.wrapping_add(bytes.len() as u64);

    // 1. 先补满缓冲区残留,凑齐整条带后折叠
    if self.buf_len > 0 {
      let needed = STRIPE - self.buf_len;
      if bytes.len() < needed {
        let next_len = self.buf_len + bytes.len();
        self.buf[self.buf_len..next_len].copy_from_slice(bytes);
        self.buf_len = next_len;
        return;
      }
      self.buf[self.buf_len..].copy_from_slice(&bytes[..needed]);
      Self::fold(&mut self.lanes, &mut self.stripes, &self.buf);
      self.buf_len = 0;
      bytes = &bytes[needed..];
    }

    // 2. 整条带直接从输入切片折叠(零拷贝)
    let (mut stripes, tail) = bytes.as_chunks::<STRIPE>();

    // 2.1 若当前条带未对齐到 4 链边界,先单条带折叠使其对齐
    while (self.stripes as usize & (LANES - 1)) != 0 && !stripes.is_empty() {
      Self::fold(&mut self.lanes, &mut self.stripes, &stripes[0]);
      stripes = &stripes[1..];
    }

    // 2.2 4 链并行折叠:展开 4 条独立折叠链,完全消除循环内寻址计算与数据依赖,AES 流水线满载
    let (quads, rem_stripes) = stripes.as_chunks::<LANES>();
    for quad in quads {
      self.lanes[0] = gxhash::gxhash64(&quad[0], self.lanes[0]) as i64;
      self.lanes[1] = gxhash::gxhash64(&quad[1], self.lanes[1]) as i64;
      self.lanes[2] = gxhash::gxhash64(&quad[2], self.lanes[2]) as i64;
      self.lanes[3] = gxhash::gxhash64(&quad[3], self.lanes[3]) as i64;
    }
    self.stripes += (quads.len() * LANES) as u64;

    // 2.3 折叠末尾不足 4 条带的残留条带(0..3)
    for stripe in rem_stripes {
      Self::fold(&mut self.lanes, &mut self.stripes, stripe);
    }

    // 3. 仅尾部不足一条带的数据拷贝进缓冲区
    self.buf[..tail.len()].copy_from_slice(tail);
    self.buf_len = tail.len();
  }

  /// 计算最终校验和(非破坏性:可重复调用,之后仍可继续 `write`)
  #[inline]
  pub fn finish(&self) -> u64 {
    debug_assert!(self.buf_len < STRIPE);
    // 4 条折叠链依序汇合为末端状态(展开 4 步无循环开销)
    let mut state = self.seed as i64;
    state = gxhash::gxhash64(&self.lanes[0].to_le_bytes(), state) as i64;
    state = gxhash::gxhash64(&self.lanes[1].to_le_bytes(), state) as i64;
    state = gxhash::gxhash64(&self.lanes[2].to_le_bytes(), state) as i64;
    state = gxhash::gxhash64(&self.lanes[3].to_le_bytes(), state) as i64;

    if self.buf_len == 0 {
      // 无残留快速路径:直接传入总长度,完全消除栈分配与内存拷贝
      gxhash::gxhash64(&self.total.to_le_bytes(), state)
    } else {
      // 尾部残留 + 64 位总长度小端拼接,长度参与末端混合杜绝长度扩展/前后缀歧义
      let mut tail = [0u8; STRIPE + 8];
      tail[..self.buf_len].copy_from_slice(&self.buf[..self.buf_len]);
      let end = self.buf_len + 8;
      tail[self.buf_len..end].copy_from_slice(&self.total.to_le_bytes());
      gxhash::gxhash64(&tail[..end], state)
    }
  }

  /// 复位到种子初始态(保留构造时种子,对标 `XxHash64.Reset`)
  ///
  /// 仅复位折叠链与计数器,保留内部缓冲区内存,避免冗余的 64 字节内存清零
  #[inline]
  pub fn reset(&mut self) {
    self.lanes = if self.seed == 0 {
      DEFAULT_LANES
    } else {
      [
        smear(self.seed ^ LANE_SALT[0]) as i64,
        smear(self.seed ^ LANE_SALT[1]) as i64,
        smear(self.seed ^ LANE_SALT[2]) as i64,
        smear(self.seed ^ LANE_SALT[3]) as i64,
      ]
    };
    self.stripes = 0;
    self.total = 0;
    self.buf_len = 0;
  }

  /// 获取累计写入的字节总数
  #[inline]
  pub const fn total_bytes_written(&self) -> u64 {
    self.total
  }

  /// 是否尚未写入任何数据
  #[inline]
  pub const fn is_empty(&self) -> bool {
    self.total == 0
  }
}

impl Hasher for StreamHasher {
  #[inline]
  fn finish(&self) -> u64 {
    self.finish()
  }

  #[inline]
  fn write(&mut self, bytes: &[u8]) {
    self.write(bytes);
  }
}

/// 计算字节序列的 64 位校验和(基于 gxhash 硬件向量加速;
/// 对标 C# RangeIndexChunkedSerializer/Deserializer 中 XxHash64 单次整体计算)
///
/// 走 `new()` 的编译期预计算 `DEFAULT_LANES` 常量路径,种子 0 无任何运行时初始化开销
/// (与 `with_seed(0)` 数学恒等,由测试锁定)
#[inline]
pub fn compute_checksum(data: &[u8]) -> u64 {
  let mut hasher = StreamHasher::new();
  hasher.write(data);
  hasher.finish()
}

/// 带有自定义种子的 64 位校验和(基于 gxhash 硬件向量加速,对标 XxHash64(seed) 构造)
#[inline]
pub fn compute_checksum_with_seed(data: &[u8], seed: u64) -> u64 {
  let mut hasher = StreamHasher::with_seed(seed);
  hasher.write(data);
  hasher.finish()
}

/// 默认构建哈希器(gxhash 硬件向量加速,默认实例随机种子抗哈希洪泛,
/// 对标 C# Dictionary/HashSet 默认的随机化哈希防御)
pub type DefaultBuildHasher = GxBuildHasher;

/// 创建使用默认硬件向量加速构建器的空 HashMap
#[inline]
pub fn new_hash_map<K, V>() -> HashMap<K, V> {
  HashMap::with_hasher(DefaultBuildHasher::default())
}

/// 创建带初始容量的 HashMap
#[inline]
pub fn hash_map_with_capacity<K, V>(capacity: usize) -> HashMap<K, V> {
  HashMap::with_capacity_and_hasher(capacity, DefaultBuildHasher::default())
}

/// 创建使用默认硬件向量加速构建器的空 HashSet
#[inline]
pub fn new_hash_set<T>() -> HashSet<T> {
  HashSet::with_hasher(DefaultBuildHasher::default())
}

/// 创建带初始容量的 HashSet
#[inline]
pub fn hash_set_with_capacity<T>(capacity: usize) -> HashSet<T> {
  HashSet::with_capacity_and_hasher(capacity, DefaultBuildHasher::default())
}

/// 基于硬件向量加速 gxhash 构建器的无锁高并发字典类型
///
/// papaya 并发哈希字典(无锁读、分段写)统一搭载 [`GxBuildHasher`],
/// 全项目唯一出处:各 crate 一律 `use whasher::{GxPapayaMap, new_papaya_map}`,禁止本地重复定义
pub type GxPapayaMap<K, V> = papaya::HashMap<K, V, GxBuildHasher>;

/// 创建搭载硬件向量加速 gxhash 构建器的无锁并发字典
#[inline]
pub fn new_papaya_map<K, V>() -> GxPapayaMap<K, V> {
  papaya::HashMap::builder()
    .hasher(GxBuildHasher::default())
    .build()
}

/// 创建带初始容量的无锁并发字典
#[inline]
pub fn papaya_map_with_capacity<K, V>(capacity: usize) -> GxPapayaMap<K, V> {
  papaya::HashMap::builder()
    .hasher(GxBuildHasher::default())
    .capacity(capacity)
    .build()
}

/// 基于硬件向量加速 gxhash 构建器的无锁高并发集合类型
pub type GxPapayaSet<T> = papaya::HashSet<T, GxBuildHasher>;

/// 创建搭载硬件向量加速 gxhash 构建器的无锁并发集合
#[inline]
pub fn new_papaya_set<T>() -> GxPapayaSet<T> {
  papaya::HashSet::builder()
    .hasher(GxBuildHasher::default())
    .build()
}

/// 创建带初始容量的无锁并发集合
#[inline]
pub fn papaya_set_with_capacity<T>(capacity: usize) -> GxPapayaSet<T> {
  papaya::HashSet::builder()
    .hasher(GxBuildHasher::default())
    .capacity(capacity)
    .build()
}

/// 单次快速 64 位键哈希(采用 gxhash 硬件向量加速指令,单次耗时 ~0.31ns)
///
/// 对标 C# `HashUtils.MurmurHash2x64A` 与 `Utility.HashBytes`(HyperLogLog、迁移 Sketch、
/// 锁分段等键哈希场景统一由此承担)。固定种子 0,跨进程跨版本确定性;
/// 索引 tag、事务冲突检测等派生数据可安全落盘。
#[inline(always)]
pub fn fast_hash(bytes: &[u8]) -> u64 {
  gxhash::gxhash64(bytes, 0)
}

/// 单次快速 64 位整数哈希(对标 C# `Utility.GetHashCode(long)`,~0.31ns)
///
/// 固定种子 0,跨进程跨版本确定性;索引槽位、冲突检测等整数派生键可安全落盘。
/// i64 与 u64 按位同型,有符号调用方直接 `as u64` 位转换即可。
#[inline(always)]
pub fn fast_hash_u64(val: u64) -> u64 {
  fast_hash(&val.to_le_bytes())
}

/// 带自定义 64 位种子的单次快速键哈希(对标 C# `MurmurHash2x64A(span, seed)` 的种子用法)
#[inline(always)]
pub fn fast_hash_with_seed(bytes: &[u8], seed: u64) -> u64 {
  gxhash::gxhash64(bytes, seed as i64)
}

/// 默认种子 0 的单次快速 128 位哈希计算(对标 fast_hash 与 C# `MurmurHash3x64` 取首字路径)
#[inline(always)]
pub fn fast_hash128(bytes: &[u8]) -> u128 {
  gxhash::gxhash128(bytes, 0)
}

/// 128 位无别名强抗碰撞键 ID 计算(基于 gxhash::gxhash128 硬件向量加速,
/// 对标 C# `RangeIndexManager.KeyId` 的 XxHash128 → Guid 派生)
#[inline(always)]
pub fn hash128(bytes: &[u8], seed_a: u64, seed_b: u64) -> u128 {
  let combined_seed = (seed_a ^ seed_b.rotate_left(32)) as i64;
  gxhash::gxhash128(bytes, combined_seed)
}

/// 带单一 64 位种子的 128 位硬件加速哈希计算(种子按位解释,与 fast_hash_with_seed 一致)
#[inline(always)]
pub fn hash128_with_seed(bytes: &[u8], seed: u64) -> u128 {
  gxhash::gxhash128(bytes, seed as i64)
}

/// 为支持 Hash trait 的泛型对象快速计算确定性的 64 位哈希值
#[inline]
pub fn hash_value<T: Hash + ?Sized>(value: &T) -> u64 {
  let mut hasher = gxhash::GxHasher::with_seed(0);
  value.hash(&mut hasher);
  hasher.finish()
}

/// 带自定义种子的泛型对象快速确定性 64 位哈希值计算
#[inline]
pub fn hash_value_with_seed<T: Hash + ?Sized>(value: &T, seed: u64) -> u64 {
  let mut hasher = gxhash::GxHasher::with_seed(seed as i64);
  value.hash(&mut hasher);
  hasher.finish()
}