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

Various memory optimizations #94

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open

Conversation

hack3ric
Copy link

@hack3ric hack3ric commented Nov 15, 2024

See each commit message for details.

These are a lot of breaking changes, so please take your time to check if they are properly done :)

@hack3ric
Copy link
Author

hack3ric commented Nov 15, 2024

Oops, this failed to build on Rust <1.79 because the compiler cannot decide if [Expression]: ToOwned:

error[E0277]: the trait bound `[SetItem]: ToOwned` is not satisfied in `Expression`
   --> src/stmt.rs:245:14
    |
245 |     pub dev: Option<Expression>,
    |              ^^^^^^^^^^^^^^^^^^ within `Expression`, the trait `ToOwned` is not implemented for `[SetItem]`, which is required by `Expression: Sized`
    |
    = help: the trait `ToOwned` is implemented for `[T]`
note: required because it appears within the type `Expression`
   --> src/expr.rs:10:10
    |
10  | pub enum Expression {
    |          ^^^^^^^^^^
note: required by an implicit `Sized` bound in `std::option::Option`
   --> /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6/library/core/src/option.rs:572:1

...and many, many similar errors

If bumping MSRV to version this recent is not acceptable, keeping Vecs would probably fix this issue.

A lot of strings and arrays used in nftables structures are static, but
currently we need to do allocations everywhere using owned String and Vec.
Replace them with Cow so we can pass &'static str or &'static [_] whenever
possible, while returning owned value when deserializing.
This evens out some of the large enums' sizes (e.g. Expression and Statement's)
from over 200 bytes to <=144. Further reduction could be done, as this commit
only changes the obvious ones for now.

Also moves BinaryOperation's Box up one level so it only uses one Box.
Since we only expect two elements from Range, we can just use array instead.
This fails to build on 1.65:

```
error[E0277]: the trait bound `[SetItem]: ToOwned` is not satisfied in `Expression`
   --> src/stmt.rs:245:14
    |
245 |     pub dev: Option<Expression>,
    |              ^^^^^^^^^^^^^^^^^^ within `Expression`, the trait `ToOwned` is not implemented for `[SetItem]`, which is required by `Expression: Sized`
    |
    = help: the trait `ToOwned` is implemented for `[T]`
note: required because it appears within the type `Expression`
   --> src/expr.rs:10:10
    |
10  | pub enum Expression {
    |          ^^^^^^^^^^
note: required by an implicit `Sized` bound in `std::option::Option`
   --> /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6/library/core/src/option.rs:572:1

...and many, many similar errors
```

It compiles successfully on Rust >=1.79, so this commit can be reverted once our MSRV reaches it.
@hack3ric
Copy link
Author

Fixed MSRV build and clippy. Please have another look :)

@JKRhb JKRhb requested a review from jwhb November 24, 2024 16:09
@jwhb
Copy link
Member

jwhb commented Nov 24, 2024

Amazing @hack3ric!

Please allow us some more days to review it.

@jwhb jwhb added the enhancement New feature or request label Nov 24, 2024
@jwhb jwhb added this to the v0.6.0 milestone Nov 24, 2024
Copy link
Member

@JKRhb JKRhb left a comment

Choose a reason for hiding this comment

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

Thank you, @hack3ric, these changes look very good to me :) As I am personally not that familiar with Cow, see only one question regarding its usage in the tests below.

Comment on lines +86 to +87
table: Cow::Borrowed("some_inet_table"),
chain: Cow::Borrowed("some_inet_chain"),
Copy link
Member

Choose a reason for hiding this comment

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

Would it also be possible to use .into() in places like these?

Copy link
Author

@hack3ric hack3ric Nov 25, 2024

Choose a reason for hiding this comment

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

impl<'a> From<&'a str> for Cow<'a, str> (and its fellows) is not const for now so .into() will result in a temporary value (as seen by rustc, even if it is 'static), therefore cannot be borrowed 'static. In this example both the outer slice and the inner str needs to be borrowed 'static, leading to the following error:

error[E0716]: temporary value dropped while borrowed
   --> tests/json_tests.rs:57:33
    |
57  |            objects: Cow::Borrowed(&[
    |   ________________________________-^
    |  |_________________________________|
58  | ||             NfObject::ListObject(NfListObject::Table(Table {
59  | ||                 family: NfFamily::INet,
60  | ||                 name: "some_inet_table".into(),
...   ||
106 | ||             })),
107 | ||         ]),
    | ||         ^
    | ||_________|
    |  |_________creates a temporary value which is freed while still in use
    |            cast requires that borrow lasts for `'static`
108 |        };
    |         - temporary value is freed at the end of this statement

If all of the outer containers are Cow::Owned, we can use .into() inside. Otherwise we probably need to stick to manually use Cow::Borrowed. I've also pushed a commit that allows temporary arrays to be used by replacing 'static with 'a. It will then just need to move the array in Cow::Borrowed(&[..]) to another local variable to allow the usage of .into():

let objects = [
    NfObject::ListObject(NfListObject::Table(Table {
        family: NfFamily::INet,
        name: "some_inet_table".into(), // here
        handle: None,
    })),
    // ...
];
let expected = Nftables {
    objects: objects.into(); // and here
};

@hack3ric
Copy link
Author

Added one commit that lifts 'static restriction on borrowed values, but added a lot of lifetimes, and updated examples in README as well.

This adds a bunch of lifetimes, but it works with `.into()` when a borrowed value's array container is stored in another place, without the need of `Cow::Borrowed`:

```
let objects = [
    NfObject::ListObject(NfListObject::Table(Table {
        family: NfFamily::INet,
        name: "some_inet_table".into(), // here
        handle: None,
    })),
    // ...
];
let expected = Nftables {
    objects: objects.into(); // and here
};
```

However, `Cow::{Borrowed, Owned}` still needs to be used when you want to write the above in one statement.
@hack3ric
Copy link
Author

Clippy issue should be fixed now.

@hack3ric
Copy link
Author

FYI I have another patch ready for upstream, but it depends on this PR: 52902cd. I will submit another PR after this and I hope this too can get into v0.6.0 too :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants