Skip to main content

BatchMpscQueue

Struct BatchMpscQueue 

Source
pub struct BatchMpscQueue<T> { /* private fields */ }
Expand description

Batch-draining wrapper around the base MpscQueue.

Implementations§

Source§

impl<T> BatchMpscQueue<T>

Source

pub fn new() -> Self

Examples found in repository?
examples/perf_features.rs (line 361)
360        fn filled_batch(n: usize) -> BatchMpscQueue<u64> {
361            let q = BatchMpscQueue::new();
362            for i in 0..n {
363                q.push(i as u64);
364            }
365            q
366        }
More examples
Hide additional examples
examples/sample_app.rs (line 251)
245fn batch_drain_per_tick() {
246    use subms_mpsc_queue::BatchMpscQueue;
247
248    println!("\n== batch: publish and drain a whole tick in one pass ==");
249    const TICK: usize = 256;
250    const BURST: usize = 50;
251    let mut q: BatchMpscQueue<u64> = BatchMpscQueue::new();
252    let total = 1_000usize;
253
254    // A gateway that decodes a wire frame already holds a run of orders. One
255    // head swap publishes the whole run instead of BURST of them.
256    let mut published = 0usize;
257    while published < total {
258        let base = published;
259        published += q.push_batch((base..base + BURST).map(|seq| order_id(0, seq)));
260    }
261    println!("  {published} orders published in {} swaps", total / BURST);
262    assert_eq!(published, total);
263
264    let mut buf: Vec<Option<u64>> = (0..TICK).map(|_| None).collect();
265    let mut ticks = 0usize;
266    let mut matched = 0usize;
267    loop {
268        let n = q.try_dequeue_batch(&mut buf);
269        if n == 0 {
270            break;
271        }
272        ticks += 1;
273        for slot in buf.iter_mut().take(n) {
274            let _ = slot.take();
275            matched += 1;
276        }
277    }
278    println!("  drained {matched} orders across {ticks} ticks of up to {TICK}");
279    assert_eq!(matched, total, "every queued order is drained");
280    assert_eq!(
281        ticks,
282        total.div_ceil(TICK),
283        "each tick drains a full buffer until the tail"
284    );
285
286    // The callback form skips the buffer entirely when the match loop's work
287    // is per-order anyway. Here it accumulates notional.
288    q.push_batch((0..64).map(|seq| order_id(1, seq)));
289    let mut notional = 0u64;
290    let handled = q.drain(TICK, |order| notional += order & 0xffff_ffff);
291    println!("  drain callback handled {handled} orders, notional {notional}");
292    assert_eq!(handled, 64);
293    assert_eq!(notional, (0..64u64).sum::<u64>());
294    assert!(q.is_empty());
295}
Source

pub fn push(&self, value: T)

Same as the base MpscQueue::push.

