CPU使用率下降的原因 - 并在运行多个线程时提高

时间:2014-06-11 19:50:45

标签: multithreading scheduler thread-priority

我在运行多线程模拟时观察到奇怪的行为。在开始时,所有计算线程都被最大化。然后在一些帧之后,一些线程将CPU上的工作负载减少到四分之一,而一个线程保持最大化。在进一步的步骤之后,所有线程再次最大化。我在Windows和Linux中都观察到相同的效果。什么可以导致继续运行相同代码的线程更长时间地获得较低的CPU使用率(四分之一),然后返回其最大值?

模拟在2d域(矩阵)上完成,分为N个条带,其中N等于线程数。在每个帧之后,线程被同步并且交换缓冲区。观察到帧率降低了与最大CPU使用量相同的顺序。线程处理以下代码:

void Sim2d::Test_ModelState::process(uintmax_t framecounter
,const Bufferinfo& buffer_src,Bufferinfo& buffer_dest
,unsigned int offset)
{       
auto ptr_dest=buffer_dest.bufferGet<Test_PixelData>();
auto ptr_src=buffer_src.bufferGet<Test_PixelData>();

auto W=buffer_dest.widthGet<Test_PixelData>();
auto h=buffer_dest.heightGet();
auto H=buffer_src.heightGet();

auto F=m_params.feed_rate;
auto ka=m_params.decay_rate;
auto d=m_params.diff_ratio;
auto k_start=offset==0?1:0;


for(uint32_t k=k_start; k<h && k+offset<H-1; ++k)
    {
    for(uint32_t l=1;l<W-1;++l)
        {
        auto dyy_u=ptr_src[k+offset+1][l].u - 2*ptr_src[k+offset][l].u
            + ptr_src[k+offset-1][l].u;
        auto dxx_u=ptr_src[k+offset][l+1].u - 2*ptr_src[k+offset][l].u
            + ptr_src[k+offset][l-1].u;

        auto dyy_v=ptr_src[k+offset+1][l].v - 2*ptr_src[k+offset][l].v
            + ptr_src[k+offset-1][l].v;
        auto dxx_v=ptr_src[k+offset][l+1].v - 2*ptr_src[k+offset][l].v
            + ptr_src[k+offset][l-1].v;

        auto l_u=dxx_u+dyy_u;
        auto l_v=dxx_v+dyy_v;

        auto v_u=ptr_src[k+offset][l].u;
        auto v_v=ptr_src[k+offset][l].v;

        ptr_dest[k][l].u=v_u + 0.06125*( d*l_u - v_u*v_v*v_v + F*(1-v_u) );
        ptr_dest[k][l].v=v_v + 0.06125*( l_v + v_u*v_v*v_v - (F+ka)*v_v );

        }
    ptr_dest[k][0]=ptr_dest[k][1];
    ptr_dest[k][W-1]=ptr_dest[k][W-2];
    }

if(offset + h == H)
    {
    for(uint32_t l=0;l<W;++l)
        {ptr_dest[h-1][l]=ptr_dest[h-2][l];}
    }
if(offset==0)
    {
    for(uint32_t l=0;l<W;++l)
        {ptr_dest[0][l]=ptr_dest[1][l];}
    }

}

循环更新分配给当前线程的数据块。该块在矩阵中开始偏移行。将整个矩阵作为输入缓冲区传递的原因是为了支持周期性边界条件(该示例使用Neumann条件)。

两个缓冲区由两个在两个帧之间交换的BufferinfoPair对象管理

namespace Sim2d
{
struct BufferinfoPair
    {
    BufferinfoPair(Vector::MatrixStorage<ValueType>& matrix_first
        ,Vector::MatrixStorage<ValueType>& matrix_second
        ,uint32_t height_block,uint32_t offset);

    Bufferinfo first;
    Bufferinfo second;
    };

inline void swap(BufferinfoPair& a,BufferinfoPair& b)
    {
    ::std::swap(a.first.m_buffer,b.first.m_buffer);
    ::std::swap(a.second.m_buffer,b.second.m_buffer);
    }
}

Sim2d::BufferinfoPair::BufferinfoPair(
 Vector::MatrixStorage<ValueType>& matrix_first
,Vector::MatrixStorage<ValueType>& matrix_second
,uint32_t height_block,uint32_t offset):
first
    {
     matrix_first.rowsGet()
    ,uint32_t(matrix_first.nColsGet())
    ,uint32_t(matrix_first.nRowsGet())
    }
