【发布时间】:2014-01-01 19:28:33
【问题描述】:
对于我的用例,我必须非常快速地从列表中插入和删除数据包。
在我看来有两种常见的方法来解决这个问题:
- 插入/删除指向这些数据包的指针
- 使用移动语义插入/删除副本
无论如何,我认为使用指针的解决方案应该是最有效的,即使你自己的垃圾回收存在缺点。
为了更好的比较,我实现了 3 个测试用例:
- 使用副本
- 使用移动语义
- 使用指针
为了测量每个测试用例经过的时间,我还实现了一个计时器类:
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;
};
这是我的主要课程:
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;
}
}
目前我遇到的问题是指针版本以及移动语义甚至复制版本的测量结果几乎相同。
我是不是哪里做错了?
你好!
【问题讨论】:
-
如果您将每个测试运行超过 25 次(想想几十万次)并选择最快的时间(或前 10%,例如),您将获得更可靠和准确的结果。这将减少运行之间的抖动,减少 OS 调度程序在测试过程中换出进程的影响,并启动 CPU 缓存。
-
对@Cameron 话的一个小补充:每个
Measurement_X方法调用至少需要几秒钟(最好是几十秒钟)。 -
Packet(const Packet&& rhs) : m_data(rhs.m_data) {}... 这不能调用移动语义。试试Packet(Packet&& rhs) noexcept。 -
您的代码不正确。 && 运算符不应具有 const 参数,如@DyP 所述。他们应该复制指针并在另一个实例中将它们归零。首先编写正确的代码并确保调用了 && 运算符。然后衡量性能。
-
哦,别忘了
std::move参数:Packet(Packet&& rhs) noexcept : m_data( std::move(rhs.m_data) ) {}。
标签: performance c++11 move-semantics