【问题标题】:C++11 move semantics vs. pointers - a performance measurementC++11 移动语义与指针 - 性能测量
【发布时间】: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&amp;&amp; rhs) : m_data(rhs.m_data) {}... 这不能调用移动语义。试试Packet(Packet&amp;&amp; rhs) noexcept
  • 您的代码不正确。 && 运算符不应具有 const 参数,如@DyP 所述。他们应该复制指针并在另一个实例中将它们归零。首先编写正确的代码并确保调用了 && 运算符。然后衡量性能。
  • 哦,别忘了std::move参数:Packet(Packet&amp;&amp; rhs) noexcept : m_data( std::move(rhs.m_data) ) {}

标签: performance c++11 move-semantics


【解决方案1】:

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

这里有一个修复:

Packet(Packet&& rhs) : m_data(std::move(rhs.m_data)) {}

就时间而言,在我的 PC 上指针是最快的。

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

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

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

调试模式下的结果:

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

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

【讨论】:

  • 别忘了noexcept
  • 你能用emplace_back代替push_back吗?
  • emplace_backnoexcept 没有改变任何东西(性能)
  • 只是想写一个评论,想知道指针的速度从何而来;)干得好。
  • 太棒了!这些结果符合我对指针性能的期望。也许我稍后还会尝试新的测量。到目前为止,谢谢!
猜你喜欢
  • 2014-08-16
  • 1970-01-01
  • 1970-01-01
  • 2012-12-10
  • 2012-12-26
  • 1970-01-01
  • 1970-01-01
  • 2019-01-02
  • 2019-06-30
相关资源
最近更新 更多