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
//! `MmfDispatcher` - per-call routing across the MMF primitive
//! families.
//!
//! Generalizes the [`DequeDispatcher`] pattern (WorkloadShape ->
//! deque-variant selection) to the cross-family case: a workload may
//! want a streaming MPMC ring, a work-stealing deque, or a key-value
//! map, and the dispatcher picks between them by signature
//! containment.
//!
//! ## The three MMF families
//!
//! | Family | Workload pattern | Signature axes engaged |
//! |---|---|---|
//! | [`SharedRing`](crate::SharedRing) | MPMC streaming queue, arrival order | (none - cube origin for streaming) |
//! | [`SharedDeque`](crate::SharedDeque) family (6 variants) | Single owner, multi-thief work-stealing | union of deque-domain axes |
//! | [`SharedHashMap`](crate::SharedHashMap) | MPMC key-value lookup | `K_content_prefix` (hash-keyed) |
//!
//! Family signatures are unions of their member variants' signatures.
//! [`DequeDispatcher`] handles the within-family pick once
//! `MmfDispatcher` has selected the deque family.
//!
//! ## Routing decision
//!
//! 1. Compute the workload's required signature.
//! 2. If `K_content_prefix` is required -> [`MmfFamily::SharedHashMap`].
//! 3. If any deque-domain axis is required -> [`MmfFamily::SharedDeque`]
//! (delegate within-family to [`DequeDispatcher::pick`]).
//! 4. Otherwise -> [`MmfFamily::SharedRing`].
//!
//! The signature-based path
//! ([`MmfDispatcher::pick_by_signature`]) and the categorical path
//! ([`MmfDispatcher::pick`]) agree on every canonical workload shape;
//! they co-exist so downstream callers can pick the style that fits.
#![allow(clippy::missing_errors_doc)]
use subetha_core::{Axis, AxisMask};
use crate::dispatch_deque::{DequeDispatcher, DequeVariant, WorkloadShape};
/// The three MMF primitive families the dispatcher routes across.
/// `SharedDeque` carries the within-family variant so callers see the
/// full routing decision in one value.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MmfFamily {
/// MPMC streaming queue. Maps to [`SharedRing`](crate::SharedRing).
SharedRing,
/// Single-owner, multi-thief work-stealing deque. The variant is
/// the within-family pick from [`DequeDispatcher`].
SharedDeque(DequeVariant),
/// MPMC key-value lookup. Maps to
/// [`SharedHashMap`](crate::SharedHashMap).
SharedHashMap,
}
impl MmfFamily {
/// Union signature of this family: which axes any variant in the
/// family engages at a non-default value.
pub const fn signature(self) -> AxisMask {
match self {
Self::SharedRing => AxisMask::EMPTY,
Self::SharedDeque(v) => v.signature(),
Self::SharedHashMap => AxisMask::from_axes(&[Axis::ContentPrefix]),
}
}
}
/// Caller-supplied workload shape spanning all three families. Each
/// arm carries the parameters the dispatcher needs to route within
/// its family.
#[derive(Debug, Clone, Copy)]
pub enum MmfWorkloadShape {
/// MPMC streaming queue (arrival order). Caller specifies the
/// expected producer + consumer counts; the dispatcher routes
/// to [`MmfFamily::SharedRing`] in every case (the ring is the
/// MPMC-streaming family's only member).
StreamingMpmc {
/// Number of producers expected to push concurrently.
n_producers: usize,
/// Number of consumers expected to drain concurrently.
n_consumers: usize,
},
/// Work-stealing access (single owner, multi-thief). Carries the
/// deque-family [`WorkloadShape`] for the within-family pick.
WorkStealing(WorkloadShape),
/// Key-value lookup. Caller specifies the expected reader /
/// writer concurrency; the dispatcher routes to
/// [`MmfFamily::SharedHashMap`] in every case.
KeyValueLookup {
/// Number of readers expected to look up concurrently.
n_readers: usize,
/// Number of writers expected to insert / update concurrently.
n_writers: usize,
},
}
impl MmfWorkloadShape {
/// The direction signature this workload requires of its MMF
/// transport: which axes the chosen family must engage.
pub const fn required_signature(&self) -> AxisMask {
match self {
Self::StreamingMpmc { .. } => AxisMask::EMPTY,
Self::WorkStealing(shape) => shape.required_signature(),
Self::KeyValueLookup { .. } => {
AxisMask::from_axes(&[Axis::ContentPrefix])
}
}
}
}
/// MMF cross-family routing dispatcher. Stateless and zero-sized;
/// the picks are pure functions of the workload shape.
pub struct MmfDispatcher;
impl MmfDispatcher {
/// Categorical pick: route by the workload kind tag.
pub fn pick(workload: MmfWorkloadShape) -> MmfFamily {
match workload {
MmfWorkloadShape::StreamingMpmc { .. } => MmfFamily::SharedRing,
MmfWorkloadShape::WorkStealing(shape) => {
MmfFamily::SharedDeque(DequeDispatcher::pick(shape))
}
MmfWorkloadShape::KeyValueLookup { .. } => MmfFamily::SharedHashMap,
}
}
/// Signature-set pick: family selection honors the workload's
/// kind tag (the caller's declared intent); the within-family
/// pick uses signature containment via
/// [`DequeDispatcher::pick_by_signature`]. Agrees with
/// [`pick`](Self::pick) at the family level on every canonical
/// workload shape; differs only in which within-family routing
/// path it delegates to.
pub fn pick_by_signature(workload: MmfWorkloadShape) -> MmfFamily {
match workload {
MmfWorkloadShape::StreamingMpmc { .. } => MmfFamily::SharedRing,
MmfWorkloadShape::WorkStealing(shape) => MmfFamily::SharedDeque(
DequeDispatcher::pick_by_signature(shape),
),
MmfWorkloadShape::KeyValueLookup { .. } => MmfFamily::SharedHashMap,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn streaming_routes_to_shared_ring() {
let shape = MmfWorkloadShape::StreamingMpmc {
n_producers: 4,
n_consumers: 4,
};
assert_eq!(MmfDispatcher::pick(shape), MmfFamily::SharedRing);
assert_eq!(
MmfDispatcher::pick_by_signature(shape),
MmfFamily::SharedRing,
);
}
#[test]
fn work_stealing_routes_to_shared_deque_family() {
let shape = MmfWorkloadShape::WorkStealing(WorkloadShape::producer_fast(64));
assert_eq!(
MmfDispatcher::pick(shape),
MmfFamily::SharedDeque(DequeVariant::Khl),
);
assert_eq!(
MmfDispatcher::pick_by_signature(shape),
MmfFamily::SharedDeque(DequeVariant::Khl),
);
}
#[test]
fn multi_thief_work_stealing_routes_to_urd() {
let shape = MmfWorkloadShape::WorkStealing(WorkloadShape::fan_out(4, 64));
assert_eq!(
MmfDispatcher::pick(shape),
MmfFamily::SharedDeque(DequeVariant::Urd),
);
assert_eq!(
MmfDispatcher::pick_by_signature(shape),
MmfFamily::SharedDeque(DequeVariant::Urd),
);
}
#[test]
fn key_value_routes_to_shared_hash_map() {
let shape = MmfWorkloadShape::KeyValueLookup {
n_readers: 4,
n_writers: 2,
};
assert_eq!(MmfDispatcher::pick(shape), MmfFamily::SharedHashMap);
assert_eq!(
MmfDispatcher::pick_by_signature(shape),
MmfFamily::SharedHashMap,
);
}
#[test]
fn signature_pick_agrees_with_categorical_pick_across_shapes() {
let shapes = [
MmfWorkloadShape::StreamingMpmc {
n_producers: 1,
n_consumers: 1,
},
MmfWorkloadShape::StreamingMpmc {
n_producers: 4,
n_consumers: 4,
},
MmfWorkloadShape::WorkStealing(WorkloadShape::request_reply()),
MmfWorkloadShape::WorkStealing(WorkloadShape::producer_fast(4)),
MmfWorkloadShape::WorkStealing(WorkloadShape::producer_fast(64)),
MmfWorkloadShape::WorkStealing(WorkloadShape::fan_out(2, 16)),
MmfWorkloadShape::WorkStealing(WorkloadShape::fan_out(4, 64)),
MmfWorkloadShape::KeyValueLookup {
n_readers: 1,
n_writers: 1,
},
MmfWorkloadShape::KeyValueLookup {
n_readers: 8,
n_writers: 4,
},
];
for shape in shapes {
let categorical = MmfDispatcher::pick(shape);
let signature = MmfDispatcher::pick_by_signature(shape);
assert_eq!(
categorical, signature,
"shape {shape:?}: categorical {categorical:?}, signature {signature:?}",
);
}
}
#[test]
fn family_signatures_distinguish_families() {
assert_ne!(
MmfFamily::SharedRing.signature(),
MmfFamily::SharedHashMap.signature(),
);
assert_ne!(
MmfFamily::SharedRing.signature(),
MmfFamily::SharedDeque(DequeVariant::Khl).signature(),
);
assert_ne!(
MmfFamily::SharedDeque(DequeVariant::Khl).signature(),
MmfFamily::SharedHashMap.signature(),
);
}
#[test]
fn shared_hash_map_engages_content_prefix() {
assert!(MmfFamily::SharedHashMap.signature().contains(Axis::ContentPrefix));
}
#[test]
fn shared_ring_signature_is_empty() {
assert_eq!(MmfFamily::SharedRing.signature(), AxisMask::EMPTY);
}
#[test]
fn key_value_workload_requires_content_prefix() {
let shape = MmfWorkloadShape::KeyValueLookup {
n_readers: 1,
n_writers: 1,
};
assert!(shape.required_signature().contains(Axis::ContentPrefix));
}
#[test]
fn streaming_workload_has_empty_required_signature() {
let shape = MmfWorkloadShape::StreamingMpmc {
n_producers: 4,
n_consumers: 4,
};
assert_eq!(shape.required_signature(), AxisMask::EMPTY);
}
}