2014-12-07 3 views
-3

Я абсолютный новичок в многозадачности, который читает некоторые основы и пытается получить его в моем проекте для визуализации объектов. Проблема в том, что мое многопоточное решение, которое я реализовал, медленнее, чем однопоточный, и я не знаю, почему и у меня есть неожиданный код приложения по неизвестной причине. Я даю вам два случая, когда я пытался добиться лучшей производительности. Я хотел бы знать, чего я не понимаю, и где у меня есть ошибки в общей точке зрения. Я даю вам часть исходного кода и суммирую все вопросы в конце.C++ 11 Многопоточность медленнее одиночного потока

Вот моя реализация нить завод (очень простой, но это только начало):

threadfactory.h

#pragma once 

#include <vector> 
#include "ThreadInterface.h" 
#include "../MemoryManagement/MemoryMgr.h" 
#include "../Logging/LoggingDefines.h" 

class CThreadFactory : public CThreadIntearface 
{ 
    public: 
     CThreadFactory(); 
     CThreadFactory(BYTE max_threads); 
     ~CThreadFactory(); 

     void Init(BYTE max_threads); 
     void Clear(void); 

     //update waves 
     virtual void UpdateWavesInternalPoints(CWaves& waves); 
     virtual void UpdateWavesNormals(CWaves& waves); 

     //update vertices 
     virtual void TransformVertices(const CObject& object, const vector<TVertex>& input, vector<XMFLOAT3>& output, const CXNAMatrix& matrix); 

     static const char* GetHeapName(void) { return "Thread factory"; } 
#if (defined(DEBUG) | defined(_DEBUG)) 
     /** 
     * Return class name. This function is compiled only in debug mode. 
     * \return class name 
     */ 
     NAME_FUNC(); 
#endif 

    private: 
     void Join(vector<std::thread>& threads); 
     void ReleaseThreads(vector<std::thread>& threads); 

    private: 
     UINT muiNumberofThreads; 

    private: 
     DECLARE_HEAP; 
}; 

threadfactory.cpp

#include "ThreadFactory.h" 

CThreadFactory::CThreadFactory() 
{ 
    TRACE(LOG_DEBUG, string("Start of initialization of object \"") + GetName() + string("\"")); 
    muiNumberofThreads = 1; 
    TRACE(LOG_DEBUG, string("End of initialization of object \"") + GetName() + string("\"")); 
} 

CThreadFactory::CThreadFactory(BYTE max_threads) 
{ 
    TRACE(LOG_DEBUG, string("Start of initialization of object \"") + GetName() + string("\"")); 
    Init(max_threads); 
    TRACE(LOG_DEBUG, string("End of initialization of object \"") + GetName() + string("\"")); 
} 

CThreadFactory::~CThreadFactory() 
{ 
    TRACE(LOG_DEBUG, string("Start of releasing of object \"") + GetName() + string("\"")); 
    Clear(); 
    TRACE(LOG_DEBUG, string("End of releasing of object \"") + GetName() + string("\"")); 
} 

void CThreadFactory::Init(BYTE max_threads) 
{ 
    muiNumberofThreads = max_threads; 
} 

void CThreadFactory::Clear(void) 
{ 

} 

void CThreadFactory::Join(vector<std::thread>& threads) 
{ 
    for (auto& it : threads) 
    { 
     if (it.joinable()) 
      it.join(); 
    } 
} 

void CThreadFactory::ReleaseThreads(vector<std::thread>& threads) 
{ 
    /*for (auto& it : threads) 
    { 

    }*/ 

    threads.clear(); 
} 

void CThreadFactory::UpdateWavesInternalPoints(CWaves& waves) 
{ 
    if (muiNumberofThreads <= 1) 
    { 
     waves.UpdateWaveInteriorPoints(1, waves.RowCount() - 1); 
    } 
    else 
    { 
     vector<std::thread> threads(muiNumberofThreads - 1); 
     UINT dwWavePartDifference = waves.RowCount()/muiNumberofThreads; 

     DWORD dwMinRow = 1, dwMaxRow = 1 + dwWavePartDifference; 
     for (UINT i = 0; i < muiNumberofThreads - 1; i++) 
     { 
      threads[i] = move(std::thread{ &CWaves::UpdateWaveInteriorPoints, &waves, dwMinRow, dwMaxRow }); 

      dwMinRow += dwWavePartDifference; 
      dwMaxRow += dwWavePartDifference; 
     } 

     waves.UpdateWaveInteriorPoints(dwMinRow, dwMaxRow); 

     Join(threads); 
     ReleaseThreads(threads); 
    } 
} 

