C++ 11移动语义与指针 - 性能测量

Ste*_*ock 9 performance move-semantics c++11

对于我的用例,我必须非常快速地从列表中插入和删除数据包.

在我看来,有两种常见的解决方法:

  • 插入/删除指向这些数据包的指针
  • 使用移动语义插入/删除副本

无论如何,我认为使用指针的解决方案应该是最有效的,即使你自己的垃圾收集的缺点.

为了更好的比较,我实现了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;

};
Run Code Online (Sandbox Code Playgroud)

这是我的主要课程:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

目前我遇到的问题是,我收到的指针版本以及移动语义甚至复制版本的测量结果几乎相同.

我在任何地方都犯了错误吗?

映入眼帘!

egu*_*gur 4

移动构造函数实际上复制了另一个向量。它调用向量的复制构造函数。

这是一个修复:

Packet(Packet&& rhs) : m_data(std::move(rhs.m_data)) {}
Run Code Online (Sandbox Code Playgroud)

就计时而言,我的电脑上的指针是最快的。

  • 指针:平均5500
  • 修复前移动:平均8100
  • 修复后移动:平均5500
  • 份数:平均8700

结果非常一致(以微秒为单位)。

编辑: 修复后,移动运算符在运行足够时间时显示出与指针时间相似的性能。增加测试时间显示出相似的比率。

调试模式下的结果:

  • 指针:平均16500
  • 修复后移动:平均19000
  • 份数:平均30000

在调试版本中,指针速度更快,可能是由于实现(调用了多少内联函数)。

  • `emplace_back` 和 `noexcept` 没有改变任何东西(性能) (2认同)