for my use case I have to insert and remove data packets from a list very fast.
In my opinion there are two common ways to solve this:
Anyhow I thought the solution with pointers should be the most efficient even with the disadvantage of your own garbage collection.
I implemented 3 test cases for a better comparison:
To measure the time elapsed for each test case I have also implemented a timer class:
class HighPerformanceTimer {
public:
enum TimerResolution {
SECONDS = 1,
MILLISECONDS = SECONDS * 1000,
MICROSECONDS = MILLISECONDS * 1000,
NANOSECONDS = MICROSECONDS * 1000
};
explicit HighPerformanceTimer(const TimerResolution resolution = NANOSECONDS)
: m_resolution(resolution)
, m_frequency(0.0)
, m_startTime({0})
, m_stopTime({0}) {}
~HighPerformanceTimer(void) {}
bool Init(void) {
LARGE_INTEGER frequency;
if(0 == ::QueryPerformanceFrequency(&frequency)) {
return false;
}
/* Check for zero divisor. */
if(0 == frequency.QuadPart) {
return false;
}
/* Change frequency to double for internal timer resolution. */
switch(m_resolution) {
case NANOSECONDS:
m_frequency = frequency.QuadPart / (NANOSECONDS * 1.0);
break;
case MICROSECONDS:
m_frequency = frequency.QuadPart / (MICROSECONDS * 1.0);
break;
case MILLISECONDS:
m_frequency = frequency.QuadPart / (MILLISECONDS * 1.0);
break;
default:
/**
* SECONDS
* m_frequency has a resolution in seconds by default
*/
m_frequency = frequency.QuadPart * 1.0;
break;
}
return true;
}
void Start(void) {
::QueryPerformanceCounter(&m_startTime);
}
void Stop(double& intervall) {
::QueryPerformanceCounter(&m_stopTime);
intervall = ((m_stopTime.QuadPart - m_startTime.QuadPart) / m_frequency);
}
private:
const TimerResolution m_resolution;
double m_frequency;
LARGE_INTEGER m_startTime;
LARGE_INTEGER m_stopTime;
CRITICAL_SECTION m_timerLock;
};
And here is my main class:
class Packet {
public:
Packet(const uint8* data, uint16 length) {
if(0 != length) {
if(nullptr == data) {
m_data = std::vector<uint8>(length, 0x00);
} else {
m_data.assign(data, data + length);
}
}
}
Packet(const Packet& rhs) : m_data(rhs.m_data) {}
Packet& operator=(const Packet& rhs) {
m_data = rhs.m_data;
return *this;
}
Packet(const Packet&& rhs) : m_data(rhs.m_data) {}
Packet& operator=(const Packet&& rhs) {
m_data = rhs.m_data;
return *this;
}
std::vector<uint8> m_data;
};
void Measurement_1(void);
void Measurement_2(void);
void Measurement_3(void);
constexpr uint16 payloadLength = 15000;
uint8 payload[payloadLength];
/* Initialize high performance timer. */
HighPerformanceTimer hpt(HighPerformanceTimer::MICROSECONDS);
int main(void) {
hpt.Init();
/* Fill packet data. */
for(unsigned int j = 0; j < payloadLength; ++j) {
payload[j] = 0xFF;
}
Measurement_1();
Measurement_2();
Measurement_3();
return EXIT_SUCCESS;
}
void Measurement_1(void) {
/* Measurement with copies. */
double result[25];
for(unsigned int k = 0; k < 25; ++k) {
/* Start measurement. */
double timeElapsed = 0.0;
std::list<Packet> mylist;
hpt.Start();
/* Begin insertion. */
for(unsigned int i = 0; i < 1000; ++i) {
Packet f(payload, payloadLength);
mylist.push_back(f);
}
/* End insertion. */
/* Begin removal. */
for(unsigned int i = 0; i < 1000; ++i) {
Packet f = mylist.front();
mylist.pop_front();
}
/* End removal. */
hpt.Stop(timeElapsed);
result[k] = timeElapsed;
/* Stop measurement. */
}
for(unsigned int i = 0; i < 25; ++i) {
std::cout << "with copies: " << std::setprecision(3) << std::fixed << result[i] << std::endl;
}
}
void Measurement_2(void) {
/* Measurement with move semantics. */
double result[25];
for(unsigned int k = 0; k < 25; ++k) {
/* Start measurement. */
double timeElapsed = 0.0;
std::list<Packet> mylist;
hpt.Start();
/* Begin insertion. */
for(unsigned int i = 0; i < 1000; ++i) {
Packet f(payload, payloadLength);
mylist.push_back(std::move(f));
}
/* End insertion. */
/* Begin removal. */
for(unsigned int i = 0; i < 1000; ++i) {
Packet f = std::move(mylist.front());
mylist.pop_front();
}
/* End removal. */
hpt.Stop(timeElapsed);
result[k] = timeElapsed;
/* Stop measurement. */
}
for(unsigned int i = 0; i < 25; ++i) {
std::cout << "with moves: " << std::setprecision(3) << std::fixed << result[i] << std::endl;
}
}
void Measurement_3(void) {
/* Measurement with pointers. */
double result[25];
for(unsigned int k = 0; k < 25; ++k) {
/* Start measurement. */
double timeElapsed = 0.0;
std::list<Packet*> mylist;
hpt.Start();
/* Begin insertion. */
for(unsigned int i = 0; i < 1000; ++i) {
mylist.push_back(new Packet(payload, payloadLength));
}
/* End insertion. */
/* Begin removal. */
Packet* f = nullptr;
for(unsigned int i = 0; i < 1000; ++i) {
f = mylist.front();
if(nullptr != f) {
mylist.pop_front();
delete f;
}
}
/* End removal. */
hpt.Stop(timeElapsed);
result[k] = timeElapsed;
/* Stop measurement. */
}
for(unsigned int i = 0; i < 25; ++i) {
std::cout << "with pointers: " << std::setprecision(3) << std::fixed << result[i] << std::endl;
}
}
At the moment I have the problem that I receive nearly the same measurement result for the pointer version as well as for the move semantics and even the copy version.
Did I do a mistake anywhere?
Greets!
The move constructor actually copies the other vector. It calls vector's copy constructor.
Here's a fix:
Packet(Packet&& rhs) : m_data(std::move(rhs.m_data)) {}
As far as timings, on my PC pointers was fastest.
Results were quite consistent (in microseconds).
Edit: After the fix, the move operator when ran enough time showed similar performance to the pointers time. Increasing the test time showed similar ratios.
Results in debug mode:
In debug builds, pointers is faster, probably due to implementation (how many inline functions are called).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With