[multiprecision] Some rvalue reference experiments (performance inhancements #1)

Folks, I've started to work through some of the points that came up in the multiprecision review, starting with further rvalue reference support. The idea (due to Marc Glisse) was to add operator overloads that look something like: Number&& operator+(Number&&a, const Number& b) { return static_cast<Number&&>(a += b); } Using my Polynomial Horner evaluation scheme as an example the number of temporaries dropped as follows: Type # temporaries No expression templates, no rvalue refs 24 No expression templates, rvalue refs 1 Expression template 0 Which is pretty impressive, but not necessarily typical usage, so by way of "real world" tests, I compared the times taken to evaluate Boost.Math's Bessel function test cases to 50 decimal places: Library Time/s # Allocations MPFR/mpreal (no ET's or rvalue refs) 9.4 13226029 MPFR/mpfr_class (ET's) 6.3 3948316 MPFR/mp_number(ET's) 6.0 2683646 GMP/mp_number(no ET's) 5.2 4186977 GMP/mp_number(ET's) 5.0 2594441 As you can see the number of allocations required when using mp_number with ET's turned off is almost double that without, I guess this is because many statements are similar to: a = b op c; Where a temporary is still required when relying on rvalue refs, but not for expression templates. Having said that the times are getting much closer now.... John.

On Sat, 30 Jun 2012, John Maddock wrote:
I've started to work through some of the points that came up in the multiprecision review, starting with further rvalue reference support.
The idea (due to Marc Glisse) was to add operator overloads that look something like:
Number&& operator+(Number&&a, const Number& b) { return static_cast<Number&&>(a += b); }
The safe solution is returning Number, not Number&&. If Dave reads this message, he can probably point us to previous discussions about this. Being able to return Number&& would be great... Note that changing the return type from Number&& to Number cancels the allocation gain when using a type like GMP that doesn't have an empty state. -- Marc Glisse

The safe solution is returning Number, not Number&&. If Dave reads this message, he can probably point us to previous discussions about this. Being able to return Number&& would be great...
Can you explain why it's unsafe given that we know that the value returned is an rvalue ref already?
Note that changing the return type from Number&& to Number cancels the allocation gain when using a type like GMP that doesn't have an empty state.
Which would not be so good :-( Cheers, John.

AMDG On 06/30/2012 11:08 AM, John Maddock wrote:
The safe solution is returning Number, not Number&&. If Dave reads this message, he can probably point us to previous discussions about this. Being able to return Number&& would be great...
Can you explain why it's unsafe given that we know that the value returned is an rvalue ref already?
I'm guessing that it has something to do with temporary lifetimes? In Christ, Steven Watanabe

On Sat, 30 Jun 2012, Steven Watanabe wrote:
On 06/30/2012 11:08 AM, John Maddock wrote:
The safe solution is returning Number, not Number&&. If Dave reads this message, he can probably point us to previous discussions about this. Being able to return Number&& would be great...
Can you explain why it's unsafe given that we know that the value returned is an rvalue ref already?
I'm guessing that it has something to do with temporary lifetimes?
I think so. Trying to find back how some people managed to convince me it was unsafe, all I managed to find right now is a statement by Dave that Howard generally advised against function returning rvalue references... One easy issue I can see (in code right in front of me) is: const NT& result=a*d-b*c; Using const references instead of plain variables is something that quite a number of people do, and which (I think) would fail if operator- returned a reference. Note that it also means that the unary operator+ (which I hope noone ever uses) is "unsafe" in a number of libraries. -- Marc Glisse

One easy issue I can see (in code right in front of me) is:
const NT& result=a*d-b*c;
Using const references instead of plain variables is something that quite a number of people do, and which (I think) would fail if operator- returned a reference.
Killer argument :-(
Note that it also means that the unary operator+ (which I hope noone ever uses) is "unsafe" in a number of libraries.
Nod :-( So, I've updated to return by value, discovered that my move constructors weren't actually moving (more care needed in calling base class constructors to prevent them from copying!), and generally added more move support throughout. Results are now: Horner polynomial case: Expression templates: 0 temporaries. Move semantics: 0 temporaries! This isn't quite as good as it looks - in the test one temporary is created in the move case, but it gets wiped out by the move constructor. So were we assigning to an already constructed variable these two cases would differ by one temporary. Oh, and I'm using allocations as a proxy measurement for temporaries, but in reality we are creating temporaries in the move case (lot's of them), they just don't need to allocate. Bessel function test: Time for mpf_float_50 = 4.96608 seconds Total allocations for mpf_float_50 = 2592537 Time for mpf_float_50 (no expression templates = 5.2808 seconds Total allocations for mpf_float_50 (no expression templates = 4174720 And again with BOOST_NO_RVALUE_REFERENCES defined: Time for mpf_float_50 = 5.20759 seconds Total allocations for mpf_float_50 = 2594441 Time for mpf_float_50 (no expression templates = 6.10358 seconds Total allocations for mpf_float_50 (no expression templates = 6498034 So basically, the rvalue ref no-ET code does as well as the no-rvalue expression template code, except of course turning on rvalue ref support helps the ET code as well, so ET's still win overall - in other words as you might expect, best performance comes from having both. Hope that makes sense, John.

[Finally going through my back-log of MP discussion emails...] On Sat, Jun 30, 2012 at 10:17 AM, Marc Glisse <marc.glisse@inria.fr> wrote:
On Sat, 30 Jun 2012, John Maddock wrote:
I've started to work through some of the points that came up in the
multiprecision review, starting with further rvalue reference support.
The idea (due to Marc Glisse) was to add operator overloads that look something like:
Number&& operator+(Number&&a, const Number& b) { return static_cast<Number&&>(a += b); }
The safe solution is returning Number, not Number&&. If Dave reads this message, he can probably point us to previous discussions about this. Being able to return Number&& would be great...
The problem is illustrated via something like a = move(a) + b; Of course, the above is better written as "a += b", but it's exemplary of more elaborate assignments, e.g., "a = c * move(a) + b". Indeed, an even simpler example is just "a = move(a)", given John's implementation of operator+ above and ignoring the arithmetic (which is not relevant). And the problem with "a = move(a)" in a generic context is that T::operator=(T&& x) may freely assume that this != &x [1], mostly for efficiency purposes (Dave, correct me if I'm wrong here). So, in other words, don't return an rvalue reference from a function which is a reference to one of its arguments unless this is explicitly intended and documented (e.g., move and forward). Note that changing the return type from Number&& to Number cancels the
allocation gain when using a type like GMP that doesn't have an empty state.
Huh, really? That's no good. - Jeff [1] http://cpp-next.com/archive/2009/09/your-next-assignment/

The problem is illustrated via something like
a = move(a) + b;
Of course, the above is better written as "a += b", but it's exemplary of more elaborate assignments, e.g., "a = c * move(a) + b". Indeed, an even simpler example is just "a = move(a)", given John's implementation of operator+ above and ignoring the arithmetic (which is not relevant). And the problem with "a = move(a)" in a generic context is that T::operator=(T&& x) may freely assume that this != &x [1], mostly for efficiency purposes (Dave, correct me if I'm wrong here).
So, in other words, don't return an rvalue reference from a function which is a reference to one of its arguments unless this is explicitly intended and documented (e.g., move and forward).
Note that changing the return type from Number&& to Number cancels the
allocation gain when using a type like GMP that doesn't have an empty state.
Huh, really? That's no good.
Don't panic it's OK ;-) The current sandbox code, does have these rvalue ref operator overloads, does return by value for safety, and still manages to avoid the extra allocations - so for example in my horner test case, evaluating: Real result = (((((a[6] * x + a[5]) * x + a[4]) * x + a[3]) * x + a[2]) * x + a[1]) * x + a[0]; Reuslts in just one allocation even when expression templates are turned off - the first operator overload called generates a temporary, which then gets moved and reused, eventually ending up in the result. Of course rvalue refs can't help in simple cases such as: a = b * c; For that you still need expression templates if you want to avoid temporaries. I've posted these results before, but here are the current Bessel function evaluation tests using mpf_t: With rvalue refs: Testing Bessel Functions at 50 digits..... Time for mpf_float_50 = 5.00486 seconds Total allocations for mpf_float_50 = 2592797 Time for mpf_float_50 (no expression templates = 5.30183 seconds Total allocations for mpf_float_50 (no expression templates = 4174980 And again with BOOST_NO_RVALUE_REFERENCES defined Time for mpf_float_50 = 4.93078 seconds Total allocations for mpf_float_50 = 2594701 Time for mpf_float_50 (no expression templates = 5.68103 seconds Total allocations for mpf_float_50 (no expression templates = 6498294 As expected the biggest hit is in the no-ET code, where the number of allocations rises dramatically. Cheers, John.

On Sun, 5 Aug 2012, John Maddock wrote:
As noted in a recent post - the cost of wrapping a native type inside mp_number should be close to zero now *when expression templates are off*.
When ET's are turned on, this will always IMO be slower, consider an operator returning an expression template - it's basically returning a pair of references (and larger objects still for more complex expressions), where as say a wrapped integer is actually returning a smaller cheaper to copy object in that case. So for a wrapped integer, returning the result by value will always win out, and that's before you even consider the cost of unpacking the expression template.
I believe you are a bit pessimistic. Compilers are not bad at inlining functions and removing wrappers that do nothing, even for expression-templates. And yes, returning a pair of references is doing nothing if your function is inlined. Some analysis of what remains would be interesting. What usually happens is that: * expression template wrappers sometimes contain runtime checks (e.g. for aliasing between variables), some of which can be impossible to determine at compile-time; * some compiler optimization opportunities that would have happened early are now only exposed after a lot of inlining / simplification has taken place, which may be too late for some compilers (but then it is not too hard for compilers to make progress there, if it is pointed out to them).
I have an open question for you all: I was planning on working on cpp_int performance next, and trying to improve the computation geometry use cases. Obviously that pushes back a potential release. The alternative is to address all the issues relating to the front end first, and aim for an earlier first release. Which would folks prefer?
IMHO: front-end first, back-ends later. On Sun, 5 Aug 2012, John Maddock wrote:
The problem is illustrated via something like
a = move(a) + b;
Of course, the above is better written as "a += b", but it's exemplary of more elaborate assignments, e.g., "a = c * move(a) + b". Indeed, an even simpler example is just "a = move(a)", given John's implementation of operator+ above and ignoring the arithmetic (which is not relevant). And the problem with "a = move(a)" in a generic context is that T::operator=(T&& x) may freely assume that this != &x [1], mostly for efficiency purposes (Dave, correct me if I'm wrong here).
So, in other words, don't return an rvalue reference from a function which is a reference to one of its arguments unless this is explicitly intended and documented (e.g., move and forward).
Note that changing the return type from Number&& to Number cancels the
allocation gain when using a type like GMP that doesn't have an empty state.
Huh, really? That's no good.
Don't panic it's OK ;-)
The current sandbox code, does have these rvalue ref operator overloads, does return by value for safety, and still manages to avoid the extra allocations - so for example in my horner test case, evaluating:
Real result = (((((a[6] * x + a[5]) * x + a[4]) * x + a[3]) * x + a[2]) * x + a[1]) * x + a[0];
Reuslts in just one allocation even when expression templates are turned off - the first operator overload called generates a temporary, which then gets moved and reused, eventually ending up in the result.
Uh? This is indeed what happens, but for GMP types, unless you added in your wrapper a special 0 state (which you then have to test in every operation), every constructor has to allocate, including the move constructor, since a moved-from object must still be in a valid state. Did you add an empty state then? -- Marc Glisse

When ET's are turned on, this will always IMO be slower, consider an operator returning an expression template - it's basically returning a pair of references (and larger objects still for more complex expressions), where as say a wrapped integer is actually returning a smaller cheaper to copy object in that case. So for a wrapped integer, returning the result by value will always win out, and that's before you even consider the cost of unpacking the expression template.
I believe you are a bit pessimistic. Compilers are not bad at inlining functions and removing wrappers that do nothing, even for expression-templates. And yes, returning a pair of references is doing nothing if your function is inlined. Some analysis of what remains would be interesting. What usually happens is that: * expression template wrappers sometimes contain runtime checks (e.g. for aliasing between variables), some of which can be impossible to determine at compile-time; * some compiler optimization opportunities that would have happened early are now only exposed after a lot of inlining / simplification has taken place, which may be too late for some compilers (but then it is not too hard for compilers to make progress there, if it is pointed out to them).
Nod. Lot's to investigate I guess ... of course if a returned ET can be optimised away, so can the wrapped type that's returned directly (if it's small enough).
Note that changing the return type from Number&& to Number cancels the
allocation gain when using a type like GMP that doesn't have an empty state.
Huh, really? That's no good.
Don't panic it's OK ;-)
The current sandbox code, does have these rvalue ref operator overloads, does return by value for safety, and still manages to avoid the extra allocations - so for example in my horner test case, evaluating:
Real result = (((((a[6] * x + a[5]) * x + a[4]) * x + a[3]) * x + a[2]) * x + a[1]) * x + a[0];
Reuslts in just one allocation even when expression templates are turned off - the first operator overload called generates a temporary, which then gets moved and reused, eventually ending up in the result.
Uh? This is indeed what happens, but for GMP types, unless you added in your wrapper a special 0 state (which you then have to test in every operation), every constructor has to allocate, including the move constructor, since a moved-from object must still be in a valid state.
Did you add an empty state then?
The move constructor doesn't allocate - it takes ownership of the GMP variable, and sets the variable in the moved-from object to a null state. The *destructor* then has an added check to ensure it doesn't try and clear null GMP objects: that's basically the only change. IMO the cost of the extra if statement in the destructor is worth it - and should be trivial compared to calling the external library routine to clear the GMP variable. John.

On Sun, 5 Aug 2012, John Maddock wrote:
Uh? This is indeed what happens, but for GMP types, unless you added in your wrapper a special 0 state (which you then have to test in every operation), every constructor has to allocate, including the move constructor, since a moved-from object must still be in a valid state.
Did you add an empty state then?
The move constructor doesn't allocate - it takes ownership of the GMP variable, and sets the variable in the moved-from object to a null state. The *destructor* then has an added check to ensure it doesn't try and clear null GMP objects: that's basically the only change. IMO the cost of the extra if statement in the destructor is worth it - and should be trivial compared to calling the external library routine to clear the GMP variable.
Well, what happens then if you do something to a moved-from object other than destructing it? number a=2; number b=move(a); a=2; // or a=b; or a=b+1; or ... -- Marc Glisse

The move constructor doesn't allocate - it takes ownership of the GMP variable, and sets the variable in the moved-from object to a null state. The *destructor* then has an added check to ensure it doesn't try and clear null GMP objects: that's basically the only change. IMO the cost of the extra if statement in the destructor is worth it - and should be trivial compared to calling the external library routine to clear the GMP variable.
Well, what happens then if you do something to a moved-from object other than destructing it?
number a=2; number b=move(a); a=2; // or a=b; or a=b+1; or ...
It blows up your computer ;-) IMO It's a constraint (and the intent of move semantics), that moved-from objects can only be destroyed: if you require that the moved-from object remain usable, then move-assign become identical to a regular assign, and move semantics are basically useless. Not just for this lib, but for the std lib as well. John.

On Sun, 5 Aug 2012, John Maddock wrote:
Well, what happens then if you do something to a moved-from object other than destructing it?
number a=2; number b=move(a); a=2; // or a=b; or a=b+1; or ...
It blows up your computer ;-)
IMO It's a constraint (and the intent of move semantics), that moved-from objects can only be destroyed: if you require that the moved-from object remain usable, then move-assign become identical to a regular assign, and move semantics are basically useless. Not just for this lib, but for the std lib as well.
That doesn't match my understanding. What I got from previous discussions is that you need to be able to assign to a moved-from object. For instance, std::swap will only work for you if you implemented move assignment as a swap. vector::insert will move elements to the right (one move construction, the rest are move assignments) and then copy-assign a new value to the moved-from hole (at least that's how it is implemented in libcxx). -- Marc Glisse

On Sun, Aug 5, 2012 at 5:57 AM, Marc Glisse <marc.glisse@inria.fr> wrote:
On Sun, 5 Aug 2012, John Maddock wrote:
Well, what happens then if you do something to a moved-from object other
than destructing it?
number a=2; number b=move(a); a=2; // or a=b; or a=b+1; or ...
It blows up your computer ;-)
IMO It's a constraint (and the intent of move semantics), that moved-from objects can only be destroyed: if you require that the moved-from object remain usable, then move-assign become identical to a regular assign, and move semantics are basically useless. Not just for this lib, but for the std lib as well.
That doesn't match my understanding. What I got from previous discussions is that you need to be able to assign to a moved-from object.
For instance, std::swap will only work for you if you implemented move assignment as a swap. vector::insert will move elements to the right (one move construction, the rest are move assignments) and then copy-assign a new value to the moved-from hole (at least that's how it is implemented in libcxx).
I believe Marc is correct. You must be able to destruct *and* assign-to a moved-from object. - Jeff

I believe Marc is correct. You must be able to destruct *and* assign-to a moved-from object.
Just checked the std and you're both correct. OK I'll have to rethink, and make sure everything other than arithmetic is supported for moved-from types. John.

On Sun, 5 Aug 2012, John Maddock wrote:
I believe Marc is correct. You must be able to destruct *and* assign-to a moved-from object.
Just checked the std and you're both correct.
OK I'll have to rethink, and make sure everything other than arithmetic is supported for moved-from types.
Actually, in principle, a moved-from object should be a valid object, which just has an unspecified value. But computing x-x should be legal, unless it is documented that the type has a valid singular state (accessible by being moved from or swapped (=move-assigned)) and that all operations other than assignment have a precondition that the arguments are not singular. And then it is strange not to have a way to test if a value is singular. Most uses of a moved-from object I can think of other than destruction and assignment are rather contrived. For a container, it can make sense to look at its size and assign to the elements. For a bignum, maybe if you are interested in an arbitrary number < y (to pass it to a nextafter-like function), before setting x to y-1, you could check if x<y and not want that to crash the program. If you are going to use a bignum as a bitfield, the same argument as for the container could be made. Your remark about this making rvalue references useless is one that has been made a number of times and usually leads to the destructive move semantics proposal. In this case, ideally gmp should have an empty state ("lazy allocation" in the projects page). In general, what to do with types that do not have an empty state is still a bit unclear. There is a proposal to add an overloadable library function that does a destructive move, but it wouldn't help here. -- Marc Glisse

OK I'll have to rethink, and make sure everything other than arithmetic is supported for moved-from types.
Actually, in principle, a moved-from object should be a valid object, which just has an unspecified value. But computing x-x should be legal, unless it is documented that the type has a valid singular state (accessible by being moved from or swapped (=move-assigned)) and that all operations other than assignment have a precondition that the arguments are not singular. And then it is strange not to have a way to test if a value is singular.
Most uses of a moved-from object I can think of other than destruction and assignment are rather contrived. For a container, it can make sense to look at its size and assign to the elements. For a bignum, maybe if you are interested in an arbitrary number < y (to pass it to a nextafter-like function), before setting x to y-1, you could check if x<y and not want that to crash the program. If you are going to use a bignum as a bitfield, the same argument as for the container could be made.
The problem with your comparison example, is it's not clear whether the singular state is less than a non-singular value or not. Basically all operations are undefined. I think unless there are strong objections, I'll only support destruct-or-assign-to moved-from objects, and document that as a limitation. I can add asserts for the other operations, but basically I think that should be enough for the types to interoperate with the std lib.
Your remark about this making rvalue references useless is one that has been made a number of times and usually leads to the destructive move semantics proposal.
In this case, ideally gmp should have an empty state ("lazy allocation" in the projects page). In general, what to do with types that do not have an empty state is still a bit unclear. There is a proposal to add an overloadable library function that does a destructive move, but it wouldn't help here.
Understood, John.

on Tue Aug 07 2012, Marc Glisse <marc.glisse-AT-inria.fr> wrote:
On Sun, 5 Aug 2012, John Maddock wrote:
I believe Marc is correct. You must be able to destruct *and* assign-to a moved-from object.
Just checked the std and you're both correct.
OK I'll have to rethink, and make sure everything other than arithmetic is supported for moved-from types.
Actually, in principle, a moved-from object should be a valid object, which just has an unspecified value. But computing x-x should be legal, unless it is documented that the type has a valid singular state (accessible by being moved from or swapped (=move-assigned)) and that all operations other than assignment have a precondition that the arguments are not singular. And then it is strange not to have a way to test if a value is singular.
This is a very good summary.
Most uses of a moved-from object I can think of other than destruction and assignment are rather contrived. For a container, it can make sense to look at its size and assign to the elements. For a bignum, maybe if you are interested in an arbitrary number < y (to pass it to a nextafter-like function), before setting x to y-1, you could check if x<y and not want that to crash the program. If you are going to use a bignum as a bitfield, the same argument as for the container could be made.
This sounds kind of like programming with iostreams whose badbit has been set. Lots of times it's possible to write the code so that it can barrel ahead as though nothing is wrong and only check for a problem much later. But it sounds kind of implausible that you'd be able to pick a value for empty bignums that would allow that in most or even many cases. -- Dave Abrahams BoostPro Computing Software Development Training http://www.boostpro.com Clang/LLVM/EDG Compilers C++ Boost

On Wed, 8 Aug 2012, Dave Abrahams wrote:
Most uses of a moved-from object I can think of other than destruction and assignment are rather contrived. For a container, it can make sense to look at its size and assign to the elements. For a bignum, maybe if you are interested in an arbitrary number < y (to pass it to a nextafter-like function), before setting x to y-1, you could check if x<y and not want that to crash the program. If you are going to use a bignum as a bitfield, the same argument as for the container could be made.
This sounds kind of like programming with iostreams whose badbit has been set. Lots of times it's possible to write the code so that it can barrel ahead as though nothing is wrong and only check for a problem much later. But it sounds kind of implausible that you'd be able to pick a value for empty bignums that would allow that in most or even many cases.
Hi Dave, I am not sure what you mean here. The safe solution is that whenever you move-construct from a bignum, you set it to zero (move-assignment is a swap). I am not sure, but it sounds like you are saying this wouldn't be safe? The issue here is that setting it to zero is expensive (less than a copy, but still quite a bit), so John instead decided to put it in some "broken" state that he can detect at destruction/assignment time. This means that doing x-y or x<y may crash (although if he sets _mp_size to 0, most read operations are likely not to notice that this isn't a true zero). This is probably a good compromise: better performance for regular code, crashes only for very odd code. The question is where the limit is between regular and odd code... -- Marc Glisse

on Wed Aug 08 2012, Marc Glisse <marc.glisse-AT-inria.fr> wrote:
On Wed, 8 Aug 2012, Dave Abrahams wrote:
Most uses of a moved-from object I can think of other than destruction and assignment are rather contrived. For a container, it can make sense to look at its size and assign to the elements. For a bignum, maybe if you are interested in an arbitrary number < y (to pass it to a nextafter-like function), before setting x to y-1, you could check if x<y and not want that to crash the program. If you are going to use a bignum as a bitfield, the same argument as for the container could be made.
This sounds kind of like programming with iostreams whose badbit has been set. Lots of times it's possible to write the code so that it can barrel ahead as though nothing is wrong and only check for a problem much later. But it sounds kind of implausible that you'd be able to pick a value for empty bignums that would allow that in most or even many cases.
Hi Dave,
I am not sure what you mean here. The safe solution is that whenever you move-construct from a bignum, you set it to zero (move-assignment is a swap). I am not sure, but it sounds like you are saying this wouldn't be safe?
Not exactly. With floats, we have non-signalling NaN, which IIUC is used in exactly that way: you might end up with a few NaNs in your big linear algebra solution but they might well turn out to be unimportant. What you do know is that anything that touches them will also yield a NaN, sort of like anything that uses a stream with badbit set yields a stream with badbit set. Zero doesn't work that way: after all, it's an additive identity. -- Dave Abrahams BoostPro Computing Software Development Training http://www.boostpro.com Clang/LLVM/EDG Compilers C++ Boost

-----Original Message----- From: boost-bounces@lists.boost.org [mailto:boost-bounces@lists.boost.org] On Behalf Of Dave Abrahams Sent: Sunday, August 19, 2012 1:52 AM To: boost@lists.boost.org Subject: Re: [boost] several messages
on Wed Aug 08 2012, Marc Glisse <marc.glisse-AT-inria.fr> wrote:
On Wed, 8 Aug 2012, Dave Abrahams wrote:
Most uses of a moved-from object I can think of other than destruction and assignment are rather contrived. For a container, it can make sense to look at its size and assign to the elements. For a bignum, maybe if you are interested in an arbitrary number < y (to pass it to a nextafter-like function), before setting x to y-1, you could check if x<y and not want that to crash the program. If you are going to use a bignum as a bitfield, the same argument as for the container could be made.
This sounds kind of like programming with iostreams whose badbit has been set. Lots of times it's possible to write the code so that it can barrel ahead as though nothing is wrong and only check for a problem much later. But it sounds kind of implausible that you'd be able to pick a value for empty bignums that would allow that in most or even many cases.
Hi Dave,
I am not sure what you mean here. The safe solution is that whenever you move-construct from a bignum, you set it to zero (move-assignment is a swap). I am not sure, but it sounds like you are saying this wouldn't be safe?
Not exactly. With floats, we have non-signalling NaN, which IIUC is used in exactly that way: you might end up with a few NaNs in your big linear algebra solution but they might well turn out to be unimportant. What you do know is that anything that touches them will also yield a NaN, sort of like anything
that uses
a stream with badbit set yields a stream with badbit set. Zero doesn't work that way: after all, it's an additive identity.
+1 for this view. William Kahan and the rest of the IEE754 designers wisely built in the concept of NotANumber. It provides protection against spurious, zero-information results. And, as we have discovered in writing functions and distributions in Boost.Math, it is a big simplification if all number systems also support a NaN concept, however it is implemented. Paul --- Paul A. Bristow, Prizet Farmhouse, Kendal LA8 8AB UK +44 1539 561830 07714330204 pbristow@hetp.u-net.com

Paul A. Bristow wrote:
William Kahan and the rest of the IEE754 designers wisely built in the concept of NotANumber. It provides protection against spurious, zero-information results.
And, as we have discovered in writing functions and distributions in Boost.Math, it is a big simplification if all number systems also support a NaN concept, however it is implemented.
This is a topic which I'm curious about. I would have thought that code which either produces or uses a NaN should always throw an exception. But this is not the case - though apparently it's a compile time option in some environments. What is the reasoning behind this design? I would think that plowing forward with an invalid result would always be a bad idea. Then only thing I could think of was at the time (ca 1984) makiing a hardware interrupt was too complicated - on an alread complicated hardware spec (sub normal values, etc.) and a software implemntation was considered too slow and didn't work with fortran. I'm just curious about this, BTW we once had an opportunity to have Kahan himself speak a Boost Con but it didn't work out. Oh Well Robert Ramey
Paul
--- Paul A. Bristow, Prizet Farmhouse, Kendal LA8 8AB UK +44 1539 561830 07714330204 pbristow@hetp.u-net.com
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

-----Original Message----- From: boost-bounces@lists.boost.org [mailto:boost-bounces@lists.boost.org] On Behalf Of Robert Ramey Sent: Monday, August 20, 2012 6:44 PM To: boost@lists.boost.org Subject: Re: [boost] several messages
Paul A. Bristow wrote:
William Kahan and the rest of the IEE754 designers wisely built in the concept of NotANumber. It provides protection against spurious, zero-information results.
And, as we have discovered in writing functions and distributions in Boost.Math, it is a big simplification if all number systems also support a NaN concept, however it is implemented.
This is a topic which I'm curious about. I would have thought that code which either produces or uses a NaN should always throw an exception. But this is not the case - though apparently it's a compile time option in some environments. What is the reasoning behind this design? I would
think that
plowing forward with an invalid result would always be a bad idea. Then only thing I could think of was at the time (ca 1984) makiing a hardware interrupt was too complicated - on an alread complicated hardware spec (sub normal values, etc.) and a software implemntation was considered too slow and didn't work with fortran. I'm just curious about this, BTW we once had an opportunity to have Kahan himself speak a Boost Con but it didn't work out. Oh Well
Well there is/used to be a std::numeric_limits<FPT>::signaling_NaN() but it has fallen into disuse (or never got into use?). What you suggest has some merits but now poses insuperable portability issues. And sometimes, as Dave explained, you don't want to abort just because one value is duff. It's easier to wait until you try to use it before squeaking loudly. Paul

OK I'll have to rethink, and make sure everything other than arithmetic is supported for moved-from types.
Actually, in principle, a moved-from object should be a valid object, which just has an unspecified value.
IMHO any code that reads such unspecified values is buggy. I wonder if the compiler could produce warnings for reading an unspecified value. It seems like a bad idea 99.9% of the time. It'd be cool if Valgrind could be given a hint to mark such data as non-inited. If I had a movable class with a double I'd set it's value signaling NAN at least for a debug build. Chris

OK I'll have to rethink, and make sure everything other than arithmetic is supported for moved-from types.
Actually, in principle, a moved-from object should be a valid object, which just has an unspecified value.
IMHO any code that reads such unspecified values is buggy.
Right.
I wonder if the compiler could produce warnings for reading an unspecified value. It seems like a bad idea 99.9% of the time. It'd be cool if Valgrind could be given a hint to mark such data as non-inited.
If I had a movable class with a double I'd set it's value signaling NAN at least for a debug build.
Currently, any attempt to "use" a moved-from value results in an assertion, the only things you can safely do are destruct, assign-to, swap, or copy such values. There is a slight inconsistency though - not all number type Backends support (or need to support) move semantics. So if you switch from a fixed precision integer which simply copies (no move), to an arbitrary precision integer (moves) then you get a change in behavior. Of course any code that relies on an object having a specific value after it's been moved was IMO buggy to begin with, it's just that you might not see the asserts until you test with a move-enabled type. This is unfortunate, but I really don't want to hobble the fixed precision integers by giving them a special singular state (more code more checking), when most of the negative review comments were of the opinion that they're too heavyweight already. John.

On Thu, Aug 9, 2012 at 8:22 AM, John Maddock <boost.regex@virgin.net> wrote:
OK I'll have to rethink, and make sure everything other than
arithmetic is supported for moved-from types.
Actually, in principle, a moved-from object should be a valid object,
which just has an unspecified value.
IMHO any code that reads such unspecified values is buggy.
Right.
I wonder if the compiler could produce warnings for reading an
unspecified value. It seems like a bad idea 99.9% of the time. It'd be cool if Valgrind could be given a hint to mark such data as non-inited.
If I had a movable class with a double I'd set it's value signaling NAN at least for a debug build.
Currently, any attempt to "use" a moved-from value results in an assertion, the only things you can safely do are destruct, assign-to, swap, or copy such values.
There is a slight inconsistency though - not all number type Backends support (or need to support) move semantics. So if you switch from a fixed precision integer which simply copies (no move), to an arbitrary precision integer (moves) then you get a change in behavior. Of course any code that relies on an object having a specific value after it's been moved was IMO buggy to begin with, it's just that you might not see the asserts until you test with a move-enabled type. This is unfortunate, but I really don't want to hobble the fixed precision integers by giving them a special singular state (more code more checking), when most of the negative review comments were of the opinion that they're too heavyweight already.
John.
I'd go even further and just say it's undefined behavior...you should say "if you're lucky, it'll assert", and maybe explain how the backends affect behavior in the present implementation, but I wouldn't go so far as to guarantee it. - Jeff
participants (8)
-
Dave Abrahams
-
Hite, Christopher
-
Jeffrey Lee Hellrung, Jr.
-
John Maddock
-
Marc Glisse
-
Paul A. Bristow
-
Robert Ramey
-
Steven Watanabe