// Developer tool: measure dictionary lookup on the real OpenCC data.
//
// It exists so that "the index made lookups fast enough" is a number in the
// repository rather than an opinion, and so regressions show up as a slower run.
// Two corpora are measured: the official golden input (dense Chinese) and a
// synthetic ASCII-heavy one (markup, code, logs), because they stress different
// parts of the engine.
//
// Usage:
//     moon run tools/bench_lookup --target native
//     moon run tools/bench_lookup --target native -- --input 

///|
/// Read a text file, exiting with a message when it cannot be read.
fn read_text(path : String) -> String {
  @fs.read_file_to_string(path) catch {
    _ => {
      println("bench_lookup: cannot read " + path)
      @sys.exit(1)
      ""
    }
  }
}

///|
/// A synthetic ASCII-heavy corpus of about 30k code units.
fn ascii_corpus() -> String {
  let out = StringBuilder()
  let mut index = 0
  while index < 400 {
    out.write_string("

The quick brown fox jumps over the lazy dog. 0123456789

\n", ) index = index + 1 } out.to_string() } ///| /// Run the chain until the clock is meaningful and report (passes, code units, ms). /// /// `now()` has millisecond resolution, so a single pass over a small document /// would read as 0 ms; the batch grows until the measurement is above 50 ms. fn measure(chain : @core.Chain, text : String) -> (Int, Int, UInt64) { let mut passes = 1 let mut elapsed : UInt64 = 0 let mut output = "" while passes <= 4096 { let start = @env.now() let mut pass = 0 while pass < passes { output = chain.apply(text) pass = pass + 1 } elapsed = @env.now() - start if elapsed >= 50 { break } passes = passes * 2 } ignore(output) (passes, text.length() * passes, elapsed) } ///| /// Entry point. fn main { let args = @env.args() let mut input_path = "test/fixtures/golden/input/us_constitution_zhs.txt" let mut index = 0 while index < args.length() && !args[index].has_prefix("--") { index = index + 1 } while index < args.length() { let arg = args[index] if arg == "--input" && index + 1 < args.length() { input_path = args[index + 1] index = index + 2 } else { index = index + 1 } } let chinese = read_text(input_path) let ascii = ascii_corpus() let build_start = @env.now() let phrases = @core.Dict::parse("STPhrases", @dict.st_phrases) let characters = @core.Dict::parse("STCharacters", @dict.st_characters) let build_ms = @env.now() - build_start println( "index build : " + build_ms.to_string() + " ms (" + phrases.size().to_string() + " + " + characters.size().to_string() + " entries)", ) // Bucket statistics are the whole point of the index: they say what a single // lookup actually has to scan. let mut max_bucket = 0 let mut total = 0 for entry in phrases.buckets.iter() { let length = entry.1.length() total = total + length if length > max_bucket { max_bucket = length } } let buckets = phrases.buckets.length() println( "buckets : " + buckets.to_string() + " first-code-unit groups, largest " + max_bucket.to_string() + ", average " + (total / buckets).to_string(), ) let pass = @core.Node::group(@core.ShortCircuit, [ @core.Node::leaf(phrases), @core.Node::leaf(characters), ]) let chain = @core.Chain::new("s2t-lite", [], None, [pass]) let (passes1, units1, elapsed1) = measure(chain, chinese) println("") println( "corpus : Chinese (opencc golden input, " + chinese.length().to_string() + " code units)", ) report(passes1, units1, elapsed1) let (passes2, units2, elapsed2) = measure(chain, ascii) println("") println( "corpus : ASCII-heavy (synthetic markup, " + ascii.length().to_string() + " code units)", ) report(passes2, units2, elapsed2) println("") println( "note : s2t-lite = short_circuit(STPhrases, STCharacters); the", ) println(" official chain adds the CJK normalisation pass (F5).") } ///| /// Print one measurement line. fn report(passes : Int, units : Int, elapsed : UInt64) -> Unit { let milliseconds = elapsed.to_int() + 1 println( "converted : " + units.to_string() + " code units over " + passes.to_string() + " pass(es) in " + elapsed.to_string() + " ms", ) println( "throughput : " + (units / milliseconds).to_string() + " code units/ms", ) }