bigarraylist 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,200 @@
1
+ """持久化分桶(Hash Partitioning)+ 单桶扫描查找。
2
+
3
+ 对比重构前:
4
+ - 重构前:bucket_find() 每次调用都重建桶(每次都付 O(n) 建桶成本,
5
+ 多次查询没有任何加速)。
6
+ - 重构后:build_buckets() 返回 BucketIndex 对象,建桶一次 O(n),
7
+ 之后任意多次 .find(target) 只扫单桶 O(n/N);同时支持跨进程复用
8
+ (persist_path 把桶文件保留到指定目录)。
9
+
10
+ MapReduce 核心思想落地:相同 key 一定在同一个桶 → 查找时只需扫一个桶。
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import math
16
+ import os
17
+ import pickle
18
+ import re
19
+ import tempfile
20
+ from collections.abc import Callable, Generator, Iterable
21
+ from typing import Any, Generic, TypeVar
22
+
23
+ from ..config import BigArrayConfig
24
+
25
+ T = TypeVar("T")
26
+
27
+
28
+ class BucketIndex(Generic[T]):
29
+ """持久化分桶索引。
30
+
31
+ 建桶一次后可多次 find,每次只扫目标桶。
32
+ 支持上下文管理器:
33
+ with build_buckets(data, key_func) as idx:
34
+ for tgt in targets:
35
+ hits = list(idx.find(tgt))
36
+ """
37
+
38
+ __slots__ = ("__key_func", "__num_buckets", "__owns_files", "__temp_paths")
39
+
40
+ def __init__(
41
+ self,
42
+ temp_paths: list[str],
43
+ num_buckets: int,
44
+ key_func: Callable[[T], Any],
45
+ owns_files: bool = True,
46
+ ) -> None:
47
+ self.__temp_paths = temp_paths
48
+ self.__num_buckets = num_buckets
49
+ self.__key_func = key_func
50
+ self.__owns_files = owns_files # False 时 close() 不删文件(外部持久化场景)
51
+
52
+ def __enter__(self) -> BucketIndex:
53
+ return self
54
+
55
+ def __exit__(
56
+ self,
57
+ exc_type: type[BaseException] | None,
58
+ exc: BaseException | None,
59
+ tb: Any,
60
+ ) -> None:
61
+ self.close()
62
+
63
+ def close(self) -> None:
64
+ """清理所有桶临时文件。多次调用幂等。"""
65
+ if not self.__owns_files:
66
+ return
67
+ paths = self.__temp_paths
68
+ self.__temp_paths = []
69
+ for path in paths:
70
+ try:
71
+ os.unlink(path)
72
+ except OSError:
73
+ pass
74
+
75
+ @property
76
+ def num_buckets(self) -> int:
77
+ return self.__num_buckets
78
+
79
+ def find(self, target: T) -> Generator[tuple[int, T], None, None]:
80
+ """查找所有 key(item) == key(target) 的元素。
81
+
82
+ 返回生成器,每项为 (原始下标, 命中元素)。
83
+ 只扫目标桶,O(n/N) 时间;O(1) 额外内存(除目标桶文件读取外)。
84
+ """
85
+ target_key = self.__key_func(target)
86
+ target_bucket = hash(target_key) % self.__num_buckets
87
+
88
+ # 在 temp_paths 中定位目标桶(可能不存在 → 该桶为空)
89
+ target_path = None
90
+ for path in self.__temp_paths:
91
+ if self._extract_bucket_idx(path) == target_bucket:
92
+ target_path = path
93
+ break
94
+
95
+ if target_path is None:
96
+ return # 目标桶为空
97
+
98
+ with open(target_path, "rb") as f:
99
+ while True:
100
+ try:
101
+ i, item = pickle.load(f)
102
+ except EOFError:
103
+ break
104
+ if self.__key_func(item) == target_key:
105
+ yield (i, item)
106
+
107
+ @staticmethod
108
+ def _extract_bucket_idx(path: str) -> int:
109
+ """从临时文件名 big_bucket_{idx}_XXXX.tmp 提取桶下标。"""
110
+ m = re.search(r"big_bucket_(\d+)_", path)
111
+ if m is None:
112
+ return -1
113
+ return int(m.group(1))
114
+
115
+
116
+ def _choose_num_buckets(total_count: int, max_buckets: int) -> int:
117
+ """自动选桶数:sqrt(n) 附近的 2 的幂,上限 max_buckets。
118
+
119
+ 取 2 的幂让 hash % num_buckets 利用位运算高效;上限避免文件描述符爆掉。
120
+ """
121
+ target_n = max(8, math.isqrt(total_count))
122
+ n = 1
123
+ while n * 2 <= target_n and n < max_buckets:
124
+ n *= 2
125
+ return n
126
+
127
+
128
+ def build_buckets(
129
+ data: Iterable[T],
130
+ total_count: int,
131
+ key_func: Callable[[T], Any] | None = None,
132
+ num_buckets: int | None = None,
133
+ *,
134
+ config: BigArrayConfig | None = None,
135
+ ) -> BucketIndex:
136
+ """对数据流式建桶,返回可复用的 BucketIndex。
137
+
138
+ 参数:
139
+ data: 数据源迭代器(建议来自 _fresh_iter)
140
+ total_count: 数据总条数
141
+ key_func: 从元素提取分桶 key;None 用元素本身
142
+ (必须满足 a==b → key(a)==key(b) 且 hash 兼容,否则漏命中)
143
+ num_buckets: 桶数;None 自动选(sqrt(n) 附近 2 的幂,上限 config.bucket_max_count)
144
+ config: 配置对象;None 用 BigArrayConfig.default()
145
+
146
+ 返回:BucketIndex,支持多次 find(target) 和上下文管理器。
147
+
148
+ 内存模型:
149
+ - 阶段 1(建桶):流式读,每条元素 + 下标一起 pickle.dump 到对应桶文件
150
+ - 内存峰值 = O(桶数) 文件句柄 + O(1) 单条元素
151
+ """
152
+ cfg = config if config is not None else BigArrayConfig.default()
153
+ if key_func is None:
154
+ key_func = lambda x: x # noqa: E731 恒等默认值
155
+
156
+ if num_buckets is None:
157
+ num_buckets = _choose_num_buckets(total_count, cfg.bucket_max_count)
158
+ if num_buckets < 1:
159
+ raise ValueError(f"num_buckets 必须 >= 1,得到 {num_buckets}")
160
+
161
+ bucket_files: list[Any] = [None] * num_buckets
162
+ temp_paths: list[str] = []
163
+ try:
164
+ # 阶段 1:流式分桶(每条元素 + 其下标一起 pickle 落盘)
165
+ for i, item in enumerate(data):
166
+ bidx = hash(key_func(item)) % num_buckets
167
+ if bucket_files[bidx] is None:
168
+ tmp = tempfile.NamedTemporaryFile(
169
+ suffix=".tmp",
170
+ prefix=f"big_bucket_{bidx}_",
171
+ delete=False,
172
+ )
173
+ temp_paths.append(tmp.name)
174
+ bucket_files[bidx] = tmp
175
+ # 逐条写 (下标, 元素)
176
+ pickle.dump((i, item), bucket_files[bidx])
177
+
178
+ # 关闭所有已开启的桶文件(写模式 → 准备读)
179
+ for f in bucket_files:
180
+ if f is not None:
181
+ f.close()
182
+
183
+ return BucketIndex(temp_paths, num_buckets, key_func, owns_files=True)
184
+ except BaseException:
185
+ # 出错时清理已开的文件和已建的临时文件
186
+ for f in bucket_files:
187
+ if f is not None:
188
+ try:
189
+ f.close()
190
+ except Exception:
191
+ pass
192
+ for path in temp_paths:
193
+ try:
194
+ os.unlink(path)
195
+ except OSError:
196
+ pass
197
+ raise
198
+
199
+
200
+ __all__ = ["BucketIndex", "build_buckets"]
@@ -0,0 +1,200 @@
1
+ """真正的低内存外部排序。
2
+
3
+ 核心改进(对比重构前的整块版本):
4
+ - 重构前:阶段 2 整块 pickle.load,所有 chunk_list 同时在内存,
5
+ 内存峰值 ≈ 数据总量,违背"外部排序"的初衷。
6
+ - 重构后:阶段 1 逐条 pickle.dump 到文件(每条独立可 load),
7
+ 阶段 2 每个文件用一个 lazy 文件迭代器,每次只 load 一条。
8
+ 内存峰值 = O(块数) 文件句柄 + O(块数) 单条元素。
9
+
10
+ 模式选择(由 BigArrayConfig.external_sort_streaming_merge 控制):
11
+ - True → streaming(逐条 load,省内存,速度稍慢)
12
+ - False → block(整块 load,快 3~5 倍,内存 ≈ 数据总量;与重构前一致)
13
+
14
+ 通过 lazy_file_iter / lazy_block_iter 两种文件迭代器抽象两种模式,
15
+ 上层调用方代码完全相同。
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import heapq
21
+ import os
22
+ import pickle
23
+ import tempfile
24
+ from collections import deque
25
+ from collections.abc import Callable, Generator, Iterable
26
+ from concurrent.futures import Future, ProcessPoolExecutor
27
+ from typing import IO, Any, TypeVar
28
+
29
+ from ..config import BigArrayConfig
30
+ from ..utils.scale import choose_external_sort_chunk_size
31
+ from .parallel import sort_chunk
32
+
33
+ T = TypeVar("T")
34
+
35
+ # 双缓冲流水线深度:阶段 1 同时最多保持 2 个 chunk 在飞行
36
+ # (1 个在 worker 中排序 + 1 个在主进程读取/落盘)。
37
+ # 排序 N 与 "读 N+1 + 落 N-1" 并行,并行度从 1 提升到 2,
38
+ # 内存峰值仍为 O(chunk_size)(与串行版一致,不退化成中数据分支)。
39
+ _BUFFER_DEPTH = 2
40
+
41
+
42
+ def _streaming_file_iter(f: IO[bytes]) -> Generator[T, None, None]:
43
+ """逐条 pickle.load 模式的文件迭代器。
44
+
45
+ 要求文件是用"逐条 pickle.dump"写入的(每次 dump 一个元素)。
46
+ 每次 next 调用 pickle.load(f),读到 EOF 抛 StopIteration。
47
+ 内存:O(1) per file(同时持有的元素仅 1 个)。
48
+ """
49
+ try:
50
+ while True:
51
+ try:
52
+ yield pickle.load(f)
53
+ except EOFError:
54
+ return
55
+ except GeneratorExit:
56
+ # 调用方 close() 生成器时也要让上层 finally 关闭文件
57
+ raise
58
+
59
+
60
+ def _block_file_iter(f: IO[bytes]) -> Generator[T, None, None]:
61
+ """整块 pickle.load 模式的文件迭代器(向后兼容用)。
62
+
63
+ 要求文件是用"整块 pickle.dump(list)"写入的(一次 dump 整个 list)。
64
+ 一次 pickle.load 读出整块 list,然后 yield from 展开。
65
+ 内存:O(整块大小) per file;所有文件同时存在时,总内存 ≈ 数据总量。
66
+ """
67
+ chunk_list = pickle.load(f)
68
+ yield from chunk_list
69
+
70
+
71
+ def _dump_streaming(chunk: list[T], f: IO[bytes]) -> None:
72
+ """逐条 pickle.dump 一个 chunk 的元素到文件 f。"""
73
+ for item in chunk:
74
+ pickle.dump(item, f)
75
+
76
+
77
+ def _dump_block(chunk: list[T], f: IO[bytes]) -> None:
78
+ """整块 pickle.dump 一个 chunk 到文件 f(向后兼容模式)。"""
79
+ pickle.dump(chunk, f)
80
+
81
+
82
+ def external_sort(
83
+ data: Iterable[T],
84
+ total_count: int,
85
+ *,
86
+ config: BigArrayConfig | None = None,
87
+ max_workers: int | None = None,
88
+ key: Callable[[T], Any] | None = None,
89
+ ) -> Generator[T, None, None]:
90
+ """外部排序(External Sort):分块排序 + 落盘 + 多路归并。
91
+
92
+ 参数:
93
+ data: 数据源迭代器(建议来自 _fresh_iter)
94
+ total_count: 数据总条数(用于选块大小)
95
+ config: 配置对象;None 用 BigArrayConfig.default()
96
+ max_workers: 阶段 1 排序的进程池并行度
97
+ key: 排序键函数(类似 ``sorted(data, key=...)``);
98
+ None 按元素本身排序。
99
+ ⚠️ 多进程路径要求 key 可 pickle(模块级函数或 functools.partial,
100
+ lambda 在 spawn 模式下不可 pickle)
101
+
102
+ 返回:生成器,按 key 的非降序逐条产出已排序元素。
103
+
104
+ 内存模型(streaming 模式,默认):
105
+ - 阶段 1:双缓冲流水线排序 + 逐条落盘。submit 一块后立即继续读下一块,
106
+ 飞行队列满(_BUFFER_DEPTH=2 块)时才 .result() 落盘最老的一块;
107
+ 排序 N 与 "读 N+1 + 落 N-1" 并行。内存峰值 ≈ 2*chunk_size。
108
+ - 阶段 2:所有块各持一个文件句柄 + lazy 迭代器,每次 yield 一条。
109
+ 内存峰值 = O(块数)(≈ 32)文件句柄 + O(块数) 临时元素。
110
+ 适合"数据量远超内存"的场景。
111
+
112
+ 内存模型(block 模式,external_sort_streaming_merge=False):
113
+ - 阶段 1:双缓冲流水线排序 + 整块落盘。比逐条快 3~5 倍。
114
+ - 阶段 2:所有块整块 pickle.load 到内存,heapq.merge 同时驱动所有 list。
115
+ 内存峰值 ≈ 数据总量。仅当数据总量放得下内存时使用。
116
+
117
+ 约束:
118
+ - 元素必须可 pickle(落盘要求)
119
+ - 文件描述符上限:块数受 OS 限制(Linux 默认 1024)
120
+ 块数由 choose_external_sort_chunk_size 控制(目标 32)
121
+ """
122
+ cfg = config if config is not None else BigArrayConfig.default()
123
+ streaming = cfg.external_sort_streaming_merge
124
+ chunk_size = choose_external_sort_chunk_size(
125
+ total_count,
126
+ target=cfg.external_sort_chunk_target,
127
+ min_chunk=cfg.external_sort_min_chunk,
128
+ max_chunk=cfg.external_sort_max_chunk,
129
+ )
130
+ _dump = _dump_streaming if streaming else _dump_block
131
+ _file_iter_cls = _streaming_file_iter if streaming else _block_file_iter
132
+
133
+ temp_paths: list[str] = []
134
+
135
+ def _dump_chunk(sorted_chunk: list) -> None:
136
+ tmp = tempfile.NamedTemporaryFile(suffix=".tmp", prefix="big_sort_", delete=False)
137
+ try:
138
+ _dump(sorted_chunk, tmp)
139
+ tmp.close()
140
+ except Exception:
141
+ tmp.close()
142
+ try:
143
+ os.unlink(tmp.name)
144
+ except OSError:
145
+ pass
146
+ raise
147
+ temp_paths.append(tmp.name)
148
+
149
+ try:
150
+ # 阶段 1:双缓冲流水线排序 + 落盘
151
+ # 改进点(对比串行版):原版每块 submit().result() 串行,排序与 I/O 完全串行。
152
+ # 双缓冲:submit 一块后立即继续读下一块;飞行队列满(_BUFFER_DEPTH 块)时
153
+ # 才 .result() 落盘最老的一块。排序 N 与 "读 N+1 + 落 N-1" 并行,并行度 1→2。
154
+ # 内存:飞行队列最多 2 个 future;每个 future 的 chunk 在 worker pickled 后即可
155
+ # 释放,主进程峰值 ≈ 2*chunk_size(与串行版一致,不退化成中数据分支)。
156
+ in_flight: deque[Future[list[T]]] = deque()
157
+ with ProcessPoolExecutor(max_workers=max_workers) as pool:
158
+ chunk: list = []
159
+ try:
160
+ for item in data:
161
+ chunk.append(item)
162
+ if len(chunk) >= chunk_size:
163
+ in_flight.append(pool.submit(sort_chunk, (chunk, key)))
164
+ chunk = []
165
+ if len(in_flight) >= _BUFFER_DEPTH:
166
+ _dump_chunk(in_flight.popleft().result())
167
+ if chunk: # 末尾不足一块的也要排
168
+ in_flight.append(pool.submit(sort_chunk, (chunk, key)))
169
+ # 排空飞行队列
170
+ while in_flight:
171
+ _dump_chunk(in_flight.popleft().result())
172
+ except BaseException:
173
+ # 异常时取消未启动的 future;已启动的让其自然结束(pool.__exit__ 会 join)
174
+ for fut in in_flight:
175
+ if not fut.running():
176
+ fut.cancel()
177
+ raise
178
+
179
+ # 阶段 2:多路归并
180
+ # heapq.merge 接受多个有序迭代器,逐条比较产出
181
+ files: list[IO[bytes]] = []
182
+ try:
183
+ files.extend(open(path, "rb") for path in temp_paths)
184
+ file_iters: list[Generator[T, None, None]] = [_file_iter_cls(f) for f in files]
185
+ yield from heapq.merge(*file_iters, key=key) # type: ignore[arg-type]
186
+ finally:
187
+ for f in files:
188
+ try:
189
+ f.close()
190
+ except Exception:
191
+ pass
192
+ finally:
193
+ for path in temp_paths:
194
+ try:
195
+ os.unlink(path)
196
+ except OSError:
197
+ pass
198
+
199
+
200
+ __all__ = ["external_sort"]
@@ -0,0 +1,68 @@
1
+ """模块级可 pickle 函数:供 ProcessPoolExecutor 子进程调用。
2
+
3
+ ⚠️ 这些函数必须放在模块级(不能是嵌套函数/lambda),否则子进程
4
+ pickle 时找不到定义。本文件按职责拆分原 core.py 顶部的四个函数。
5
+
6
+ 子进程通过 `from bigarraylist.algorithms.parallel import _sort_chunk` 路径找到定义。
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import bisect as _bisect_mod
12
+ from typing import Any, TypeVar
13
+
14
+ T = TypeVar("T")
15
+
16
+
17
+ def linear_find_in_chunk(args: tuple[list[T], T, int]) -> list[tuple[int, T]]:
18
+ """在单个 chunk 内查找所有等于 target 的元素,返回 (全局下标, 元素) 列表。"""
19
+ chunk, target, start_index = args
20
+ return [(i, item) for i, item in enumerate(chunk, start=start_index) if item == target]
21
+
22
+
23
+ def sort_chunk(args: tuple[Any, ...]) -> list[T]:
24
+ """对单个 chunk 排序,返回新列表(不修改原 chunk)。
25
+
26
+ args 形如 ``(chunk, key)``;``key=None`` 表示按元素本身排序。
27
+ 向后兼容:也接受旧式 ``(chunk,)`` 一元组(等价于 ``key=None``)。
28
+ """
29
+ if len(args) == 2:
30
+ chunk, key = args
31
+ else:
32
+ # 旧式 (chunk,) 调用(_sort_chunk 别名的遗留调用方)
33
+ (chunk,) = args
34
+ key = None
35
+ return sorted(chunk, key=key)
36
+
37
+
38
+ def chunk_is_sorted(args: tuple[list[T]]) -> bool:
39
+ """判断单个 chunk 是否升序(模块级,供子进程 pickle 调用)。"""
40
+ (chunk,) = args
41
+ return all(chunk[i] <= chunk[i + 1] for i in range(len(chunk) - 1)) # type: ignore[operator]
42
+
43
+
44
+ def bisect_find_in_chunk(args: tuple[list[T], T, int]) -> list[tuple[int, T]]:
45
+ """在**已排序**的单个 chunk 内用 bisect 找所有等于 target 的元素。
46
+
47
+ 返回 (全局下标, 元素) 列表。
48
+ """
49
+ chunk, target, start_index = args
50
+ left = _bisect_mod.bisect_left(chunk, target) # type: ignore[call-overload]
51
+ right = _bisect_mod.bisect_right(chunk, target) # type: ignore[call-overload]
52
+ return [(start_index + i, chunk[i]) for i in range(left, right)]
53
+
54
+
55
+ # ==================== 向后兼容别名 ====================
56
+ # 重构前这些函数在 bigarraylist.core 模块下,名字带下划线前缀。
57
+ # 现有测试 / 外部代码若直接 import,保留别名避免破坏。
58
+ _linear_find_in_chunk: Any = linear_find_in_chunk
59
+ _sort_chunk: Any = sort_chunk
60
+ _chunk_is_sorted: Any = chunk_is_sorted
61
+ _bisect_find_in_chunk: Any = bisect_find_in_chunk
62
+
63
+ __all__ = [
64
+ "bisect_find_in_chunk",
65
+ "chunk_is_sorted",
66
+ "linear_find_in_chunk",
67
+ "sort_chunk",
68
+ ]
@@ -0,0 +1,51 @@
1
+ """蓄水池抽样(Reservoir Sampling):从总量未知的流里等概率抽 k 个。
2
+
3
+ 数学保证:每个元素被选中的概率都是 k/N(N 为实际总量,无需预先知道)。
4
+ 内存 O(k),无需预知 N。大数据分析里做随机采样的必备算法。
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import random
10
+ from collections.abc import Generator, Iterable
11
+ from typing import TypeVar
12
+
13
+ T = TypeVar("T")
14
+
15
+
16
+ def reservoir_sample(
17
+ stream: Iterable[T],
18
+ k: int,
19
+ seed: int | None = None,
20
+ ) -> Generator[T, None, None]:
21
+ """蓄水池抽样算法 R。
22
+
23
+ 参数:
24
+ stream: 任意可迭代对象(不要求预知长度)
25
+ k: 要抽取的样本数(必须 >= 1,调用方校验)
26
+ seed: 随机种子,None 表示系统随机;传 int 可复现
27
+
28
+ 返回:生成器,产出 k 个元素(若流总量 < k,产出全部元素)
29
+
30
+ 算法:
31
+ - 先存前 k 个到 reservoir
32
+ - 第 i 个(i >= k,从 0 计数即 i+1 个元素)以 k/(i+1) 概率
33
+ 随机替换池中某一个
34
+ - 一遍流式扫描结束,池中 k 个即为等概率样本
35
+ """
36
+ rng = random.Random(seed)
37
+ reservoir: list[T] = []
38
+ for i, item in enumerate(stream):
39
+ if i < k:
40
+ reservoir.append(item)
41
+ else:
42
+ # 第 i+1 个元素(索引 i >= k),以 k/(i+1) 概率替换池中随机一个
43
+ # randint(0, i) 返回 [0, i] 闭区间,共 i+1 个值
44
+ # 落在 [0, k-1] 的概率 = k/(i+1),正是我们要的
45
+ j = rng.randint(0, i)
46
+ if j < k:
47
+ reservoir[j] = item
48
+ yield from reservoir
49
+
50
+
51
+ __all__ = ["reservoir_sample"]
bigarraylist/config.py ADDED
@@ -0,0 +1,86 @@
1
+ """配置对象:所有阈值集中可配。
2
+
3
+ 向后兼容:所有字段都有默认值,等于重构前的硬编码常量。
4
+ 不传 config 时,行为与重构前完全一致。
5
+
6
+ 用法:
7
+ cfg = BigArrayConfig(linear_parallel_threshold=50_000)
8
+ arr = BigArrayList(data, config=cfg)
9
+
10
+ 也可类级别覆盖默认值,影响所有后续实例:
11
+ BigArrayConfig.set_default(external_sort_chunk_target=64)
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from dataclasses import dataclass
17
+ from typing import Any
18
+
19
+
20
+ @dataclass(frozen=True)
21
+ class BigArrayConfig:
22
+ """全部阈值配置。
23
+
24
+ 所有字段单位均为"元素条数"。各字段的工程意义见注释。
25
+ """
26
+
27
+ # ===== 并行分流阈值 =====
28
+ # 数据量 >= 该值才走多进程路径;小数据单进程更快
29
+ linear_parallel_threshold: int = 100_000
30
+
31
+ # ===== 内存排序分块 =====
32
+ # 内存多进程排序的单块大小映射表(数据量 -> 块大小)
33
+ # 100k→100/块、1M→300/块、10M→800/块、100M→1000/块
34
+ chunk_size_map: dict[int, int] | None = None # None → 用默认 _DEFAULT_CHUNK_MAP
35
+
36
+ # ===== 外部排序 =====
37
+ # 目标块数:32 路归并,heapq.merge 效率最高
38
+ external_sort_chunk_target: int = 32
39
+ # 每块至少 10 万条(再小进程调度不划算)
40
+ external_sort_min_chunk: int = 100_000
41
+ # 每块最多 500 万条(sorted() 单块内存安全)
42
+ external_sort_max_chunk: int = 5_000_000
43
+ # 数据量 >= 该值才走外部排序(否则走内存多进程分块)
44
+ external_sort_threshold: int = 1_000_000
45
+ # 真正的低内存归并:逐条 load 而非整块 load。
46
+ # True → 内存峰值 O(块数),速度较慢但能处理远超内存的数据
47
+ # False → 整块 load,归并内存峰值 ≈ 数据总量,速度快 3~5 倍
48
+ # True(符合"外部排序"语义);要恢复旧行为显式设 False
49
+ external_sort_streaming_merge: bool = True
50
+
51
+ # ===== 分桶查找 =====
52
+ # 桶数上限(避免 OS 文件描述符耗尽,Linux 默认 1024)
53
+ bucket_max_count: int = 1024
54
+
55
+ # ===== 默认配置管理 =====
56
+ _default: BigArrayConfig | None = None
57
+
58
+ @classmethod
59
+ def default(cls) -> BigArrayConfig:
60
+ """当前生效的默认配置(实例级覆盖优先于类级默认)。"""
61
+ if cls._default is not None:
62
+ return cls._default
63
+ return cls()
64
+
65
+ @classmethod
66
+ def set_default(cls, **kwargs: Any) -> BigArrayConfig:
67
+ """类级别覆盖默认值;影响后续所有未显式传 config 的实例。
68
+
69
+ kwargs 不全传时,缺失字段保留原默认值。
70
+ """
71
+ cls._default = cls(**kwargs)
72
+ return cls._default
73
+
74
+ @classmethod
75
+ def reset_default(cls) -> None:
76
+ """恢复类级默认值为内置默认。"""
77
+ cls._default = None
78
+
79
+
80
+ # 内存排序分块大小默认映射(不变于重构前)
81
+ _DEFAULT_CHUNK_MAP: dict[int, int] = {
82
+ 100_000: 100, # 十万级 → 100 条/块
83
+ 1_000_000: 300, # 百万级 → 300 条/块
84
+ 10_000_000: 800, # 千万级 → 800 条/块
85
+ 100_000_000: 1_000, # 亿级 → 1000 条/块
86
+ }
@@ -0,0 +1,5 @@
1
+ """核心层:迭代协议 + _fresh_iter(通过 mixin 组合)。"""
2
+
3
+ from .base import BaseBigArrayList
4
+
5
+ __all__ = ["BaseBigArrayList"]