Examples found in repository?
examples/perf_features.rs (line 363)
195fn main() -> io::Result<()> {
196    let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
197        .join("..")
198        .join(".subms")
199        .join("features")
200        .join("rust.json");
201    let existing = std::fs::read_to_string(&path).unwrap_or_default();
202    let mut manifest = SubMsFeatureManifest::load_str("rust", &existing);
203    // Stamp the box these numbers came from. The bench runs wherever it is
204    // invoked, so an unstamped manifest is indistinguishable from a fleet
205    // capture; the renderer will not publish one it cannot attribute.
206    let (source, instance) = SubMsP99Source::from_env();
207    manifest.set_p99_source(source, instance.as_deref());
208
209    // Optional, off by default: pin this thread to one core for the whole run.
210    // On a heterogeneous laptop the scheduler moves the bench between core
211    // clusters and every measurement lands in one of two clock states 1.31x
212    // apart - a spread three times wider than the deltas being classified, and
213    // large enough on its own to flip a feature between auxiliary and hot-path.
214    // Pinned, the same sweep repeats to within 1%. Left OFF by default because a
215    // fleet box isolates cores outside the process, and pinning from in here
216    // would override that placement with a core the orchestrator did not choose.
217    #[cfg(feature = "affinity")]
218    if let Some(core) = std::env::var("SUBMS_PIN").ok().and_then(|v| v.parse().ok()) {
219        let _ = subms_mpsc_queue::set_affinity(&[core]);
220    }
221
222    // Burn before the first measurement, not just before each one. Every
223    // `batched` call warms itself, but the FIRST measurement in the process pays
224    // a ramp the per-measurement warm sits inside rather than absorbs, and the
225    // sweep runs smallest-first: without this the base curve read 71800 / 45300 /
226    // 46500 ns, a 1.6x fall with size that is the process settling, not the
227    // queue.
228    {
229        let mut q = filled(CANON);
230        let start = std::time::Instant::now();
231        while (start.elapsed().as_nanos() as u64) < BURN_NANOS {
232            for i in 0..ITEMS_PER_SAMPLE {
233                q.push(i as u64);
234                black_box(q.try_pop());
235            }
236        }
237    }
238
239    // The baseline: the base queue's push + try_pop round trip. Swept as well as
240    // sampled, because whether queue depth moves the BASE op is the context
241    // every feature curve is read against.
242    let base_sweep = sweep("base/push+pop", |n| {
243        let mut q = filled(n);
244        batched(ITEMS_PER_SAMPLE, |i| {
245            q.push(i as u64);
246            black_box(q.try_pop());
247        })
248    });
249    let base_p50 = base_sweep
250        .iter()
251        .find(|(n, _)| *n == CANON)
252        .map_or(0, |(_, v)| *v);
253    eprintln!("base push+pop p50 per {ITEMS_PER_SAMPLE}-item sample: {base_p50}ns");
254
255    // ---------- bounded: fixed-capacity ring, backpressure on enqueue ----------
256    #[cfg(feature = "bounded")]
257    {
258        use subms_mpsc_queue::BoundedMpscQueue;
259        // Half full at every sweep point. Filled to a FIXED element count
260        // instead, the big rings would sit 98% empty and the enqueue would be
261        // measuring the fill fraction rather than the footprint.
262        fn ring(n: usize) -> BoundedMpscQueue<u64> {
263            let q = BoundedMpscQueue::new(n);
264            for i in 0..n / 2 {
265                let _ = q.try_enqueue(i as u64);
266            }
267            q
268        }
269        let sw = sweep("bounded/enqueue+dequeue", |n| {
270            let mut q = ring(n);
271            batched(ITEMS_PER_SAMPLE, |i| {
272                let _ = q.try_enqueue(i as u64);
273                black_box(q.try_dequeue());
274            })
275        });
276        let (cat, reason) = classify_feature(&sw, Some(base_p50), None);
277
278        let mut q = ring(CANON);
279        let mut p99 = BTreeMap::new();
280        p99.insert(
281            "enqueue".to_string(),
282            single(|st, i| {
283                st.time(|| {
284                    let _ = q.try_enqueue(i as u64);
285                });
286                black_box(q.try_dequeue());
287            }),
288        );
289        p99.insert(
290            "dequeue".to_string(),
291            single(|st, i| {
292                let _ = q.try_enqueue(i as u64);
293                st.time(|| black_box(q.try_dequeue()));
294            }),
295        );
296        // The reject path, which is the reason the feature exists. The ring is
297        // filled to capacity once, OUTSIDE the timed region; every timed call
298        // then takes the full branch and hands the value back to the caller.
299        let full: BoundedMpscQueue<u64> = BoundedMpscQueue::new(CANON);
300        while full.try_enqueue(0).is_ok() {}
301        p99.insert(
302            "enqueue_full".to_string(),
303            single(|st, i| {
304                st.time(|| {
305                    let _ = full.try_enqueue(i as u64);
306                });
307            }),
308        );
309        manifest.set_feature("bounded", cat, &p99, &reason);
310    }
311
312    // ---------- mpmc: bounded ring, sequence CAS on both ends ----------
313    #[cfg(feature = "mpmc")]
314    {
315        use subms_mpsc_queue::MpmcQueue;
316        fn ring(n: usize) -> MpmcQueue<u64> {
317            let q = MpmcQueue::new(n);
318            for i in 0..n / 2 {
319                let _ = q.try_enqueue(i as u64);
320            }
321            q
322        }
323        // Uncontended, so every CAS succeeds first try. That is the figure the
324        // category is about: what the multi-consumer claim costs a queue that is
325        // NOT contended, which is the state a well-sized pipeline runs in.
326        let sw = sweep("mpmc/enqueue+dequeue", |n| {
327            let q = ring(n);
328            batched(ITEMS_PER_SAMPLE, |i| {
329                let _ = q.try_enqueue(i as u64);
330                black_box(q.try_dequeue());
331            })
332        });
333        let (cat, reason) = classify_feature(&sw, Some(base_p50), None);
334
335        let q = ring(CANON);
336        let mut p99 = BTreeMap::new();
337        p99.insert(
338            "enqueue".to_string(),
339            single(|st, i| {
340                st.time(|| {
341                    let _ = q.try_enqueue(i as u64);
342                });
343                black_box(q.try_dequeue());
344            }),
345        );
346        p99.insert(
347            "dequeue".to_string(),
348            single(|st, i| {
349                let _ = q.try_enqueue(i as u64);
350                st.time(|| black_box(q.try_dequeue()));
351            }),
352        );
353        manifest.set_feature("mpmc", cat, &p99, &reason);
354    }
355
356    // ---------- batch: drain up to BATCH items behind one acquire fence ----------
357    #[cfg(feature = "batch")]
358    {
359        use subms_mpsc_queue::BatchMpscQueue;
360        fn filled_batch(n: usize) -> BatchMpscQueue<u64> {
361            let q = BatchMpscQueue::new();
362            for i in 0..n {
363                q.push(i as u64);
364            }
365            q
366        }
367        // A sample moves ITEMS_PER_SAMPLE items either way; only the call width
368        // differs. That is why the reps count is divided rather than the batch
369        // grown - growing it would sweep the batch size, and the number would
370        // stop being comparable to the base round trip.
371        let sw = sweep("batch/push+dequeue_batch", |n| {
372            let mut q = filled_batch(n);
373            let mut buf: Vec<Option<u64>> = (0..BATCH).map(|_| None).collect();
374            batched(ITEMS_PER_SAMPLE / BATCH, |i| {
375                for j in 0..BATCH {
376                    q.push((i + j) as u64);
377                }
378                black_box(q.try_dequeue_batch(&mut buf));
379            })
380        });
381        let (cat, reason) = classify_feature(&sw, Some(base_p50), None);
382
383        let mut q = filled_batch(CANON);
384        let mut buf: Vec<Option<u64>> = (0..BATCH).map(|_| None).collect();
385        let mut p99 = BTreeMap::new();
386        // The refill is outside the timed region: timing it would put a BATCH of
387        // pushes inside the drain's number and the stage would stop being a
388        // drain figure at all.
389        p99.insert(
390            "dequeue_batch".to_string(),
391            single(|st, i| {
392                st.time(|| black_box(q.try_dequeue_batch(&mut buf)));
393                for j in 0..BATCH {
394                    q.push((i + j) as u64);
395                }
396            }),
397        );
398        p99.insert(
399            "enqueue".to_string(),
400            single(|st, i| {
401                st.time(|| q.push(i as u64));
402                let _ = q.try_dequeue_batch(&mut buf[..1]);
403            }),
404        );
405        // The producer mirror: BATCH items published behind one head swap. The
406        // drain that puts the queue back is outside the timed region for the
407        // same reason the refill is above.
408        p99.insert(
409            "enqueue_batch".to_string(),
410            single(|st, i| {
411                let base = i as u64;
412                st.time(|| black_box(q.push_batch(base..base + BATCH as u64)));
413                let _ = q.try_dequeue_batch(&mut buf);
414            }),
415        );
416        manifest.set_feature("batch", cat, &p99, &reason);
417    }
418
419    // ---------- metrics: relaxed atomic counters around each op ----------
420    #[cfg(feature = "metrics")]
421    {
422        use subms_mpsc_queue::MetricsMpscQueue;
423        fn filled_metrics(n: usize) -> MetricsMpscQueue<u64> {
424            let q = MetricsMpscQueue::new();
425            for i in 0..n {
426                q.push(i as u64);
427            }
428            q
429        }
430        let sw = sweep("metrics/push+pop", |n| {
431            let mut q = filled_metrics(n);
432            batched(ITEMS_PER_SAMPLE, |i| {
433                q.push(i as u64);
434                black_box(q.try_pop());
435            })
436        });
437        let (cat, reason) = classify_feature(&sw, Some(base_p50), None);
438
439        let mut q = filled_metrics(CANON);
440        let mut p99 = BTreeMap::new();
441        p99.insert(
442            "enqueue".to_string(),
443            single(|st, i| {
444                st.time(|| q.push(i as u64));
445                black_box(q.try_pop());
446            }),
447        );
448        p99.insert(
449            "dequeue".to_string(),
450            single(|st, i| {
451                q.push(i as u64);
452                st.time(|| black_box(q.try_pop()));
453            }),
454        );
455        p99.insert(
456            "snapshot".to_string(),
457            single(|st, _| {
458                st.time(|| black_box(q.snapshot()));
459            }),
460        );
461        manifest.set_feature("metrics", cat, &p99, &reason);
462    }
463
464    // ---------- affinity: pin the calling thread, once, at startup ----------
465    // Runs LAST because measuring it pins THIS process to core 0, and every
466    // number taken afterwards would be a number taken on one core.
467    #[cfg(feature = "affinity")]
468    {
469        use subms_mpsc_queue::set_affinity;
470        // Swept over the same axis to show what it is: a call that touches no
471        // queue state and cannot move with queue size. PINNED auxiliary rather
472        // than left to the base-delta test, which would see a syscall costing
473        // more than an enqueue and call it hot-path. It is not on the hot path at
474        // any price - `set_affinity` is called once per thread at startup and
475        // appears in neither `push` nor `try_pop`. The two ports are not even
476        // measuring the same thing: Rust issues a real `SetThreadAffinityMask` /
477        // `sched_setaffinity`, while the Java sibling validates its argument and
478        // returns UNSUPPORTED because the stock JDK has no pinning API. The
479        // per-call figure, not the sample, is the interpretable one and it is in
480        // `p99ByStage`.
481        let sw = sweep("affinity/set_affinity", |_| {
482            batched(ITEMS_PER_SAMPLE, |_| {
483                let _ = set_affinity(&[0]);
484            })
485        });
486        let (cat, reason) = classify_feature(
487            &sw,
488            Some(base_p50),
489            Some(subms::SubMsFeatureCategory::Auxiliary),
490        );
491
492        let mut p99 = BTreeMap::new();
493        p99.insert(
494            "set_affinity".to_string(),
495            single(|st, _| {
496                st.time(|| {
497                    let _ = set_affinity(&[0]);
498                });
499            }),
500        );
501        manifest.set_feature("affinity", cat, &p99, &reason);
502
503        let cores: Vec<usize> = (0..std::thread::available_parallelism()
504            .map_or(1, std::num::NonZeroUsize::get))
505            .collect();
506        let _ = set_affinity(&cores);
507    }
508
509    std::fs::create_dir_all(path.parent().unwrap())?;
510    std::fs::write(&path, manifest.to_json())?;
511    io::stdout().write_all(manifest.to_json().as_bytes())?;
512    Ok(())
513}
Source

