Hello, Complex World

B

Brian

I've been working on adding support for std::complex to the C++
Middleware Writer -- http://webEbenezer.net/build_integration.html.

So far this is what I have:

template <typename T>
void
complexCount(Counter& cntr, std::complex<T> const& cmplx)
{
cntr.Add(sizeof(T) * 2);
}


template <typename B, typename T>
void
complexSend(B* buf, std::complex<T> const& cmplx)
{
T real = cmplx.real();
buf->Receive(&real, sizeof(T));
T imaginary = cmplx.imag();
buf->Receive(&imaginary, sizeof(T));
}


template <typename B, typename T>
void
complexReceive(B* buf, std::complex<T>& cmplx)
{
T real, imaginary;
buf->Give(real);
cmplx.real(real);
buf->Give(imaginary);
cmplx.imag(imaginary);
}


Both the send and receive functions are suboptimal due
to the marshalling being done externally. Is there interest
in having this common type retrofitted for marshalling?
To my knowledge the C++ Middleware Writer offers the
most efficient marshalling approach available today. I
would like to ask that some accommodations be made
wrt to the standard so as to allow people to marshall
instances of this common type more efficiently than
is possible with the current standard.

If complex were given a stream constructor, rather
than needing complexReceive we would have
something like this:

template <typename B>
explicit complex(B* buf)
{
buf->Give(real_);
buf->Give(imag_);
}

where real_ and imag_ are the names of the private
data members of the complex type.

The C++ Middleware Writer could implement the
constructor based on reading the complex header,
but it would be necessary to add a prototype to
the header --

template <typename B>
explicit complex(B*);

Alternatively it could be --

template <typename B>
explicit complex(B&);

if that were thought to be more appropriate. I'm aware
of comp.std.c++ and may post something there if there
is some interest here.


Brian Wood
http://webEbenezer.net
(651) 251-9384

When a man's ways please the L-RD, He makes
even his enemies to be at peace with him. Proverbs 16:7
 
J

Joshua Maurice

I've been working on adding support for std::complex to the C++
Middleware Writer --http://webEbenezer.net/build_integration.html.

So far this is what I have:

template <typename T>
void
complexCount(Counter& cntr, std::complex<T> const& cmplx)
{
  cntr.Add(sizeof(T) * 2);

}

template <typename B, typename T>
void
complexSend(B* buf, std::complex<T> const& cmplx)
{
  T real = cmplx.real();
  buf->Receive(&real, sizeof(T));
  T imaginary = cmplx.imag();
  buf->Receive(&imaginary, sizeof(T));

}

template <typename B, typename T>
void
complexReceive(B* buf, std::complex<T>& cmplx)
{
  T real, imaginary;
  buf->Give(real);
  cmplx.real(real);
  buf->Give(imaginary);
  cmplx.imag(imaginary);

}

Both the send and receive functions are suboptimal due
to the marshalling being done externally.  Is there interest
in having this common type retrofitted for marshalling?
To my knowledge the C++ Middleware Writer offers the
most efficient marshalling approach available today.  I
would like to ask that some accommodations be made
wrt to the standard so as to allow people to marshall
instances of this common type more efficiently than
is possible with the current standard.

If complex were given a stream constructor, rather
than needing complexReceive we would have
something like this:

template <typename B>
explicit complex(B* buf)
{
  buf->Give(real_);
  buf->Give(imag_);

}

where real_ and imag_ are the names of the private
data members of the complex type.

The C++ Middleware Writer could implement the
constructor based on reading the complex header,
but it would be necessary to add a prototype to
the header --

template <typename B>
explicit complex(B*);

Alternatively it could be --

template <typename B>
explicit complex(B&);

