Skip to content

Use small intrusive pointer in irep #786

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

Closed
wants to merge 2 commits into from

Conversation

reuk
Copy link
Contributor

@reuk reuk commented Apr 9, 2017

Follows up #685. Rather than using std::shared_ptr which uses more memory than is strictly necessary (an extra pointer to the control block in the shared_ptr itself, plus the control block memory) we use a custom small_shared_ptr which is based on boost's intrusive_ptr.

This approach uses exactly the same amount of memory as was used previously, but separating the resource management into a separate class makes the logic a bit clearer, and greatly simplifies the copy- and move-assignment operators. It also means we don't need to explicitly redeclare custom move/copy constructors/assignment-ops in irep.h.

@reuk reuk force-pushed the small-shared-ptr branch from d6ee7ec to 0e57b66 Compare April 9, 2017 21:48
Copy link
Collaborator

@tautschnig tautschnig left a comment

Choose a reason for hiding this comment

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

A few questions; I overall like this approach, but what we really need for this to move forward is a solid performance testing set up that has long been talked about.

src/util/irep.h Outdated
{
std::swap(irep.data, data);
using std::swap;
swap(data, rhs.data);
Copy link
Collaborator

Choose a reason for hiding this comment

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

What is the rationale for this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

See following comment.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Same here: keep it simple. We don't need to solve problems we don't have.

void swap(small_shared_ptrt &rhs)
{
using std::swap;
swap(t_, rhs.t_);
Copy link
Collaborator

Choose a reason for hiding this comment

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

What's the idea of the separate using directive, instead of just doing std::swap(t_, rhs.t_); ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the general case, using std::swap allows you to place a custom swap for your type in a namespace other than std, because manually overloading std::swap isn't allowed. This overload can be found by Koenig lookup, but if not found will fall back to std::swap. In this case, this behaviour isn't strictly required, so I could just use std::swap directly.

Copy link
Collaborator

@tautschnig tautschnig Apr 10, 2017

Choose a reason for hiding this comment

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

Keep it simple.

}

template <typename T, typename... Ts>
small_shared_ptrt<T> make_small_shared_ptr(Ts &&... ts)
Copy link
Collaborator

Choose a reason for hiding this comment

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

What is the use case of this one?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Like std::make_unique and std::make_shared, this has exception-safety benefits.

In the expression my_func(small_shared_ptr<X>(new X), small_shared_ptr<Y>(new Y)), the compiler is allowed to sequence the operations like so:

new X
new Y <-- This may throw and leak previously-allocated X
construct small_shared_ptr<X>
construct small_shared_ptr<Y>
my_func

Whereas my_func(make_small_shared_ptr<X>(), make_small_shared_ptr<Y>()) cannot leak because the calls to make_small_shared_ptr won't be interleaved.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't that be my_func(make_small_shared_ptr<X, Y>()) ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, empty parameter packs are valid. You can see at irep.cpp:30 and irep.h:242 how the function is used in practice.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ok, I think I just misunderstood: your argument was about the order of invoking new vs the construction of small_shared_ptr, and not the order of the small_shared_ptr constructs (which is still unspecified). Fair enough. You may wish to add a comment to the constructor of small_shared_ptr to say that make_small_shared_ptr should be used in case of a new T argument.

What would be a use case where the parameter pack is non-empty? Is this just to support types other than dt that require >= 1 argument in their constructor?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, if you have a type with a constructor like my_type(Foo, Bar, Baz) then you can create a small_shared_ptr to it using make_small_shared_ptr<my_type>(Foo(), Bar(), Baz()) (for example).

This also avoids repetition of the identifier my_type (the alternative would be small_shared_ptr<my_type>(new my_type(Foo(), Bar(), Baz()) which is needlessly repetitive).

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ok, thanks!

@reuk reuk force-pushed the small-shared-ptr branch from 0e57b66 to d86342e Compare April 10, 2017 08:42
@tautschnig
Copy link
Collaborator

As said before, this looks good to me, and includes quite a bit of long overdue cleanup. Yet I don't dare setting any approval here in absence of a performance test suite.

@tautschnig
Copy link
Collaborator

With 22170b7 my looks-good-to-me no longer applies.

@reuk
Copy link
Contributor Author

reuk commented Apr 12, 2017

Yes, sorry about that - I think the copy-on-write implementation currently in irep is buggy because it allows you to make changes which affect copies in unexpected ways:

irept a;
auto& comments = a.get_comments();
const auto b(a);
comments.<some mutating method>();
// b is changed here!

I'm guessing this is why get_comments and other similar methods are marked DANGEROUS. True copy-on-write would mark the internal structure 'unshareable' when giving out a mutable reference to a data member. Then, the example above becomes:

irept a;
auto& comments = a.get_comments(); // This detaches if necessary, and
                                   // marks the internal data unshareable
const auto b(a); // This is a deep copy because a is unshareable
comments.<some mutating method>(); // Only affects 'a'

I'll close the PR and re-open when I have something that fixes this bug.

@reuk reuk closed this Apr 12, 2017
@tautschnig
Copy link
Collaborator

I don't necessarily think you have to go as far as closing this one. How about making get_comments and get_sub private? There are only a handful of users of those out there.

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.

2 participants