Skip to main content
gamedev.net
🔒 Locked

Assignment operator performance

Started by Kest 12 replies 1.5k views
Original Post
Kest
Kest
With this type of C/C++ data..
struct Type
{
  int a,b,c,d,e,f,g;
  int data[300];
};

Type data_a[500], data_b[500];
Would there be any difference in performance with the following?

for(int e=0; e<500; e++)
  data_a[e] = data_b[e];

..or..

memcpy( data_a, data_b, sizeof(Type) * 500 );
How is data copied with default assignment operators? Does it optimize the same way memcpy does? Or perhaps better?
ToohrVyk
ToohrVyk
What you are doing here is copy every element of a sequence to another sequence. In C++, this is done with std::copy. On all serious compilers, std::copy will detect that the copied type is a POD, and will use a high-speed area memory copy that will be at least as fast as either memcpy or manual assignment with a loop.

On the other hand, memcpy will break your code if the structure stops being a POD, and provides no advantage over std::copy.


Adam_42
Adam_42
In that specific case I wouldn't expect the memcpy to be significantly quicker. Each struct is about 1228 bytes, and the default assignment operator will typically be a call to memcpy in release builds. The difference between one memcpy call and 500 isn't very much. However the only way to be sure is to measure it.

Assuming you really need to do the copy, one optimization option is to try and do that copy at the same time as whatever other processing you do on that data. That way you shouldn't miss the cache so much, and cache misses are very expensive.

If you don't need to copy the whole of that 1200 byte array in each struct then that will obviously help too - just write a custom assignment operator that copies only what's needed.
ddlox
ddlox
IMHO:

Operators are just like functions, so all function overheads apply to them as well (i.e. argument passing, stack push and pop, type check, etc...) so in your for loop you will be calling this operator 500 times with all these overheads at each iteration.

memcpy, on the other hand, is ASM-optimised (at least on Win platforms) and would only cost 1 function overhead for all copied data.

Use the assignment operator on built-in types (int, float, etc..) or on classes or structs in which you want to control what gets copied.

Use memcpy for times and cases such as the one you are asking about. Memcpy doesn't care what gets copied, it only cares about a valid source pointer, destination and about the number of bytes to copy.

I hope this helps.
Spoonbender
Spoonbender
Quote:
Original post by ddlox
Operators are just like functions, so all function overheads apply to them as well (i.e. argument passing, stack push and pop, type check, etc...) so in your for loop you will be calling this operator 500 times with all these overheads at each iteration.

memcpy, on the other hand, is ASM-optimised (at least on Win platforms) and would only cost 1 function overhead for all copied data.

True, assuming a non-optimizing compiler, but modern compilers are quite capable of inlining simle functions.
Moreover, as said above, memcpy is a bad idea in C++. It's no faster than a sensible implementation of std::copy, and it breaks for non-POD types.
Kest
Kest
I appreciate all of the useful replies :)
implicit
implicit
Quote:
Original post by ToohrVyk
On all serious compilers, std::copy will detect that the copied type is a POD, and will use a high-speed area memory copy that will be at least as fast as either memcpy or manual assignment with a loop.
GCC 3.4.5 (the version which comes with MinGW) doesn't, would you consider that a serious compiler?

To be specific it does use memcpy but its called per-instance rather then being joined into a single block copy. The same thing happens with a manual loop but unlike a straight loop std::copy also includes code to handle the remainder (effectively doubling the code size) when loop unrolling is enabled, even though the loop count is constant.
And to be fair it ends up having little effect on the performance of this particular case, aside from increasing the code cache pressure a bit, as the data array inside the structure is fairly large. But the difference could be much larger for a small structure where std::copy ends up copying member-by-member while memcpy is optimized with vector instructions and the like.

For the record here's the code I tested, with the usual set of optimization enabled of course.
enum { N = 500 };struct T { int a, b, c, d, e, f, g; int data[300];};void copy_memcpy(T *dst, const T *src) { std::memcpy(dst, src, sizeof(T) * N);}void copy_loop(T *dst, const T *src) { for(size_t i = 0; i < N; ++i) {  dst = src; }}void copy_stl(T *dst, const T *src) { std::copy(src, src + N, dst);}
Not to say that you should avoid std::copy or anything, but I've been burned way to many times to have much faith in what a compiler "should" be generating anymore. In other words if you care about performance then it's a good idea to check out the disassembly once in a while to verify your assumptions about the code.

By the way why not write a memcpy wrapper which verifies that the type truly is POD before copying. You know for critical loops and the like when you can't afford to rely on the compiled doing the right thing.
template <typename T>inline T *pod_copy(T *dst, T *src, size_t len) { union { T instance; } verify_pod; return (T *) std::memcpy(dst, src, len * sizeof(T));}
There's likely better ways of implementing it but you get the general idea..
Sneftel
Sneftel
Quote:
Original post by implicit
Quote:
Original post by ToohrVyk
On all serious compilers, std::copy will detect that the copied type is a POD, and will use a high-speed area memory copy that will be at least as fast as either memcpy or manual assignment with a loop.
GCC 3.4.5 (the version which comes with MinGW) doesn't, would you consider that a serious compiler?

Serious but outdated. 3.4 was released in 2004. Up-to-date versions of GCC most certainly have this optimization.
implicit
implicit
Quote:
Original post by Sneftel
Serious but outdated. 3.4 was released in 2004. Up-to-date versions of GCC most certainly have this optimization.
Oddly enough that's the version which comes with MinGW and Cygwin, as well as what happened to be installed on the one Unix machine I happened to have shell access to.
I'd really like to see how GCC 4 handles things but I'm honestly having trouble finding a distribution of it to run. At any rate it seems likely that I'm not the only one stuck with it.
SiCrane
SiCrane
It's a chicken/egg problem with cygwin at least. Since cygwin doesn't come packaged with a recent version of gcc, the gcc people don't get enough bug reports and what have you to make recent versions of gcc stable on cygwin. The cygwin people don't package recent versions of gcc since they aren't as stable; the only thing that the gcc people guarantee is that it'll compile on cygwin. However, I personally haven't run into any major difficulties running gcc 4 under cygwin.
the_edd
the_edd
Quote:
Original post by implicit
Quote:
Original post by Sneftel
Serious but outdated. 3.4 was released in 2004. Up-to-date versions of GCC most certainly have this optimization.
Oddly enough that's the version which comes with MinGW and Cygwin, as well as what happened to be installed on the one Unix machine I happened to have shell access to.
I'd really like to see how GCC 4 handles things but I'm honestly having trouble finding a distribution of it to run. At any rate it seems likely that I'm not the only one stuck with it.


MinGW GCC 4
TDM's experimental MinGW builds
CmpDev
CmpDev
Quote:
Original post by SiCrane
It's a chicken/egg problem with cygwin at least. Since cygwin doesn't come packaged with a recent version of gcc, the gcc people don't get enough bug reports and what have you to make recent versions of gcc stable on cygwin. The cygwin people don't package recent versions of gcc since they aren't as stable; the only thing that the gcc people guarantee is that it'll compile on cygwin. However, I personally haven't run into any major difficulties running gcc 4 under cygwin.


Which version of gcc you are running on cygwin 4.?.? . I have just compiled and started to test 4.3.2 yet it gives internal compiler errors :(
SiCrane
SiCrane
I've used at least 4.2.3 and 4.3.0.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.