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

Box value encoded in a variant to reduce enum stack space #996

Merged
merged 1 commit into from May 20, 2022

Conversation

RCasatta
Copy link
Collaborator

before

print-type-size type: `util::psbt::error::Error`: 120 bytes, alignment: 8 bytes
print-type-size     discriminant: 1 bytes
print-type-size     variant `CombineInconsistentKeySources`: 115 bytes
print-type-size         padding: 3 bytes
print-type-size         field `.0`: 112 bytes, alignment: 4 bytes
print-type-size     variant `InvalidKey`: 39 bytes
print-type-size         padding: 7 bytes
print-type-size         field `.0`: 32 bytes, alignment: 8 bytes

after

print-type-size type: `util::psbt::error::Error`: 40 bytes, alignment: 8 bytes
print-type-size     discriminant: 1 bytes
print-type-size     variant `InvalidKey`: 39 bytes
print-type-size         padding: 7 bytes
print-type-size         field `.0`: 32 bytes, alignment: 8 bytes
print-type-size     variant `DuplicateKey`: 39 bytes
print-type-size         padding: 7 bytes
print-type-size         field `.0`: 32 bytes, alignment: 8 bytes

util::psbt::error::Error is wrapped also in consensus::encode::Error and stack savings are gained there also

before

```
print-type-size type: `util::psbt::error::Error`: 120 bytes, alignment: 8 bytes
print-type-size     discriminant: 1 bytes
print-type-size     variant `CombineInconsistentKeySources`: 115 bytes
print-type-size         padding: 3 bytes
print-type-size         field `.0`: 112 bytes, alignment: 4 bytes
print-type-size     variant `InvalidKey`: 39 bytes
print-type-size         padding: 7 bytes
print-type-size         field `.0`: 32 bytes, alignment: 8 bytes
```

after
```
print-type-size type: `util::psbt::error::Error`: 40 bytes, alignment: 8 bytes
print-type-size     discriminant: 1 bytes
print-type-size     variant `InvalidKey`: 39 bytes
print-type-size         padding: 7 bytes
print-type-size         field `.0`: 32 bytes, alignment: 8 bytes
print-type-size     variant `DuplicateKey`: 39 bytes
print-type-size         padding: 7 bytes
print-type-size         field `.0`: 32 bytes, alignment: 8 bytes
```
Copy link
Member

@apoelstra apoelstra left a comment

Choose a reason for hiding this comment

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

ACK 9906cea

@tcharding
Copy link
Member

Interesting PR, I rarely think about the stack space of error enums. To educate me, can you please explain a few things if/when you have the time:

  • What led you to do this?
  • Why stop at the 115 byte variant, why not box all variants below X bytes?
  • What made you do this enum in particular? Should we be doing this by convention for all the error enums codebase wide?

No rush, just if you have time. Thanks

Copy link
Member

@tcharding tcharding left a comment

Choose a reason for hiding this comment

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

ACK 9906cea

@TheBlueMatt
Copy link
Member

What made you do this enum in particular? Should we be doing this by convention for all the error enums codebase wide?

In general, Boxing is something that should be avoided - it adds some overhead by waiting on malloc+free and done too much leads to heap fragmentation and can substantially increase total application memory usage even after objects are free'd. However, this is an error variant - hopefully during normal operation we never hit this code anyway. In that case, by reducing the size of the error, we may be able to reduce the size of the ultimate Result<Success, Err> as long as Success is smaller than the whole Err. Its not exactly a critical thing for a few hundred bytes, but if we have a lot of those results, then it could add up.

@tcharding
Copy link
Member

I see, paraphrasing to make sure I get you, the Result<T, E> has the same size as the biggest of T and E so even in the success path the size of the error effects stack usage.

So if we want to be particularly careful with stack usage we'd have to check the relation between T and E everywhere - sounds like hard work. Assuming the error path is infrequent then it would seem like good practice to box all error variants above some reasonable threshold, just to be proactive?

And we may want to be careful with stack usage as we endevour to support smaller devices (WASM, no-std, ect.)?

@TheBlueMatt
Copy link
Member

TheBlueMatt commented May 19, 2022

And we may want to be careful with stack usage as we endevour to support smaller devices (WASM, no-std, ect.)?

