Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add metering for deserialization #524

Merged
merged 33 commits into from
Feb 27, 2024
Merged

add metering for deserialization #524

merged 33 commits into from
Feb 27, 2024

Conversation

chenyan-dfinity
Copy link
Contributor

No description provided.

Copy link

Benchmark for 5c2aca5

Click to view benchmark
Test Base PR %
Blob/&str 607.6±86.37µs 615.2±87.49µs +1.25%
Blob/ByteBuf 355.8±92.24µs 353.7±96.51µs -0.59%
Blob/Bytes 360.6±60.86µs 394.6±62.52µs +9.43%
Blob/String 337.5±138.96µs 333.7±139.83µs -1.13%
Collections/vec (text, nat) 50.5±1.23ms 52.9±1.46ms +4.75%
Collections/vec int 23.9±0.35ms 24.2±0.13ms +1.26%
Collections/vec int64 15.7±0.28ms 17.3±0.13ms +10.19%
Collections/vec nat8 11.1±0.03ms 13.1±0.07ms +18.02%
option list/1024 951.3±9.64µs 979.0±22.67µs +2.91%
profiles/1024 1793.4±22.26µs 1938.3±72.65µs +8.08%
variant list/1024 811.3±12.60µs 840.7±23.88µs +3.62%

rust/candid/src/de.rs Outdated Show resolved Hide resolved
rust/candid/src/de.rs Outdated Show resolved Hide resolved
@dfinity dfinity deleted a comment from github-actions bot Feb 20, 2024
Copy link

github-actions bot commented Feb 20, 2024

Name Max Mem (Kb) Encode Decode
blob 4_224 20_464_387 ($\textcolor{green}{-0.00\%}$) 12_084_211 ($\textcolor{red}{0.01\%}$)
btreemap 75_456 4_814_047_548 ($\textcolor{red}{0.00\%}$) 15_372_278_345 ($\textcolor{red}{2.26\%}$)
nns 192 2_276_767 ($\textcolor{green}{-0.01\%}$) 14_346_699 ($\textcolor{red}{7.97\%}$)
option_list 576 7_311_272 ($\textcolor{red}{0.00\%}$) 33_823_889 ($\textcolor{red}{6.53\%}$)
text 6_336 28_847_752 ($\textcolor{green}{-0.00\%}$) 17_839_902 ($\textcolor{red}{0.00\%}$)
variant_list 128 7_518_911 ($\textcolor{green}{-0.00\%}$) 25_488_924 ($\textcolor{red}{7.19\%}$)
vec_int16 (new) 16_704 168_591_100 1_027_635_875
  • Parser cost: 18_351_038 ($\textcolor{green}{-0.00\%}$)
  • Extra args: 3_353_208 ($\textcolor{red}{5040.12\%}$)
Click to see raw report

---------------------------------------------------

Benchmark: blob
  total:
    instructions: 32.55 M (0.00%) (change within noise threshold)
    heap_increase: 66 pages (no change)
    stable_memory_increase: 0 pages (no change)

  1. Encoding (scope):
    instructions: 20.46 M (-0.00%) (change within noise threshold)
    heap_increase: 66 pages (no change)
    stable_memory_increase: 0 pages (no change)

  2. Decoding (scope):
    instructions: 12.08 M (0.01%) (change within noise threshold)
    heap_increase: 0 pages (no change)
    stable_memory_increase: 0 pages (no change)

---------------------------------------------------

Benchmark: text
  total:
    instructions: 46.69 M (0.00%) (change within noise threshold)
    heap_increase: 99 pages (no change)
    stable_memory_increase: 0 pages (no change)

  1. Encoding (scope):
    instructions: 28.85 M (-0.00%) (change within noise threshold)
    heap_increase: 99 pages (no change)
    stable_memory_increase: 0 pages (no change)

  2. Decoding (scope):
    instructions: 17.84 M (0.00%) (change within noise threshold)
    heap_increase: 0 pages (no change)
    stable_memory_increase: 0 pages (no change)

---------------------------------------------------

Benchmark: vec_int16 (new)
  total:
    instructions: 1.20 B (new)
    heap_increase: 261 pages (new)
    stable_memory_increase: 0 pages (new)

  1. Encoding (scope):
    instructions: 168.59 M (new)
    heap_increase: 261 pages (new)
    stable_memory_increase: 0 pages (new)

  2. Decoding (scope):
    instructions: 1.03 B (new)
    heap_increase: 0 pages (new)
    stable_memory_increase: 0 pages (new)

---------------------------------------------------

Benchmark: btreemap
  total:
    instructions: 20.19 B (1.71%) (change within noise threshold)
    heap_increase: 1179 pages (no change)
    stable_memory_increase: 0 pages (no change)

  1. Encoding (scope):
    instructions: 4.81 B (0.00%) (change within noise threshold)
    heap_increase: 257 pages (no change)
    stable_memory_increase: 0 pages (no change)

  2. Decoding (scope):
    instructions: 15.37 B (regressed by 2.26%)
    heap_increase: 922 pages (no change)
    stable_memory_increase: 0 pages (no change)

---------------------------------------------------

Benchmark: option_list
  total:
    instructions: 41.14 M (regressed by 5.31%)
    heap_increase: 9 pages (no change)
    stable_memory_increase: 0 pages (no change)

  1. Encoding (scope):
    instructions: 7.31 M (0.00%) (change within noise threshold)
    heap_increase: 0 pages (no change)
    stable_memory_increase: 0 pages (no change)

  2. Decoding (scope):
    instructions: 33.82 M (regressed by 6.53%)
    heap_increase: 9 pages (no change)
    stable_memory_increase: 0 pages (no change)

