【问题标题】:C++ Merge sort slower with threadsC ++合并排序与线程较慢
【发布时间】:2019-08-15 19:50:27
【问题描述】:

我已经用 C++ 实现了一个归并排序算法。
在算法内部,它会检查数组的大小是否大于min_size_to_thread,如果是:使用线程递归调用函数。

但是当我增加min_size_to_thread: 减少正在使用的线程数时,函数变得更快。即使从 1 个线程变为 2 个线程。

我的假设是函数速度会随着更多线程的增加而增加,然后再次开始下降。这对我来说没有任何意义,所以我开始相信我的实现在某种程度上是错误的。

template <typename T>
void merge_sort(T S[], int S_size, int min_size_to_thread)
{
    if (S_size < 2) return;

    // Left Sequence
    int L_size = S_size / 2;
    T* L = new T[L_size];
    for (int i = 0; i < L_size; i++)
    {
        L[i] = S[i];
    }

    // Right Sequence
    int R_size = (S_size + 1) / 2;
    T* R = new T[R_size];
    for (int i = 0; i < R_size; i++)
    {
        R[i] = S[i + L_size];
    }

    if (S_size > min_size_to_thread)
    {
        std::thread thread_left(merge_sort<T>, L, L_size, min_size_to_thread);
        std::thread thread_right(merge_sort<T>, R, R_size, min_size_to_thread);
        thread_right.join();
        thread_left.join();
    }
    else
    {
        merge_sort<T>(L, L_size, min_size_to_thread);
        merge_sort<T>(R, R_size, min_size_to_thread);
    }

    int S_iterator = 0;
    int L_iterator = 0;
    int R_iterator = 0;

    while ((L_iterator < L_size) && (R_iterator < R_size))
    {
        if (L[L_iterator] < R[R_iterator])
        {
            S[S_iterator] = L[L_iterator];
            ++L_iterator;
        }
        else
        {
            S[S_iterator] = R[R_iterator];
            ++R_iterator;
        }
        ++S_iterator;
    }

    while (L_iterator < L_size)
    {
        S[S_iterator] = L[L_iterator];
        ++L_iterator;
        ++S_iterator;
    }

    while (R_iterator < R_size)
    {
        S[S_iterator] = R[R_iterator];
        ++R_iterator;
        ++S_iterator;
    }

    delete[] L;
    delete[] R;
}

int main()
{
    const int S_size = 500000;
    unsigned char S[S_size];
    for (int i = 0; i < S_size; ++i)
    {
        S[i] = i % 255;
    }

    int min_size_to_thread;

    min_size_to_thread = 250;
    auto t1 = std::chrono::high_resolution_clock::now();
    merge_sort(S, S_size, min_size_to_thread);
    auto t2 = std::chrono::high_resolution_clock::now();
    std::cout << "size > " << min_size_to_thread << ": " << (t2 - t1) / std::chrono::milliseconds(1) << std::endl;

    for (int i = 0; i < S_size; ++i)
    {
        S[i] = i % 255;
    }

    min_size_to_thread = 500;
    t1 = std::chrono::high_resolution_clock::now();
    merge_sort(S, S_size, min_size_to_thread);
    t2 = std::chrono::high_resolution_clock::now();
    std::cout << "size > " << min_size_to_thread << ": " << (t2 - t1) / std::chrono::milliseconds(1) << std::endl;

    for (int i = 0; i < S_size; ++i)
    {
        S[i] = i % 255;
    }

    min_size_to_thread = 1000;
    t1 = std::chrono::high_resolution_clock::now();
    merge_sort(S, S_size, min_size_to_thread);
    t2 = std::chrono::high_resolution_clock::now();
    std::cout << "size > " << min_size_to_thread << ": " << (t2 - t1) / std::chrono::milliseconds(1) << std::endl;

    for (int i = 0; i < S_size; ++i)
    {
        S[i] = i % 255;
    }

    min_size_to_thread = 10000;
    t1 = std::chrono::high_resolution_clock::now();
    merge_sort(S, S_size, min_size_to_thread);
    t2 = std::chrono::high_resolution_clock::now();
    std::cout << "size > " << min_size_to_thread << ": " << (t2 - t1) / std::chrono::milliseconds(1) << std::endl;

    for (int i = 0; i < S_size; ++i)
    {
        S[i] = i % 255;
    }

    min_size_to_thread = 250000;
    t1 = std::chrono::high_resolution_clock::now();
    merge_sort(S, S_size, min_size_to_thread);
    t2 = std::chrono::high_resolution_clock::now();
    std::cout << "size > " << min_size_to_thread << ": " << (t2 - t1) / std::chrono::milliseconds(1) << std::endl;

    for (int i = 0; i < S_size; ++i)
    {
        S[i] = i % 255;
    }

    min_size_to_thread = 500000;
    t1 = std::chrono::high_resolution_clock::now();
    merge_sort(S, S_size, min_size_to_thread);
    t2 = std::chrono::high_resolution_clock::now();
    std::cout << "size > " << min_size_to_thread << ": " << (t2 - t1) / std::chrono::milliseconds(1) << std::endl;

    return 0;
}