,second
    {
     matrix_second.rowsGet()+offset
    ,uint32_t(matrix_second.nColsGet())
    ,uint32_t(height_block)
    }
{}

Bufferinfo看起来像

namespace Sim2d
{
class BufferinfoPair;
void swap(BufferinfoPair& a,BufferinfoPair& b);

class Bufferinfo
    {
    public:
        Bufferinfo(ValueType* const* buffer,uint32_t width,uint32_t height)
            :m_buffer(buffer),m_width(width),m_height(height)
            {}

        template<class T>
        const T* const* bufferGet() const
            {return (const T* const*)m_buffer;}

        template<class T>
        T* const* bufferGet() 
            {return (T* const*)m_buffer;}

        template<class T>
        uint32_t widthGet() const
            {return m_width/ (sizeof(T)/sizeof(ValueType));}

        uint32_t heightGet() const
            {return m_height;}

    private:
        ValueType* const* m_buffer;
        uint32_t m_width;
        uint32_t m_height;

        friend void swap(BufferinfoPair& a,BufferinfoPair& b);          
    };
}

最后,每个线程执行的主循环[是一个goto但我真的不喜欢while(true)构造,因为它们没有语义含义]:

int Sim2d::DatablockProcessor::run()
{
m_stop=0;
next_frame:
    {
    start.wait();
    if(m_stop)
        {return STATUS_OK;}
    m_model->process(m_framecounter,m_buffers[0].first,m_buffers[0].second
        ,m_offset);
    swap(m_buffers[0],m_buffers[1]);
    ready.set();
    goto next_frame;
    }
}

m_buffer被声明为

BufferinfoPair m_buffers[2];

内部DatablockProcessor

数据块区域的计算如下所示

double bh_avg=double(m_buffers.first.nRowsGet())/N;
uint32_t offset=uint32_t(k*bh_avg);
uint32_t height_block=uint32_t((k+1)*bh_avg) - offset;
Sim2d::BufferinfoPair buffers[2]=
    {
     {m_buffers.first,m_buffers.second,height_block,offset}
    ,{m_buffers.second,m_buffers.first,height_block,offset}
    };

我想到的可能原因是

  1. 交换错误(导致内存冲突)
  2. 启动每个线程数据区域的错误(导致内存冲突)
  3. 有偏见的同步
  4. 缓存未命中
  5. 零值的处理速度比x86-64
  6. 上的其他值更快
  7. 与5
  8. 相反
  9. 边界条件使边界块的时间稍长一些
  10. 操作系统观察到即使代码写入内存内容也不会改变
  11. 我排除了原因3,因为如果我尝试其他一些代码(填充白噪声),我也不会遇到同样的问题。

    原因4不太可能,因为它一遍又一遍地处理相同的数据。

    可以排除原因5,因为初始状态在给定所有线程时都具有非零值,但是当出现零值时,帧速率将丢弃

    可以排除原因6,因为在慢处理期间,大多数像素的渐变中出现小值。那么,除了一个线程之外,更有可能拥有大量的CPU使用率。

    排除原因7,因为每个帧应该相同

    原因8可能听起来很奇怪,但我发现在域名完全填满后速度会上升。发生这种情况时,像素值随处可见。引入噪声支持这种假设,因为噪声使问题消失。另一方面:这样的测量是否真的可行

    观察到的行为最可能的原因是什么?

    编辑:

    主线程如下所示:

    proc_ptr=processors.begin();
    while(proc_ptr!=processors.end())
        {
        proc_ptr->frameNext();
        ++proc_ptr;
        }
    
    while(proc_ptr!=processors.begin())
        {
        --proc_ptr;
        proc_ptr->wait();
        }
    

    这段代码是否支持某些线程?我尝试以更随机的顺序开始,没有成功。需要注意的另一件事是更改模型,以便它只吐出一个常量,如

        ptr_dest[k][l].u=0; //+ 0.06125*( d*l_u - v_u*v_v*v_v + F*(1-v_u) );
        ptr_dest[k][l].v=1; //+ 0.06125*( l_v + v_u*v_v*v_v - (F+ka)*v_v );
    

    使问题消失。我得出结论,从结果帧率来看,双循环还没有被优化出来。

    编辑2:

    停止模拟线程并从当前状态重新启动它会导致性能不佳,因此我得出结论,像素值会影响调度。

0 个答案:

没有答案