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

Convenience constructor templates for buffer_info #860

Merged
merged 3 commits into from
May 29, 2017

Conversation

YannickJadoul
Copy link
Collaborator

In order to reduce the amount of error-prone constructor arguments, buffer_info would now have a constructor template similar to array to automatically get the item size, default format string and number of dimensions.

@dean0x7d
Copy link
Member

That's a nice reduction of arguments. Looks good to me.

@YannickJadoul
Copy link
Collaborator Author

Somehow the builds are failing on gcc 4.8, though. gcc 6 and clang do work. And on my own system (gcc 5.4), I had to use brace-initialization.

For some reason, it seems that the compiler first moves the arguments to the other constructor, and only then gets the size() of the shape vector.
Or, of course, I'm missing something else and there's other undefined behaviour...

…ize, format string, and number of dimensions from the pointer type and the shape container
@dean0x7d
Copy link
Member

I think the frequent false positives on AppVeyor have desensitized me to seeing red in CI status. Totally missed the gcc 4.8 error.

@dean0x7d
Copy link
Member

dean0x7d commented May 17, 2017

It's an argument evaluation order issue. shape_in is being used and moved out of in the same call -- undefined behavior. GCC >5 and clang evaluate shape_in->size() before std::move(shape_in) but the order isn't specified by the C++ standard. Edit: Didn't see the brace initialization.

@YannickJadoul
Copy link
Collaborator Author

I'd agree, but there's two things why I'm not completely convinced:

  • First, std::move should be nothing more than a cast to an rvalue reference. The actual move should only happen at the construction time of the callee's arguments, no?
  • Second, the brace-initialization dóes have guaranteed ordering according to the C++11 standard.

@YannickJadoul
Copy link
Collaborator Author

YannickJadoul commented May 17, 2017

Ah, but, stack overflow knows about a gcc bug: https://stackoverflow.com/questions/14060264/order-of-evaluation-of-elements-in-list-initialization
So maybe I'm wrong about that first point, then.

Edit: another interesting StackOverflow thread: https://stackoverflow.com/questions/24814696/move-semantics-and-function-order-evaluation

@dean0x7d
Copy link
Member

Regarding point 1, each argument is fully evaluated before moving on to the next one, so the rvalue-reference cast and move-constructor call happen back-to-back.

MSVC has the same bug as gcc 4.8 regarding brace evaluation order. AFAIK this only affects calling constructors with braces, but not std::initializer_list and aggregates.

Unless someone has a better solution, I think making a copy of shape_in might be an acceptable workaround here.

@jagerman
Copy link
Member

jagerman commented May 18, 2017

I ran into exactly the same issue in numpy.h when implementing this there in the array classes, and copying was what I went with. So if someone finds a better solution, apply it there too! worked around it.

@YannickJadoul
Copy link
Collaborator Author

YannickJadoul commented May 18, 2017

Let's see... *fingers crossed*
I've implemented this suggestion from StackOverflow, basically adding an intermediate private constructor that takes an rvalue reference instead. This way, the strides_in variable doesn't get moved until this private constructor runs (and by that time, we have surely queried its size).

Edit: woohoo, GCC 4.8 already seems to have succeeded. Should I also implement this solution for array in this same PR, or ... ?

@@ -79,6 +72,18 @@ struct buffer_info {
}

private:
struct private_ctr_tag { explicit private_ctr_tag() = default; };
Copy link
Member

Choose a reason for hiding this comment

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

Is there any pointer in declaring the default constructor here? (I.e. instead of just struct private_ctr_tag {};?)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think I saw that somewhere, and reckoned explicit would be appropriate for the constructor. But there's actually no point, since it's all private, anyway.

@jagerman
Copy link
Member

Edit: woohoo, GCC 4.8 already seems to have succeeded. Should I also implement this solution for array in this same PR, or ... ?

Actually, I don't think it's needed anymore; I forget exactly where it happened, but it looks like the current array implementation just moves (and just gets ndim from shape->size() rather than passing it separately).

@YannickJadoul
Copy link
Collaborator Author

Actually, I don't think it's needed anymore; I forget exactly where it happened, but it looks like the current array implementation just moves (and just gets ndim from shape->size() rather than passing it separately).

Is there a reason for these two interfaces (buffer_info and array) to differ from each other? It feels like they're pretty similar in function. Things like e.g. default strides could also be useful for creating a buffer_info?

@YannickJadoul
Copy link
Collaborator Author

Oh, also, on the topic of default_strides: I noticed that the current implementation

std::fill(strides.begin(), strides.end(), itemsize);
for (size_t i = 0; i < ndim - 1; i++)
    for (size_t j = 0; j < ndim - 1 - i; j++)
        strides[j] *= shape[ndim - 1 - i];

could be simplified to the more efficient

strides[ndim - 1] = itemsize;
for (size_t i = 1; i < ndim; ++i)
    strides[ndim - 1 - i] = strides[ndim - i] * shape[ndim - i];

If we leave array alone in this PR, this change could be a different PR (or none at all, if it's not worth the change), but I thought to just mention it.

@jagerman
Copy link
Member

could be simplified to the more efficient

Already pending in #762.

…uctor taking rvalue reference, solving the evaluation order move problem
@jagerman
Copy link
Member

I need the same trick in #762, so how about moving the struct private_ctr_tag {}; into the pybind11::detail namespace in common.h. (Also minor nitpick: let's rename to private_ctor_tag {};, using the more common constructor abbreviation).

@jagerman
Copy link
Member

Actually, never mind that. I'll move it as part of #762; you can leave this PR as-is.

for (size_t i = 0; i < (size_t) ndim; ++i)
size *= shape[i];
}

Copy link
Member

Choose a reason for hiding this comment

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

Perhaps this could be redesigned a bit to only use this private constructor when you need the delegation, i.e. just in the one constructor at line 34 that wants to both move and call ->size. It makes the code a little easier to follow (one fewer delegation for most constructors), and invokes fewer moves. It's also essentially a workaround for MSVC and old gcc, rather than a particularly desirable design.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is what you mean?

This costs one extra move when you use the buffer_info(T *ptr, detail::any_container<ssize_t> shape_in, detail::any_container<ssize_t> strides_in) overload (as the private constuctor forwards it to the original overload and that one takes the containers by value before moving it into the data members, while before, the private constructor moved it immediately into the private members).
Then again, even if the optimizer doesn't figure that out, that extra move won't be thát costly, of course.

Copy link
Member

Choose a reason for hiding this comment

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

Oh, right, it's not actually fewer delegations. Still, I like the idea of keeping the workaround as out-of-the-way as possible rather than pushing everything through it.

@dean0x7d dean0x7d merged commit b700c5d into pybind:master May 29, 2017
@dean0x7d
Copy link
Member

Merged, thanks @YannickJadoul!

@YannickJadoul YannickJadoul deleted the buffer_info-creator branch July 10, 2017 21:54
@dean0x7d dean0x7d modified the milestone: v2.2 Aug 13, 2017
wojdyr added a commit to project-gemmi/gemmi that referenced this pull request Feb 26, 2019
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