void CThreadFactory::UpdateWavesNormals(CWaves& waves) 
{ 
    if (muiNumberofThreads <= 1) 
    { 
     waves.UpdateWaveNormals(1, waves.RowCount() - 1); 
    } 
    else 
    { 
     vector<std::thread> threads(muiNumberofThreads - 1); 
     UINT dwWavePartDifference = waves.RowCount()/muiNumberofThreads; 

     DWORD dwMinRow = 1, dwMaxRow = 1 + dwWavePartDifference; 
     for (UINT i = 0; i < muiNumberofThreads - 1; i++) 
     { 
      threads[i] = move(std::thread{ &CWaves::UpdateWaveNormals, &waves, dwMinRow, dwMaxRow }); 

      dwMinRow += dwWavePartDifference; 
      dwMaxRow += dwWavePartDifference; 
     } 

     waves.UpdateWaveNormals(dwMinRow, dwMaxRow); 

     Join(threads); 
     ReleaseThreads(threads); 
    } 
} 

void CThreadFactory::TransformVertices(const CObject& object, const vector<TVertex>& input, vector<XMFLOAT3>& output, const CXNAMatrix& matrix) 
{ 
    if (output.size() != input.size()) 
     output.resize(input.size()); 

    if ((muiNumberofThreads <= 1) || (input.size() < 1000)) 
    { 
     object.TransformVerticesSet(input.begin(), output.begin(), input.size() - 1, matrix); 
    } 
    else 
    { 
     vector<std::thread> threads(muiNumberofThreads - 1); 
     UINT uiThreadVertexCount = input.size()/muiNumberofThreads; 
     UINT uiStartVertexIndex = 0; 

     for (UINT i = 0; i < muiNumberofThreads - 1; i++) 
     { 
      if (uiStartVertexIndex >= input.size()) 
       uiStartVertexIndex = input.size() - 1; 

      threads[i] = move(std::thread{ &CObject::TransformVerticesSet, &object, input.begin() + uiStartVertexIndex, output.begin() + uiStartVertexIndex, uiThreadVertexCount - 1, matrix }); 

      uiStartVertexIndex += uiThreadVertexCount; 
     } 

     object.TransformVerticesSet(input.begin() + uiStartVertexIndex, output.begin() + uiStartVertexIndex, uiThreadVertexCount - 1, matrix); 

     Join(threads); 
     ReleaseThreads(threads); 
    } 
} 

#if (defined(DEBUG) | defined(_DEBUG)) 

NAME_BODY(CThreadFactory, "Threads"); 

#endif 

DEFINE_HEAP(CThreadFactory, GetHeapName()); 

1. Обновление волны:

Я использую объект под названием Wave. Этот объект имеет неявно около 40 000 вершин. Я использую эти функции, чтобы обновить его для каждого кадра:

void CWaves::UpdateWaveInteriorPoints(DWORD min_row, DWORD max_row) 
{ 
    if (min_row < 1) 
     min_row = 1; 

    if (max_row > (RowCount() - 1)) 
     max_row = (RowCount() - 1); 

    for (DWORD i = min_row; i < max_row; ++i) 
    { 
     for (DWORD j = 1; j < ColumnCount() - 1; ++j) 
     { 
      // After this update we will be discarding the old previous 
      // buffer, so overwrite that buffer with the new update. 
      // Note how we can do this inplace (read/write to same element) 
      // because we won't need prev_ij again and the assignment happens last. 

      // Note j indexes x and i indexes z: h(x_j, z_i, t_k) 
      // Moreover, our +z axis goes "down"; this is just to 
      // keep consistent with our row indices going down. 

      GetPrevSolutionVertices()[i*ColumnCount() + j].Position.y = 
       GetK1()*GetPrevSolutionVertices()[i*ColumnCount() + j].Position.y + 
       GetK2()*mpObjectMesh->mVertices[i*ColumnCount() + j].Position.y + 
       GetK3()*(mpObjectMesh->mVertices[(i + 1)*ColumnCount() + j].Position.y + 
       mpObjectMesh->mVertices[(i - 1)*ColumnCount() + j].Position.y + 
       mpObjectMesh->mVertices[i*ColumnCount() + j + 1].Position.y + 
       mpObjectMesh->mVertices[i*ColumnCount() + j - 1].Position.y); 
     } 
    } 
} 

