| # Max out pagination |
| |
| This document describes the best ways to calculate the size both in terms of |
| bytes and handles of elements as they are added to a vector. This should be |
| done in order to maximize the number of elements that can be batched at once |
| while satisfying the kernel caps on channel writes. |
| |
| Note: Use the [measure-tape](/tools/fidl/measure-tape/) tool to implement the |
| techniques described below. In the Fuchsia Source Tree, this tool has direct |
| build integration. |
| |
| ## Summary |
| |
| To maximize throughput through a channel, it’s common to batch large responses |
| as multiple vectors of things, for instance by [using a pagination |
| API][pagination-api]. Since channels are capped at [64K bytes and 64 |
| handles][channel-byte-and-handle-caps], comes the question of how many elements |
| can be batched in the vector to max out the capacity (and yet, be just under the |
| byte size and handle count thresholds). |
| |
| The key reference document for the following is the [FIDL wire |
| format][fidl-wire-format] specification. |
| |
| There are various examples that explain the best ways to max out pagination: |
| |
| * [Bluetooth `WatchPeers` method](#bluetooth-watchpeers-method) |
| * [Scenic `Enqueue` method](#scenic-enqueue-method) |
| |
| ## Bluetooth `WatchPeers` method {#bluetooth-watchpeers-method} |
| |
| Consider the [WatchPeers][bts-watch-peers] method of the |
| `fuchsia.bluetooth.sys.Access` protocol, defined as: |
| |
| ```fidl |
| WatchPeers() -> (vector<Peer>:MAX updated, vector<bt.PeerId>:MAX removed); |
| ``` |
| |
| First, a request or response is preceded by a header, i.e. a fixed 16 bytes or |
| `sizeof(fidl_message_header_t)` as [defined here][fidl-message-header-t]. |
| |
| Each vector has a 16 bytes header `sizeof(fidl_vector_t)`, followed by the |
| content. |
| |
| Since `bt.PeerId` is a `struct{uint64}` ([defined here][bt-peer-id]) it is a |
| fixed 8 bytes, and therefore the `removed` vector’s content is the number of |
| elements * 8 bytes. |
| |
| Next, we need to estimate the size of `Peer`, which is [defined as a |
| table][bts-peer]. Tables are essentially a [vector of envelopes][fidl-table-t], |
| where each envelope then points to the field content. Estimating the size must |
| be done in two steps: |
| |
| 1. Determine the largest field ordinal used (a.k.a. `max_set_ordinal`) |
| 2. Determine the size of each present field |
| |
| The size of `Peer` is then the table header -- i.e. `sizeof(fidl_table_t)`, 16 |
| bytes -- plus the largest set ordinal * envelope header (16 bytes) -- i.e. |
| `max_set_ordinal * sizeof(fidl_envelope_t)` -- plus the total size of the |
| content, that is, each present field’s content added. |
| |
| Fields are relatively easy to size, many are primitives or wrappers thereof, |
| hence result in 8 bytes (due to padding). The `bt.Address` [field][bt-address] |
| is also 8 bytes since it’s definition reduces to `struct{uint8; |
| array<uint8>:6}`. The `string` field is a vector of bytes, i.e. |
| `sizeof(fidl_vector_t) + len(name)`, and padded to the nearest 8 bytes boundary. |
| |
| ## Scenic `Enqueue` method {#scenic-enqueue-method} |
| |
| Consider the [Enqueue][scenic-enqueue] method of the |
| `fuchsia.scenic.Session` protocol, defined as: |
| |
| ```fidl |
| Enqueue(vector<Command>:MAX cmds); |
| ``` |
| |
| A request or response is preceded by a header, i.e. a fixed 16 bytes or |
| `sizeof(fidl_message_header_t)` from [zircon/fidl.h][fidl-message-header-t]. |
| Then, the vector has a 16 bytes header `sizeof(fidl_vector_t)`, followed by the |
| content of the vector, which are the actual commands. As a result, before you |
| account for the size of each individual command, there is a fixed size of 32 |
| bytes. |
| |
| A [command][scenic-command] is a [union][fidl-wire-format-union] that has a 24 |
| bytes header (i.e. `sizeof(fidl_xunion_t)`) followed by the content, which is 8 |
| bytes aligned. |
| |
| The size of a `Command` union content depends on the variant selected. This |
| example uses the `input` variant of type |
| [`fuchsia.ui.input.Command`][input-command]. |
| |
| The `input` variant (of the scenic command) is itself a union, which adds |
| another 24 bytes header, followed by the content of that union, such as a |
| `send_pointer_input` of type |
| [`SendPointerInputCmd`][input-send-pointer-input-cmd]. |
| |
| The simplified definition of `SendPointerInputCmd` and all transitively |
| reachable types through this struct is provided below: |
| |
| ```fidl |
| type SendPointerInputCmd = struct { |
| compositor_id uint32; |
| pointer_event PointerEvent; |
| }; |
| |
| type PointerEvent = struct { |
| event_time uint64; |
| device_id uint32; |
| pointer_id uint32; |
| type PointerEventType; |
| phase PointerEventPhase; |
| x float32; |
| y float32; |
| radius_major float32; |
| radius_minor float32; |
| buttons uint32; |
| }; |
| |
| type PointerEventType = flexible enum { |
| // members elided |
| }; |
| |
| type PointerEventPhase = flexible enum { |
| // members elided |
| }; |
| ``` |
| |
| Both enums `PointerEventType` and `PointerEventPhase` |
| [default][fidl-language-enums] to an underlying representation of `uint32`. You |
| can reduce the sizing of `SendPointerInputCmd` to the struct: |
| |
| ```fidl |
| struct { |
| uint32; // 4 bytes, total 4 |
| // 4 bytes (padding due to increase in alignment), total 8 |
| uint64; // 8 bytes, total 16 |
| uint32; // 4 bytes, total 20 |
| uint32; // 4 bytes, total 24 |
| uint32; // 4 bytes, total 28 |
| uint32; // 4 bytes, total 32 |
| float32; // 4 bytes, total 36 |
| float32; // 4 bytes, total 40 |
| float32; // 4 bytes, total 44 |
| float32; // 4 bytes, total 48 |
| uint32; // 4 bytes, total 52 |
| }; |
| ``` |
| |
| Therefore, the size of the `SendPointerInputCmd` struct is 52 bytes. For more |
| information on struct sizing calculation, see [The Lost Art of Structure |
| Packing][lostart]. |
| |
| Now that you have sized all the pieces of a command, you add the total size: |
| |
| * Header of `fuchsia.ui.scenic.Command`: 24 bytes, i.e `sizeof(fidl_xunion_t)` |
| * Content with variant `input`: |
| * Header of `fuchsia.ui.input.Command`: 24 bytes, i.e `sizeof(fidl_xunion_t)` |
| * Content with variant `set_hard_keyboard_delivery`: |
| * Struct `SendPointerInputCmd`: 52 bytes |
| * Padding to align to 8 bytes: 4 bytes |
| |
| This results in a total size of 104 bytes. |
| |
| <!-- xrefs --> |
| [lostart]: http://www.catb.org/esr/structure-packing/ |
| [pagination-api]: /docs/development/api/fidl.md#pagination |
| |
| [fidl-wire-format]: /docs/reference/fidl/language/wire-format |
| [fidl-wire-format-union]: /docs/reference/fidl/language/wire-format#unions |
| [fidl-language-enums]: /docs/reference/fidl/language/language.md#enums |
| |
| [channel-byte-and-handle-caps]: https://fuchsia.googlesource.com/fuchsia/+/b7840e772fccb93be4fff73a9cb83f978095eac2/zircon/system/public/zircon/types.h#296 |
| [fidl-message-header-t]: https://fuchsia.googlesource.com/fuchsia/+/b7840e772fccb93be4fff73a9cb83f978095eac2/zircon/system/public/zircon/fidl.h#358 |
| [fidl-table-t]: https://fuchsia.googlesource.com/fuchsia/+/b7840e772fccb93be4fff73a9cb83f978095eac2/zircon/system/public/zircon/fidl.h#328 |
| [bt-peer-id]: https://fuchsia.googlesource.com/fuchsia/+/b7840e772fccb93be4fff73a9cb83f978095eac2/sdk/fidl/fuchsia.bluetooth/id.fidl#13 |
| [bt-address]: https://fuchsia.googlesource.com/fuchsia/+/b7840e772fccb93be4fff73a9cb83f978095eac2/sdk/fidl/fuchsia.bluetooth/address.fidl#16 |
| [bts-watch-peers]: https://fuchsia.googlesource.com/fuchsia/+/b7840e772fccb93be4fff73a9cb83f978095eac2/sdk/fidl/fuchsia.bluetooth.sys/access.fidl#100 |
| [bts-peer]: https://fuchsia.googlesource.com/fuchsia/+/b7840e772fccb93be4fff73a9cb83f978095eac2/sdk/fidl/fuchsia.bluetooth.sys/peer.fidl#16 |
| [scenic-enqueue]: https://fuchsia.googlesource.com/fuchsia/+/b7840e772fccb93be4fff73a9cb83f978095eac2/sdk/fidl/fuchsia.ui.scenic/session.fidl#54 |
| [scenic-command]: https://fuchsia.googlesource.com/fuchsia/+/b7840e772fccb93be4fff73a9cb83f978095eac2/sdk/fidl/fuchsia.ui.scenic/commands.fidl#12 |
| [input-command]: https://fuchsia.googlesource.com/fuchsia/+/b7840e772fccb93be4fff73a9cb83f978095eac2/sdk/fidl/fuchsia.ui.input/commands.fidl#7 |
| [input-send-pointer-input-cmd]: https://fuchsia.googlesource.com/fuchsia/+/b7840e772fccb93be4fff73a9cb83f978095eac2/sdk/fidl/fuchsia.ui.input/commands.fidl#25 |