【问题标题】:Fair comparison of fork() Vs Thread [closed]fork() 与 Thread 的公平比较 [关闭]
【发布时间】:2011-04-25 12:01:07
【问题描述】:

我正在讨论 fork() 与 thread() 用于并行化任务的相对成本。

我们了解进程与线程之间的基本区别

线程:

  • 线程间易于通信
  • 快速上下文切换。

进程:

  • 容错。
  • 与父母沟通不是真正的问题(打开管道)
  • 难以与其他子进程通信

但我们在进程与线程的启动成本上存在分歧。
因此,为了测试这些理论,我编写了以下代码。我的问题:这是衡量启动成本的有效测试还是我遗漏了什么。此外,我会对每个测试在不同平台上的执行情况感兴趣。

fork.cpp

#include <boost/lexical_cast.hpp>
#include <vector>
#include <unistd.h>
#include <iostream>
#include <stdlib.h>
#include <time.h>

extern "C" int threadStart(void* threadData)
{
    return 0;
}

int main(int argc,char* argv[])
{
    int threadCount =  boost::lexical_cast<int>(argv[1]);

    std::vector<pid_t>   data(threadCount);
    clock_t                 start   = clock();
    for(int loop=0;loop < threadCount;++loop)
    {
        data[loop]  = fork();
        if (data[looo] == -1)
        {
            std::cout << "Abort\n";
            exit(1);
        }
        if (data[loop] == 0)
        {
            exit(threadStart(NULL));
        }
    }
    clock_t                 middle   = clock();
    for(int loop=0;loop < threadCount;++loop)
    {
        int   result;
        waitpid(data[loop], &result, 0);
    }
    clock_t                 end   = clock();

   std::cout << threadCount << "\t" << middle - start << "\t" << end - middle << "\t"<< end - start << "\n";
}

线程.cpp

#include <boost/lexical_cast.hpp>
#include <vector>
#include <iostream>
#include <pthread.h>
#include <time.h>


extern "C" void* threadStart(void* threadData)
{
    return NULL;
}   

int main(int argc,char* argv[])
{
    int threadCount =  boost::lexical_cast<int>(argv[1]);

    std::vector<pthread_t>   data(threadCount);

    clock_t                 start   = clock();
    for(int loop=0;loop < threadCount;++loop)
    {
        if (pthread_create(&data[loop], NULL, threadStart, NULL) != 0)
        {
            std::cout << "Abort\n";
            exit(1);
        }
    }   
    clock_t                 middle   = clock();
    for(int loop=0;loop < threadCount;++loop)
    {   
        void*   result;
        pthread_join(data[loop], &result);
    }
    clock_t                 end   = clock();

   std::cout << threadCount << "\t" << middle - start << "\t" << end - middle << "\t"<< end - start << "\n";

}

我希望 Windows 在进程创建方面做得更差。
但我希望现代类 Unix 系统的分叉成本相当低,并且至少可以与线程相媲美。在较旧的 Unix 风格系统上(在 fork() 被实现为在写入页面上使用复制之前),情况会更糟。

反正我的计时结果是:

> uname -a
Darwin Alpha.local 10.4.0 Darwin Kernel Version 10.4.0: Fri Apr 23 18:28:53 PDT 2010; root:xnu-1504.7.4~1/RELEASE_I386 i386
> gcc --version | grep GCC
i686-apple-darwin10-gcc-4.2.1 (GCC) 4.2.1 (Apple Inc. build 5659)
> g++ thread.cpp -o thread -I~/include
> g++ fork.cpp -o fork -I~/include
> foreach a ( 1 2 3 4 5 6 7 8 9 10 12 15 20 30 40 50 60 70 80 90 100 )
foreach? ./thread ${a} >> A
foreach? end
> foreach a ( 1 2 3 4 5 6 7 8 9 10 12 15 20 30 40 50 60 70 80 90 100 )
foreach? ./fork ${a}  >> A
foreach? end
vi A 

Thread:                             Fork:
C   Start   Wait    Total           C   Start   Wait    Total
==============================================================
 1    26     145     171             1   160     37      197
 2    44     198     242             2   290     37      327
 3    62     234     296             3   413     41      454
 4    77     275     352             4   499     59      558
 5    91     107   10808             5   599     57      656
 6    99     332     431             6   665     52      717
 7   130     388     518             7   741     69      810
 8   204     468     672             8   833     56      889
 9   164     469     633             9  1067     76     1143
10   165     450     615            10  1147     64     1211
12   343     585     928            12  1213     71     1284
15   232     647     879            15  1360    203     1563
20   319     921    1240            20  2161     96     2257
30   461    1243    1704            30  3005    129     3134
40   559    1487    2046            40  4466    166     4632
50   686    1912    2598            50  4591    292     4883
60   827    2208    3035            60  5234    317     5551
70   973    2885    3858            70  7003    416     7419
80  3545    2738    6283            80  7735    293     8028
90  1392    3497    4889            90  7869    463     8332
100 3917    4180    8097            100 8974    436     9410

编辑:

做 1000 个孩子导致 fork 版本失败。
所以我减少了孩子的数量。但是做一个单一的测试也似乎不公平,所以这里有一个值范围。

【问题讨论】:

  • 看看这个关于线程和进程在性能方面有何不同的答案:stackoverflow.com/questions/3609469/…
  • 很难看出这将是一个有意义的测试。你还没有做任何事情来衡量设置 fork 所需的通信的成本。
  • fork 和 thread 之间的选择(几乎?)从不由性能驱动。功能完全不同;司机是“你想做什么”
  • @Hans Passant:公平点。不幸的是,这变得非常特定于任务。有什么建议。我只是想表明启动成本不是选择是否使用线程/进程的因素。
  • @Matt Joiner:这些都是在选择天气来使用线程/进程时要考虑的充分理由。我正在尝试(尚未成功)启动成本不是影响您决定的因素(差异很小,其他因素更重要)。

标签: c++ c multithreading unix fork


【解决方案1】:

mumble ...我不喜欢你的解决方案有很多原因:

  1. 您没有考虑子进程/线程的执行时间。

  2. 您应该比较 cpu-usage 而不是实际经过的时间。这样,您的统计信息就不会依赖于磁盘访问拥塞等因素。

  3. 让您的子进程做一些事情。请记住,“现代”fork 使用写时复制机制来避免在需要时将内存分配给子进程。立即退出太容易了。这样你就避免了 fork 的所有缺点。

  4. CPU 时间并不是您必须考虑的唯一成本。内存消耗和IPC速度慢都是fork方案的缺点。

您可以使用“rusage”而不是“clock”来衡量实际资源使用情况。

附:我认为您无法真正衡量编写简单测试程序的进程/线程开销。有太多的因素,通常,线程和进程之间的选择是由其他原因驱动的,而不仅仅是 cpu 使用率。

【讨论】:

  • 最后这是我的论点。创建线程或进程之间的成本差异是由许多使用因素驱动的。但是创建孩子的成本不应该是这些因素之一,因为差异是如此之小。
  • 您是否需要 CPU 时间或挂起时间取决于您是尝试使用更少的 CPU 周期还是只是更快地完成某件事。并行化时,您不能指望使用更少的周期,因此您可能只关心尽快完成某项工作,这意味着墙上时间是正确的衡量标准。
【解决方案2】:

在 Linux 下,fork 是对sys_clone 的特殊调用,无论是在库中还是在内核中。 Clone 有很多开关可以打开和关闭,每个开关都会影响启动成本。

实际的库函数 clone 可能比 fork 更昂贵,因为它做的更多,尽管大部分是在子端(堆栈交换和通过指针调用函数)。

【讨论】:

  • fork() 还有很多工作要做,例如调用 atfork 函数并清理其他线程的堆栈/TLS...
  • @psmears:你是对的。由于 glibc 的 fork 与 sys_fork 并不完全相同,因此还有很多额外的工作要做。
【解决方案3】:

该微基准测试显示的是线程创建和加入(我写这篇文章时没有分叉结果)需要几十或几百微秒(假设你的系统有 CLOCKS_PER_SEC=1000000,它可能有,因为它是XSI 要求)。

既然你说 fork() 花费的线程成本是线程成本的 3 倍,我们仍然在谈论最坏的十分之一毫秒。如果这在应用程序上很明显,您可以使用进程/线程池,就像 Apache 1.3 所做的那样。无论如何,我会说启动时间是一个有争议的问题。

线程与进程(在 Linux 和大多数类 Unix 上)的重要区别在于,在进程上,您可以明确选择要共享的内容,使用 IPC、共享内存(SYSV 或 mmap 样式)、管道、套接字(您可以发送AF_UNIX 套接字上的文件描述符,这意味着您可以选择要共享的 fd),...虽然在线程上,默认情况下几乎所有内容都是共享的,无论是否需要共享。事实上,这就是 Plan 9 有 rfork() 而 Linux 有 clone()(最近还有 unshare())的原因,所以你可以选择分享什么。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-02-21
    • 2011-01-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-30
    • 2016-04-01
    相关资源
    最近更新 更多