jsonld 0.24.0

JSON-LD implementation
Documentation
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unreachable)]
use std::{env, hint::black_box, process::ExitCode, time::Instant};

use jsonld::{JsonLdProcessor, NoLoader, compaction::Compact};
use tokio::runtime::Builder;

#[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;

#[path = "../benches/common.rs"]
mod common;

use common::{Scenario, corpus, parse_remote_doc, parse_syntax_context, pre_expand, pre_process_context};

const DEFAULT_ITERS: usize = 200;

enum Mode {
    Expand,
    Compact,
    Both,
}

fn parse_mode(s: &str) -> Result<Mode, String> {
    match s {
        "expand" => Ok(Mode::Expand),
        "compact" => Ok(Mode::Compact),
        "both" => Ok(Mode::Both),
        other => Err(format!("unknown mode `{other}` (expected expand|compact|both)")),
    }
}

fn usage() {
    eprintln!("usage: profile <expand|compact|both> <scenario|all> [iters]");
    eprintln!("       iters defaults to {DEFAULT_ITERS}");
}

fn select_scenarios(name: &str) -> Result<Vec<Scenario>, String> {
    let all = corpus();
    if name == "all" {
        return Ok(all);
    }
    let picked: Vec<Scenario> = all.into_iter().filter(|s| s.name == name).collect();
    if picked.is_empty() {
        Err(format!("scenario `{name}` not found; use `all` or one of the corpus names"))
    } else {
        Ok(picked)
    }
}

struct Prepared {
    name: &'static str,
    remote: jsonld::RemoteDocument,
    expanded: jsonld::ExpandedDocument<jsonld::IriBuf, jsonld::BlankIdBuf>,
    processed: jsonld::context_processing::ProcessedOwned<jsonld::IriBuf, jsonld::BlankIdBuf>,
}

async fn prepare(scenarios: Vec<Scenario>) -> Vec<Prepared> {
    let mut out = Vec::with_capacity(scenarios.len());
    for s in scenarios {
        let remote = parse_remote_doc(&s.doc);
        let expanded = pre_expand(&remote).await;
        let ctx = parse_syntax_context(&s.context);
        let processed = pre_process_context(ctx).await;
        out.push(Prepared {
            name: s.name,
            remote,
            expanded,
            processed,
        });
    }
    out
}

async fn run_expand(p: &Prepared, iters: usize) {
    let loader = NoLoader;
    for _ in 0..iters {
        let expanded = p.remote.expand(&loader).await.expect("expand");
        black_box(expanded);
    }
}

async fn run_compact(p: &Prepared, iters: usize) {
    let loader = NoLoader;
    for _ in 0..iters {
        let out = p
            .expanded
            .compact_full(
                jsonld::rdfx::vocabulary::no_vocabulary_mut(),
                p.processed.as_ref(),
                &loader,
                jsonld::compaction::Options::default(),
            )
            .await
            .expect("compact");
        black_box(out);
    }
}

fn main() -> ExitCode {
    let args: Vec<String> = env::args().collect();
    if args.len() < 3 || args.len() > 4 {
        usage();
        return ExitCode::from(2);
    }
    let mode = match parse_mode(&args[1]) {
        Ok(m) => m,
        Err(e) => {
            eprintln!("{e}");
            usage();
            return ExitCode::from(2);
        }
    };
    let scenarios = match select_scenarios(&args[2]) {
        Ok(v) => v,
        Err(e) => {
            eprintln!("{e}");
            return ExitCode::from(2);
        }
    };
    let iters: usize = match args.get(3) {
        Some(s) => {
            if let Ok(n) = s.parse() {
                n
            } else {
                eprintln!("iters must be a positive integer");
                return ExitCode::from(2);
            }
        }
        None => DEFAULT_ITERS,
    };

    // Current-thread runtime with no I/O or time driver: the profiled work runs
    // against `NoLoader` and never reaches a suspension point.
    let runtime = match Builder::new_current_thread().build() {
        Ok(runtime) => runtime,
        Err(e) => {
            eprintln!("could not start the async runtime: {e}");
            return ExitCode::FAILURE;
        }
    };

    runtime.block_on(async {
        let prepared = prepare(scenarios).await;
        let start = Instant::now();
        for p in &prepared {
            let scenario_start = Instant::now();
            match mode {
                Mode::Expand => run_expand(p, iters).await,
                Mode::Compact => run_compact(p, iters).await,
                Mode::Both => {
                    run_expand(p, iters).await;
                    run_compact(p, iters).await;
                }
            }
            println!("{:<36} {} iters in {:?}", p.name, iters, scenario_start.elapsed());
        }
        println!("total elapsed: {:?}", start.elapsed());
    });

    ExitCode::SUCCESS
}