pub fn push_batch<I: IntoIterator<Item = T>>(&self, values: I) -> usize

Publish a whole run with one head swap. The producer-side mirror of Self::try_dequeue_batch: N items cost one atomic exchange rather than N. Returns the number published.

Examples found in repository?
examples/sample_app.rs (line 259)
245fn batch_drain_per_tick() {
246    use subms_mpsc_queue::BatchMpscQueue;
247
248    println!("\n== batch: publish and drain a whole tick in one pass ==");
249    const TICK: usize = 256;
250    const BURST: usize = 50;
251    let mut q: BatchMpscQueue<u64> = BatchMpscQueue::new();
252    let total = 1_000usize;
253
254    // A gateway that decodes a wire frame already holds a run of orders. One
255    // head swap publishes the whole run instead of BURST of them.
256    let mut published = 0usize;
257    while published < total {
258        let base = published;
259        published += q.push_batch((base..base + BURST).map(|seq| order_id(0, seq)));
260    }
261    println!("  {published} orders published in {} swaps", total / BURST);
262    assert_eq!(published, total);
263
264    let mut buf: Vec<Option<u64>> = (0..TICK).map(|_| None).collect();
265    let mut ticks = 0usize;
266    let mut matched = 0usize;
267    loop {
268        let n = q.try_dequeue_batch(&mut buf);
269        if n == 0 {
270            break;
271        }
272        ticks += 1;
273        for slot in buf.iter_mut().take(n) {
274            let _ = slot.take();
275            matched += 1;
276        }
277    }
278    println!("  drained {matched} orders across {ticks} ticks of up to {TICK}");
279    assert_eq!(matched, total, "every queued order is drained");
280    assert_eq!(
281        ticks,
282        total.div_ceil(TICK),
283        "each tick drains a full buffer until the tail"
284    );
285
286    // The callback form skips the buffer entirely when the match loop's work
287    // is per-order anyway. Here it accumulates notional.
288    q.push_batch((0..64).map(|seq| order_id(1, seq)));
289    let mut notional = 0u64;
290    let handled = q.drain(TICK, |order| notional += order & 0xffff_ffff);
291    println!("  drain callback handled {handled} orders, notional {notional}");
292    assert_eq!(handled, 64);
293    assert_eq!(notional, (0..64u64).sum::<u64>());
294    assert!(q.is_empty());
295}
More examples
Hide additional examples
examples/perf_features.rs (line 412)
195fn main() -> io::Result<()> {
196    let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
197        .join("..")
198        .join(".subms")
199        .join("features")
200        .join("rust.json");
201    let existing = std::fs::read_to_string(&path).unwrap_or_default();
202    let mut manifest = SubMsFeatureManifest::load_str("rust", &existing);
203    // Stamp the box these numbers came from. The bench runs wherever it is
204    // invoked, so an unstamped manifest is indistinguishable from a fleet
205    // capture; the renderer will not publish one it cannot attribute.
206    let (source, instance) = SubMsP99Source::from_env();
207    manifest.set_p99_source(source, instance.as_deref());
208
209    // Optional, off by default: pin this thread to one core for the whole run.
210    // On a heterogeneous laptop the scheduler moves the bench between core
211    // clusters and every measurement lands in one of two clock states 1.31x
212    // apart - a spread three times wider than the deltas being classified, and
213    // large enough on its own to flip a feature between auxiliary and hot-path.
214    // Pinned, the same sweep repeats to within 1%. Left OFF by default because a
215    // fleet box isolates cores outside the process, and pinning from in here
216    // would override that placement with a core the orchestrator did not choose.
217    #[cfg(feature = "affinity")]
218    if let Some(core) = std::env::var("SUBMS_PIN").ok().and_then(|v| v.parse().ok()) {
219        let _ = subms_mpsc_queue::set_affinity(&[core]);
220    }
221
222    // Burn before the first measurement, not just before each one. Every
223    // `batched` call warms itself, but the FIRST measurement in the process pays
224    // a ramp the per-measurement warm sits inside rather than absorbs, and the
225    // sweep runs smallest-first: without this the base curve read 71800 / 45300 /
226    // 46500 ns, a 1.6x fall with size that is the process settling, not the
227    // queue.
228    {
229        let mut q = filled(CANON);
230        let start = std::time::Instant::now();
231        while (start.elapsed().as_nanos() as u64) < BURN_NANOS {
232            for i in 0..ITEMS_PER_SAMPLE {
233                q.push(i as u64);
234                black_box(q.try_pop());
235            }
236        }
237    }
238
239    // The baseline: the base queue's push + try_pop round trip. Swept as well as
240    // sampled, because whether queue depth moves the BASE op is the context
241    // every feature curve is read against.
242    let base_sweep = sweep("base/push+pop", |n| {
243        let mut q = filled(n);
244        batched(ITEMS_PER_SAMPLE, |i| {
245            q.push(i as u64);
246            black_box(q.try_pop());
247        })
248    });
249    let base_p50 = base_sweep
250        .iter()
251        .find(|(n, _)| *n == CANON)
252        .map_or(0, |(_, v)| *v);
253    eprintln!("base push+pop p50 per {ITEMS_PER_SAMPLE}-item sample: {base_p50}ns");
254
255    // ---------- bounded: fixed-capacity ring, backpressure on enqueue ----------
256    #[cfg(feature = "bounded")]
257    {
258        use subms_mpsc_queue::BoundedMpscQueue;
259        // Half full at every sweep point. Filled to a FIXED element count
260        // instead, the big rings would sit 98% empty and the enqueue would be
261        // measuring the fill fraction rather than the footprint.
262        fn ring(n: usize) -> BoundedMpscQueue<u64> {
263            let q = BoundedMpscQueue::new(n);
264            for i in 0..n / 2 {
265                let _ = q.try_enqueue(i as u64);
266            }
267            q
268        }
269        let sw = sweep("bounded/enqueue+dequeue", |n| {
270            let mut q = ring(n);
271            batched(ITEMS_PER_SAMPLE, |i| {
272                let _ = q.try_enqueue(i as u64);
273                black_box(q.try_dequeue());
274            })
275        });
276        let (cat, reason) = classify_feature(&sw, Some(base_p50), None);
277
278        let mut q = ring(CANON);
279        let mut p99 = BTreeMap::new();
280        p99.insert(
281            "enqueue".to_string(),
282            single(|st, i| {
283                st.time(|| {
284                    let _ = q.try_enqueue(i as u64);
285                });
286                black_box(q.try_dequeue());
287            }),
288        );
289        p99.insert(
290            "dequeue".to_string(),
291            single(|st, i| {
292                let _ = q.try_enqueue(i as u64);
293                st.time(|| black_box(q.try_dequeue()));
294            }),
295        );
296        // The reject path, which is the reason the feature exists. The ring is
297        // filled to capacity once, OUTSIDE the timed region; every timed call
298        // then takes the full branch and hands the value back to the caller.
299        let full: BoundedMpscQueue<u64> = BoundedMpscQueue::new(CANON);
300        while full.try_enqueue(0).is_ok() {}
301        p99.insert(
302            "enqueue_full".to_string(),
303            single(|st, i| {
304                st.time(|| {
305                    let _ = full.try_enqueue(i as u64);
306                });
307            }),
308        );
309        manifest.set_feature("bounded", cat, &p99, &reason);
310    }
311
312    // ---------- mpmc: bounded ring, sequence CAS on both ends ----------
313    #[cfg(feature = "mpmc")]
314    {
315        use subms_mpsc_queue::MpmcQueue;
316        fn ring(n: usize) -> MpmcQueue<u64> {
317            let q = MpmcQueue::new(n);
318            for i in 0..n / 2 {
319                let _ = q.try_enqueue(i as u64);
320            }
321            q
322        }
323        // Uncontended, so every CAS succeeds first try. That is the figure the
324        // category is about: what the multi-consumer claim costs a queue that is
325        // NOT contended, which is the state a well-sized pipeline runs in.
326        let sw = sweep("mpmc/enqueue+dequeue", |n| {
327            let q = ring(n);
328            batched(ITEMS_PER_SAMPLE, |i| {
329                let _ = q.try_enqueue(i as u64);
330                black_box(q.try_dequeue());
331            })
332        });
333        let (cat, reason) = classify_feature(&sw, Some(base_p50), None);
334
335        let q = ring(CANON);
336        let mut p99 = BTreeMap::new();
337        p99.insert(
338            "enqueue".to_string(),
339            single(|st, i| {
340                st.time(|| {
341                    let _ = q.try_enqueue(i as u64);
342                });
343                black_box(q.try_dequeue());
344            }),
345        );
346        p99.insert(
347            "dequeue".to_string(),
348            single(|st, i| {
349                let _ = q.try_enqueue(i as u64);
350                st.time(|| black_box(q.try_dequeue()));
351            }),
352        );
353        manifest.set_feature("mpmc", cat, &p99, &reason);
354    }
355
356    // ---------- batch: drain up to BATCH items behind one acquire fence ----------
357    #[cfg(feature = "batch")]
358    {
359        use subms_mpsc_queue::BatchMpscQueue;
360        fn filled_batch(n: usize) -> BatchMpscQueue<u64> {
361            let q = BatchMpscQueue::new();
362            for i in 0..n {
363                q.push(i as u64);
364            }
365            q
366        }
367        // A sample moves ITEMS_PER_SAMPLE items either way; only the call width
368        // differs. That is why the reps count is divided rather than the batch
369        // grown - growing it would sweep the batch size, and the number would
370        // stop being comparable to the base round trip.
371        let sw = sweep("batch/push+dequeue_batch", |n| {
372            let mut q = filled_batch(n);
373            let mut buf: Vec<Option<u64>> = (0..BATCH).map(|_| None).collect();
374            batched(ITEMS_PER_SAMPLE / BATCH, |i| {
375                for j in 0..BATCH {
376                    q.push((i + j) as u64);
377                }
378                black_box(q.try_dequeue_batch(&mut buf));
379            })
380        });
381        let (cat, reason) = classify_feature(&sw, Some(base_p50), None);
382
383        let mut q = filled_batch(CANON);
384        let mut buf: Vec<Option<u64>> = (0..BATCH).map(|_| None).collect();
385        let mut p99 = BTreeMap::new();
386        // The refill is outside the timed region: timing it would put a BATCH of
387        // pushes inside the drain's number and the stage would stop being a
388        // drain figure at all.
389        p99.insert(
390            "dequeue_batch".to_string(),
391            single(|st, i| {
392                st.time(|| black_box(q.try_dequeue_batch(&mut buf)));
393                for j in 0..BATCH {
394                    q.push((i + j) as u64);
395                }
396            }),
397        );
398        p99.insert(
399            "enqueue".to_string(),
400            single(|st, i| {
401                st.time(|| q.push(i as u64));
402                let _ = q.try_dequeue_batch(&mut buf[..1]);
403            }),
404        );
405        // The producer mirror: BATCH items published behind one head swap. The
406        // drain that puts the queue back is outside the timed region for the
407        // same reason the refill is above.
408        p99.insert(
409            "enqueue_batch".to_string(),
410            single(|st, i| {
411                let base = i as u64;
412                st.time(|| black_box(q.push_batch(base..base + BATCH as u64)));
413                let _ = q.try_dequeue_batch(&mut buf);
414            }),
415        );
416        manifest.set_feature("batch", cat, &p99, &reason);
417    }
418
419    // ---------- metrics: relaxed atomic counters around each op ----------
420    #[cfg(feature = "metrics")]
421    {
422        use subms_mpsc_queue::MetricsMpscQueue;
423        fn filled_metrics(n: usize) -> MetricsMpscQueue<u64> {
424            let q = MetricsMpscQueue::new();
425            for i in 0..n {
426                q.push(i as u64);
427            }
428            q
429        }
430        let sw = sweep("metrics/push+pop", |n| {
431            let mut q = filled_metrics(n);
432            batched(ITEMS_PER_SAMPLE, |i| {
433                q.push(i as u64);
434                black_box(q.try_pop());
435            })
436        });
437        let (cat, reason) = classify_feature(&sw, Some(base_p50), None);
438
439        let mut q = filled_metrics(CANON);
440        let mut p99 = BTreeMap::new();
441        p99.insert(
442            "enqueue".to_string(),
443            single(|st, i| {
444                st.time(|| q.push(i as u64));
445                black_box(q.try_pop());
446            }),
447        );
448        p99.insert(
449            "dequeue".to_string(),
450            single(|st, i| {
451                q.push(i as u64);
452                st.time(|| black_box(q.try_pop()));
453            }),
454        );
455        p99.insert(
456            "snapshot".to_string(),
457            single(|st, _| {
458                st.time(|| black_box(q.snapshot()));
459            }),
460        );
461        manifest.set_feature("metrics", cat, &p99, &reason);
462    }
463
464    // ---------- affinity: pin the calling thread, once, at startup ----------
465    // Runs LAST because measuring it pins THIS process to core 0, and every
466    // number taken afterwards would be a number taken on one core.
467    #[cfg(feature = "affinity")]
468    {
469        use subms_mpsc_queue::set_affinity;
470        // Swept over the same axis to show what it is: a call that touches no
471        // queue state and cannot move with queue size. PINNED auxiliary rather
472        // than left to the base-delta test, which would see a syscall costing
473        // more than an enqueue and call it hot-path. It is not on the hot path at
474        // any price - `set_affinity` is called once per thread at startup and
475        // appears in neither `push` nor `try_pop`. The two ports are not even
476        // measuring the same thing: Rust issues a real `SetThreadAffinityMask` /
477        // `sched_setaffinity`, while the Java sibling validates its argument and
478        // returns UNSUPPORTED because the stock JDK has no pinning API. The
479        // per-call figure, not the sample, is the interpretable one and it is in
480        // `p99ByStage`.
481        let sw = sweep("affinity/set_affinity", |_| {
482            batched(ITEMS_PER_SAMPLE, |_| {
483                let _ = set_affinity(&[0]);
484            })
485        });
486        let (cat, reason) = classify_feature(
487            &sw,
488            Some(base_p50),
489            Some(subms::SubMsFeatureCategory::Auxiliary),
490        );
491
492        let mut p99 = BTreeMap::new();
493        p99.insert(
494            "set_affinity".to_string(),
495            single(|st, _| {
496                st.time(|| {
497                    let _ = set_affinity(&[0]);
498                });
499            }),
500        );
501        manifest.set_feature("affinity", cat, &p99, &reason);
502
503        let cores: Vec<usize> = (0..std::thread::available_parallelism()
504            .map_or(1, std::num::NonZeroUsize::get))
505            .collect();
506        let _ = set_affinity(&cores);
507    }
508
509    std::fs::create_dir_all(path.parent().unwrap())?;
510    std::fs::write(&path, manifest.to_json())?;
511    io::stdout().write_all(manifest.to_json().as_bytes())?;
512    Ok(())
513}
Source

