Initial Dever 0.0.1
This commit is contained in:
commit
81233d098c
1479 files changed
+163782
No files matched your search
@@ -0,0 +1,67 @@
|
||||
long cmd record_update = app.recordUpdate
|
||||
|
||||
long cmd continuous_record_update = app.continuousRecordUpdate
|
||||
|
||||
long cmd integer = app.integer
|
||||
|
||||
long cmd record_inspect = app.recordInspect
|
||||
|
||||
long cmd continuous_record_inspect = app.continuousRecordInspect
|
||||
|
||||
long cmd map_lookup = app.mapLookup
|
||||
|
||||
long cmd continuous_map_lookup = app.continuousMapLookup
|
||||
|
||||
long cmd map_text_lookup = app.mapTextLookup
|
||||
|
||||
long cmd continuous_map_text_lookup = app.continuousMapTextLookup
|
||||
|
||||
long cmd text_equal = app.textEqual
|
||||
|
||||
long cmd continuous_text_equal = app.continuousTextEqual
|
||||
|
||||
long cmd text_contains = app.textContains
|
||||
|
||||
long cmd continuous_text_contains = app.continuousTextContains
|
||||
|
||||
long cmd float = app.float
|
||||
|
||||
long cmd bytes_concat = app.bytesConcat
|
||||
|
||||
long cmd text_concat = app.textConcat
|
||||
|
||||
long cmd list = app.list
|
||||
|
||||
long cmd map = app.map
|
||||
|
||||
long cmd list_read = app.listRead
|
||||
|
||||
long cmd map_read = app.mapRead
|
||||
|
||||
long cmd list_record = app.listRecord
|
||||
|
||||
long cmd list_record_read = app.listRecordRead
|
||||
|
||||
long cmd continuous_integer = app.continuousInteger
|
||||
|
||||
long cmd continuous_float = app.continuousFloat
|
||||
|
||||
long cmd continuous_list = app.continuousList
|
||||
|
||||
long cmd continuous_map = app.continuousMap
|
||||
|
||||
long cmd continuous_list_read = app.continuousListRead
|
||||
|
||||
long cmd continuous_map_read = app.continuousMapRead
|
||||
|
||||
long cmd continuous_list_record = app.continuousListRecord
|
||||
|
||||
long cmd continuous_list_record_read = app.continuousListRecordRead
|
||||
|
||||
long cmd continuous_text_concat = app.continuousTextConcat
|
||||
|
||||
long cmd continuous_bytes_concat = app.continuousBytesConcat
|
||||
|
||||
long cmd call_read = app.callRead
|
||||
|
||||
long cmd continuous_call_read = app.continuousCallRead
|
||||
@@ -0,0 +1,393 @@
|
||||
type Lookup {
|
||||
values: Map<Int, Int>
|
||||
index: Int
|
||||
total: Int
|
||||
}
|
||||
|
||||
selected(value: Int) (answer: Int) { answer = value }
|
||||
selected(value: null) (answer: Int) { answer = 0 }
|
||||
|
||||
lookup(value: Int, state: Lookup) (next: Lookup) {
|
||||
found = selected(get(state.values, state.index))
|
||||
next = Lookup { values = state.values
|
||||
index = state.index + 1
|
||||
total = state.total + found }
|
||||
}
|
||||
|
||||
mapLookup(ticks: Text) (sample: Sample) {
|
||||
input = dever.bytes.fromText(ticks)
|
||||
values = reduce(putValue, input, {})
|
||||
initial = Lookup { values = values
|
||||
index = 0
|
||||
total = 0 }
|
||||
begin = dever.time.monotonicNanos()
|
||||
final = reduce(lookup, input, initial)
|
||||
end = dever.time.monotonicNanos()
|
||||
sample = Sample { elapsed_ns = end - begin
|
||||
checksum = final.total }
|
||||
}
|
||||
|
||||
continuousMapLookup(ticks: Text) (samples: List<Sample>) {
|
||||
samples = each(mapLookup, [ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks])
|
||||
}
|
||||
|
||||
type TextLookup {
|
||||
values: Map<Text, Int>
|
||||
total: Int
|
||||
}
|
||||
|
||||
type TextPair {
|
||||
left: Text
|
||||
right: Text
|
||||
}
|
||||
|
||||
textEntry(entry: MapEntry<Int, Int>, state: Map<Text, Int>) (next: Map<Text, Int>) {
|
||||
next = put(state, "lookup-key-" + int.toText(entry.key), entry.value)
|
||||
}
|
||||
|
||||
textKey(entry: MapEntry<Text, Int>) (key: Text) { key = entry.key }
|
||||
|
||||
textLookup(key: Text, state: TextLookup) (next: TextLookup) {
|
||||
found = selected(get(state.values, key))
|
||||
next = TextLookup { values = state.values
|
||||
total = state.total + found }
|
||||
}
|
||||
|
||||
mapTextLookup(ticks: Text) (sample: Sample) {
|
||||
input = dever.bytes.fromText(ticks)
|
||||
values = reduce(textEntry, entries(reduce(putValue, input, {})), {})
|
||||
keys = each(textKey, entries(values))
|
||||
initial = TextLookup { values = values
|
||||
total = 0 }
|
||||
begin = dever.time.monotonicNanos()
|
||||
final = reduce(textLookup, keys, initial)
|
||||
end = dever.time.monotonicNanos()
|
||||
sample = Sample { elapsed_ns = end - begin
|
||||
checksum = final.total + length(keys) - length(input) }
|
||||
}
|
||||
|
||||
continuousMapTextLookup(ticks: Text) (samples: List<Sample>) {
|
||||
samples = each(mapTextLookup, [ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks])
|
||||
}
|
||||
|
||||
textPair(value: Int) (pair: TextPair) {
|
||||
left = "compare-value-" + int.toText(value)
|
||||
pair = TextPair { left = left
|
||||
right = text.lower(text.upper(left)) }
|
||||
}
|
||||
|
||||
matchedValue(matched: true) (value: Int) { value = 120 }
|
||||
matchedValue(matched: false) (value: Int) { value = 0 }
|
||||
|
||||
equalTextPair(row: TextPair, total: Int) (next: Int) {
|
||||
next = total + matchedValue(row.left == row.right)
|
||||
}
|
||||
|
||||
containsTextPair(row: TextPair, total: Int) (next: Int) {
|
||||
next = total + matchedValue(text.contains(row.left, row.right))
|
||||
}
|
||||
|
||||
textEqual(ticks: Text) (sample: Sample) {
|
||||
input = dever.bytes.fromText(ticks)
|
||||
rows = each(textPair, input)
|
||||
begin = dever.time.monotonicNanos()
|
||||
total = reduce(equalTextPair, rows, 0)
|
||||
end = dever.time.monotonicNanos()
|
||||
sample = Sample { elapsed_ns = end - begin
|
||||
checksum = total + length(rows) - length(input) }
|
||||
}
|
||||
|
||||
continuousTextEqual(ticks: Text) (samples: List<Sample>) {
|
||||
samples = each(textEqual, [ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks])
|
||||
}
|
||||
|
||||
textContains(ticks: Text) (sample: Sample) {
|
||||
input = dever.bytes.fromText(ticks)
|
||||
rows = each(textPair, input)
|
||||
begin = dever.time.monotonicNanos()
|
||||
total = reduce(containsTextPair, rows, 0)
|
||||
end = dever.time.monotonicNanos()
|
||||
sample = Sample { elapsed_ns = end - begin
|
||||
checksum = total + length(rows) - length(input) }
|
||||
}
|
||||
|
||||
continuousTextContains(ticks: Text) (samples: List<Sample>) {
|
||||
samples = each(textContains, [ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks])
|
||||
}
|
||||
|
||||
type Sample {
|
||||
elapsed_ns: Int
|
||||
checksum: Int
|
||||
}
|
||||
|
||||
type FloatSample {
|
||||
elapsed_ns: Int
|
||||
checksum: Float
|
||||
}
|
||||
|
||||
addFloat(value: Int, state: Float) (next: Float) {
|
||||
next = state + float.fromInt(value)
|
||||
}
|
||||
|
||||
float(ticks: Text) (sample: FloatSample) {
|
||||
bytes = dever.bytes.fromText(ticks)
|
||||
zero = float.fromInt(0)
|
||||
begin = dever.time.monotonicNanos()
|
||||
total = reduce(addFloat, bytes, zero)
|
||||
end = dever.time.monotonicNanos()
|
||||
sample = FloatSample { elapsed_ns = end - begin
|
||||
checksum = total }
|
||||
}
|
||||
|
||||
joinBytes(value: Bytes, state: Bytes) (next: Bytes) {
|
||||
next = dever.bytes.concat(state, value)
|
||||
}
|
||||
|
||||
bytesConcat(ticks: Text) (sample: Sample) {
|
||||
chunks = each(dever.bytes.fromText, text.split(ticks, ""))
|
||||
empty = dever.bytes.fromText("")
|
||||
begin = dever.time.monotonicNanos()
|
||||
output = reduce(joinBytes, chunks, empty)
|
||||
end = dever.time.monotonicNanos()
|
||||
total = reduce(add, output, 0)
|
||||
sample = Sample { elapsed_ns = end - begin
|
||||
checksum = total }
|
||||
}
|
||||
|
||||
joinText(value: Text, state: Text) (next: Text) {
|
||||
next = state + value
|
||||
}
|
||||
|
||||
textConcat(ticks: Text) (sample: Sample) {
|
||||
chunks = text.split(ticks, ",")
|
||||
begin = dever.time.monotonicNanos()
|
||||
output = reduce(joinText, chunks, "")
|
||||
end = dever.time.monotonicNanos()
|
||||
sample = Sample {
|
||||
elapsed_ns = end - begin
|
||||
checksum = reduce(add, dever.bytes.fromText(output), 0)
|
||||
}
|
||||
}
|
||||
|
||||
add(value: Int, state: Int) (next: Int) {
|
||||
next = state + value
|
||||
}
|
||||
|
||||
appendValue(value: Int, state: List<Int>) (next: List<Int>) {
|
||||
next = append(state, value)
|
||||
}
|
||||
|
||||
putValue(value: Int, state: Map<Int, Int>) (next: Map<Int, Int>) {
|
||||
key = length(state)
|
||||
next = put(state, key, value)
|
||||
}
|
||||
|
||||
mapped(entry: MapEntry<Int, Int>) (value: Int) {
|
||||
value = entry.value
|
||||
}
|
||||
|
||||
integer(ticks: Text) (sample: Sample) {
|
||||
input = dever.bytes.fromText(ticks)
|
||||
begin = dever.time.monotonicNanos()
|
||||
value = reduce(add, input, 0)
|
||||
end = dever.time.monotonicNanos()
|
||||
sample = Sample {
|
||||
elapsed_ns = end - begin
|
||||
checksum = value
|
||||
}
|
||||
}
|
||||
|
||||
list(ticks: Text) (sample: Sample) {
|
||||
input = dever.bytes.fromText(ticks)
|
||||
begin = dever.time.monotonicNanos()
|
||||
value = reduce(appendValue, input, [])
|
||||
end = dever.time.monotonicNanos()
|
||||
sample = Sample {
|
||||
elapsed_ns = end - begin
|
||||
checksum = sum(value)
|
||||
}
|
||||
}
|
||||
|
||||
map(ticks: Text) (sample: Sample) {
|
||||
input = dever.bytes.fromText(ticks)
|
||||
begin = dever.time.monotonicNanos()
|
||||
value = reduce(putValue, input, {})
|
||||
end = dever.time.monotonicNanos()
|
||||
sample = Sample {
|
||||
elapsed_ns = end - begin
|
||||
checksum = sum(each(mapped, entries(value)))
|
||||
}
|
||||
}
|
||||
|
||||
listRead(ticks: Text) (sample: Sample) {
|
||||
values = reduce(appendValue, dever.bytes.fromText(ticks), [])
|
||||
begin = dever.time.monotonicNanos()
|
||||
total = sum(values)
|
||||
end = dever.time.monotonicNanos()
|
||||
sample = Sample { elapsed_ns = end - begin
|
||||
checksum = total }
|
||||
}
|
||||
|
||||
mapRead(ticks: Text) (sample: Sample) {
|
||||
values = reduce(putValue, dever.bytes.fromText(ticks), {})
|
||||
begin = dever.time.monotonicNanos()
|
||||
total = sum(mapped, entries(values))
|
||||
end = dever.time.monotonicNanos()
|
||||
sample = Sample { elapsed_ns = end - begin
|
||||
checksum = total }
|
||||
}
|
||||
|
||||
type Record {
|
||||
first: Int
|
||||
second: Int
|
||||
third: Int
|
||||
}
|
||||
|
||||
appendRecord(value: Int, state: List<Record>) (next: List<Record>) {
|
||||
next = append(state, Record { first = value
|
||||
second = 0
|
||||
third = 0 })
|
||||
}
|
||||
|
||||
recordValue(row: Record) (value: Int) {
|
||||
value = row.first + row.second + row.third
|
||||
}
|
||||
|
||||
listRecord(ticks: Text) (sample: Sample) {
|
||||
input = dever.bytes.fromText(ticks)
|
||||
begin = dever.time.monotonicNanos()
|
||||
values = reduce(appendRecord, input, [])
|
||||
end = dever.time.monotonicNanos()
|
||||
sample = Sample { elapsed_ns = end - begin
|
||||
checksum = sum(recordValue, values) }
|
||||
}
|
||||
|
||||
listRecordRead(ticks: Text) (sample: Sample) {
|
||||
values = reduce(appendRecord, dever.bytes.fromText(ticks), [])
|
||||
begin = dever.time.monotonicNanos()
|
||||
total = sum(recordValue, values)
|
||||
end = dever.time.monotonicNanos()
|
||||
sample = Sample { elapsed_ns = end - begin
|
||||
checksum = total }
|
||||
}
|
||||
|
||||
continuousInteger(ticks: Text) (samples: List<Sample>) {
|
||||
samples = each(integer, [ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks])
|
||||
}
|
||||
|
||||
continuousFloat(ticks: Text) (samples: List<FloatSample>) {
|
||||
samples = each(float, [ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks])
|
||||
}
|
||||
|
||||
continuousList(ticks: Text) (samples: List<Sample>) {
|
||||
samples = each(list, [ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks])
|
||||
}
|
||||
|
||||
continuousMap(ticks: Text) (samples: List<Sample>) {
|
||||
samples = each(map, [ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks])
|
||||
}
|
||||
|
||||
continuousListRead(ticks: Text) (samples: List<Sample>) {
|
||||
samples = each(listRead, [ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks])
|
||||
}
|
||||
|
||||
continuousMapRead(ticks: Text) (samples: List<Sample>) {
|
||||
samples = each(mapRead, [ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks])
|
||||
}
|
||||
|
||||
continuousListRecord(ticks: Text) (samples: List<Sample>) {
|
||||
samples = each(listRecord, [ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks])
|
||||
}
|
||||
|
||||
continuousListRecordRead(ticks: Text) (samples: List<Sample>) {
|
||||
samples = each(listRecordRead, [ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks])
|
||||
}
|
||||
|
||||
continuousTextConcat(ticks: Text) (samples: List<Sample>) {
|
||||
samples = each(textConcat, [ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks])
|
||||
}
|
||||
|
||||
continuousBytesConcat(ticks: Text) (samples: List<Sample>) {
|
||||
samples = each(bytesConcat, [ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks])
|
||||
}
|
||||
|
||||
type OwnedRecord {
|
||||
name: Text
|
||||
tags: List<Text>
|
||||
count: Int
|
||||
}
|
||||
|
||||
ownedRecord(value: Int) (row: OwnedRecord) {
|
||||
name = "row-value-" + int.toText(value)
|
||||
row = OwnedRecord { name = name
|
||||
tags = [name]
|
||||
count = value - length(name) - 1 }
|
||||
}
|
||||
|
||||
inspectOwned(row: OwnedRecord) (value: Int) {
|
||||
value = row.count + length(row.name) + length(row.tags)
|
||||
}
|
||||
|
||||
forwardOwned(row: OwnedRecord) (value: Int) {
|
||||
value = inspectOwned(row)
|
||||
}
|
||||
|
||||
callRead(ticks: Text) (sample: Sample) {
|
||||
rows = each(ownedRecord, dever.bytes.fromText(ticks))
|
||||
begin = dever.time.monotonicNanos()
|
||||
total = sum(forwardOwned, rows)
|
||||
end = dever.time.monotonicNanos()
|
||||
preserved = length(rows)
|
||||
sample = Sample { elapsed_ns = end - begin
|
||||
checksum = total + preserved - length(rows) }
|
||||
}
|
||||
|
||||
continuousCallRead(ticks: Text) (samples: List<Sample>) {
|
||||
samples = each(callRead, [ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks])
|
||||
}
|
||||
|
||||
type Accumulator {
|
||||
values: List<Int>
|
||||
count: Int
|
||||
}
|
||||
|
||||
appendField(value: Int, state: Accumulator) (next: Accumulator) {
|
||||
values = append(state.values, value)
|
||||
next = Accumulator { values = values
|
||||
count = state.count + 1 }
|
||||
}
|
||||
|
||||
recordUpdate(ticks: Text) (sample: Sample) {
|
||||
input = dever.bytes.fromText(ticks)
|
||||
begin = dever.time.monotonicNanos()
|
||||
state = reduce(appendField, input, Accumulator { values = []
|
||||
count = 0 })
|
||||
end = dever.time.monotonicNanos()
|
||||
sample = Sample { elapsed_ns = end - begin
|
||||
checksum = sum(state.values) + state.count - length(state.values) }
|
||||
}
|
||||
|
||||
continuousRecordUpdate(ticks: Text) (samples: List<Sample>) {
|
||||
samples = each(recordUpdate, [ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks])
|
||||
}
|
||||
|
||||
appendInspected(value: Int, state: Accumulator) (next: Accumulator) {
|
||||
count = length(state.values) + 1
|
||||
values = append(state.values, value)
|
||||
next = Accumulator { values = values
|
||||
count = count }
|
||||
}
|
||||
|
||||
recordInspect(ticks: Text) (sample: Sample) {
|
||||
input = dever.bytes.fromText(ticks)
|
||||
begin = dever.time.monotonicNanos()
|
||||
state = reduce(appendInspected, input, Accumulator { values = []
|
||||
count = 0 })
|
||||
end = dever.time.monotonicNanos()
|
||||
sample = Sample { elapsed_ns = end - begin
|
||||
checksum = sum(state.values) + state.count - length(state.values) }
|
||||
}
|
||||
|
||||
continuousRecordInspect(ticks: Text) (samples: List<Sample>) {
|
||||
samples = each(recordInspect, [ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks, ticks])
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
use std::hint::black_box;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct Fault {
|
||||
message: String,
|
||||
locations: Vec<&'static str>,
|
||||
causes: Vec<String>,
|
||||
}
|
||||
|
||||
impl Fault {
|
||||
fn overflow(location: &'static str) -> Self {
|
||||
Self {
|
||||
message: "Int overflow".into(),
|
||||
locations: vec![location],
|
||||
causes: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn at(mut self, location: &'static str) -> Self {
|
||||
self.locations.push(location);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
struct Record {
|
||||
first: i64,
|
||||
second: i64,
|
||||
third: i64,
|
||||
}
|
||||
|
||||
struct OwnedRecord {
|
||||
name: String,
|
||||
tags: Vec<String>,
|
||||
count: i64,
|
||||
}
|
||||
|
||||
fn inspect_owned(row: &OwnedRecord) -> Result<i64, Fault> {
|
||||
let partial = row
|
||||
.count
|
||||
.checked_add(row.name.chars().count() as i64)
|
||||
.ok_or_else(|| Fault::overflow("inspect_owned"))?;
|
||||
partial
|
||||
.checked_add(row.tags.len() as i64)
|
||||
.ok_or_else(|| Fault::overflow("inspect_owned"))
|
||||
}
|
||||
|
||||
fn forward_owned(row: &OwnedRecord) -> Result<i64, Fault> {
|
||||
inspect_owned(row).map_err(|error| error.at("forward_owned"))
|
||||
}
|
||||
|
||||
fn owned_sum(rows: &[OwnedRecord]) -> Result<i64, Fault> {
|
||||
let mut total = 0_i64;
|
||||
for row in rows {
|
||||
let projected = forward_owned(row).map_err(|error| error.at("call_read"))?;
|
||||
total = total
|
||||
.checked_add(projected)
|
||||
.ok_or_else(|| Fault::overflow("call_read"))?;
|
||||
}
|
||||
Ok(total)
|
||||
}
|
||||
|
||||
fn add(total: i64, value: i64) -> Result<i64, Fault> {
|
||||
total
|
||||
.checked_add(value)
|
||||
.ok_or_else(|| Fault::overflow("add"))
|
||||
}
|
||||
|
||||
fn record_value(row: &Record) -> Result<i64, Fault> {
|
||||
let partial = row
|
||||
.first
|
||||
.checked_add(row.second)
|
||||
.ok_or_else(|| Fault::overflow("record_value"))?;
|
||||
partial
|
||||
.checked_add(row.third)
|
||||
.ok_or_else(|| Fault::overflow("record_value"))
|
||||
}
|
||||
|
||||
fn record_sum(values: &[Record]) -> Result<i64, Fault> {
|
||||
let mut total = 0i64;
|
||||
for row in values {
|
||||
let projected = record_value(row).map_err(|error| error.at("record_sum"))?;
|
||||
total = total
|
||||
.checked_add(projected)
|
||||
.ok_or_else(|| Fault::overflow("record_sum"))?;
|
||||
}
|
||||
Ok(total)
|
||||
}
|
||||
|
||||
fn measure(operation: &str, ticks: &str) -> Result<(i64, Duration), Fault> {
|
||||
let source = black_box(ticks.as_bytes());
|
||||
match operation {
|
||||
"call_read" => {
|
||||
let rows = source
|
||||
.iter()
|
||||
.map(|value| {
|
||||
let name = format!("row-value-{value}");
|
||||
OwnedRecord {
|
||||
count: i64::from(*value) - name.chars().count() as i64 - 1,
|
||||
tags: vec![name.clone()],
|
||||
name,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let borrowed = black_box(&rows);
|
||||
let begin = Instant::now();
|
||||
let total = owned_sum(borrowed)?;
|
||||
black_box(total);
|
||||
let elapsed = begin.elapsed();
|
||||
black_box(&rows);
|
||||
Ok((total, elapsed))
|
||||
}
|
||||
"integer" => {
|
||||
let begin = Instant::now();
|
||||
let mut total = 0;
|
||||
for value in source {
|
||||
total = add(total, i64::from(*value)).map_err(|error| error.at("integer"))?;
|
||||
}
|
||||
Ok((black_box(total), begin.elapsed()))
|
||||
}
|
||||
"list_record_read" => {
|
||||
let mut values = Vec::new();
|
||||
for value in source {
|
||||
values.push(Record {
|
||||
first: i64::from(*value),
|
||||
second: 0,
|
||||
third: 0,
|
||||
});
|
||||
}
|
||||
let borrowed = black_box(&values);
|
||||
let begin = Instant::now();
|
||||
let total = record_sum(borrowed)?;
|
||||
black_box(total);
|
||||
drop(values);
|
||||
Ok((total, begin.elapsed()))
|
||||
}
|
||||
_ => panic!("unknown checked kernel"),
|
||||
}
|
||||
}
|
||||
|
||||
fn verify_errors() {
|
||||
let fault = owned_sum(&[OwnedRecord {
|
||||
name: "overflow".into(),
|
||||
tags: vec!["preserved".into()],
|
||||
count: i64::MAX,
|
||||
}])
|
||||
.unwrap_err();
|
||||
assert_eq!(fault.message, "Int overflow");
|
||||
assert_eq!(
|
||||
fault.locations,
|
||||
["inspect_owned", "forward_owned", "call_read"]
|
||||
);
|
||||
assert!(fault.causes.is_empty());
|
||||
assert_eq!(add(i64::MAX, 0).unwrap(), i64::MAX);
|
||||
assert_eq!(add(i64::MIN, 0).unwrap(), i64::MIN);
|
||||
for (left, right) in [(i64::MAX, 1), (i64::MIN, -1)] {
|
||||
let fault = add(left, right).unwrap_err().at("integer");
|
||||
assert_eq!(fault.message, "Int overflow");
|
||||
assert_eq!(fault.locations, ["add", "integer"]);
|
||||
assert!(fault.causes.is_empty());
|
||||
}
|
||||
let fault = record_sum(&[Record {
|
||||
first: i64::MAX,
|
||||
second: 1,
|
||||
third: 0,
|
||||
}])
|
||||
.unwrap_err();
|
||||
assert_eq!(fault.locations, ["record_value", "record_sum"]);
|
||||
let fault = record_sum(&[
|
||||
Record {
|
||||
first: i64::MAX,
|
||||
second: 0,
|
||||
third: 0,
|
||||
},
|
||||
Record {
|
||||
first: 1,
|
||||
second: 0,
|
||||
third: 0,
|
||||
},
|
||||
])
|
||||
.unwrap_err();
|
||||
assert_eq!(fault.locations, ["record_sum"]);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let arguments = std::env::args().collect::<Vec<_>>();
|
||||
if arguments[1] == "--verify-errors" {
|
||||
verify_errors();
|
||||
println!("checked overflow and error frames verified");
|
||||
return;
|
||||
}
|
||||
let input: serde_json::Value = serde_json::from_str(&arguments[2]).expect("input object");
|
||||
let ticks = input["ticks"].as_str().expect("ticks string");
|
||||
let (operation, count) = arguments[1]
|
||||
.strip_prefix("continuous_")
|
||||
.map_or((arguments[1].as_str(), 1), |operation| (operation, 10));
|
||||
let mut samples = Vec::new();
|
||||
for _ in 0..count {
|
||||
match measure(operation, ticks) {
|
||||
Ok((checksum, elapsed)) => samples
|
||||
.push(serde_json::json!({"checksum": checksum, "elapsed_ns": elapsed.as_nanos()})),
|
||||
Err(error) => {
|
||||
eprintln!("{error:?}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
let output = if count == 1 {
|
||||
samples.pop().unwrap()
|
||||
} else {
|
||||
serde_json::Value::Array(samples)
|
||||
};
|
||||
println!("{output}");
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type input struct {
|
||||
Ticks string `json:"ticks"`
|
||||
}
|
||||
type sample struct {
|
||||
Elapsed int64 `json:"elapsed_ns"`
|
||||
Checksum int64 `json:"checksum"`
|
||||
}
|
||||
type record struct {
|
||||
First int64
|
||||
Second int64
|
||||
Third int64
|
||||
}
|
||||
|
||||
type ownedRecord struct {
|
||||
Name string
|
||||
Tags []string
|
||||
Count int64
|
||||
}
|
||||
|
||||
func inspectOwned(row ownedRecord) int64 {
|
||||
return checkedAdd(checkedAdd(row.Count, int64(utf8.RuneCountInString(row.Name))), int64(len(row.Tags)))
|
||||
}
|
||||
|
||||
func forwardOwned(row ownedRecord) int64 {
|
||||
return inspectOwned(row)
|
||||
}
|
||||
|
||||
func checkedAdd(total, value int64) int64 {
|
||||
if value > 0 && total > math.MaxInt64-value {
|
||||
panic("overflow")
|
||||
}
|
||||
return total + value
|
||||
}
|
||||
|
||||
func measure(operation string, ticks string) sample {
|
||||
source := []byte(ticks)
|
||||
total := int64(0)
|
||||
elapsed := int64(0)
|
||||
switch operation {
|
||||
case "record_update":
|
||||
var values []int64
|
||||
count := int64(0)
|
||||
begin := time.Now()
|
||||
for _, value := range source {
|
||||
values = append(values, int64(value))
|
||||
count = checkedAdd(count, 1)
|
||||
}
|
||||
elapsed = time.Since(begin).Nanoseconds()
|
||||
for _, value := range values {
|
||||
total = checkedAdd(total, value)
|
||||
}
|
||||
total = checkedAdd(total, count) - int64(len(values))
|
||||
case "record_inspect":
|
||||
var values []int64
|
||||
count := int64(0)
|
||||
begin := time.Now()
|
||||
for _, value := range source {
|
||||
count = checkedAdd(int64(len(values)), 1)
|
||||
values = append(values, int64(value))
|
||||
}
|
||||
elapsed = time.Since(begin).Nanoseconds()
|
||||
for _, value := range values {
|
||||
total = checkedAdd(total, value)
|
||||
}
|
||||
total = checkedAdd(total, count) - int64(len(values))
|
||||
case "map_lookup":
|
||||
values := make(map[int64]int64, len(source))
|
||||
for index, value := range source {
|
||||
values[int64(index)] = int64(value)
|
||||
}
|
||||
index := int64(0)
|
||||
begin := time.Now()
|
||||
for range source {
|
||||
total = checkedAdd(total, values[index])
|
||||
index = checkedAdd(index, 1)
|
||||
}
|
||||
elapsed = time.Since(begin).Nanoseconds()
|
||||
case "text_equal", "text_contains":
|
||||
rows := make([][2]string, len(source))
|
||||
for index, value := range source {
|
||||
left := "compare-value-" + strconv.Itoa(int(value))
|
||||
rows[index] = [2]string{left, strings.ToLower(strings.ToUpper(left))}
|
||||
}
|
||||
begin := time.Now()
|
||||
if operation == "text_equal" {
|
||||
for _, row := range rows {
|
||||
matched := int64(0)
|
||||
if row[0] == row[1] {
|
||||
matched = 120
|
||||
}
|
||||
total = checkedAdd(total, matched)
|
||||
}
|
||||
} else {
|
||||
for _, row := range rows {
|
||||
matched := int64(0)
|
||||
if strings.Contains(row[0], row[1]) {
|
||||
matched = 120
|
||||
}
|
||||
total = checkedAdd(total, matched)
|
||||
}
|
||||
}
|
||||
elapsed = time.Since(begin).Nanoseconds()
|
||||
runtime.KeepAlive(rows)
|
||||
total = checkedAdd(total, int64(len(rows))) - int64(len(source))
|
||||
case "map_text_lookup":
|
||||
keys := make([]string, len(source))
|
||||
values := make(map[string]int64, len(source))
|
||||
for index, value := range source {
|
||||
key := "lookup-key-" + strconv.Itoa(index)
|
||||
keys[index] = key
|
||||
values[key] = int64(value)
|
||||
}
|
||||
begin := time.Now()
|
||||
for _, key := range keys {
|
||||
total = checkedAdd(total, values[key])
|
||||
}
|
||||
elapsed = time.Since(begin).Nanoseconds()
|
||||
runtime.KeepAlive(values)
|
||||
runtime.KeepAlive(keys)
|
||||
total = checkedAdd(total, int64(len(keys))) - int64(len(source))
|
||||
case "call_read":
|
||||
rows := make([]ownedRecord, 0, len(source))
|
||||
for _, value := range source {
|
||||
name := "row-value-" + strconv.Itoa(int(value))
|
||||
rows = append(rows, ownedRecord{name, []string{name}, int64(value) - int64(utf8.RuneCountInString(name)) - 1})
|
||||
}
|
||||
begin := time.Now()
|
||||
for _, row := range rows {
|
||||
total = checkedAdd(total, forwardOwned(row))
|
||||
}
|
||||
elapsed = time.Since(begin).Nanoseconds()
|
||||
case "float":
|
||||
floating := 0.0
|
||||
begin := time.Now()
|
||||
for _, value := range source {
|
||||
floating += float64(value)
|
||||
}
|
||||
elapsed = time.Since(begin).Nanoseconds()
|
||||
total = int64(floating)
|
||||
case "bytes_concat":
|
||||
chunks := make([][]byte, len(source))
|
||||
for index := range source {
|
||||
chunks[index] = source[index : index+1]
|
||||
}
|
||||
var output []byte
|
||||
begin := time.Now()
|
||||
for _, chunk := range chunks {
|
||||
output = append(output, chunk...)
|
||||
}
|
||||
elapsed = time.Since(begin).Nanoseconds()
|
||||
for _, value := range output {
|
||||
total = checkedAdd(total, int64(value))
|
||||
}
|
||||
case "text_concat":
|
||||
chunks := strings.Split(ticks, ",")
|
||||
begin := time.Now()
|
||||
var output strings.Builder
|
||||
for _, chunk := range chunks {
|
||||
output.WriteString(chunk)
|
||||
}
|
||||
joined := output.String()
|
||||
elapsed = time.Since(begin).Nanoseconds()
|
||||
for index := 0; index < len(joined); index++ {
|
||||
total = checkedAdd(total, int64(joined[index]))
|
||||
}
|
||||
case "integer":
|
||||
begin := time.Now()
|
||||
for _, value := range source {
|
||||
total = checkedAdd(total, int64(value))
|
||||
}
|
||||
elapsed = time.Since(begin).Nanoseconds()
|
||||
case "list":
|
||||
var values []int64
|
||||
begin := time.Now()
|
||||
for _, value := range source {
|
||||
values = append(values, int64(value))
|
||||
}
|
||||
elapsed = time.Since(begin).Nanoseconds()
|
||||
for _, value := range values {
|
||||
total = checkedAdd(total, value)
|
||||
}
|
||||
case "map":
|
||||
values := make(map[int64]int64)
|
||||
var order []int64
|
||||
begin := time.Now()
|
||||
for _, value := range source {
|
||||
key := int64(len(values))
|
||||
values[key] = int64(value)
|
||||
order = append(order, key)
|
||||
}
|
||||
elapsed = time.Since(begin).Nanoseconds()
|
||||
for _, key := range order {
|
||||
total = checkedAdd(total, values[key])
|
||||
}
|
||||
case "list_read":
|
||||
var values []int64
|
||||
for _, value := range source {
|
||||
values = append(values, int64(value))
|
||||
}
|
||||
begin := time.Now()
|
||||
for _, value := range values {
|
||||
total = checkedAdd(total, value)
|
||||
}
|
||||
elapsed = time.Since(begin).Nanoseconds()
|
||||
case "map_read":
|
||||
values := make(map[int64]int64)
|
||||
var order []int64
|
||||
for _, value := range source {
|
||||
key := int64(len(values))
|
||||
values[key] = int64(value)
|
||||
order = append(order, key)
|
||||
}
|
||||
begin := time.Now()
|
||||
for _, key := range order {
|
||||
total = checkedAdd(total, values[key])
|
||||
}
|
||||
elapsed = time.Since(begin).Nanoseconds()
|
||||
case "list_record":
|
||||
var values []record
|
||||
begin := time.Now()
|
||||
for _, value := range source {
|
||||
values = append(values, record{int64(value), 0, 0})
|
||||
}
|
||||
elapsed = time.Since(begin).Nanoseconds()
|
||||
for _, value := range values {
|
||||
total = checkedAdd(total, checkedAdd(checkedAdd(value.First, value.Second), value.Third))
|
||||
}
|
||||
case "list_record_read":
|
||||
var values []record
|
||||
for _, value := range source {
|
||||
values = append(values, record{int64(value), 0, 0})
|
||||
}
|
||||
begin := time.Now()
|
||||
for _, value := range values {
|
||||
total = checkedAdd(total, checkedAdd(checkedAdd(value.First, value.Second), value.Third))
|
||||
}
|
||||
elapsed = time.Since(begin).Nanoseconds()
|
||||
default:
|
||||
panic("unknown kernel")
|
||||
}
|
||||
return sample{elapsed, total}
|
||||
}
|
||||
|
||||
func main() {
|
||||
var arguments input
|
||||
if err := json.Unmarshal([]byte(os.Args[2]), &arguments); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var result any
|
||||
if operation, continuous := strings.CutPrefix(os.Args[1], "continuous_"); continuous {
|
||||
samples := make([]sample, 0, 10)
|
||||
for range 10 {
|
||||
samples = append(samples, measure(operation, arguments.Ticks))
|
||||
}
|
||||
result = samples
|
||||
} else {
|
||||
result = measure(os.Args[1], arguments.Ticks)
|
||||
}
|
||||
output, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(string(output))
|
||||
}
|
||||
@@ -0,0 +1,306 @@
|
||||
use indexmap::IndexMap;
|
||||
use std::hint::black_box;
|
||||
use std::time::Instant;
|
||||
|
||||
struct Record {
|
||||
first: i64,
|
||||
second: i64,
|
||||
third: i64,
|
||||
}
|
||||
|
||||
struct OwnedRecord {
|
||||
name: String,
|
||||
tags: Vec<String>,
|
||||
count: i64,
|
||||
}
|
||||
|
||||
fn inspect_owned(row: &OwnedRecord) -> i64 {
|
||||
add(
|
||||
add(row.count, row.name.chars().count() as i64),
|
||||
row.tags.len() as i64,
|
||||
)
|
||||
}
|
||||
|
||||
fn forward_owned(row: &OwnedRecord) -> i64 {
|
||||
inspect_owned(row)
|
||||
}
|
||||
|
||||
fn add(total: i64, value: i64) -> i64 {
|
||||
total.checked_add(value).expect("integer overflow")
|
||||
}
|
||||
|
||||
fn record_value(row: &Record) -> i64 {
|
||||
add(add(row.first, row.second), row.third)
|
||||
}
|
||||
|
||||
fn measure(operation: &str, ticks: &str) -> serde_json::Value {
|
||||
let source = black_box(ticks.as_bytes());
|
||||
let (checksum, elapsed) = match operation {
|
||||
"record_update" => {
|
||||
let begin = Instant::now();
|
||||
let mut values = Vec::new();
|
||||
let mut count = 0;
|
||||
for value in source {
|
||||
values.push(i64::from(*value));
|
||||
count = add(count, 1);
|
||||
}
|
||||
black_box((&values, count));
|
||||
let elapsed = begin.elapsed();
|
||||
let total = values.iter().fold(0, |total, value| add(total, *value));
|
||||
let checksum = add(total, count)
|
||||
.checked_sub(values.len() as i64)
|
||||
.expect("integer overflow");
|
||||
(checksum, elapsed)
|
||||
}
|
||||
"record_inspect" => {
|
||||
let begin = Instant::now();
|
||||
let mut values = Vec::new();
|
||||
let mut count = 0;
|
||||
for value in source {
|
||||
count = add(values.len() as i64, 1);
|
||||
values.push(i64::from(*value));
|
||||
}
|
||||
black_box((&values, count));
|
||||
let elapsed = begin.elapsed();
|
||||
let total = values.iter().fold(0, |total, value| add(total, *value));
|
||||
let checksum = add(total, count)
|
||||
.checked_sub(values.len() as i64)
|
||||
.expect("integer overflow");
|
||||
(checksum, elapsed)
|
||||
}
|
||||
"map_lookup" => {
|
||||
let values = source
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, value)| (index as i64, i64::from(*value)))
|
||||
.collect::<IndexMap<_, _>>();
|
||||
let borrowed = black_box(&values);
|
||||
let mut index = 0;
|
||||
let mut total = 0;
|
||||
let begin = Instant::now();
|
||||
for _ in source {
|
||||
total = add(total, borrowed.get(&index).copied().unwrap_or(0));
|
||||
index = add(index, 1);
|
||||
}
|
||||
black_box(total);
|
||||
let elapsed = begin.elapsed();
|
||||
black_box(&values);
|
||||
(total, elapsed)
|
||||
}
|
||||
"text_equal" | "text_contains" => {
|
||||
let rows = source
|
||||
.iter()
|
||||
.map(|value| {
|
||||
let left = format!("compare-value-{value}");
|
||||
let right = left.to_uppercase().to_lowercase();
|
||||
(left, right)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let borrowed = black_box(&rows);
|
||||
let mut total = 0;
|
||||
let begin = Instant::now();
|
||||
if operation == "text_equal" {
|
||||
for (left, right) in borrowed {
|
||||
total = add(total, if left == right { 120 } else { 0 });
|
||||
}
|
||||
} else {
|
||||
for (left, right) in borrowed {
|
||||
total = add(
|
||||
total,
|
||||
if left.contains(right.as_str()) {
|
||||
120
|
||||
} else {
|
||||
0
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
black_box(total);
|
||||
let elapsed = begin.elapsed();
|
||||
black_box(&rows);
|
||||
let checksum = add(total, rows.len() as i64)
|
||||
.checked_sub(source.len() as i64)
|
||||
.expect("integer overflow");
|
||||
(checksum, elapsed)
|
||||
}
|
||||
"map_text_lookup" => {
|
||||
// 与 Dever 使用相同的拥有键和插入顺序;HashMap<&str, _> 是不同的数据结构。
|
||||
let values = source
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, value)| (format!("lookup-key-{index}"), i64::from(*value)))
|
||||
.collect::<IndexMap<_, _>>();
|
||||
let keys = values.keys().cloned().collect::<Vec<_>>();
|
||||
let borrowed = black_box(&values);
|
||||
let mut total = 0;
|
||||
let begin = Instant::now();
|
||||
for key in &keys {
|
||||
total = add(total, borrowed.get(key.as_str()).copied().unwrap_or(0));
|
||||
}
|
||||
black_box(total);
|
||||
let elapsed = begin.elapsed();
|
||||
black_box((&keys, &values));
|
||||
let checksum = add(total, keys.len() as i64)
|
||||
.checked_sub(source.len() as i64)
|
||||
.expect("integer overflow");
|
||||
(checksum, elapsed)
|
||||
}
|
||||
"call_read" => {
|
||||
let rows = source
|
||||
.iter()
|
||||
.map(|value| {
|
||||
let name = format!("row-value-{value}");
|
||||
OwnedRecord {
|
||||
count: i64::from(*value) - name.chars().count() as i64 - 1,
|
||||
tags: vec![name.clone()],
|
||||
name,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let borrowed = black_box(&rows);
|
||||
let begin = Instant::now();
|
||||
let total = borrowed
|
||||
.iter()
|
||||
.fold(0, |total, row| add(total, forward_owned(row)));
|
||||
black_box(total);
|
||||
let elapsed = begin.elapsed();
|
||||
black_box(&rows);
|
||||
(total, elapsed)
|
||||
}
|
||||
"float" => {
|
||||
let begin = Instant::now();
|
||||
let total = source
|
||||
.iter()
|
||||
.fold(0.0, |total, value| total + f64::from(*value));
|
||||
black_box(total);
|
||||
let elapsed = begin.elapsed();
|
||||
(total as i64, elapsed)
|
||||
}
|
||||
"bytes_concat" => {
|
||||
let chunks = source.chunks(1).map(<[u8]>::to_vec).collect::<Vec<_>>();
|
||||
let begin = Instant::now();
|
||||
let mut output = Vec::new();
|
||||
for chunk in &chunks {
|
||||
output.extend_from_slice(chunk);
|
||||
}
|
||||
black_box(&output);
|
||||
let elapsed = begin.elapsed();
|
||||
drop(chunks);
|
||||
let total = output
|
||||
.iter()
|
||||
.fold(0, |total, byte| add(total, i64::from(*byte)));
|
||||
(total, elapsed)
|
||||
}
|
||||
"text_concat" => {
|
||||
let chunks = std::str::from_utf8(source)
|
||||
.unwrap()
|
||||
.split(',')
|
||||
.map(str::to_owned)
|
||||
.collect::<Vec<_>>();
|
||||
let begin = Instant::now();
|
||||
let mut output = String::new();
|
||||
for chunk in &chunks {
|
||||
output.push_str(chunk);
|
||||
}
|
||||
black_box(&output);
|
||||
let elapsed = begin.elapsed();
|
||||
drop(chunks);
|
||||
let total = output
|
||||
.bytes()
|
||||
.fold(0, |total, byte| add(total, i64::from(byte)));
|
||||
(total, elapsed)
|
||||
}
|
||||
"integer" => {
|
||||
let begin = Instant::now();
|
||||
let total = source
|
||||
.iter()
|
||||
.fold(0, |total, value| add(total, i64::from(*value)));
|
||||
(black_box(total), begin.elapsed())
|
||||
}
|
||||
"list" | "list_read" => {
|
||||
let mut values = Vec::new();
|
||||
let begin = Instant::now();
|
||||
values.extend(source.iter().map(|value| i64::from(*value)));
|
||||
let construction = begin.elapsed();
|
||||
let borrowed = black_box(&values);
|
||||
let begin = Instant::now();
|
||||
let total = borrowed.iter().fold(0, |total, value| add(total, *value));
|
||||
black_box(total);
|
||||
let traversal = begin.elapsed();
|
||||
drop(values);
|
||||
(
|
||||
total,
|
||||
if operation == "list" {
|
||||
construction
|
||||
} else {
|
||||
traversal
|
||||
},
|
||||
)
|
||||
}
|
||||
"list_record" | "list_record_read" => {
|
||||
let mut values = Vec::new();
|
||||
let begin = Instant::now();
|
||||
for value in source {
|
||||
values.push(Record {
|
||||
first: i64::from(*value),
|
||||
second: 0,
|
||||
third: 0,
|
||||
});
|
||||
}
|
||||
let construction = begin.elapsed();
|
||||
let borrowed = black_box(&values);
|
||||
let begin = Instant::now();
|
||||
let total = borrowed
|
||||
.iter()
|
||||
.fold(0, |total, row| add(total, record_value(row)));
|
||||
black_box(total);
|
||||
let traversal = begin.elapsed();
|
||||
drop(values);
|
||||
(
|
||||
total,
|
||||
if operation == "list_record" {
|
||||
construction
|
||||
} else {
|
||||
traversal
|
||||
},
|
||||
)
|
||||
}
|
||||
"map" | "map_read" => {
|
||||
let mut values = IndexMap::new();
|
||||
let begin = Instant::now();
|
||||
for value in source {
|
||||
let key = i64::try_from(values.len()).expect("map size");
|
||||
values.insert(key, i64::from(*value));
|
||||
}
|
||||
let construction = begin.elapsed();
|
||||
let borrowed = black_box(&values);
|
||||
let begin = Instant::now();
|
||||
let total = borrowed.values().fold(0, |total, value| add(total, *value));
|
||||
black_box(total);
|
||||
drop(values);
|
||||
let traversal = begin.elapsed();
|
||||
(
|
||||
total,
|
||||
if operation == "map" {
|
||||
construction
|
||||
} else {
|
||||
traversal
|
||||
},
|
||||
)
|
||||
}
|
||||
_ => panic!("unknown kernel"),
|
||||
};
|
||||
serde_json::json!({"checksum": checksum, "elapsed_ns": elapsed.as_nanos()})
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let arguments = std::env::args().collect::<Vec<_>>();
|
||||
let input: serde_json::Value = serde_json::from_str(&arguments[2]).expect("input object");
|
||||
let ticks = input["ticks"].as_str().expect("ticks string");
|
||||
let output = if let Some(operation) = arguments[1].strip_prefix("continuous_") {
|
||||
serde_json::Value::Array((0..10).map(|_| measure(operation, ticks)).collect())
|
||||
} else {
|
||||
measure(&arguments[1], ticks)
|
||||
};
|
||||
println!("{output}");
|
||||
}
|
||||
Reference in new issue
Block a user