---------------------------------------------------

Benchmark: variant_list
  total:
    instructions: 33.01 M (regressed by 5.46%)
    heap_increase: 2 pages (no change)
    stable_memory_increase: 0 pages (no change)

  1. Encoding (scope):
    instructions: 7.52 M (-0.00%) (change within noise threshold)
    heap_increase: 0 pages (no change)
    stable_memory_increase: 0 pages (no change)

  2. Decoding (scope):
    instructions: 25.49 M (regressed by 7.19%)
    heap_increase: 2 pages (no change)
    stable_memory_increase: 0 pages (no change)

---------------------------------------------------

Benchmark: nns
  total:
    instructions: 35.72 M (regressed by 3.05%)
    heap_increase: 3 pages (no change)
    stable_memory_increase: 0 pages (no change)

  0. Parsing (scope):
    instructions: 18.35 M (-0.00%) (change within noise threshold)
    heap_increase: 3 pages (no change)
    stable_memory_increase: 0 pages (no change)

  1. Encoding (scope):
    instructions: 2.28 M (-0.01%) (change within noise threshold)
    heap_increase: 0 pages (no change)
    stable_memory_increase: 0 pages (no change)

  2. Decoding (scope):
    instructions: 14.35 M (regressed by 7.97%)
    heap_increase: 0 pages (no change)
    stable_memory_increase: 0 pages (no change)

---------------------------------------------------

Benchmark: extra_args
  total:
    instructions: 3.35 M (regressed by 5040.12%)
    heap_increase: 0 pages (no change)
    stable_memory_increase: 0 pages (no change)

---------------------------------------------------
Successfully persisted results to canbench_results.yml

@chenyan-dfinity chenyan-dfinity marked this pull request as ready for review February 23, 2024 05:17
* use the coercion function `C[(<t>,*) <: (<t'>,*)]((<v>,*))` to understand the decoded values at the expected type.
* use the coercion function `v : t ~> v' : t'` to understand the decoded values at the expected type.

Note on implementation:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I guess this is admitting defeat for having an abstract cost model?
I can see that the formulation using inference rules makes this hard.
Would it still be hard if we specified the decoder as a monadic function, returning a cumulative cost and optional value, making the backtracking and skips explicit?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or would that be too ugly or prescriptive?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's possible to have an abstract cost model by assigning weights for each derivation rule. But I see several problems with this approach:

  1. It forces the implementation to strictly follow the derivation rules;
  2. Types in the host language is not completely captured by the model. For example, [Nat8] vs Blob in Motoko, and Vec<(K, V)> vs HashMap<K,V> in Rust. They map to the same Candid type, but have different cost in the host language. Even with the same record type, it can be more expensive to construct a record in Rust than in Motoko (or vice versa);
  3. Even if we have a cost model, it would be hard to come up with a threshold in the spec. It's application dependent (canister endpoints and inter-canister calls may need different bounds, and stable memory won't need any limit). Plus there is always a gap between the cost model and the real implementation.

Looking from a different angle, this is a resource problem originating from a specific platform and a specific implementation. Given infinite resource, the Candid spec doesn't need to reject these large payload. Just like we don't specify stack depth in the spec, we can leave the concrete cost metering to the implementation, so that it matches better with the platform and language runtime.

/// C : <val> -> <constype> -> nat
/// C(null : opt <datatype>) = 2
/// C(?v : opt <datatype>) = 2 + C(v : <datatype>)
/// C(?v : opt <datatype'>) = 2 + C(v : <datatype>) * 50 + 10 // when v cannot be converted to <datatype'>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// C(?v : opt <datatype'>) = 2 + C(v : <datatype>) * 50 + 10 // when v cannot be converted to <datatype'>
/// C(?v : opt <datatype'>) = 2 + C(v : <datatype>) * 50 + 10 // when v cannot be converted to <datatype'>

where do 50 and 10 come from?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are some arbitrary numbers. 50x is the multiply when we skip values in the decoder. 10 accounts for the cost of restoring states in the backtracking.

/// C(?v : opt <datatype>) = 2 + C(v : <datatype>)
/// C(?v : opt <datatype'>) = 2 + C(v : <datatype>) * 50 + 10 // when v cannot be converted to <datatype'>
/// C(v^N : vec <datatype>) = 2 + 3 * N + sum_i C(v[i] : <datatype>)
/// C(kv* : record {<fieldtype>*}) = 2 + sum_skipped_i C(kv : <fieldtype>*[skipped_i]) * 50 + sum_expected_i C(kv : <fieldtype>*[expected_i])
Copy link
Contributor

@crusso crusso Feb 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you know which are skipped/expected without having the expected type as an argument pf C(.)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, ideally it should be C(v : t ~> t'), but it's also verbose. Maybe I can define just C(v:t), and say that skipping values cost 50x more.

Copy link
Contributor

@crusso crusso left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some comments. Maybe we could discuss a bit tomorrow, or @mraszyk can fill me in on how we got here.

Copy link
Contributor

@crusso crusso left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed offline

@chenyan-dfinity chenyan-dfinity merged commit 53cda2e into master Feb 27, 2024
5 checks passed
@chenyan-dfinity chenyan-dfinity deleted the cost-metering branch February 27, 2024 19:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants