Подтвердить что ты не робот

Справедливое сравнение fork() Vs Thread

Я обсуждал относительную стоимость fork() Vs thread() для распараллеливания задачи.

Мы понимаем основные различия между процессами Vs Thread

Тема:

  • Простая связь между потоками
  • Быстрое переключение контекста.

Процессы:

  • Отказоустойчивость.
  • Общение с родителем не является реальной проблемой (откройте канал)
  • Связь с другими дочерними процессами жесткая

Но мы не согласились на стартовую стоимость процессов Vs-потоков.
Поэтому для проверки теорий я написал следующий код. Мой вопрос: Является ли это действительным тестом на измерение начальной стоимости или чего-то не хватает. Также мне будет интересно, как каждый тест выполняется на разных платформах.

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";
}

Thread.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.
Поэтому я уменьшил количество детей. Но выполнение одного теста также кажется несправедливым, поэтому здесь представлен ряд значений.

4b9b3361

Ответ 1

mumble... Мне не нравится ваше решение по многим причинам:

  • Вы не учитываете время выполнения дочерних процессов/потоков.

  • Вы должны сравнить использование процессора не годовое истекшее время. Таким образом, ваша статистика не будет зависеть, например, от перегрузки на диске.

  • Пусть ваш ребенок обрабатывает что-то. Помните, что "современная" вилка использует механизмы копирования на запись, чтобы избежать выделения памяти дочернему процессу до тех пор, пока это не понадобится. Слишком легко выйти немедленно. Таким образом, вы избегаете совершенно всех недостатков вилки.

  • Время процессора - это не единственная стоимость, которую вы должны учитывать. Потребление памяти и медлительность IPC являются недостатками решения fork.

Вы можете использовать "rusage" вместо "clock" для измерения реального использования ресурсов.

P.S. Я не думаю, что вы действительно можете измерить процесс/поток накладных, написание простой тестовой программы. Слишком много факторов, и, как правило, выбор между потоками и процессами обусловлен другими причинами, чем просто использование процессора.

Ответ 2

В Linux fork есть специальный вызов sys_clone, либо внутри библиотеки, либо внутри ядра. Clone имеет много переключателей для включения и выключения, и каждый из них влияет на то, как дорого начать.

Фактическая функция библиотеки clone, вероятно, дороже, чем 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()), поэтому вы можете выбрать, что поделиться.