pub fn try_dequeue_batch(&mut self, out: &mut [Option<T>]) -> usize

Drain up to out.len() items into out. Returns the count.

Stops early on dangling-tail or empty. Caller can spin / back off and re-call.

Examples found in repository?
examples/sample_app.rs (line 268)
245fn batch_drain_per_tick() {
246    use subms_mpsc_queue::BatchMpscQueue;
247
248    println!("\n== batch: publish and drain a whole tick in one pass ==");
249    const TICK: usize = 256;
250    const BURST: usize = 50;
251    let mut q: BatchMpscQueue<u64> = BatchMpscQueue::new();
252    let total = 1_000usize;
253
254    // A gateway that decodes a wire frame already holds a run of orders. One
255    // head swap publishes the whole run instead of BURST of them.
256    let mut published = 0usize;
257    while published < total {
258        let base = published;
259        published += q.push_batch((base..base + BURST).map(|seq| order_id(0, seq)));
260    }
261    println!("  {published} orders published in {} swaps", total / BURST);
262    assert_eq!(published, total);
263
264    let mut buf: Vec<Option<u64>> = (0..TICK).map(|_| None).collect();
265    let mut ticks = 0usize;
266    let mut matched = 0usize;
267    loop {
268        let n = q.try_dequeue_batch(&mut buf);
269        if n == 0 {
270            break;
271        }
272        ticks += 1;
273        for slot in buf.iter_mut().take(n) {
274            let _ = slot.take();
275            matched += 1;
276        }
277    }
278    println!("  drained {matched} orders across {ticks} ticks of up to {TICK}");
279    assert_eq!(matched, total, "every queued order is drained");
280    assert_eq!(
281        ticks,
282        total.div_ceil(TICK),
283        "each tick drains a full buffer until the tail"
284    );
285
286    // The callback form skips the buffer entirely when the match loop's work
287    // is per-order anyway. Here it accumulates notional.
288    q.push_batch((0..64).map(|seq| order_id(1, seq)));
289    let mut notional = 0u64;
290    let handled = q.drain(TICK, |order| notional += order & 0xffff_ffff);
291    println!("  drain callback handled {handled} orders, notional {notional}");
292    assert_eq!(handled, 64);
293    assert_eq!(notional, (0..64u64).sum::<u64>());
294    assert!(q.is_empty());
295}
More examples
Hide additional examples
examples/perf_features.rs (line 378)
195fn main() -> io::Result<()> {
196    let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
197        .join("..")
198        .join(".subms")
199        .join("features")
200        .join("rust.json");
201    let existing = std::fs::read_to_string(&path).unwrap_or_default();
202    let mut manifest = SubMsFeatureManifest::load_str("rust", &existing);
203    // Stamp the box these numbers came from. The bench runs wherever it is
204    // invoked, so an unstamped manifest is indistinguishable from a fleet
205    // capture; the renderer will not publish one it cannot attribute.
206    let (source, instance) = SubMsP99Source::from_env();
207    manifest.set_p99_source(source, instance.as_deref());
208
209    // Optional, off by default: pin this thread to one core for the whole run.
210    // On a heterogeneous laptop the scheduler moves the bench between core
211    // clusters and every measurement lands in one of two clock states 1.31x
212    // apart - a spread three times wider than the deltas being classified, and
213    // large enough on its own to flip a feature between auxiliary and hot-path.
214    // Pinned, the same sweep repeats to within 1%. Left OFF by default because a
215    // fleet box isolates cores outside the process, and pinning from in here
216    // would override that placement with a core the orchestrator did not choose.
217    #[cfg(feature = "affinity")]
218    if let Some(core) = std::env::var("SUBMS_PIN").ok().and_then(|v| v.parse().ok()) {
219        let _ = subms_mpsc_queue::set_affinity(&[core]);
220    }
221
222    // Burn before the first measurement, not just before each one. Every
223    // `batched` call warms itself, but the FIRST measurement in the process pays
224    // a ramp the per-measurement warm sits inside rather than absorbs, and the
225    // sweep runs smallest-first: without this the base curve read 71800 / 45300 /
226    // 46500 ns, a 1.6x fall with size that is the process settling, not the
227    // queue.
228    {
229        let mut q = filled(CANON);
230        let start = std::time::Instant::now();
231        while (start.elapsed().as_nanos() as u64) < BURN_NANOS {
232            for i in 0..ITEMS_PER_SAMPLE {
233                q.push(i as u64);
234                black_box(q.try_pop());
235            }
236        }
237    }
238
239    // The baseline: the base queue's push + try_pop round trip. Swept as well as
240    // sampled, because whether queue depth moves the BASE op is the context
241    // every feature curve is read against.
242    let base_sweep = sweep("base/push+pop", |n| {
243        let mut q = filled(n);
244        batched(ITEMS_PER_SAMPLE, |i| {
245            q.push(i as u64);
246            black_box(q.try_pop());
247        })
248    });
249    let base_p50 = base_sweep
250        .iter()
251        .find(|(n, _)| *n == CANON)
252        .map_or(0, |(_, v)| *v);
253    eprintln!("base push+pop p50 per {ITEMS_PER_SAMPLE}-item sample: {base_p50}ns");
254
255    // ---------- bounded: fixed-capacity ring, backpressure on enqueue ----------
256    #[cfg(feature = "bounded")]
257    {
258        use subms_mpsc_queue::BoundedMpscQueue;
259        // Half full at every sweep point. Filled to a FIXED element count
260        // instead, the big rings would sit 98% empty and the enqueue would be
261        // measuring the fill fraction rather than the footprint.
262        fn ring(n: usize) -> BoundedMpscQueue<u64> {
263            let q = BoundedMpscQueue::new(n);
264            for i in 0..n / 2 {
265                let _ = q.try_enqueue(i as u64);
266            }
267            q
268        }
269        let sw = sweep("bounded/enqueue+dequeue", |n| {
270            let mut q = ring(n);
271            batched(ITEMS_PER_SAMPLE, |i| {
272                let _ = q.try_enqueue(i as u64);
273                black_box(q.try_dequeue());
274            })
275        });
276        let (cat, reason) = classify_feature(&sw, Some(base_p50), None);
277
278        let mut q = ring(CANON);
279        let mut p99 = BTreeMap::new();
280        p99.insert(
281            "enqueue".to_string(),
282            single(|st, i| {
283                st.time(|| {
284                    let _ = q.try_enqueue(i as u64);
285                });
286                black_box(q.try_dequeue());
287            }),
288        );
289        p99.insert(
290            "dequeue".to_string(),
291            single(|st, i| {
292                let _ = q.try_enqueue(i as u64);
293                st.time(|| black_box(q.try_dequeue()));
294            }),
295        );
296        // The reject path, which is the reason the feature exists. The ring is
297        // filled to capacity once, OUTSIDE the timed region; every timed call
298        // then takes the full branch and hands the value back to the caller.
299        let full: BoundedMpscQueue<u64> = BoundedMpscQueue::new(CANON);
300        while full.try_enqueue(0).is_ok() {}
301        p99.insert(
302            "enqueue_full".to_string(),
303            single(|st, i| {
304                st.time(|| {
305                    let _ = full.try_enqueue(i as u64);
306                });
307            }),
308        );
309        manifest.set_feature("bounded", cat, &p99, &reason);
310    }
311
312    // ---------- mpmc: bounded ring, sequence CAS on both ends ----------
313    #[cfg(feature = "mpmc")]
314    {
315        use subms_mpsc_queue::MpmcQueue;
316        fn ring(n: usize) -> MpmcQueue<u64> {
317            let q = MpmcQueue::new(n);
318            for i in 0..n / 2 {
319                let _ = q.try_enqueue(i as u64);
320            }
321            q
322        }
323        // Uncontended, so every CAS succeeds first try. That is the figure the
324        // category is about: what the multi-consumer claim costs a queue that is
325        // NOT contended, which is the state a well-sized pipeline runs in.
326        let sw = sweep("mpmc/enqueue+dequeue", |n| {
327            let q = ring(n);
328            batched(ITEMS_PER_SAMPLE, |i| {
329                let _ = q.try_enqueue(i as u64);
330                black_box(q.try_dequeue());
331            })
332        });
333        let (cat, reason) = classify_feature(&sw, Some(base_p50), None);
334
335        let q = ring(CANON);
336        let mut p99 = BTreeMap::new();
337        p99.insert(
338            "enqueue".to_string(),
339            single(|st, i| {
340                st.time(|| {
341                    let _ = q.try_enqueue(i as u64);
342                });
343                black_box(q.try_dequeue());
344            }),
345        );
346        p99.insert(
347            "dequeue".to_string(),
348            single(|st, i| {
349                let _ = q.try_enqueue(i as u64);
350                st.time(|| black_box(q.try_dequeue()));
351            }),
352        );
353        manifest.set_feature("mpmc", cat, &p99, &reason);
354    }
355
356    // ---------- batch: drain up to BATCH items behind one acquire fence ----------
357    #[cfg(feature = "batch")]
358    {
359        use subms_mpsc_queue::BatchMpscQueue;
360        fn filled_batch(n: usize) -> BatchMpscQueue<u64> {
361            let q = BatchMpscQueue::new();
362            for i in 0..n {
363                q.push(i as u64);
364            }
365            q
366        }
367        // A sample moves ITEMS_PER_SAMPLE items either way; only the call width
368        // differs. That is why the reps count is divided rather than the batch
369        // grown - growing it would sweep the batch size, and the number would
370        // stop being comparable to the base round trip.
371        let sw = sweep("batch/push+dequeue_batch", |n| {
372            let mut q = filled_batch(n);
373            let mut buf: Vec<Option<u64>> = (0..BATCH).map(|_| None).collect();
374            batched(ITEMS_PER_SAMPLE / BATCH, |i| {
375                for j in 0..BATCH {
376                    q.push((i + j) as u64);
377                }
378                black_box(q.try_dequeue_batch(&mut buf));
379            })
380        });
381        let (cat, reason) = classify_feature(&sw, Some(base_p50), None);
382
383        let mut q = filled_batch(CANON);
384        let mut buf: Vec<Option<u64>> = (0..BATCH).map(|_| None).collect();
385        let mut p99 = BTreeMap::new();
386        // The refill is outside the timed region: timing it would put a BATCH of
387        // pushes inside the drain's number and the stage would stop being a
388        // drain figure at all.
389        p99.insert(
390            "dequeue_batch".to_string(),
391            single(|st, i| {
392                st.time(|| black_box(q.try_dequeue_batch(&mut buf)));
393                for j in 0..BATCH {
394                    q.push((i + j) as u64);
395                }
396            }),
397        );
398        p99.insert(
399            "enqueue".to_string(),
400            single(|st, i| {
401                st.time(|| q.push(i as u64));
402                let _ = q.try_dequeue_batch(&mut buf[..1]);
403            }),
404        );
405        // The producer mirror: BATCH items published behind one head swap. The
406        // drain that puts the queue back is outside the timed region for the
407        // same reason the refill is above.
408        p99.insert(
409            "enqueue_batch".to_string(),
410            single(|st, i| {
411                let base = i as u64;
412                st.time(|| black_box(q.push_batch(base..base + BATCH as u64)));
413                let _ = q.try_dequeue_batch(&mut buf);
414            }),
415        );
416        manifest.set_feature("batch", cat, &p99, &reason);
417    }
418
419    // ---------- metrics: relaxed atomic counters around each op ----------
420    #[cfg(feature = "metrics")]
421    {
422        use subms_mpsc_queue::MetricsMpscQueue;
423        fn filled_metrics(n: usize) -> MetricsMpscQueue<u64> {
424            let q = MetricsMpscQueue::new();
425            for i in 0..n {
426                q.push(i as u64);
427            }
428            q
429        }
430        let sw = sweep("metrics/push+pop", |n| {
431            let mut q = filled_metrics(n);
432            batched(ITEMS_PER_SAMPLE, |i| {
433                q.push(i as u64);
434                black_box(q.try_pop());
435            })
436        });
437        let (cat, reason) = classify_feature(&sw, Some(base_p50), None);
438
439        let mut q = filled_metrics(CANON);
440        let mut p99 = BTreeMap::new();
441        p99.insert(
442            "enqueue".to_string(),
443            single(|st, i| {
444                st.time(|| q.push(i as u64));
445                black_box(q.try_pop());
446            }),
447        );
448        p99.insert(
449            "dequeue".to_string(),
450            single(|st, i| {
451                q.push(i as u64);
452                st.time(|| black_box(q.try_pop()));
453            }),
454        );
455        p99.insert(
456            "snapshot".to_string(),
457            single(|st, _| {
458                st.time(|| black_box(q.snapshot()));
459            }),
460        );
461        manifest.set_feature("metrics", cat, &p99, &reason);
462    }
463
464    // ---------- affinity: pin the calling thread, once, at startup ----------
465    // Runs LAST because measuring it pins THIS process to core 0, and every
466    // number taken afterwards would be a number taken on one core.
467    #[cfg(feature = "affinity")]
468    {
469        use subms_mpsc_queue::set_affinity;
470        // Swept over the same axis to show what it is: a call that touches no
471        // queue state and cannot move with queue size. PINNED auxiliary rather
472        // than left to the base-delta test, which would see a syscall costing
473        // more than an enqueue and call it hot-path. It is not on the hot path at
474        // any price - `set_affinity` is called once per thread at startup and
475        // appears in neither `push` nor `try_pop`. The two ports are not even
476        // measuring the same thing: Rust issues a real `SetThreadAffinityMask` /
477        // `sched_setaffinity`, while the Java sibling validates its argument and
478        // returns UNSUPPORTED because the stock JDK has no pinning API. The
479        // per-call figure, not the sample, is the interpretable one and it is in
480        // `p99ByStage`.
481        let sw = sweep("affinity/set_affinity", |_| {
482            batched(ITEMS_PER_SAMPLE, |_| {
483                let _ = set_affinity(&[0]);
484            })
485        });
486        let (cat, reason) = classify_feature(
487            &sw,
488            Some(base_p50),
489            Some(subms::SubMsFeatureCategory::Auxiliary),
490        );
491
492        let mut p99 = BTreeMap::new();
493        p99.insert(
494            "set_affinity".to_string(),
495            single(|st, _| {
496                st.time(|| {
497                    let _ = set_affinity(&[0]);
498                });
499            }),
500        );
501        manifest.set_feature("affinity", cat, &p99, &reason);
502
503        let cores: Vec<usize> = (0..std::thread::available_parallelism()
504            .map_or(1, std::num::NonZeroUsize::get))
505            .collect();
506        let _ = set_affinity(&cores);
507    }
508
509    std::fs::create_dir_all(path.parent().unwrap())?;
510    std::fs::write(&path, manifest.to_json())?;
511    io::stdout().write_all(manifest.to_json().as_bytes())?;
512    Ok(())
513}
Source

