| // 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, "/", "_")) |
| } |