【问题标题】:Is the cost of atomic and std::mutex killing my performance for multi-threaded RLNC Encodingatomic 和 std::mutex 的成本是否会扼杀我的多线程 RLNC 编码性能
【发布时间】:2018-10-19 11:39:09
【问题描述】:

我正在尝试通过随机线性网络编码 (RLNC) 使用多线程进行编码以提高性能。 但是,我遇到了性能问题,我的多线程解决方案速度较慢,比当前的非线程版本慢得多。我有一个暂停,它是 m_completed 上的 atomic 访问和用于将元素插入到 m_results 的 std::mutex 正在扼杀我的表现。但是,我不知道如何确认这一点。

所以更多信息函数completed() 在主线程while(!encoder.completed()){}while-loop 中被调用,这会导致大量的原子访问,但我找不到合适的方法来做它没有原子或互斥锁。您可以在下面找到代码。

所以,如果有人可以看到错误或指导我找到更好的方法,我会非常满意。我花了 1.5 周的时间找出问题所在,我唯一的想法是 atomicstd::mutex locks

#include <cstdint>
#include <vector>
#include <mutex>

#include <memory>
#include <atomic>
...
namespace master_thesis
{
namespace encoder
{
class smart_encoder
{
...   
    void start()
    {
    ... 
    // Incase there are more an uneven amount 
    // of symbols we adjust this abov

            else
            {

                m_pool.enqueue([this, encoder](){
                        std::vector<std::vector<uint8_t>> total_payload(this->m_coefficients,
                                                                        std::vector<uint8_t>(encoder->payload_size()));

                        std::vector<uint8_t> payload(encoder->payload_size());

                        for (uint32_t j = 0; j < this->m_coefficients; ++j)
                        {
                            encoder->write_payload(payload.data());
                            total_payload[j] = payload; //.insert(total_payload.begin() + j, payload);
                        }





                        this->m_mutex.lock();

                        this->m_result.insert(std::end(this->m_result),
                                              std::begin(total_payload),
                                              std::end(total_payload));
                        ++(this->m_completed);

                        this->m_mutex.unlock();
                    });
            }

        }
    }

    bool completed()
    {
        return m_completed.load() >= (m_threads - 1);
    }

    std::vector<std::vector<uint8_t>> result()
    {
        return m_result;
    }

private:
    uint32_t m_symbols;
    uint32_t m_symbol_size;
    std::atomic<uint32_t> m_completed;
    unsigned int m_threads;
    uint32_t m_coefficients;

    std::mutex m_mutex;

    std::vector<uint8_t> m_data;
    std::vector<std::vector<uint8_t>> m_result;

    ThreadPool m_pool;

    std::vector<std::shared_ptr<rlnc_encoder>> m_encoders;
};
}
}

【问题讨论】:

  • 在没有争用的情况下两者都很快。
  • 而不是像那样循环 - 使用条件变量。
  • @stark 但问题是两者都可能存在争用,至少我能做到
  • 要避免复制,请尝试m_result.push_back(std::move(total_payload));。这仍然可能会重新分配m_result,所以如果您预先知道结果的数量,然后使用resize/reserve,这样它就不会在持有互斥锁时重新分配。
  • 移动而不是复制,同时将insert 保持在互斥区域内,请参阅here

标签: c++ multithreading performance


【解决方案1】:

瓶颈可能不是对Completed() 的调用。

在 x86 上,从字对齐的 uint32_t 读取自动是原子操作,std::atomic 与否。 std::atomic 在 x86 上对 uint32_t 所做的唯一事情是确保它是字对齐的,并且编译器不会重新排序或优化它。

紧密循环负载不是总线争用的原因。第一次读取时会有缓存未命中,但后续加载将是缓存命中,直到缓存因另一个线程对该地址的写入而失效。有一个警告——缓存行的意外共享(“错误共享”)。关于如何通过切换到在原子两侧填充 60 个字节的未使用字节的数组来消除这种可能性的一个想法(仅使用中间那个)。 std::atomic&lt;uint32_t&gt; m_buffered[31]; std::atomic&lt;uint_32t&gt;&amp; m_completed = m_buffered[15];

请记住,紧密循环会占用您的一个内核,除了查看其缓存之外什么都不做。那是浪费钱... ;) 这很可能是您的问题的原因。你应该把你的代码改成这样:

int m_completed = 0;  // no longer atomic
std::condition_variable cv;

// in main...(pseudocode)
lock (unique) m_mutex  // the m_mutex from the class
    while !Completed()
        cv.wait(m_mutex)

// in thread (pseudocode)
bool toSignal = false;
lock guard m_mutex
    this->m_result.insert(std::end(this->m_result),
                          std::begin(total_payload),
                          std::end(total_payload));
    ++m_completed;
    toSignal = Completed();
if toSignal
    cv.signalOne()

也可能是您的性能损失与互斥锁临界区有关。这个关键部分有可能比缓存未命中长很多数量级。我建议比较线程池中 1 个线程、2 个线程和 4 个线程的时间。如果 2 个线程不比 1 个线程快,那么您的代码实际上是按顺序运行的。

如何测量?当您不知道要优化什么时,分析工具很有用。我对它们没有太多经验,但我知道(至少是一些较老的)在多线程方面可能会有点粗略。你也可以使用一个好的老式计时器。 C++11 有一个 high_resolution_clock,如果你有不错的硬件,它的分辨率可能是 1 微秒。

最后,我看到了很多算法/标量优化的机会。预先分配向量,而不是每次都这样做。使用指针或 std::move 来避免不必要的深度复制。预先分配 m_result 并让线程写入特定的索引偏移量。

【讨论】:

    猜你喜欢
    • 2020-09-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多