【问题讨论】:

  • 你不应该通过复制子数组。那会扼杀你的表现。只需保留一个数组,然后传递您正在使用的索引,尽管这会导致错误的共享问题。
  • @NathanOliver 是线程速度变慢的原因吗?
  • 很有可能。
  • 根本没有使用thread_right 的意义,因为您创建它并立即等待它(在左侧等待之后),因此原始线程在完成之前不会做任何事情。创建左线程后,在当前线程中做右线程的事情,然后等待左线程完成。
  • 您的实现创建并连接了两个线程,而您却无缘无故地拥有一个已经很热且已调度的线程停放。您至少应该在新线程中对左侧进行排序,而在当前线程中对右侧进行排序(然后加入),反之亦然。

标签: c++ algorithm performance sorting


【解决方案1】:

我已经编译并运行了您的确切程序,除了添加包含之外没有任何修改,结果或多或少与您预期的一样:

size > 250: 169
size > 500: 85
size > 1000: 50
size > 10000: 29
size > 250000: 42
size > 500000: 89

根据您的屏幕截图,我推测您正在 Visual Studio 中运行您的代码。默认运行按钮会将调试器附加到您的可执行文件并降低运行时性能。相反,请按 Ctrl+F5 以在没有调试器的情况下运行,或者从菜单 Debug -> Start without Debugging 中运行。

【讨论】:

  • 这是发布版本还是调试版本?
  • 我使用cl.exe构建的所有默认设置。
  • 很奇怪。我试图在不调试的情况下开始,但我仍然得到相同的结果(只是一切都快了很多)。收到875, 810, 761, 768, 536, 449
  • 更改为发布 x64 并在没有调试器的情况下启动。现在我得到和你一样的结果。谢谢
【解决方案2】:

我认为这是缓存的问题。具体来说,false-sharing 会减慢算法的速度,因为数据会写入多个线程之间共享的页面。 (不同的处理器内核试图跟上共享内存页面)如果 min_size_to_thread 是您的处理器的 page-size 的倍数,并且您的数组是 aligned在页面边界上,性能会提高。在这种情况下,线程之间不会共享页面。

我总是将线程的创建限制在一个恒定的数量,在四核机器上运行 100 个线程只是为了对数组进行排序是没有意义的。 由于繁重的上下文切换,在单个核心上运行多个线程会产生成本。根据我的经验,最大线程数始终是核心数乘以 2。一个核心可以处理大约 2 个线程,而无需性能成本。对于四核 CPU,程序一次最多应该运行 8 个线程。 这意味着一个算法可以创建8个子线程,父线程只是joins线程,创建7个子线程,在父线程中运行算法的一部分,最后join另外7个线程。

总是配置文件,它可能有完全不同的原因。

【讨论】:

  • 我是新手,所以我必须阅读有关虚假共享和分析的信息。但是对于您答案的第二部分:仅查看最后两个测试,其中min_size_to_thread = 250000500000:第一个在两个线程上运行,第二个在单个线程上运行。还是单线程更快。
猜你喜欢
  • 2011-09-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-25
  • 1970-01-01
相关资源
最近更新 更多