pub fn drain<F: FnMut(T)>(&mut self, limit: usize, f: F) -> usize

Drain up to limit items straight into f, with no intermediate buffer. The callback form of JCTools’ drain(Consumer, limit), and the one to reach for when the consumer’s work is per-item anyway.

Stops early on empty or dangling-tail, exactly as Self::try_dequeue_batch does. Returns the count handed to f.

Examples found in repository?
examples/sample_app.rs (line 290)
245fn batch_drain_per_tick() {
246    use subms_mpsc_queue::BatchMpscQueue;
247
248    println!("\n== batch: publish and drain a whole tick in one pass ==");
249    const TICK: usize = 256;
250    const BURST: usize = 50;
251    let mut q: BatchMpscQueue<u64> = BatchMpscQueue::new();
252    let total = 1_000usize;
253
254    // A gateway that decodes a wire frame already holds a run of orders. One
255    // head swap publishes the whole run instead of BURST of them.
256    let mut published = 0usize;
257    while published < total {
258        let base = published;
259        published += q.push_batch((base..base + BURST).map(|seq| order_id(0, seq)));
260    }
261    println!("  {published} orders published in {} swaps", total / BURST);
262    assert_eq!(published, total);
263
264    let mut buf: Vec<Option<u64>> = (0..TICK).map(|_| None).collect();
265    let mut ticks = 0usize;
266    let mut matched = 0usize;
267    loop {
268        let n = q.try_dequeue_batch(&mut buf);
269        if n == 0 {
270            break;
271        }
272        ticks += 1;
273        for slot in buf.iter_mut().take(n) {
274            let _ = slot.take();
275            matched += 1;
276        }
277    }
278    println!("  drained {matched} orders across {ticks} ticks of up to {TICK}");
279    assert_eq!(matched, total, "every queued order is drained");
280    assert_eq!(
281        ticks,
282        total.div_ceil(TICK),
283        "each tick drains a full buffer until the tail"
284    );
285
286    // The callback form skips the buffer entirely when the match loop's work
287    // is per-order anyway. Here it accumulates notional.
288    q.push_batch((0..64).map(|seq| order_id(1, seq)));
289    let mut notional = 0u64;
290    let handled = q.drain(TICK, |order| notional += order & 0xffff_ffff);
291    println!("  drain callback handled {handled} orders, notional {notional}");
292    assert_eq!(handled, 64);
293    assert_eq!(notional, (0..64u64).sum::<u64>());
294    assert!(q.is_empty());
295}
Source