void CWaves::UpdateWaveNormals(DWORD min_row, DWORD max_row) 
{ 
    if (min_row < 1) 
     min_row = 1; 

    if (max_row >(RowCount() - 1)) 
     max_row = (RowCount() - 1); 

    for (UINT i = min_row; i < max_row; ++i) 
    { 
     for (UINT j = 1; j < ColumnCount() - 1; ++j) 
     { 
      float l = mpObjectMesh->mVertices[i*ColumnCount() + j - 1].Position.y; 
      float r = mpObjectMesh->mVertices[i*ColumnCount() + j + 1].Position.y; 
      float t = mpObjectMesh->mVertices[(i - 1)*ColumnCount() + j].Position.y; 
      float b = mpObjectMesh->mVertices[(i + 1)*ColumnCount() + j].Position.y; 
      mpObjectMesh->mVertices[i*ColumnCount() + j].Normal.x = -r + l; 
      mpObjectMesh->mVertices[i*ColumnCount() + j].Normal.y = 2.0f*GetSpatialStep(); 
      mpObjectMesh->mVertices[i*ColumnCount() + j].Normal.z = b - t; 

      XMVECTOR n = XMVector3Normalize(XMLoadFloat3(&mpObjectMesh->mVertices[i*ColumnCount() + j].Normal)); 
      XMStoreFloat3(&mpObjectMesh->mVertices[i*ColumnCount() + j].Normal, n); 

      mpObjectMesh->mVertices[i*ColumnCount() + j].TangentU = XMFLOAT3(2.0f*GetSpatialStep(), r - l, 0.0f); 
      XMVECTOR T = XMVector3Normalize(XMLoadFloat3(&mpObjectMesh->mVertices[i*ColumnCount() + j].TangentU)); 
      XMStoreFloat3(&mpObjectMesh->mVertices[i*ColumnCount() + j].TangentU, T); 
     } 
    } 
} 

void CWaves::UpdateWave(float dt) 
{ 
    static float t_base = 0.0f; 
    if ((g_Timer->TotalTime() - t_base) >= 0.25f) 
    { 
     t_base += 0.25f; 

     DWORD i, j; 

     do 
     { 
      i = 5 + rand() % (RowCount() - 5); 
      j = 5 + rand() % (ColumnCount() - 5); 
     } while (!((i > 1) && (i < (RowCount() - 2)) && 
      (j > 1) && (j < (ColumnCount() - 2)))); 

     float r = MathHelper::RandF(1.0f, 2.0f); 

     Disturb(i, j, r); 
    } 

    static float t = 0; 

    // Accumulate time. 
    t += dt; 

    // Only update the simulation at the specified time step. 
    if (t >= TimeStep()) 
    { 
     // Only update interior points; we use zero boundary conditions. 
     if (g_ThreadFactory) 
     { 
      g_ThreadFactory->UpdateWavesInternalPoints(*this); 
     } 
     else 
     { 
      UpdateWaveInteriorPoints(1, RowCount() - 1); 
     } 

     // We just overwrote the previous buffer with the new data, so 
     // this data needs to become the current solution and the old 
     // current solution becomes the new previous solution. 
     std::swap(GetPrevSolutionVertices(), mpObjectMesh->mVertices); 

     t = 0.0f; // reset time 

     if (mShapeDescription.mShapeProperties.bLightedObject) 
     { 
      // 
      // Compute normals using finite difference scheme. 
      // 
      if (g_ThreadFactory) 
      { 
       g_ThreadFactory->UpdateWavesNormals(*this); 
      } 
      else 
      { 
       UpdateWaveNormals(1, RowCount() - 1); 
      } 
     } 
    } 
} 

В этом случае я думал, что проблема заключается в CWaves объект, который я дал все темы, и я подумал, что эта причина постоянно блокировки. Поэтому я изменяю подход для другого случая, когда пытаюсь преобразовать вершины с помощью заданной матрицы преобразования. Вместо целого объекта я использую итераторы контейнеров.

2. Вершина преобразование

Вершина метод преобразования вызывается из потока завода отображается выше:

void CObject::TransformVerticesSet(vector<TVertex>::const_iterator input, vector<XMFLOAT3>::iterator output, UINT number_of_vertices, const CXNAMatrix& matrix) const 
{ 
    for (UINT i = 0; i <= number_of_vertices; i++) 
    { 
     CMatrixTransformations::TransformPoint(input[i].Position, matrix, output[i]); 
    } 
} 

В этом случае я пытался использовать итераторы вместо предоставления всей вершины вектора, но результат равным предыдущему решению. Это медленнее, чем однопоточное решение.

< EDIT 8.12.2014>

В предыдущем коде я использую следующие макросы:

TRACE - использование для системы регистрации в режиме выпуска пуст

NAME_FUNC, NAME_BODY - макрос для объявления и определение метода класса, возвращающее имя класса

DECLARE_HEAP, DEFINE_HEAP - создать объявление и определение для перегруженных операторов new и delete

Ничего из этого не должно влиять на производительность при многопоточных операциях.

Вот выход из VS 2013 после того, как я закрываю мое заявление (заметим, что я не использую многопоточности для предыдущих ситуаций в этом случае):

The thread 0x229c has exited with code 27 (0x1b). 
The thread 0x22dc has exited with code 27 (0x1b). 
The thread 0x11ac has exited with code 27 (0x1b). 
The thread 0x328c has exited with code 27 (0x1b). 
The thread 0x205c has exited with code 27 (0x1b). 
The thread 0xf4c has exited with code 27 (0x1b). 
The thread 0x894 has exited with code 27 (0x1b). 
The thread 0x3094 has exited with code 27 (0x1b). 
The thread 0x2eb4 has exited with code 27 (0x1b). 
The thread 0x2ef8 has exited with code 27 (0x1b). 
The thread 0x22f4 has exited with code 27 (0x1b). 
The thread 0x2810 has exited with code 27 (0x1b). 
The thread 0x29e0 has exited with code 27 (0x1b). 
The thread 0x2e54 has exited with code 27 (0x1b). 
D3D11 WARNING: Process is terminating. Using simple reporting. Please call ReportLiveObjects() at runtime for standard reporting. [ STATE_CREATION WARNING #0: UNKNOWN] 
D3D11 WARNING: Live Producer at 0x012F05A0, Refcount: 8. [ STATE_CREATION WARNING #0: UNKNOWN] 
D3D11 WARNING: Live Object at 0x012F1D38, Refcount: 0. [ STATE_CREATION WARNING #0: UNKNOWN] 
D3D11 WARNING: Live Object at 0x013BA3F8, Refcount: 0. [ STATE_CREATION WARNING #0: UNKNOWN] 

The program '[13272] EngineDX.exe' has exited with code 27 (0x1b). 

кажется, что третья сторона API (возможно DX) является создавая потоки, но в диспетчере процессов я вижу использование только одного потока. Это может быть проблемой ...

Так вот мои вопросы:

  1. Является ли моя реализация нити завода так неправильного или обновления 40 000 вершин не должна быть разделена на несколько потоков?
  2. Если бы я получил блокировку, я хотел бы знать, почему. Решение для преобразования вершин использует итераторы, а векторный контейнер вершин разделен, поэтому я не должен блокировать.
  3. Я решил создать потоки для каждой функции, вызванной одной причиной. Сначала у меня был векторный контейнер нитей в качестве члена класса фабрики нитей. Но это вызвало утечку памяти в режиме отладки (в режиме выпуска не было этой проблемы). Просто чистая декларация, ничего не делая. Я никогда не узнал, почему. Есть ли что-то еще, что необходимо для правильного выделения потока?
  4. Теперь мое приложение завершилось кодом 27, потому что все потоки вернули этот код ошибки. Что это значит?
  5. Странно думать, что, когда я использую 8 потоков (7 + основной поток на 8-поточном процессоре), в режиме отладки я вижу, что все 8 потоков что-то делают. Но в режиме выпуска нет изменений в соответствии с использованием только одного потока (основного). Неправильное поведение или его можно ожидать по некоторым причинам?

Извините за длинный текст, но я хотел быть более точным, чтобы избежать неправильного толкования. Спасибо за ответы.

EDIT 17.12.2014:

Я переписана функция, которая использует темы (и сделать его независимым от класса Wave), нет ссылок на объектах или переменных долей, но она по-прежнему не работает. Я не понимаю, почему ... Интересно, что когда я устанавливаю 8 потоков для использования, в исполняемом файле отладки я вижу, что мой Core i7 работает на 100%, но без каких-либо преимуществ в частоте кадров. С исполнением исполняемого файла я вижу только 4 потока и CPU на 25%.

Новая многопоточная функция:

void UpdateWaveInteriorPoints(TVertexFieldIterator previous_vertex_field, TVertexFieldIterator actual_vertex_field, DWORD min_row, DWORD max_row, float k1, float k2, float k3, UINT column_count) 
{ 
    if (min_row < 1) 
     min_row = 1; 

    /*if (max_row >(RowCount() - 1)) 
     max_row = (RowCount() - 1);*/ 

    for (DWORD i = min_row; i < max_row; ++i) 
    { 
     for (DWORD j = 1; j < column_count - 1; ++j) 
     { 
      // After this update we will be discarding the old previous 
      // buffer, so overwrite that buffer with the new update. 
      // Note how we can do this inplace (read/write to same element) 
      // because we won't need prev_ij again and the assignment happens last. 

      // Note j indexes x and i indexes z: h(x_j, z_i, t_k) 
      // Moreover, our +z axis goes "down"; this is just to 
      // keep consistent with our row indices going down. 

      previous_vertex_field[i*column_count + j].Position.y = 
       k1*previous_vertex_field[i*column_count + j].Position.y + 
       k2*actual_vertex_field[i*column_count + j].Position.y + 
       k3*(actual_vertex_field[(i + 1)*column_count + j].Position.y + 
       actual_vertex_field[(i - 1)*column_count + j].Position.y + 
       actual_vertex_field[i*column_count + j + 1].Position.y + 
       actual_vertex_field[i*column_count + j - 1].Position.y); 
     } 
    } 
} 

Функция, которая создавать темы:

TVertexFieldIterator tActualVertexIterator = waves.mpObjectMesh->mVertices.begin(); 
     TVertexFieldIterator tPreviousVertexIterator = waves.GetPrevSolutionVertices().begin(); 
     std::vector<std::thread> threads; 
     //std::vector<std::future<void>> threads; 
     UINT dwWavePartDifference = waves.RowCount()/muiNumberofThreads; 

     DWORD dwMinRow = 1, dwMaxRow = dwWavePartDifference; 
     DWORD dwVertexCount = dwWavePartDifference*waves.ColumnCount(); 

     for (UINT i = 0; i < muiNumberofThreads - 1; i++) 
     { 
      //threads.emplace_back(std::async(std::launch::async, &CWaves::UpdateWaveInteriorPoints, &waves, tPreviousVertexIterator, tActualVertexIterator, dwMinRow, dwMaxRow, waves.GetK1(), waves.GetK2(), waves.GetK3(), waves.ColumnCount())); 
      threads.emplace_back(std::thread(&UpdateWaveInteriorPoints, tPreviousVertexIterator, tActualVertexIterator, dwMinRow, dwMaxRow, waves.GetK1(), waves.GetK2(), waves.GetK3(), waves.ColumnCount())); 

      tActualVertexIterator += dwVertexCount; 
      tPreviousVertexIterator += dwVertexCount; 
     } 

     tPreviousVertexIterator -= waves.ColumnCount(); //row - 1 
     tActualVertexIterator -= waves.ColumnCount(); //row - 1 
     waves.UpdateWaveInteriorPoints(tPreviousVertexIterator, tActualVertexIterator, dwMinRow, dwMaxRow, waves.GetK1(), waves.GetK2(), waves.GetK3(), waves.ColumnCount()); 

     for (UINT i = 0; i < muiNumberofThreads -1; i++) 
     { 
      //threads[i].wait(); 
      threads[i].join(); 
     } 

Marek

+0

Вам нужно отделить логику обработки от логики "волновой" нить. Резьбовая и фактическая работа ортогональны (или, по крайней мере, должны быть как можно больше). – didierc

+0

Другие проблемы: название вашего вопроса просто слишком общее, представьте, все ли назвали их такими вопросами, как это было бы полезно для людей, ищущих проблемы, подобные их? Попробуйте найти более точный заголовок. – didierc

+1

Код, который вы указываете *, не компилируется *.Я понимаю, что вы не хотите делиться слишком большим количеством своего кода, или, может быть, вы считаете, что слишком долго будет включать все в вопрос. В этом случае попробуйте уменьшить свою проблему до управляемой реализации. – didierc

ответ

2

@mareknr Когда я вытащил свой вопрос, было 10 связанных вопросов с ответами в боковая панель, все из которых связаны с тем, почему многопоточная реализация была медленнее, чем однопоточная. Я думаю, что один или несколько из них рассмотрят ваш вопрос. Вот ссылки на некоторые из них:

Multi-threaded GEMM slower than single threaded one?

C++ Boost Multithread is slower than single thread because of CPU type?

Why is this OpenMP program slower than single-thread?

2 threads slower than 1?

+0

Я внесла некоторые изменения, но без изменения результата. См. Раздел EDIT в конце описания проблемы. – mareknr

Смежные вопросы