|
30 | 30 | from typing import Annotated, Any, ClassVar, cast |
31 | 31 |
|
32 | 32 | import pyarrow as pa |
| 33 | +import pyarrow.compute as pc |
33 | 34 | from vgi_rpc import ArrowSerializableDataclass |
34 | 35 | from vgi_rpc.rpc import OutputCollector |
35 | 36 |
|
@@ -945,7 +946,7 @@ def process( |
945 | 946 | # sequence [0..rows) so COUNT=rows and SUM=rows*(rows-1)/2 regardless of how the |
946 | 947 | # chunks were distributed across workers. Positional Arg(0) so the direct path |
947 | 948 | # `vgi_table_function(w, 'cache_parallel', [rows])` honors the size; a named |
948 | | -# `batch_size` (default 24000) controls batch width for the 2 GB streaming guard. |
| 949 | +# `batch_size` (default 24000) controls batch width for the disk-streaming guard (parallel_2gb.test_slow). |
949 | 950 | _PC_ITEM_FMT = ">QQ" # (start, end) as two uint64 |
950 | 951 | _PC_ITEM_SIZE = struct.calcsize(_PC_ITEM_FMT) |
951 | 952 |
|
@@ -1001,6 +1002,16 @@ class Meta: |
1001 | 1002 |
|
1002 | 1003 | FunctionArguments = CacheParallelArgs |
1003 | 1004 |
|
| 1005 | + _OFFSETS: ClassVar[dict[int, pa.Int64Array]] = {} |
| 1006 | + |
| 1007 | + @classmethod |
| 1008 | + def _offsets(cls, n: int) -> pa.Int64Array: |
| 1009 | + """0 .. n-1 as int64, built once per batch size per process.""" |
| 1010 | + arr = cls._OFFSETS.get(n) |
| 1011 | + if arr is None: |
| 1012 | + arr = cls._OFFSETS[n] = pa.array(range(n), type=pa.int64()) |
| 1013 | + return arr |
| 1014 | + |
1004 | 1015 | @classmethod |
1005 | 1016 | def on_init(cls, params: InitParams[CacheParallelArgs]) -> GlobalInitResponse: |
1006 | 1017 | """Primary worker enqueues (start, end) chunks covering [0, rows).""" |
@@ -1032,8 +1043,11 @@ def process( |
1032 | 1043 | state.current_idx = state.current_start |
1033 | 1044 |
|
1034 | 1045 | batch_end = min(state.current_idx + params.args.batch_size, state.current_end or 0) |
1035 | | - values = list(range(state.current_idx, batch_end)) |
1036 | | - batch = pa.RecordBatch.from_pydict({"v": values}, schema=params.output_schema) |
| 1046 | + # v = current_idx .. batch_end-1, as one vectorised add to a cached 0..n-1 |
| 1047 | + # (a Python list costs ~1 µs a row, which the 640 MB streaming guard pays 80M times). |
| 1048 | + base = cls._offsets(params.args.batch_size).slice(0, batch_end - state.current_idx) |
| 1049 | + values = pc.add(base, pa.scalar(state.current_idx, type=pa.int64())) |
| 1050 | + batch = pa.RecordBatch.from_arrays([values], schema=params.output_schema) |
1037 | 1051 | cache_control = CacheControl(ttl=_DEFAULT_TTL_SECONDS) if not state.advertised else None |
1038 | 1052 | cast(VgiOutputCollector, out).emit(batch, cache_control=cache_control) |
1039 | 1053 | state.advertised = True |
|
0 commit comments