pub fn drain_into_vec(&mut self, out: &mut Vec<T>, cap: usize) -> usize

Convenience: drain into a Vec, returning the count drained. Pre-sizes the vec to cap before draining.

Source

pub fn peek(&mut self) -> Option<&T>

Borrow the next value without consuming it. See MpscQueue::peek.

Source

pub fn is_empty(&mut self) -> bool

Examples found in repository?
examples/sample_app.rs (line 294)
245fn batch_drain_per_tick() {
246    use subms_mpsc_queue::BatchMpscQueue;
247
248    println!("\n== batch: publish and drain a whole tick in one pass ==");
249    const TICK: usize = 256;
250    const BURST: usize = 50;
251    let mut q: BatchMpscQueue<u64> = BatchMpscQueue::new();
252    let total = 1_000usize;
253
254    // A gateway that decodes a wire frame already holds a run of orders. One
255    // head swap publishes the whole run instead of BURST of them.
256    let mut published = 0usize;
257    while published < total {
258        let base = published;
259        published += q.push_batch((base..base + BURST).map(|seq| order_id(0, seq)));
260    }
261    println!("  {published} orders published in {} swaps", total / BURST);
262    assert_eq!(published, total);
263
264    let mut buf: Vec<Option<u64>> = (0..TICK).map(|_| None).collect();
265    let mut ticks = 0usize;
266    let mut matched = 0usize;
267    loop {
268        let n = q.try_dequeue_batch(&mut buf);
269        if n == 0 {
270            break;
271        }
272        ticks += 1;
273        for slot in buf.iter_mut().take(n) {
274            let _ = slot.take();
275            matched += 1;
276        }
277    }
278    println!("  drained {matched} orders across {ticks} ticks of up to {TICK}");
279    assert_eq!(matched, total, "every queued order is drained");
280    assert_eq!(
281        ticks,
282        total.div_ceil(TICK),
283        "each tick drains a full buffer until the tail"
284    );
285
286    // The callback form skips the buffer entirely when the match loop's work
287    // is per-order anyway. Here it accumulates notional.
288    q.push_batch((0..64).map(|seq| order_id(1, seq)));
289    let mut notional = 0u64;
290    let handled = q.drain(TICK, |order| notional += order & 0xffff_ffff);
291    println!("  drain callback handled {handled} orders, notional {notional}");
292    assert_eq!(handled, 64);
293    assert_eq!(notional, (0..64u64).sum::<u64>());
294    assert!(q.is_empty());
295}
Source

pub fn len(&mut self) -> usize

See MpscQueue::len. O(n) in the backlog.

Source

pub fn clear(&mut self) -> usize

Trait Implementations§

Source§

impl<T> Default for BatchMpscQueue<T>

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

§

impl<T> !Freeze for BatchMpscQueue<T>

§

impl<T> !RefUnwindSafe for BatchMpscQueue<T>

§

impl<T> !UnwindSafe for BatchMpscQueue<T>

§

impl<T> Send for BatchMpscQueue<T>
where T: Send,

§

impl<T> Sync for BatchMpscQueue<T>
where T: Send,

§

impl<T> Unpin for BatchMpscQueue<T>

§

impl<T> UnsafeUnpin for BatchMpscQueue<T>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.