blob: a4ed08286df94d46f1a8dbcecc0cb83eea4a5272 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package rust
import (
"bytes"
"fmt"
"strings"
"text/template"
gidlconfig "go.fuchsia.dev/fuchsia/tools/fidl/gidl/config"
gidlir "go.fuchsia.dev/fuchsia/tools/fidl/gidl/ir"
gidlmixer "go.fuchsia.dev/fuchsia/tools/fidl/gidl/mixer"
"go.fuchsia.dev/fuchsia/tools/fidl/lib/fidlgen"
)
var benchmarkTmpl = template.Must(template.New("benchmarkTmpls").Parse(`
#![allow(unused_imports)]
use {
fidl::{
encoding::{Context, Decodable, Decoder, Encoder, with_tls_encode_buf},
handle::Handle, handle::HandleDisposition, handle::HandleInfo, handle::HandleOp,
ObjectType, Rights, UnknownData,
},
fidl_benchmarkfidl{{ .CrateSuffix }} as benchmarkfidl{{ .CrateSuffix }},
fuchsia_criterion::criterion::{BatchSize, Bencher, black_box},
fuchsia_async::futures::{future, stream::StreamExt},
fuchsia_zircon as zx,
gidl_util::{HandleSubtype, create_handles, copy_handle, copy_handles_at, disown_vec},
std::mem::ManuallyDrop
};
// BENCHMARKS is aggregated by a generated benchmark_suite.rs file, which is ultimately
// used in benchmarks in src/tests/benchmarks/fidl/rust/src/main.rs.
pub const BENCHMARKS: [(&'static str, fn(&mut Bencher)); {{ .NumBenchmarks }}] = [
{{- range .Benchmarks }}
("Builder/{{ .ChromeperfPath }}", benchmark_{{ .Name }}_builder),
("Encode/{{ .ChromeperfPath }}", benchmark_{{ .Name }}_encode),
("Decode/{{ .ChromeperfPath }}", benchmark_{{ .Name }}_decode),
{{ if .EnableSendEventBenchmark }}
("SendEvent/{{ .ChromeperfPath }}", benchmark_{{ .Name }}_send_event),
{{- end -}}
{{ if .EnableEchoCallBenchmark }}
("EchoCall/{{ .ChromeperfPath }}", benchmark_{{ .Name }}_echo_call),
{{- end -}}
{{- end }}
];
const _V1_CONTEXT: &Context = &Context {};
{{ range .Benchmarks }}
fn benchmark_{{ .Name }}_builder(b: &mut Bencher) {
b.iter_batched_ref(
|| {
{{- if .HandleDefs }}
let handle_defs = create_handles(&{{ .HandleDefs }}).unwrap();
unsafe { disown_vec(handle_defs) }
{{- else }}
{{- /* Use empty vector with b.iter_batched_ref, rather than b.iter,
for consistency between handle and non-handle benchmarks. */}}
Vec::<Handle>::new()
{{- end }}
},
|_handle_defs| {
{{- if .HandleDefs }}
let handle_defs = _handle_defs.as_ref();
{{- end }}
black_box({{ .Value }}); {{- /* semicolon: include drop time. */}}
},
{{- /* Consider using LargeInput or NumIterations if this causes memory issues. */}}
BatchSize::SmallInput,
);
}
fn benchmark_{{ .Name }}_encode(b: &mut Bencher) {
b.iter_batched_ref(
|| {
{{- if .HandleDefs }}
let handle_defs = create_handles(&{{ .HandleDefs }}).unwrap();
let handle_defs = unsafe { disown_vec(handle_defs) };
let handle_defs = handle_defs.as_ref();
{{- end }}
{{ .Value }}
},
|value| {
{{- /* Encode to TLS buffers since that's what the bindings do in practice. */}}
with_tls_encode_buf(|bytes, handles| {
Encoder::encode_with_context(_V1_CONTEXT, bytes, handles, value).unwrap();
{{- /* Return the underlying heap storage of handles, since with_tls_encode_buf
clears it after calling this closure, which otherwise would close all the
handles. By returning the actual handles, we avoid including handle close
time in the benchmark. Note that this means the handle vector must reallocate
on every iteration if handles are used. */}}
{{- if .HandleDefs }}
std::mem::take(handles)
{{- end }}
}) {{- /* no semicolon: exclude drop time. */}}
},
{{- /* Consider using LargeInput or NumIterations if this causes memory issues. */}}
BatchSize::SmallInput,
);
}
fn benchmark_{{ .Name }}_decode(b: &mut Bencher) {
b.iter_batched_ref(
|| {
{{- if .HandleDefs }}
let handle_defs = create_handles(&{{ .HandleDefs }}).unwrap();
let handle_defs = unsafe { disown_vec(handle_defs) };
let handle_defs = handle_defs.as_ref();
{{- end }}
let mut bytes = Vec::<u8>::new();
let mut handles = Vec::<HandleDisposition<'static>>::new();
let original_value = &mut {{ .Value }};
Encoder::encode_with_context(_V1_CONTEXT, &mut bytes, &mut handles, original_value).unwrap();
let handle_infos : Vec::<HandleInfo> = handles.into_iter().map(|h| {
HandleInfo {
handle: match h.handle_op {
HandleOp::Move(hdl) => hdl,
_ => panic!("unexpected handle op"),
},
object_type: h.object_type,
rights: h.rights,
}
}).collect();
{{- /* Wrap handle in an Option to allow low-overhead measurement of drop time */}}
(bytes, handle_infos, ManuallyDrop::new({{ .ValueType }}::new_empty()))
},
|(bytes, handle_infos, manually_drop_value)| {
// Cast &mut ManuallyDrop<T> to &mut T.
let value : &mut {{ .ValueType }} = unsafe { std::mem::transmute(manually_drop_value as *mut _) };
Decoder::decode_with_context(_V1_CONTEXT, bytes, handle_infos, value).unwrap();
// Count the drop time in the benchmark.
unsafe { ManuallyDrop::drop(manually_drop_value) };
},
{{- /* Consider using LargeInput or NumIterations if this causes memory issues. */}}
BatchSize::SmallInput,
);
}
{{ if .EnableSendEventBenchmark }}
async fn {{ .Name }}_send_event_receiver_thread(receiver_fidl_chan_end: zx::Channel, sender_fifo: std::sync::mpsc::SyncSender<()>) {
let async_receiver_fidl_chan_end = fuchsia_async::Channel::from_channel(receiver_fidl_chan_end).unwrap();
let proxy = {{ .ValueType }}EventProtocolProxy::new(async_receiver_fidl_chan_end);
let mut event_stream = proxy.take_event_stream();
while let Some(_event) = event_stream.next().await {
sender_fifo.send(()).unwrap();
};
}
fn benchmark_{{ .Name }}_send_event(b: &mut Bencher) {
let (sender_fifo, receiver_fifo) = std::sync::mpsc::sync_channel(1);
let (sender_fidl_chan_end, receiver_fidl_chan_end) = zx::Channel::create().unwrap();
std::thread::spawn(|| {
fuchsia_async::Executor::new()
.unwrap()
.run_singlethreaded(async move {
{{ .Name }}_send_event_receiver_thread(receiver_fidl_chan_end, sender_fifo).await;
});
});
fuchsia_async::Executor::new()
.unwrap()
.run_singlethreaded(async move {
let async_sender_fidl_chan_end = fuchsia_async::Channel::from_channel(sender_fidl_chan_end).unwrap();
let sender = <{{ .ValueType }}EventProtocolRequestStream as fidl::endpoints::RequestStream>::from_channel(async_sender_fidl_chan_end);
b.iter_batched_ref(|| {
{{- if .HandleDefs }}
let handle_defs = create_handles(&{{ .HandleDefs }}).unwrap();
let handle_defs = unsafe { disown_vec(handle_defs) };
let handle_defs = handle_defs.as_ref();
{{- end }}
{{ .Value }}
},
|value| {
fidl::endpoints::RequestStream::control_handle(&sender).send_send_(value).unwrap();
receiver_fifo.recv().unwrap();
},
BatchSize::SmallInput);
});
}
{{- end -}}
{{ if .EnableEchoCallBenchmark }}
async fn {{ .Name }}_echo_call_server_thread(server_end: zx::Channel) {
let async_server_end = fuchsia_async::Channel::from_channel(server_end).unwrap();
let stream = <{{ .ValueType }}EchoCallRequestStream as fidl::endpoints::RequestStream>::from_channel(async_server_end);
const MAX_CONCURRENT: usize = 10;
stream.for_each_concurrent(MAX_CONCURRENT, |request| {
match request {
Ok({{ .ValueType }}EchoCallRequest::Echo { mut val, responder }) => {
responder.send(&mut val).unwrap();
},
Err(_) => {
panic!("unexpected err request")
},
}
future::ready(())
}).await;
}
fn benchmark_{{ .Name }}_echo_call(b: &mut Bencher) {
let (client_end, server_end) = zx::Channel::create().unwrap();
std::thread::spawn(|| {
fuchsia_async::Executor::new()
.unwrap()
.run_singlethreaded(async move {
{{ .Name }}_echo_call_server_thread(server_end).await;
});
});
let mut proxy = {{ .ValueType }}EchoCallSynchronousProxy::new(client_end);
b.iter_batched_ref(|| {
{{- if .HandleDefs }}
let handle_defs = create_handles(&{{ .HandleDefs }}).unwrap();
let handle_defs = unsafe { disown_vec(handle_defs) };
let handle_defs = handle_defs.as_ref();
{{- end }}
{{ .Value }}
},
|value| {
proxy.echo(value, zx::Time::after(zx::Duration::from_seconds(1))).unwrap();
},
BatchSize::SmallInput);
}
{{- end -}}
{{ end }}
`))
type benchmarkTmplInput struct {
NumBenchmarks int
CrateSuffix string
Benchmarks []benchmark
}
type benchmark struct {
Name, ChromeperfPath, HandleDefs, Value, ValueType string
EnableSendEventBenchmark, EnableEchoCallBenchmark bool
}
// GenerateBenchmarks generates Rust benchmarks.
func GenerateBenchmarks(gidl gidlir.All, fidl fidlgen.Root, config gidlconfig.GeneratorConfig) ([]byte, error) {
schema := gidlmixer.BuildSchema(fidl)
var benchmarks []benchmark
nBenchmarks := 0
for _, gidlBenchmark := range gidl.Benchmark {
decl, err := schema.ExtractDeclaration(gidlBenchmark.Value, gidlBenchmark.HandleDefs)
if err != nil {
return nil, fmt.Errorf("benchmark %s: %s", gidlBenchmark.Name, err)
}
value := visit(gidlBenchmark.Value, decl)
benchmarks = append(benchmarks, benchmark{
Name: benchmarkName(gidlBenchmark.Name),
ChromeperfPath: gidlBenchmark.Name,
HandleDefs: buildHandleDefs(gidlBenchmark.HandleDefs),
Value: value,
ValueType: declName(decl),
EnableSendEventBenchmark: gidlBenchmark.EnableSendEventBenchmark,
EnableEchoCallBenchmark: gidlBenchmark.EnableEchoCallBenchmark,
})
nBenchmarks += 3
if gidlBenchmark.EnableSendEventBenchmark {
nBenchmarks++
}
if gidlBenchmark.EnableEchoCallBenchmark {
nBenchmarks++
}
}
input := benchmarkTmplInput{
NumBenchmarks: nBenchmarks,
CrateSuffix: config.RustBenchmarksFidlLibrary,
Benchmarks: benchmarks,
}
var buf bytes.Buffer
err := benchmarkTmpl.Execute(&buf, input)
return buf.Bytes(), err
}
func benchmarkName(gidlName string) string {
return fidlgen.ToSnakeCase(strings.ReplaceAll(gidlName, "/", "_"))
}