what's the best way/pattern to avoid copying data

Hi,

I have a class buffer, which holds a std::string as member, and a socket_receive function:
1
2
3
4
5
6
7
8
9
10
11
struct buffer {
	string data;

	buffer() {}
	buffer(buffer& b) : data(b.data) {}
};
buffer socket_receive() {
	buffer tmp;
	tmp.data = "1234";
	return tmp;
}

so when I write
 
buffer b = socket_receive();

there is a copy constructor, and the tmp variable is constructed and destructed, is there anyway to improve the performance?

I tried the new c++11 move(), but seems even worse, I must be doing wrong with move(), but I don't know how.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <iostream>
#include <string>
#include <ctime>
using namespace std;

struct buffer {
	string data;

	buffer() {}
	buffer(buffer& b) : data(b.data) {}
	
	// std::move semantics
	buffer(buffer&& b) {
		swap(data, b.data);
	}
	buffer& operator=(buffer&& b) {
		swap(data, b.data);
		return *this;
	}
};

buffer socket_receive() {
	buffer b;
	b.data = "1234";
	return b;
}

int main() {
	auto t = clock();
	for(int i=0; i<50000; i++) {
		buffer b = socket_receive(); // this costs 133 ms
		buffer b = move(socket_receive()); //and 217ms with move()
	}
	cout << "time passed: " << (clock() - t) << "ms" << endl;
}


Any suggestion?

Thanks.
buffer b = socket_receive(); does not compile, because you didn't observe const correctness.
buffer(constbuffer& b) : data(b.data) {} or better, don't do anything as the compiler already provides that constructor.

> there is a copy constructor, and the tmp variable is constructed and destructed
RVO



By the way, `clock()' does not measure ms.
> what's the best way/pattern to avoid copying data?

In this case, letting the compiler do what is best for you.

1
2
3
4
5
struct buffer 
{
    string data; 
    // let the compiler decide about construction, copy, move etc.
} ;


1
2
3
4
5
6
buffer socket_receive() // rely on copy-elision
{
	buffer b;
	b.data = "1234";
	return b;
}


1
2
3
4
int main()
{
    buffer b = socket_receive(); 
}


http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/
Lines 31 and 32 do exactly the same thing.

You aren't compiling with optimzations enabled.

If you are using VC++ (where no implicit move constructor/assignment operators are implemented,) the small string optimization will be in effect for your small string and performance will not be improved by using move semantics for them.

You can find an explanation here: http://john-ahlgren.blogspot.com/2012/03/small-string-optimization-and-move.html

That said, I would expect RVO here would make either method equally fast. This seems to be born out by the following test (with optimizations enabled):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include <iostream>
#include <string>
#include <ctime>
#include <cstdlib>


using namespace std;

struct bufferA {
    std::string data ;

    bufferA() {}
    bufferA(const bufferA& buffer) : data(buffer.data) {}
    bufferA(bufferA&& buffer) : data(std::move(buffer.data)) {}

    bufferA(const char* d) : data(d) {}

    bufferA& operator=(bufferA&& b)
    {
        data = std::move(b.data) ;
        return *this ;
    }

};

struct bufferB {
    std::string data ;

    bufferB() {}
    bufferB(const char* d) : data(d) {}
};

const char* strings[] =
{
    "1234aasldkfja;oij;laskdjfa;lsdjf;alskdjfasdfa;slkdfja;lskdjf;alskdjf",
    "abcd;alskjdf;alisdjf;alskdjf;alskdjf;alskdjf;alskdjflkjleijalvknadsf",
    "efgh;lkjeaoic;aliej;lkajv;lijea;lksjd;laijea;lkejav;lkansdl;kreaa;lk",
    "ijkl;lkaj389jalkd089ua3/mnzvo;8erja/knCVosj8ia[8ejralkdcvjnao8j3ea3d"
};

unsigned stringsElements = sizeof(strings) / sizeof(strings[0]) ;

bufferA receiveA()
{
    return bufferA(strings[std::rand()%stringsElements]) ;
}

bufferB recieveB()
{
    return bufferB(strings[std::rand()%stringsElements]) ;
}

const unsigned testIterations = 2000000 ;



int main() {
    std::srand(static_cast<unsigned>(std::time(0))) ;
	auto beg = clock();
	for(unsigned i=0; i<testIterations; ++i) {
		bufferA buffer = receiveA();
	}
    auto end = clock() ;
	cout << "time passed: " << (end-beg) << "ms" << endl;

    beg = clock() ;
    for ( unsigned i=0; i< testIterations; ++i )
    {
        bufferB buffer = recieveB() ;
    }
    end = clock() ;
    std::cout << "time passed: " << (end-beg) << "ms" << endl ;
}


Output:
time passed: 390ms
time passed: 390ms
Last edited on
The RVO looks interesting, seems the compiler already did everything.
Thanks for the explanation, and the tip of 'clock' and 'small string'.
Topic archived. No new replies allowed.