I don't think WASM qualifies as a "smaller device" here, more tiny embedded things. I think we don't need to be universal about this, only parts of the library that are usable on embedded devices. e.g. no embedded (memory < 1 MiB) device is going to decode a block, as the whole thing is bigger than the embedded device's memory anyway, same goes for P2P messages, etc. For PSBT and signing transactions, on the other hand, this is the exact code that an embedded device may care about.

@tcharding
Copy link
Member

Cool, so any error path that can be hit from PSBT code, that's a non-trivial amount of work to find out, right? Follow every type used in the psbt module and check its implementations. How serious are we about folk writing embedded signing devices in Rust? Is anyone doing this now that we know of?

@TheBlueMatt
Copy link
Member

How serious are we about folk writing embedded signing devices in Rust? Is anyone doing this now that we know of?

Yea, actually no idea - is there a concrete use-case where this is important @RCasatta?

@sanket1729
Copy link
Member

@RCasatta, I am curious how do you print byte alignments?

Copy link
Member

@sanket1729 sanket1729 left a comment

Choose a reason for hiding this comment

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

utACK 9906cea

@sanket1729 sanket1729 merged commit 2b1154c into rust-bitcoin:master May 20, 2022
@RCasatta
Copy link
Collaborator Author

RCasatta commented May 20, 2022

@RCasatta, I am curious how do you print byte alignments?

RUSTFLAGS=-Zprint-type-sizes cargo +nightly build --release

Yea, actually no idea - is there a concrete use-case where this is important @RCasatta?

No concrete use-case in this case for me, I just spotted this big variant by looking at the aforementioned program. There are some examples linked from this page https://nnethercote.github.io/perf-book/type-sizes.html?highlight=print#measuring-type-sizes where changes like this make performance impact

@apoelstra
Copy link
Member

Specter Wallet has indicated a desire to use rust-miniscript (and therefore rust-bitcoin) in embedded contexts. I take this pretty seriously. Maybe we have no actual users, but I think that's probably because rust-bitcoin can't be used rather than that it isn't used. So anything that improves the viability of rust-bitcoin-on-embedded is good by me.

Regarding the reasoning for this, Matt's summary sounds great to me. (a) generally we should try not to have big error enums, (b) PSBT is both non-speed-critical and likely to be used on a hardware wallet, so let's try to be nice with stack space.

ChallengeDev210 pushed a commit to ChallengeDev210/rust-bitcoin that referenced this pull request Aug 1, 2022
…o reduce enum stack space

9906cea Box value encoded in a variant to reduce enum stack space (Riccardo Casatta)

Pull request description:

  before

  ```
  print-type-size type: `util::psbt::error::Error`: 120 bytes, alignment: 8 bytes
  print-type-size     discriminant: 1 bytes
  print-type-size     variant `CombineInconsistentKeySources`: 115 bytes
  print-type-size         padding: 3 bytes
  print-type-size         field `.0`: 112 bytes, alignment: 4 bytes
  print-type-size     variant `InvalidKey`: 39 bytes
  print-type-size         padding: 7 bytes
  print-type-size         field `.0`: 32 bytes, alignment: 8 bytes
  ```

  after
  ```
  print-type-size type: `util::psbt::error::Error`: 40 bytes, alignment: 8 bytes
  print-type-size     discriminant: 1 bytes
  print-type-size     variant `InvalidKey`: 39 bytes
  print-type-size         padding: 7 bytes
  print-type-size         field `.0`: 32 bytes, alignment: 8 bytes
  print-type-size     variant `DuplicateKey`: 39 bytes
  print-type-size         padding: 7 bytes
  print-type-size         field `.0`: 32 bytes, alignment: 8 bytes
  ```

  `util::psbt::error::Error` is wrapped also in `consensus::encode::Error` and stack savings are gained there also

ACKs for top commit:
  apoelstra:
    ACK 9906cea
  tcharding:
    ACK 9906cea
  sanket1729:
    utACK 9906cea

Tree-SHA512: e03988fcbc3dd87f83d00dd84ec1c538bc5c63bea97ff4a69a715621f498f57d7fe2a623e351942d9532af40c723e42a9eb6ef48ebf4c62ddf5c0f44e9ea0a07
@Kixunil Kixunil added API break This PR requires a version bump for the next release perf Performance optimizations/issues labels Aug 1, 2022
@Kixunil Kixunil added this to the 0.29.0 milestone Aug 1, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
API break This PR requires a version bump for the next release perf Performance optimizations/issues
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants