【问题标题】:Threads slowing eachother down线程彼此减慢
【发布时间】:2020-06-05 12:54:39
【问题描述】:

我有一些昂贵的计算,我想在一组线程上进行划分和分配。 我将我的代码简化为一个仍在发生这种情况的最小示例。

简而言之:

我有 N 个任务要划分为“线程”线程。

每个任务都是运行一堆简单数学运算的以下简单函数。 (实际上我在这里验证了非对称签名,但为了简化,我排除了它)

while (i++ < 100000)
        {
            for (int y = 0; y < 1000; y++)
            {
                sqrt(y);
            }
        }

使用 1 个线程运行上述代码导致每次操作需要 0.36 秒(最外层的 for 循环),因此总执行时间约为 36 秒。

因此,并行化似乎是一种明显的加速方法。但是,使用两个线程时,操作时间上升到 0.72 秒,完全破坏了任何加速。

添加更多线程通常会导致性能越来越差。

我有一个 Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz,有 6 个物理内核。 所以我希望至少在从 1 到 2 个线程时使用性能提升。但实际上,随着线程数的增加,每个操作都会变慢。

我是不是做错了什么?

完整代码:

using namespace std;

const size_t N = 100;
const size_t Threads = 1;

atomic_int counter(0);

struct ThreadData
{
    int index;
    int count;

    ThreadData(const int index, const int count): index(index), count(count){};
};

void *executeSlave(void *threadarg)
{
    struct ThreadData *my_data;
    my_data = static_cast<ThreadData *>(threadarg);
    for( int x = my_data->index; x < my_data->index + my_data->count; x++ )
    {
        cout << "Thread: " << my_data->index <<  ": " << x << endl;

        clock_t start, end;
        start = clock();
        int i = 0;

        while (i++ < 100000)
        {
            for (int y = 0; y < 1000; y++)
            {
                sqrt(y);
            }
        }
        counter.fetch_add(1);

        end = clock();
        cout << end - start << ':' << CLOCKS_PER_SEC << ':' << (((float) end - start) / CLOCKS_PER_SEC)<< endl;
    }

    pthread_exit(NULL);
}

int main() 
{
    clock_t start, end;
    start = clock();

    pthread_t threads[Threads];
    vector<ThreadData> td;
    td.reserve(Threads);
    int each = N / Threads;
    cout << each << endl;
    for (int x = 0; x < Threads; x++) {
        cout << "main() : creating thread, " << x << endl;
        td[x] = ThreadData(x * each, each);

        int rc = pthread_create(&threads[x], NULL, executeSlave, (void *) &td[x]);

        if (rc) {
            cout << "Error:unable to create thread," << rc << endl;
            exit(-1);
        }
    }

    while (counter < N) {
        std::this_thread::sleep_for(10ms);
    }

    end = clock();

    cout << "Final:" << endl;
    cout << end - start << ':' << CLOCKS_PER_SEC << ':' << (((float) end - start) / CLOCKS_PER_SEC)
         << endl;

}

【问题讨论】:

  • 为什么不用std::thread 而不是pthread
  • 您使用什么编译器和 C++ 标准?在现代 C++ 中,使用 pthread_t 是一个很大的危险信号。
  • 离题但是...您调用td.reserve(Threads),然后使用td[x] = ...,而不会以任何方式设置向量td 的大小。你的意思是resize 而不是reserve
  • 见这里:Incorrect Time in C++。您应该改用std::chrono 来测量经过的时间。
  • 与您的问题无关,但您可能需要考虑将 while 循环替换为 for (auto&amp; t : threads) { t.join(); }(或 pthread_join,如果您决定坚持使用 pthread)。这样你就可以摆脱丑陋的睡眠。

标签: c++ multithreading


【解决方案1】:

clock() 返回整个进程的近似CPU时间

最外层循环每次迭代完成固定数量的工作

    int i = 0;
    while (i++ < 100000)
    {
        for (int y = 0; y < 1000; y++)
        {
            sqrt(y);
        }
    }

因此,围绕此循环报告的进程 CPU 时间将与正在运行的线程数成正比(它仍然花费相同的时间 每个线程,乘以 N 个线程)。

改用std::chrono::steady_clock 测量挂钟时间。另请注意,std::cout 等 I/O 会占用大量挂钟时间并且不稳定。所以测得的total经过时间会因为内部的I/O而出现偏差。

一些补充说明:

  1. 从不使用sqrt()的返回值;编译器可能会完全消除调用。以某种方式使用该值是谨慎的做法。

  2. void* executeSlave() 未返回 void* 指针值 (UB)。如果它什么都不返回,它可能应该简单地声明为void

  3. td.reserve(Threads) 保留内存但不分配对象。 td[x] 然后访问不存在的对象 (UB)。使用td.emplace_back(x * each, each) 而不是td[x] = ...

  4. 技术上不是问题,但建议使用标准 C++ std::thread 而不是 pthread,以获得更好的可移植性。

通过以下内容,我看到与线程数成正比的正确加速:

#include <string>
#include <iostream>
#include <vector>
#include <atomic>
#include <cmath>
#include <thread>

using namespace std;
using namespace std::chrono_literals;

const size_t N = 12;
const size_t Threads = 2;

std::atomic<int> counter(0);
std::atomic<int> xx{ 0 };

void executeSlave(int index, int count, int n)
{
    double sum = 0;
    for (int x = index; x < index + count; x++)
    {
        cout << "Thread: " << index << ": " << x << endl;
        auto start = std::chrono::steady_clock::now();
        for (int i=0; i < 100000; i++)
        {
            for (int y = 0; y < n; y++)
            {
                sum += sqrt(y);
            }
        }
        counter++;

        auto end = std::chrono::steady_clock::now();
        cout << 1e-6 * (end - start) / 1us << " s" << endl;
    }
    xx += (int)sum; // prevent optimization

}

int main()
{
    std::thread threads[Threads];
    int each = N / Threads;
    cout << each << endl;
    auto start = std::chrono::steady_clock::now();
    for (int x = 0; x < Threads; x++) {
        cout << "main() : creating thread, " << x << endl;
        threads[x] = std::thread(executeSlave, x * each, each, 100);
    }

    for (auto& t : threads) {
        t.join();
    }

    auto end = std::chrono::steady_clock::now();

    cout << "Final:" << endl;
    cout << 1e-6 * (end - start) / 1us << " s" << endl;

}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-06-13
    • 2021-11-27
    • 2014-03-08
    • 1970-01-01
    • 2020-03-11
    • 1970-01-01
    • 1970-01-01
    • 2014-06-09
    相关资源
    最近更新 更多