Annotate noexcept and constexpr with HEDLEY, and add interval containment, ChaCha, and the dyadic range tables.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
875f09fec1
commit
0d8a5a8131
97 changed files with 9212 additions and 1159 deletions
|
|
@ -1,34 +1,126 @@
|
|||
<!-- # Evaluating DPFs {#evalaution} -->
|
||||
Once a `DPF` generated, the *eval_* * functions are used to evaluate differents inputs.
|
||||
The appropriate function depends on your specific needs. If you only need the DPF's output
|
||||
for a single input value, use `eval_point`. For evaluating a continuous range of inputs,
|
||||
`eval_interval` is suitable. To analyze the DPF's behavior across its entire domain, use `eval_full`.
|
||||
The code likely offers different implementations of memoization and output buffers, allowing you to
|
||||
optimize for memory usage or execution speed depending on your needs.\n
|
||||
Using a PRG while making a `DPF` allows the user to check how much it cost to manipulate the `DPF`s.
|
||||
<!-- # Evaluating DPFs {#evaluation} -->
|
||||
|
||||
# Memoizers
|
||||
The `memoizers` remembers the most used path while the DPF is being created. These are usefull functions
|
||||
to improve the speed and the cost of execution.
|
||||
`make_dpf(x, y)` returns one key per party. Evaluation of a key yields that
|
||||
party's share. Leaf outputs are subtractive shares: open them with
|
||||
`dpf::reconstruct`, which computes `share0 - share1`. Comparison outputs are
|
||||
additive shares: `reconstruct` computes `share0 + share1`. A single-output
|
||||
`eval_point` returns a small handle; `*handle` is the share. An unassigned
|
||||
`dpf::wildcard` output throws `std::runtime_error`.
|
||||
|
||||
`[from, to]` is inclusive. The points passed to `eval_sequence` are a
|
||||
nondecreasing range; an unsorted range throws `std::runtime_error`.
|
||||
|
||||
Memoizers hold interior nodes between calls. Output buffers hold the shares
|
||||
a multi-point evaluation writes. Pass both as mutable named objects when a
|
||||
later call should reuse them. The factories
|
||||
`make_basic_path_memoizer`, `make_basic_interval_memoizer`, and
|
||||
`make_*_sequence_memoizer` unwrap `party_key`, so a workspace built from
|
||||
either party's type accepts both parties. Name that type with
|
||||
`dpf::unwrap_party_key_t<std::decay_t<decltype(key)>>`.
|
||||
|
||||
# Memoizers {#memoizers}
|
||||
|
||||
## Path memoizers {#path_memoizers}
|
||||
|
||||
`eval_point` walks one root-to-leaf path. `make_basic_path_memoizer<Key>()`
|
||||
keeps every node of the previous point, and the next point recomputes only
|
||||
the suffix after the common prefix. `make_nonmemoizing_path_memoizer<Key>()`
|
||||
keeps one node and starts from the root on every call. A one-off
|
||||
`eval_point(key, x)` uses the nonmemoizing memoizer.
|
||||
|
||||
Pass the memoizer as a mutable lvalue. The default argument is a new
|
||||
temporary, so it has no previous point to resume from. Keep a separate
|
||||
memoizer for each key you are in the middle of evaluating. A different root
|
||||
restarts the path.
|
||||
|
||||
**Code samples**\n
|
||||
For instance, in the code below the utilization of `dpf::make_basic_path_memoizer` reduced by 10 the time of execution
|
||||
compare to the code that is commented that doesn't use the `memoizers`.
|
||||
<div class="tabbed">
|
||||
|
||||
- <b class="tab-title">memoizers.cpp</b> \include{cpp} evaluation/memoizers.cpp
|
||||
|
||||
</div>
|
||||
|
||||
## Interval memoizers {#interval_memoizers}
|
||||
|
||||
# dpf::eval_point
|
||||
This function evaluate a single input of a DPF. The XOR result of the `eval_point` for both shares will only be equal to 1 if it represents the correct input in both evaluations. As input arguments it uses the `share` and the input to evaluate (note: it can't be a wildcard, otherwise it will throw an error).\n
|
||||
`eval_interval` and `eval_full` expand every leaf in a range.
|
||||
`make_basic_interval_memoizer<Key>(from, to)` stores two levels of that
|
||||
range. That is the workspace the convenience overloads allocate.
|
||||
`make_full_tree_interval_memoizer<Key>(from, to)` keeps every level.
|
||||
`make_basic_full_memoizer<Key>()` and `make_full_tree_full_memoizer<Key>()`
|
||||
are the same workspaces sized for the whole domain.
|
||||
|
||||
**See also**\n
|
||||
PIR
|
||||
Size the memoizer for the widest interval you will pass to it. A wider
|
||||
interval throws `std::length_error`. The same key and the same endpoints
|
||||
leave the final interior level in place. A different key or a different
|
||||
interval rebuilds into the same allocation.
|
||||
|
||||
**Pro tip**\n
|
||||
Use the `dpf::pathmemoizer` for a faster execution.\n
|
||||
Passing only the memoizer still allocates a fresh output buffer and returns
|
||||
`std::pair(buffer, iterable)`.
|
||||
|
||||
## Sequence memoizers {#sequence_memoizers}
|
||||
|
||||
`make_sequence_recipe<Key>(begin, end)` compiles a sorted point list into a
|
||||
traversal. The recipe depends on the input type, and one recipe serves every
|
||||
key of that type.
|
||||
|
||||
A sequence memoizer stores a reference to the recipe object it was built
|
||||
from and checks later calls by address. Pass that same object, and keep the
|
||||
recipe alive for as long as the memoizer is used. A copy of the recipe
|
||||
throws `std::logic_error`.
|
||||
|
||||
`make_double_space_sequence_memoizer<Key>(recipe)` keeps two levels. It is
|
||||
what `eval_sequence(key, recipe, buffer)` allocates when you omit the
|
||||
memoizer. `make_inplace_reversing_sequence_memoizer<Key>(recipe)` keeps one
|
||||
level and reverses direction as it descends.
|
||||
`make_full_tree_sequence_memoizer<Key>(recipe)` retains every level. A key
|
||||
whose depth differs from the recipe throws `std::logic_error`.
|
||||
|
||||
# Output buffers {#output_buffers}
|
||||
|
||||
`output_buffer<T>` is move-only storage with `size`, iterators, `data`, and
|
||||
`operator[]`. Build it with the factory that matches the evaluation:
|
||||
|
||||
- `make_output_buffer_for_interval(key, from, to)`
|
||||
- `make_output_buffer_for_full(key)`
|
||||
- `make_output_buffer_for_subsequence(key, begin, end, tag)`
|
||||
- `make_output_buffer_for_recipe_subsequence(key, recipe, tag)`
|
||||
|
||||
On a `party_key`, leaf slots are `subtractive_share`s and comparison slots
|
||||
are `additive_share`s. `dpf::bit`, `dpf::twobit`, and `dpf::nyble` slots are
|
||||
packed. Trivially default-constructible slot types are left uninitialized;
|
||||
the evaluation overwrites every slot it is responsible for.
|
||||
|
||||
`eval_interval` and recipe `eval_sequence` take the buffer as a non-const
|
||||
reference, so the argument is a named object. The returned iterable refers
|
||||
into that buffer. Read it while the buffer is alive, and only over the
|
||||
points the iterable covers. The next evaluation overwrites those slots.
|
||||
|
||||
For one output, the convenience overload returns the buffer itself as the
|
||||
first element of the pair. For several output indices it returns a tuple of
|
||||
buffers.
|
||||
|
||||
`make_output_buffer(dpf::out<I>, key, from, to)` and
|
||||
`make_output_buffer(dpf::cmp, key, n)` size a buffer for one channel of a
|
||||
multi-output or comparison key. The slot types follow the same party-share
|
||||
rule.
|
||||
|
||||
**Code samples**\n
|
||||
<div class="tabbed">
|
||||
|
||||
- <b class="tab-title">output_buffers.cpp</b> \include{cpp} evaluation/output_buffers.cpp
|
||||
|
||||
</div>
|
||||
|
||||
# dpf::eval_point {#eval_point}
|
||||
|
||||
`eval_point(key, x)` evaluates output 0 at one input.
|
||||
`eval_point<I>(key, x)` selects another output. Two or more indices,
|
||||
`eval_point<0, 1>(key, x)`, return a tuple of shares rather than handles.
|
||||
`eval_point(key, x, path)` continues a path memoizer.
|
||||
|
||||
`eval_point(dpf::out<I>, key, x, path)` and `eval_point(dpf::cmp, key, x, path)`
|
||||
are the same walk with an explicit channel. Comparison results are additive
|
||||
shares.
|
||||
|
||||
**Code samples**\n
|
||||
<div class="tabbed">
|
||||
|
|
@ -37,12 +129,13 @@ Use the `dpf::pathmemoizer` for a faster execution.\n
|
|||
|
||||
</div>
|
||||
|
||||
# dpf::eval_interval
|
||||
This function evaluates a contiguous range of inputs. As input arguments it uses the `share` generated by `make_dpf`,
|
||||
`from` and `to` for the range of inputs to evaluate.\n
|
||||
# dpf::eval_interval {#eval_interval}
|
||||
|
||||
**See also**\n
|
||||
PIR
|
||||
`eval_interval(key, from, to)` evaluates every input from `from` through
|
||||
`to`. The iterable yields one share per input, in that order. Optional
|
||||
arguments are an output buffer and then an interval memoizer. An output
|
||||
index pack, `eval_interval<0, 1>(key, from, to, buffers, memo)`, writes each
|
||||
selected output.
|
||||
|
||||
**Code samples**\n
|
||||
<div class="tabbed">
|
||||
|
|
@ -51,8 +144,13 @@ PIR
|
|||
|
||||
</div>
|
||||
|
||||
# dpf::eval_full
|
||||
This function evaluate all the passible inputs it only uses as argument the `share` of the `DPF` to evaluate.
|
||||
# dpf::eval_full {#eval_full}
|
||||
|
||||
`eval_full(key)` is the closed interval from
|
||||
`std::numeric_limits<Input>::min()` through `max()`. The buffer and
|
||||
full-domain memoizer overloads match `eval_interval`.
|
||||
`make_output_buffer_for_full(key)` and `make_basic_full_memoizer<Key>()`
|
||||
size both for that domain.
|
||||
|
||||
**Code samples**\n
|
||||
<div class="tabbed">
|
||||
|
|
@ -61,14 +159,16 @@ This function evaluate all the passible inputs it only uses as argument the `sha
|
|||
|
||||
</div>
|
||||
|
||||
# dpf::eval_sequence {#eval_sequence}
|
||||
|
||||
# dpf::eval_sequence
|
||||
This function evaluate a subset of inputs that is not contiguous (useful for a `DPF` made with `keyword`),
|
||||
it uses as arguments the `share` generated by `make_dpf`, `from` and `to` for the subset of inputs to evaluate.\n
|
||||
For a better utilization, you can use the `make_sequence_recipe` it takes as input a sorted list and returns a `recipe`.
|
||||
The cost of creating is a little bit worse than just calling `eval_sequence`, however once the `recipe` created
|
||||
`eval_sequence` is faster and has a better cost.
|
||||
`eval_sequence(key, begin, end, tag)` evaluates a sorted list.
|
||||
`dpf::return_output_only_tag_` stores one share per listed point.
|
||||
`dpf::return_entire_node_tag_` stores whole leaves; it is the default.
|
||||
The iterable still yields one share per listed point, in list order.
|
||||
|
||||
`eval_sequence(key, recipe, buffer, memo, tag)` repeats that list.
|
||||
`memo` is a sequence memoizer bound to `recipe`. Omit `memo` to allocate a
|
||||
`double_space` workspace for that call.
|
||||
|
||||
**Code samples**\n
|
||||
<div class="tabbed">
|
||||
|
|
@ -77,4 +177,22 @@ The cost of creating is a little bit worse than just calling `eval_sequence`, ho
|
|||
|
||||
</div>
|
||||
|
||||
# Output buffers
|
||||
# Buffered PRG {#buffered_prg}
|
||||
|
||||
`dpf::randomness::buffered_prg<PRG, Ts...>` (alias
|
||||
`dpf::randomness::aes_buffered_prg<Ts...>`) is a forward cursor with one
|
||||
PRG stream per value type. `get<I>()` and `fill<I>(out, n)` consume the
|
||||
cursor. `at<I>(index)` reads an absolute index and leaves the cursor where
|
||||
it is. `sampled<I>()` is how far `get` and `fill` have advanced.
|
||||
`per_stream_buffer_elems` is at least 1.
|
||||
|
||||
`dpf::randomness::lane_table<T>` is the seekable form for a runtime set of
|
||||
roles. `value_at(role, index)` and `mask_at(role, index)` are independent
|
||||
streams, and a repeated index returns the same element.
|
||||
|
||||
**Code samples**\n
|
||||
<div class="tabbed">
|
||||
|
||||
- <b class="tab-title">buffered_prg.cpp</b> \include{cpp} evaluation/buffered_prg.cpp
|
||||
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue