(
&'a self,
graph: &mut TaskGraph<'a>,
parent_id: Option<ChunkId>,
config_override: Option<JobConfig>,
)
| 110 | T: ParcodeVisitor + Clone + Send + Sync + 'static + Serialize, |
| 111 | { |
| 112 | fn visit<'a>( |
| 113 | &'a self, |
| 114 | graph: &mut TaskGraph<'a>, |
| 115 | parent_id: Option<ChunkId>, |
| 116 | config_override: Option<JobConfig>, |
| 117 | ) { |
| 118 | let total_len = self.len(); |
| 119 | let items_per_shard; |
| 120 | |
| 121 | // --- PHASE 1: SHARDING STRATEGY CALCULATION --- |
| 122 | if total_len == 0 { |
| 123 | items_per_shard = 1; |
| 124 | } else { |
| 125 | // 1. Measure data cost (Sampling) |
| 126 | // We take up to 8 elements to estimate the real size (useful for Strings/Heap). |
| 127 | let sample_count = total_len.min(8); |
| 128 | let sample_slice = self.get(0..sample_count).unwrap_or(&[]); |
| 129 | |
| 130 | let sample_size_bytes = |
| 131 | match bincode::serde::encode_to_vec(sample_slice, bincode::config::standard()) { |
| 132 | Ok(vec) => vec.len() as u64, |
| 133 | Err(_) => 0, |
| 134 | }; |
| 135 | |
| 136 | // Calculate average bytes per item (minimum 1 byte to avoid div by zero) |
| 137 | let avg_item_size = if sample_size_bytes > 0 { |
| 138 | (sample_size_bytes / sample_count as u64).max(1) |
| 139 | } else { |
| 140 | (std::mem::size_of::<T>() as u64).max(1) |
| 141 | }; |
| 142 | |
| 143 | // 2. Calculate Strategies |
| 144 | |
| 145 | // Strategy A: Optimized for I/O (Fill 128KB chunks) |
| 146 | let count_by_io = usize::try_from((TARGET_SHARD_SIZE_BYTES / avg_item_size).max(1)) |
| 147 | .unwrap_or(usize::MAX); |
| 148 | |
| 149 | // Strategy B: Optimized for CPU (Fill cores) |
| 150 | // We want enough tasks to keep Rayon busy. |
| 151 | let num_cpus = std::thread::available_parallelism() |
| 152 | .map(|n| n.get()) |
| 153 | .unwrap_or(1); |
| 154 | let target_parallel_chunks = num_cpus * TASKS_PER_CORE; |
| 155 | let count_by_cpu = (total_len / target_parallel_chunks).max(1); |
| 156 | |
| 157 | // 3. Strategy Fusion |
| 158 | // We prefer more chunks (CPU) unless they are ridiculously small. |
| 159 | let candidate_count = count_by_io.min(count_by_cpu); |
| 160 | |
| 161 | // Verify physical size of the candidate |
| 162 | let estimated_chunk_size = candidate_count as u64 * avg_item_size; |
| 163 | |
| 164 | if estimated_chunk_size < MIN_SHARD_SIZE_BYTES { |
| 165 | // Too small. Scale to meet the 4KB minimum. |
| 166 | items_per_shard = usize::try_from((MIN_SHARD_SIZE_BYTES / avg_item_size).max(1)) |
| 167 | .unwrap_or(usize::MAX); |
| 168 | } else { |
| 169 | items_per_shard = candidate_count; |
no test coverage detected