It seems that you're using pass-by-pointer everywhere. This is good
for structs > 8 bytes (or so I've heard for the common platforms), but
for really small things like ints and doubles, it's much more
expensive to pass-by-pointer than pass-by-value. Could you add some
receive-by-value functions and send-by-value functions, co-existing
with the pass-by-pointer functions to the buf interface? Ex:

//
template <typename B, typename T>
inline void complexReceive(B* buf, std::complex<T>& cmplx)
{
cmplx.real(buf->get<T>());
cmplx.imag(buf->get<T>());
}
//

Assuming POD T types, and decent inline expansion and other compiler
optimizations, this should compile down to basically what you want. Do
you disagree? Have you actually measured otherwise?

Are you unable to modify the buf interface to have a get<T> call?

Are you concerned about std::complex<T> when T is not POD?
std::complex's interface is expressly designed for small objects and
pass-by-value. If someone is using it with some other type, then I
would say they're doing it wrong, and std::complex is the correct
choice in their situation. They'll have more speed problems than just
their serialization.
 
R

Richard Herring

In message
Are you concerned about std::complex<T> when T is not POD?

That shouldn't be a consideration. The behaviour of std::complex<T> is
unspecified for values of T other than float, double and long double
[26.2/2]
std::complex's interface is expressly designed for small objects and
pass-by-value. If someone is using it with some other type, then I
would say they're doing it wrong, and std::complex is the correct
choice in their situation. They'll have more speed problems than just
their serialization.

They'll have more than speed problems.
 
B

Brian

It seems that you're using pass-by-pointer everywhere. This is good
for structs > 8 bytes (or so I've heard for the common platforms), but
for really small things like ints and doubles, it's much more
expensive to pass-by-pointer than pass-by-value. Could you add some
receive-by-value functions and send-by-value functions, co-existing
with the pass-by-pointer functions to the buf interface? Ex:

Possibly.


//
template <typename B, typename T>
inline void complexReceive(B* buf, std::complex<T>& cmplx)
{
  cmplx.real(buf->get<T>());
  cmplx.imag(buf->get<T>());}

//

Assuming POD T types, and decent inline expansion and other compiler
optimizations, this should compile down to basically what you want.

I'm not sure if that would work out as well.
Assuming a Receive by value function like this:

template <typename T>
Receive(T const val)
{
Receive(&val, sizeof(T));
}

and then

template <typename B, typename T>
void
complexSend(B* buf, std::complex<T> const& cmplx)
{
buf->Receive(cmplx.real());
buf->Receive(cmplx.imag());
}


I don't know if that's as good as an internal
implementation:

template <typename B, typename T>
void
complex<T>::Send(B* buf)
{
buf->Receive(&real_, sizeof(T);
buf->Receive(&imag_, sizeof(T));
}


It seems to me that the former would require copies
of the floats/doubles/long doubles/ that the latter
would not.

It might not be advantageous to have an internal
implementation for sending since I think it is safe,
with the way I'm doing things byte order-wise, to
send the data with something like:

complex<float> c;
buf->Receive(&c, sizeof(c));

But when receiving a std::complex object I have to,
at least in some circumstances, handle each field
separately. If both machines involved have the
same endianess, you can copy a group of complex
instances, but if the two machines aren't the same
you have to use something like the complexReceive
function that you and I have written.

Have you actually measured otherwise?
No.


Are you unable to modify the buf interface to have a get<T> call?

That's a decent idea in general, but am not sure that it
offers a solution that's as good as doing the marshalling
internally.


Brian Wood
http://webEbenezer.net
(651) 251-9384
 
J

Joshua Maurice

I'm not sure if that would work out as well.
Assuming a Receive by value function like this:

template <typename T>
Receive(T const val)
{
Receive(&val, sizeof(T));

}

Stop right here. What are you doing? You take the address of a const
variable (val) and try to modify it with Receive. Undefined behavior.
You are also trying to modify a parameter (val) which will cease to
exist and not affect the caller's argument in any way.

I was just thinking about my earlier suggestion. I think some of the
general sentiment is true, but I'd much rather actually code and test
a lot of it. I should check an actual compiler at some point in the
near future.

Also, I was thinking that you might be wrong in the first post when
you called the following less than optimal:

template <typename B, typename T>
void complexReceive(B* buf, std::complex<T>& cmplx)
{
T real, imaginary;
buf->Give(real);
cmplx.real(real);
buf->Give(imaginary);
cmplx.imag(imaginary);
}

The compiler could expand inline to make the following:

template <typename B, typename T>
void complexReceive(B* buf, std::complex<T>& cmplx)
{
T real;
buf->Give(real);
cmplx.real_ = real;

T imaginary;
buf->Give(imaginary);
cmplx.imag_ = imaginary;
}

The compiler the might be able to do flow analysis and detect that the
stack objects "real" and "imaginary" are useless intermediaries, and
reduce it to:

template <typename B, typename T>
void complexReceive(B* buf, std::complex<T>& cmplx)
{
buf->Give(cmplx.real_);
buf->Give(cmplx.imag_);
}

So, I again ask you sir: have you actually measured any of this? (Note
that most of my arguments are in the context of very small POD
objects, like T will be for std::complex, and I have been assuming
decent levels of optimization.)

In your case, the compiler has access to buf->Give as it's a template
method (as presumably your compiler does not support "template
extern"). It may or may not be able to do that flow analysis to
determine that the objects are useless intermediaries. Perhaps buf-
Give is above the compiler's threshold for expanding inline and also
bigger than its threshold for doing this flow analysis. Then you will
have something like an extra load-from-memory and store-to-memory
assembly instruction for each member of std::complex. That is why I
suggested a parallel interface which returns by value. This would not
so easily defeat the compiler's flow analysis, and better
optimizations could be done. Ex:

template <typename B, typename T>
void complexReceive(B* buf, std::complex<T>& cmplx)
{
cmplx.real(buf->get<T>());
cmplx.imag(buf->get<T>());
}

Simply making buf::get a wrapper over buf::Give, like you did in your
followup post, would not improve performance. It just moves the
location of the questionable flow analysis somewhere else. I was
suggesting something fundamentally different. Instead of having a
framework which passes by pointer everywhere, throughout the entire
buffer interface and implementations provide a parallel interface
which returns by value instead of returns by pointer-parameter. This
would remove the extra copy. I recognize it's probably not an easy
change, but I would expect that when used properly, it would speed up
the code and make it somewhat shorter and definitely cleaner. However,
I would again measure.
 
B

Brian

Stop right here. What are you doing? You take the address of a const
variable (val) and try to modify it with Receive. Undefined behavior.
You are also trying to modify a parameter (val) which will cease to
exist and not affect the caller's argument in any way.

Receive doesn't modify anything. Sorry if it was confusing,
but I switched from focusing on complexReceive to complexSend.

I was just thinking about my earlier suggestion. I think some of the
general sentiment is true, but I'd much rather actually code and test
a lot of it. I should check an actual compiler at some point in the
near future.

Also, I was thinking that you might be wrong in the first post when
you called the following less than optimal:

template <typename B, typename T>
void complexReceive(B* buf, std::complex<T>& cmplx)
{
  T real, imaginary;
  buf->Give(real);
  cmplx.real(real);
  buf->Give(imaginary);
  cmplx.imag(imaginary);

}

The compiler could expand inline to make the following:

template <typename B, typename T>
void complexReceive(B* buf, std::complex<T>& cmplx)
{
  T real;
  buf->Give(real);
  cmplx.real_ = real;

  T imaginary;
  buf->Give(imaginary);
  cmplx.imag_ = imaginary;

}

The compiler the might be able to do flow analysis and detect that the
stack objects "real" and "imaginary" are useless intermediaries, and
reduce it to:

template <typename B, typename T>
void complexReceive(B* buf, std::complex<T>& cmplx)
{
  buf->Give(cmplx.real_);
  buf->Give(cmplx.imag_);

}

So, I again ask you sir: have you actually measured any of this? (Note
that most of my arguments are in the context of very small POD
objects, like T will be for std::complex, and I have been assuming
decent levels of optimization.)

I'm fine with the above and think your comment here is
about right. I'm not sure if some or most compilers
would be able to make these optimizations, but am of the
opinion that handling the marshalling internally would
work equally well with "smarter" compilers as it would
with simpler compilers/less optimization.


Brian Wood
http://webEbenezer.net
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,734
Messages
2,569,441
Members
44,832
Latest member
GlennSmall

Latest Threads

Top