【问题标题】:Two-threaded app is slower than single-threaded on C++ (VC++ 2010 Express). How to solve?在 C++ (VC++ 2010 Express) 上,双线程应用程序比单线程应用程序慢。怎么解决?
【发布时间】:2019-12-24 07:52:23
【问题描述】:

我有一些程序分配了很多内存,我希望通过在线程上拆分任务来提高它的速度,但这只会让我的程序变慢。

我做了这个最小的例子,它与我的真实代码无关,除了它在不同的线程中分配内存。

class ThreadStartInfo
{
public:
    unsigned char *arr_of_5m_elems;
    bool TaskDoneFlag;

    ThreadStartInfo()
    {
        this->TaskDoneFlag = false;
        this->arr_of_5m_elems = NULL;
    }

    ~ThreadStartInfo()
    {
        if (this->arr_of_5m_elems)
            free(this->arr_of_5m_elems);
    }
};

unsigned long __stdcall CalcSomething(void *tsi_ptr)
{
    ThreadStartInfo *tsi = (ThreadStartInfo*)tsi_ptr;

    for (int i = 0; i < 5000000; i++)
    {
        double *test_ptr = (double*)malloc(tsi->arr_of_5m_elems[i] * sizeof(double));
        memset(test_ptr, 0, tsi->arr_of_5m_elems[i] * sizeof(double));
        free(test_ptr);
    }

    tsi->TaskDoneFlag = true;
    return 0;
}

void main()
{
    ThreadStartInfo *tsi1 = new ThreadStartInfo();
    tsi1->arr_of_5m_elems = (unsigned char*)malloc(5000000 * sizeof(unsigned char));
    ThreadStartInfo *tsi2 = new ThreadStartInfo();
    tsi2->arr_of_5m_elems = (unsigned char*)malloc(5000000 * sizeof(unsigned char));
    ThreadStartInfo **tsi_arr = (ThreadStartInfo**)malloc(2 * sizeof(ThreadStartInfo*));
    tsi_arr[0] = tsi1;
    tsi_arr[1] = tsi2;

    time_t start_dt = time(NULL);
    CalcSomething(tsi1);
    CalcSomething(tsi2);
    printf("Task done in %i seconds.\n", time(NULL) - start_dt);
    //--

    tsi1->TaskDoneFlag = false;
    tsi2->TaskDoneFlag = false;
    //--

    start_dt = time(NULL);
    unsigned long th1_id = 0;
    void *th1h = CreateThread(NULL, 0, CalcSomething, tsi1, 0, &th1_id);
    unsigned long th2_id = 0;
    void *th2h = CreateThread(NULL, 0, CalcSomething, tsi2, 0, &th2_id);

    retry:
    for (int i = 0; i < 2; i++)
        if (!tsi_arr[i]->TaskDoneFlag)
        {
            Sleep(100);
            goto retry;
        }

    CloseHandle(th1h);
    CloseHandle(th2h);

    printf("MT Task done in %i seconds.\n", time(NULL) - start_dt);
}

它打印出这样的结果:

Task done in 16 seconds.
MT Task done in 19 seconds.

而且...我没想到会慢下来。有没有办法让多线程中的内存分配更快?

【问题讨论】:

标签: c++ windows multithreading performance winapi


【解决方案1】:

除了TaskDoneFlag 上缺乏同步导致的一些未定义行为外,所有线程都在重复调用malloc/free

Visual C++ CRT 堆是单线程的1,作为malloc/free 委托给HeapAlloc/HeapFree,它在关键节点中执行部分(一次只有一个线程)。一次从多个线程调用它们永远不会比单个线程快,并且由于锁争用开销而通常更慢。

要么减少线程中的分配,要么切换到另一个内存分配器,如 jemalloc 或 tcmalloc。


1HeapAlloc 见此注释:

当两个或多个线程尝试同时分配或释放同一堆中的块时,序列化可确保互斥。序列化的性能成本很小,但是当多个线程从同一个堆分配和释放内存时必须使用它。设置HEAP_NO_SERIALIZE 值可以消除堆上的互斥。如果没有序列化,使用相同堆句柄的两个或多个线程可能会尝试同时分配或释放内存,这可能会导致堆损坏。

【讨论】:

  • 由于 TaskDoneFlag 缺乏同步导致的未定义行为 - 我不这么认为。使用TaskDoneFlag,例如在示例中不是最好的方法(好方法是在任务计数中使用说互锁递减线程,当它达到零设置事件时,主线程等待此事件,而不是在循环中休眠)。但我认为无论如何都不需要在TaskDoneFlag 上进行任何同步。编译器不能删除tsi-&gt;TaskDoneFlag = true; 行(或者更早地执行它)。喜欢并且不能重新排序或删除从tsi_arr[i]-&gt;TaskDoneFlag(在不知道他Sleep)和TaskDoneFlag 原子的自我 - 对或错。
  • bool 不是原子的,not even on x86(否则就不需要 LOCK 指令前缀)。在不同步的情况下访问相同的变量是 UB。但是,鉴于强大的 x86 内存模型和 VC++10 编译器,最终结果可能会如预期的那样。这并不意味着代码可以移植到其他平台(但是,在这种情况下,这可能无关紧要)。
  • bool 在某种意义上是原子的,这里只有 2 个值 - 0(假)或不 0(真)因为它和原子 - 我们只能查看 false(0) 或 true(任何不是0)我们无法查看一些“部分”修改
  • 并回答您的链接不正确。即使从正式的 c++ 视图 - bool 只有 2 个阶段,我们也无法在这里阅读到非真非假的东西。只有在我们进行 RMW 操作时才需要锁定。但如果我们只从 bool 写入或读取 - 不需要锁。和这里的内存模型完全无关(在具体代码示例中)
  • 和平台(bool 是如何实现的,它保存多少字节)在这里不相关 - bool - 类型,能够保存两个值之一:true 或 false。 -这是主要的。因此,无论是否有任何“同步”,我们都会从 bool 中读取 true 或 false。内存模型 - 是关于访问不同内存位置的顺序,它可见和修改顺序。但这里只有一个内存位置。代码不是最优的。但它不依赖于 UB,也不依赖于平台
猜你喜欢
  • 2017-05-31
  • 1970-01-01
  • 1970-01-01
  • 2019-02-08
  • 1970-01-01
  • 2020-08-15
  • 1970-01-01
  • 2017-11-17
  • 1970-01-01
相关资源
最近更新 更多