Жиынтық/қосылу негізі ағынды пулға қарағанда қалай жақсы?

Жаңа fork/join framework пайдалану артықшылықтары қандай? Бастапқыда N subtasks ішіне үлкен тапсырманы жай ғана бөліп, оларды кэштелген тақырып пулына жібереді ( Executors ) және әр тапсырманы аяқтауды күте ме? Мен форманың/қосылыс абстракциясының қолданылуын мәселені жеңілдететін не шешімді бірнеше жылдан бері қолдана отырып тиімдірек ететіндігін көрмеймін.

Мысалы, оқу үлгісіндегі параллельді бұлыңғырлық алгоритмі болуы мүмкін келесідей жүзеге асырылады:

public class Blur implements Runnable {
    private int[] mSource;
    private int mStart;
    private int mLength;
    private int[] mDestination;

    private int mBlurWidth = 15;//Processing window size, should be odd.

    public ForkBlur(int[] src, int start, int length, int[] dst) {
        mSource = src;
        mStart = start;
        mLength = length;
        mDestination = dst;
    }

    public void run() {
        computeDirectly();
    }

    protected void computeDirectly() {
       //As in the example, omitted for brevity
    }
}

Бастапқы бөлуге және тапсырмалар пулына жіберіңіз:

// source image pixels are in src
// destination image pixels are in dst
// threadPool is a (cached) thread pool

int maxSize = 100000;//analogous to F-J's "sThreshold"
List futures = new ArrayList();

// Send stuff to thread pool:
for (int i = 0; i < src.length; i+= maxSize) {
    int size = Math.min(maxSize, src.length - i);
    ForkBlur task = new ForkBlur(src, i, size, dst);
    Future f = threadPool.submit(task);
    futures.add(f);
}

// Wait for all sent tasks to complete:
for (Future future : futures) {
    future.get();
}

// Done!

Тапсырмалар жұмыс ағыны қол жетімді болғанда орындалатын жіп жинағының кезегіне өтеді. Бөлу жеткілікті түйіршікті болғандықтан (ең соңғы тапсырманы күтіп тұруды болдырмау үшін) және жіп пулының жеткілікті (кем дегенде N процессорлар) жіптері бар болса, барлық процессорлар толық есептелмейінше толық жылдамдықпен жұмыс істейді.

Мен бірдеңе жетіспеймін бе? Fork/join негізін пайдаланудың қосылған құны қандай?

103

10 жауаптар

Негізгі қате түсініктеме, Fork/Join мысалдары ЕМЕС ұрлау жұмысын көрсетеді, бірақ стандартты бөлуді және басып алуды ғана көрсетеді.

Жұмысты ұрлау мына сияқты еді: Жұмысшы B өз жұмысын аяқтады. Ол мейірімді, сондықтан ол айналаға қарайды және жұмысшы А әлі жұмыс істейді. Ол серуендеп, сұрайды: «Эй, балам, сізге қолымды бере аламын». Жауаптар. «Салқын, менің 1000 тапсырмам бар, әлі күнге дейін 655-тен 345 қалдым. 673-ден 1000-ға дейін жұмыс жасай аламын, мен 346-дан 672-ге дейін жасаймын». B былай дейді: «Жарайды, бастайық, сондықтан бұрын біз пабға барамыз.»

Өздеріңіз көріп отырсыздар - жұмысшылар бір-бірімен нақты жұмыс істеуді бастағанда да бір-бірімен байланысуы керек. Бұл мысалдардағы жоғалған бөлігі.

Екінші жағынан мысалдар тек «қосалқы мердігерлерді пайдалану» дегенді білдіреді:

Жұмысшы А: «Данг, менің 1000 бірлік жұмысым бар, мен үшін өте көп, мен өзім 500-тен өзім және 500-ге қосалқы мердігерімді басқа біреуге айналдырамын». Бұл үлкен міндет әрқайсысы 10 бірліктегі шағын пакеттерге бөлінгенше жалғасады. Оларды қолда бар қызметкерлер атқарады. Бірақ егер бір пакет - бұл басқа улы таблетка түрінде болса және басқа пакеттерге қарағанда әлдеқайда ұзағырақ болса - жаман сәт болса, бөлу фазасы аяқталды.

Форк/Қосу және міндеттерді алдын-ала бөлудің жалғыз қалған айырмашылығы мынада: Алдын ала бөлінгенде жұмысыңыздың кезегінен бастап толық құқықты болады. Мысал: 1000 бірлік, шегі 10, сондықтан кезекте 100 жазбалар бар. Бұл пакеттер threadpool мүшелеріне таратылады.

Форк/Қосылу аса күрделі және кезектегі пакеттердің санын аз ұстауға тырысады:

  • 1-қадам: Бір пакетті (1 ... 1000) кезекке қойыңыз
  • 2-қадам: Бір қызметкер пакетті шығарады (1 ... 1000) және оны екі пакетпен ауыстырады: (1 ... 500) және (501 ... 1000).
  • 3-қадам: Бір қызметкер пакетті шығарады (500 ... 1000) және басу (500 ... 750) және (751 ... 1000).
  • Step n: Бұл пакетте мына пакеттер бар: (1..500), (500 ... 750), (750 ... 875) ... (991..000)
  • Қадам n + 1: Пакет (991..1000) шығарылады және орындалады
  • Қадам n + 2: Пакет (981..990) шығарылады және орындалады
  • Қадам n + 3: Пакет (961..980) шығарылады және бөлінеді (961 ... 970) және (971..980). ....

Көресіз: Пышақтағы/Тіркеуге қосылыңыз (мысалдағы 6), ал «бөлік» және «жұмыс» фазалары бір-бірімен қиылысады.

Көптеген жұмысшылар бір мезгілде попинг жасап, бір-біріне итермелесе, өзара әрекеттесу әрине анық емес.

114
қосылды
Менің ойымша, бұл шынында да жауап. Мені қызықтырады ма, егер нақты жұмыс бар ма Форк/Біріктіру мысалдар, онда оның жұмысын көрсетуге болады? Алғашқы мысалдармен жұмыс жүктемесінің мөлшері құрылғы өлшемінен (мысалы, массивтің ұзындығы) алдын-ала болжанатын болады, сондықтан алдын-ала бөліну оңай. Ұрлық, әрине, бірлікке арналған жұмыс жүктемесінің көлемі емес , өлшем бірліктерінің өлшемінен жақсы болжанатын мәселелерде әр түрлі болады.
қосылды автор Joonas Pulakka, көзі
@Marc: Кешірім сұраймын, бірақ менің қолым жоқ.
қосылды автор A.H., көзі
Жұмысты ұрлаудың кейбір мысалдары ыңғайлы болуы мүмкін: h-online.com/developer/features/…
қосылды автор volley, көзі
Oracle мысалы IMO-ның проблемасы жұмысын ұрлауды көрсетпейді (ол А.Х. сипаттайды), бірақ қарапайым ThreadPool үшін алгоритмді кодтау оңай емес (ол Джонас сияқты). F-J жұмыстың жеткілікті тәуелсіз міндеттерге дейін бөлінбеуі мүмкін, бірақ өз кезегінде тәуелсіз болып табылатын тапсырмаларға қайталануы мүмкін. Мысал үшін жауапты қараңыз
қосылды автор Edson Medina, көзі
А.Х. Егер сіздің жауапыңыз дұрыс болса, онда ол қалай түсіндірмейді. Oracle берген мысал жұмыс ұрлауға әкелмейді. Мұнда сипатталған мысалдағыдай жұмыс істеу және жұмысқа қалай қосылу керек? Сізге фуксты жасайтын және оны ұрлауға болатын кейбір Java кодын көрсету сіз оны сипаттағандай боласыз ба? рахмет
қосылды автор Marc, көзі

Егер сізде жұмыс істемейтін барлық жіптердің барлығы 100% дербес жұмыс істесе, бұл Fork-Join (FJ) бассейніндегі n threads қарағанда жақсы болады. Бірақ бұл ешқашан солай істемейді.

Мәселені дәл солай бөлуге болмайды. Тіпті егер сіз жасасаңыз, жіптерді жоспарлау әділ болып қалады. Ең баяу жіп күте тұрасыз. Егер сізде бірнеше тапсырма бар болса, онда олар әрқайсысы n-параллелизмнен (әдетте тиімдірек) аз жүгіре алады, бірақ басқа тапсырмалар аяқталған кезде n-way-ге дейін өтуге болады.

Неліктен біз бұл мәселені FJ өлшемді бөліктерге ғана кесіп қоямыз және осыған байланысты жіппен жұмыс істейміз. Әдеттегі FJ пайдалану мәселені кішкене бөліктерге бөледі. Мұны кездейсоқ тәртіпте жасау аппараттық деңгейде көп үйлестіруді талап етеді. Қажетті шығындар өлтіруші еді. FJ-де тапсырмалар соңғы біріншілік тәртіпте (LIFO/stack) оқылатын кезекке қойылады және ұрлық жұмысын (негізінен негізгі жұмыста) Бірінші Алдымен (FIFO/«кезек») жасайды. Нәтиже - ұзын жиым өңдеуді кросс бөліктерге бөлінсе де, көбінесе дәйекті түрде жасауға болады. (Сондай-ақ, бір үлкен бандағы проблеманы біркелкі мөлшерге дейін азайту үшін тривиаль болмауы мүмкін.Егер теңгерімсіз иерархияның қандай да бір түрімен айналысатын болсаңыз.)

Қорытынды: FJ аппараттық жіптерді біркелкі емес жағдайларда тиімдірек пайдалануға мүмкіндік береді, бұл сізде бірден көп жіп бар.

23
қосылды
Бірақ неге FJ ең баяу жіпді күтуді тоқтатады? Алдын ала тапсырыстар саны бар, және, әрине, олардың кейбіреуі әрқашан аяқтау үшін соңғы болады. Мысалдағы maxSize параметрін реттеу FJ мысалында («code> compute() әдісімен жасалынған)« бинарлық бөлшектеу »сияқты ұқсас субтаскалық бөлімшені береді, немесе invokeAll() ) қосалқы тапсырмалар жібереді.
қосылды автор Joonas Pulakka, көзі
Жақсы, егер қосалқы тапсырмалар саны параллельді өңдеуге болатын нәрселерден үлкенірек болса (бұл соңғы күтуге жол бермеу үшін мағынасы бар болса), онда координациялық мәселелерді көре аламын. FJ үлгісі , егер бөлу қажет болса, жаңылысуы мүмкін бұл түйіршіктелген: ол 1000x1000 кескіні үшін әрқайсысы 62500 элемент өңдейтін 16 нақты субтаскаларды шығаратын 100000 шегін пайдаланады. 10000x10000 сурет үшін 1024 субтаск болады, бұл қазірдің өзінде бірдеңе.
қосылды автор Joonas Pulakka, көзі
Себебі олар әлдеқайда аз - менің жауапымды қосатын боламын.
қосылды автор Tom Hawtin - tackline, көзі

Fork/join is different from a thread pool because it implements work stealing. From Fork/Join

Any ExecutorService секілді, fork/join құрылымы тапсырмаларды бөледі   жіптер пулындағы жұмысшы ағындарына. Форштовка/біріктіру жүйесі - бұл   өйткені ол жұмыс ұрлау алгоритмін пайдаланады. Жұмысшы тақырыптары   бұл нәрселердің аяқталуы, басқа ағындардан тапсырмаларды ұрлата алады   әлі бос емес.

Айтыңызшы, сізде екі жіп бар, және 1, 1, 5 және 6 секундты алатын а, b, c, d 4 тапсырмасы. Бастапқыда a және b 1-ге және c және d-ге жіпке 2 тағайындалады. Жиынтық пулға 11 секунд кетеді. Форманың/қосылыстың көмегімен 1-сабақ аяқталады және 2-секундтан жұмыс істей алады, сонан соң тапсырма 1-кесте бойынша орындалады. 1-тақырыпты a, b және d, 2-тақырыпты ғана орындайды. Жалпы уақыты: 8 секунд емес, 11.

EDIT: Joonas белгілегендей, тапсырмалар міндетті түрде алдын-ала бөлінген ағын болып табылмайды. Fork/join-дің идеясы мынада, бұл тапсырма бірнеше бөлікке бөлуге болады. Мәселен, жоғарыда көрсетілгендерді қайта айқындау үшін:

We have two tasks (ab) and (cd) which take 2 and 11 seconds respectively. Thread 1 starts to execute ab and split it into two sub-tasks a & b. Similarly with thread 2, it splits into two sub-tasks c & d. When thread 1 has finished a & b, it can steal d from thread 2.

12
қосылды
@ Мэтью Фаруэлл: Әр тапсырма бойынша FJ үлгісі ішінде , compute() тапсырманы есептейді немесе оны екі субтаскаға бөледі. Қандай опция тапсырма өлшеміне ( if (mLength ) байланысты, тек байланысты, сондықтан бұл тапсырманың белгіленген санын жасаудың керемет тәсілі. 1000x1000 кескін үшін шын мәнінде бірдеңені есептейтін 16 субтаск болады. Бұдан басқа, қосалқы тапсырмаларды жасайтын және шақыратын және өздігінен есептемейтін 15 (= 16-1) «аралық» тапсырмалар болады.
қосылды автор Joonas Pulakka, көзі
@ Мэтью Фарвел: Мүмкін, мен барлық FJ-ді түсінбеймін, бірақ егер subtask computeDirectly() әдісін орындауды шешсе, ештеңені ұрлауға ешқандай мүмкіндік жоқ. Барлық бөлшектеу, кем дегенде, мысалда априори орындалды.
қосылды автор Joonas Pulakka, көзі
AFAIK бар бір бір Queue for ThreadPoolExecutor, ол өз кезегінде бірнеше тақырыптарын басқарады. Бұл тапсырмаларды немесе Runnables (Жібергіш емес) тапсырмаларын орындаушыға тапсыру, сондай-ақ нақты тапсырмаларға алдын ала бөлінбейді. Сонымен қатар, FJ бұл жолы да жасайды. Қазіргі уақытта FJ-ні пайдаланудың пайдасы жоқ.
қосылды автор A.H., көзі
@JoonasPulakka: Мен осы пікірталаста айтылған нәрселерді шешуге тырысатын жауап жаздым.
қосылды автор A.H., көзі
@ А.Х. Ия, бірақ fork/join ағымдағы тапсырманы бөлуге мүмкіндік береді. Тапсырманы орындайтын жіп оны екі түрлі тапсырмаға бөлуі мүмкін. Сонымен, ThreadPoolExecutor сізде тапсырмалар тізімі бар. Fork/join арқылы орындаушы тапсырма өз тапсырмасын екіге бөліп, кейін олар өз жұмысын аяқтаған кезде басқа ағындармен алынады. Немесе сіз бірінші болып аяқтасаңыз.
қосылды автор Matthew Farwell, көзі
@Joonas, иә, бұл бөлу үшін таңдаған стратегия. Бірақ олар әрбір subtask қанша уақыт алады білмейді. Мүмкін, бір субтасканы 1 секунд алады, ал басқа (бірдей өлшемде) 15 секунд кетеді. Мұндай жағдайда жұмыс ұрлау болуы мүмкін. Мүмкін, сіз айтып жатқан нәрсені түсінбеймін.
қосылды автор Matthew Farwell, көзі

Жоғарыда айтылғандардың бәрі дұрыс жұмыс жасайды, бірақ бұл неге екенін түсіну.

Ең бастысы - жұмысшы ағындары арасындағы тиімді үйлестіру. Жұмысты үйлестіруді талап ететін бөлуге және қайта жинауға тура келеді. Жоғарыда көрсетілгендей, әрбір жіптің жеке жұмыс тізімі бар. Бұл тізімді маңызды қасиеті - ол сұрыпталған (төменгі жағында жоғарғы және шағын тапсырмалардағы үлкен тапсырмалар). Әрбір жіп тізімнің төменгі жағындағы тапсырмаларды орындайды және басқа тізбектердің жоғарғы жағындағы тапсырмаларды орындайды.

Нәтижесінде:

  • Тапсырмалар тізімдерінің басы мен жағы тізімдегі дауды азайтып, өздігінен синхрондалуы мүмкін.
  • Жұмыстың елеулі қосалқы элементтері бірдей жіппен бөлініп, қайта топтастырылады, сондықтан осы субтитрлер үшін ешқандай интервалды үйлестіру қажет емес.
  • Ілгектерді ұрлаған кезде үлкен бөлігін алады, содан кейін ол өз тізіміне бөлінеді
  • Жұмыстың құюы процестердің аяқталуына дейін жіптер толығымен қолданылатындығын білдіреді.

Көптеген басқа бөлімдер мен жіптер пулдарын қолданатын схемалар фракциялардың өзара әрекеттесуін және үйлестіруді талап етеді.

10
қосылды

Бұл мысалда Fork/Join ешқандай мәнді қосады, себебі форка қажет емес және жұмыс жүктемесі жұмысшы ағындары бойынша біркелкі бөлінеді. Форк/Қосылу тек үстемеақы қосады.

Мұнда Жақсы мақала тақырып бойынша. Цитата:

Жалпы, ThreadPoolExecutor артықшылықты болуы керек деп айта аламыз   онда жұмыс жүктемесі жұмысшы ағындары бойынша біркелкі бөлінеді. Болуы үшін   бұл кепілдік беру үшін, деректердің ненің дәл екендігін білуіңіз керек   ұқсас. Керісінше, ForkJoinPool жақсы нәтиже береді   кіріс деректеріне қарамастан, ол әлдеқайда сенімді   шешім.

10
қосылды
Өте жақсы мақала, рахмет!
қосылды автор Joonas Pulakka, көзі

Жиынтық пулдар мен Fork/Join негізгі мақсаттары бірдей: екеуі де қол жетімді CPU қуатын максималды өткізгіштікке қол жеткізу үшін барынша тиімді пайдалануды қалайды. Ең көп өткізу қабілеті - мүмкіндігінше көптеген тапсырмаларды ұзақ уақыт бойы аяқтау керек дегенді білдіреді. Мұны істеу үшін не істеу керек? (Төменде біз есеп айырысу міндеттерінің жетіспеушілігі жоқ деп есептейміз: 100% CPU пайдалану үшін әрдайым жеткілікті, сонымен қатар, гипершегілі жағдайда ядро ​​немесе виртуалды ядролар үшін «CPU» -ді қолданамын.

  1. Кем дегенде, процессорлар қол жетімді болғандықтан, қаншалықты көп жұмыс істейтінін білу керек, өйткені кемінде жұмыс істемейтіндер пайдаланылмайтын ядродан шығып кетеді.
  2. Максимумда, процессорлар қол жетімді болғандықтан, көптеген процестер орындалуы керек, себебі көп жұмыс істейтін ағындар процессорларды әртүрлі ағындарға тағайындайтын Жоспарлаушы үшін қосымша жүктеме жасайды, бұл кейбір CPU уақытын жоспарлаушыға емес, есептеуішке тапсырма.

Осылайша, максималды өткізу үшін біз процессорларға қарағанда, дәл осындай сымдардың санын білуіміз керек. Oracle-дің бұлдыр көрінісінде сіз қол жетімді CPU-дың санына тең немесе ағындық пулды қолданатын ағындардың саны бар тіркелген өлшемді пул пулын қабылдай аласыз. Бұл өзгеріс жасамайды, сіз дұрыссыз!

So when will you get into trouble with a thread pools? That is if a thread blocks, because your thread is waiting for another task to complete. Assume the following example:

class AbcAlgorithm implements Runnable {
    public void run() {
        Future aFuture = threadPool.submit(new ATask());
        StepBResult bResult = stepB();
        StepAResult aResult = aFuture.get();
        stepC(aResult, bResult);
    }
}

Бұл жерде біз А, В және С үш қадамнан тұратын A және B үш қадамнан тұратын алгоритм болып табылады. А және В бір-біріне тәуелсіз орындала алады, бірақ C қадамы A және А қадамдарының нәтижесін қажет етеді. Бұл алгоритм А тапсырмасын тапсырады threadpool және b тапсырмасын орындаңыз. Осыдан кейін, ағын А тапсырмасын орындауды күтіп, C қадамымен жалғасады. Егер A және B бір уақытта аяқталса, бәрі жақсы. Бірақ егер А қарағанда ұзағырақ болса, не істеу керек? Мүмкін, А тапсырмасының сипаты оны талап етеді, бірақ ол болмайды, себебі болмайды  тапсырма үшін ағыны Бастапқыда және тапсырмада қол жетімді Күте тұру қажет. (Егер сізде тек бір ғана CPU бар болса және сіздің threadpool-ң тек бір ғана жіп бар болса, бұл тіпті тұйықтауды тудырады, бірақ қазір бұл нүктеден басқа). Мәселе мынада, B тапсырмасын орындаған жіп толығымен блоктайды . Себебі бізде процессорлар саны бірдей және бір жіп бұғатталғандықтан, бір CPU бос тұрған дегенді білдіреді.

Форк/Қосылу осы мәселені шешеді: fork/join құрылымында сіз келесідей алгоритмді жазасыз:

class AbcAlgorithm implements Runnable {
    public void run() {
        ATask aTask = new ATask());
        aTask.fork();
        StepBResult bResult = stepB();
        StepAResult aResult = aTask.join();
        stepC(aResult, bResult);
    }
}

Солай көрінеді, олай емес пе? Алайда, aTask.join бұғатталмайды . Оның орнына мұнда work-ұрлау ойнатылып жатады: ағын өткен уақытта жалғанған басқа тапсырмалар үшін айналады және олармен жалғасады. Алдымен ол өз міндеттерін шешіп қойған-жасамағанын тексереді. Мәселен, егер А басқа бірдеңе басталмаса, ол келесі жасайды, әйтпесе ол басқа да кезектің кезегін тексеріп, олардың жұмысын ұрлайды. Басқа жіптің бұл басқа тапсырмасы аяқталғаннан кейін, ол қазір A аяқталғанын тексереді. Егер жоғарыда аталған алгоритм болса, stepC деп атауға болады. Әйтпесе, ұрлау үшін тағы бір тапсырма қажет. Осылайша, шлюздер/біріктірілген бассейндер 100% CPU-ны қолдануға қол жеткізе алады, тіпті бұғаттау әрекеттерінің алдында да .

Алайда, тұзақ бар: join қоңырауы үшін ForkJoinTask s. Сыртқы блоктау әрекеттеріне басқа бір ағынды күту немесе енгізу/шығару әрекетін күту сияқты жасалмайды. Сонымен, I/O аяқтауды күтіп отырған бұл жалпы мәселе болып табылады ма? Бұл жағдайда, егер бұғаттау әрекеті аяқталғаннан кейін қайтадан тоқталатын Форк/Біріктіру пулына қосымша жұмыс ағыны қоссаңыз, бұл екінші ең жақсы нәрсе. Және ForkJoinPool шынымен де, егер біз ManagedBlocker пайдаланатын болсақ.

Фибоначчи

In the JavaDoc for RecursiveTask is an example for calculating Фибоначчи numbers using Fork/Join. For a classic recursive solution see:

public static int fib(int n) {
    if (n <= 1) {
        return n;
    }
    return fib(n - 1) + fib(n - 2);
}

As is explained int the JavaDocs this is a pretty dump way to calculate Фибоначчи numbers, as this algorithm has O(2^n) complexity while simpler ways are possible. However this algorithm is very simple and easy to understand, so we stick with it. Let's assume we want to speed this up with Fork/Join. A naive implementation would look like this:

class Фибоначчи extends RecursiveTask {
    private final long n;

    Фибоначчи(long n) {
        this.n = n;
    }

    public Long compute() {
        if (n <= 1) {
            return n;
        }
        Фибоначчи f1 = new Фибоначчи(n - 1);
        f1.fork();
        Фибоначчи f2 = new Фибоначчи(n - 2);
        return f2.compute() + f1.join();
   }
}

Бұл тапсырмадағы қадамдар тым қысқа, сондықтан бұл өте қорқынышты болып көрінеді, бірақ құрылымның әдетте қалай жақсы жұмыс істейтінін көруге болады: Екі жиынтығын өздігінен есептеуге болады, бірақ содан кейін біз екеуіміз де финалды нәтижесі. Осылайша, бір жартысы басқа жіпте жасалады. Көңіл-күйге жол бермей-ақ жіппен бассейндермен бірдей айналысыңыз (мүмкін, бірақ оңай емес).

Just for completeness: If you'd actually want to calculate Фибоначчи numbers using this recursive approach here is an optimized version:

class ФибоначчиBigSubtasks extends RecursiveTask {
    private final long n;

    ФибоначчиBigSubtasks(long n) {
        this.n = n;
    }

    public Long compute() {
        return fib(n);
    }

    private long fib(long n) {
        if (n <= 1) {
            return 1;
        }
        if (n > 10 && getSurplusQueuedTaskCount() < 2) {
            final ФибоначчиBigSubtasks f1 = new ФибоначчиBigSubtasks(n - 1);
            final ФибоначчиBigSubtasks f2 = new ФибоначчиBigSubtasks(n - 2);
            f1.fork();
            return f2.compute() + f1.join();
        } else {
            return fib(n - 1) + fib(n - 2);
        }
    }
}

This keeps the subtasks much smaller because they are only split when n > 10 && getSurplusQueuedTaskCount() < 2 is true, which means that there are significantly more than 100 method calls to do (n > 10) and there are not very man tasks already waiting (getSurplusQueuedTaskCount() < 2).

Менің компьютерімде (4 ядро ​​(Hyper-threading санағанда 8), Intel (R) Core (TM) i7-2720QM CPU @ 2.20 ГГц) fib (50) 64 секундты классикалық тәсілмен алады және Fork/Join әдісімен 18 секунд өткенде, ол теориялық жағынан мүмкін емес.

Резюме

  • Ия, сіздің Fork/Join мысалында классикалық пішіндерден артықшылық жоқ.
  • Оқшауланған/қосылуға тыйым салу кезінде өнімділікті айтарлықтай жақсартуға болады
  • Шлюз/қосылыс кейбір тұйықталу проблемаларын айналып өтеді
8
қосылды

Тағы бір маңызды айырмашылық, F-J-мен сіз бірнеше, күрделі «қосылу» фазаларын жасай аласыз. http://faculty.ycp.edu/~dhovemey/spring2011/cs365 бөлімінен біріктіру сұрыптауын қарастырыңыз /lecture/lecture18.html , бұл жұмысты алдын-ала бөлуге қажетті тым көп оркестр болады. мысалы, Сіз келесі әрекеттерді орындауыңыз керек:

  • бірінші тоқсанды сұрыптау
  • екінші тоқсанды сұрыптау
  • бірінші 2 тоқсан біріктіреді
  • үшінші тоқсанды сұрыптау
  • төртінші тоқсанды сұрыптау
  • соңғы 2 тоқсанды біріктіру
  • 2 жартысын біріктіру

Өздеріңізге және т.б. қатысты біріктірулер алдында сортты жасау керек екенін қалай анықтайсыз?

Мен тізім элементтерінің әрқайсысы үшін белгілі бір нәрсені істеудің жақсы екенін қарап тұрдым. Мен тізімді алдын-ала бөліп, стандартты ThreadPool қолданамын деп ойлаймын. F-J жұмыс жеткілікті тәуелсіз тапсырмаларға алдын-ала бөлінбеуі мүмкін, бірақ бір-бірінен тәуелсіз болып табылатын тапсырмаларға рекурсивті түрде бөлінуі мүмкін (мысалы, жартыларды сұрыптау тәуелсіз, бірақ 2 сұрыпталған жартыларды сұрыпталған тұтасқа біріктіру мүмкін емес).

7
қосылды

F/J қымбат біріктіру операциялары болған кезде ерекше артықшылығы бар. Өйткені ол ағаш құрылымына бөлінеді, тек log2 (n) біріктіреді, n айырмашылығы желілік жіп бөлінуімен біріктіріледі. (Бұл, сізде процестер сияқты көптеген процессорлар бар, бірақ әлі де артықшылығы бар теориялық болжамды жасайды). Үй тапсырмаларын орындау үшін әрбір индекстегі мәндерді қосу арқылы бірнеше мың екі өлшемді массивтерді (бірдей өлшемдерді) біріктіру керек болды. Fork қосылымы және P процессорларымен P2 логияға (n) жақындайды, өйткені P P шексіздікке жақындайды.

1 2 3 .. 7 3 1 .... 8 5 4
4 5 6 + 2 4 3 => 6 9 9
7 8 9 .. 1 1 0 .... 8 9 9

5
қосылды

Егер мәселе басқа ағындардың аяқталуын күтуге тура келсе (массивтің массасын немесе массивтің жиыны сияқты), Орындаушы (Executors.newFixedThreadPool (2)) шектеулі болғандықтан, ағындардың саны. Forkjoin бассейні бұған ұқсас параллелизмді сақтау үшін бұғатталған жіптерді жабу үшін көп жағдайда жіптерді жасайды

Source: http://www.oracle.com/technetwork/articles/java/fork-join-422606.html

Алгоритмдерді бөлуді және алуды жүзеге асыру үшін орындаушылармен проблема субтаскаларды жасаумен байланысты емес, себебі Callable өз орындаушысына жаңа субтасканы жіберіп, оның нәтижесін синхронды немесе асинхронды түрде күте алады. Мәселе параллелизм болып табылады: егер Callable басқа Callable нәтижесін күтсе, ол күту күйіне қойылады, осылайша басқа шақыру кезегін орындауға мүмкіндік береді.

Doug Lea әрекеті арқылы Java SE 7-дегі java.util.concurrent бумасына қосылған форекс/қосылыс құрылымы бұл айырмашылықты толтырады

Source: https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ForkJoinPool.html

Пул, егер кейбір тапсырмалар басқаларға қосылуды күтіп тұрса да, ішкі жұмысшы ағындарын динамикалық түрде қосу, тоқтата тұру немесе қайта қосу арқылы белсенді (немесе қол жетімді) ағындарды ұстап тұруға тырысады. Дегенмен, бұғатталған IO немесе басқа басқарылмаған синхрондау кезінде мұндай түзетулер кепілдендірілмейді

public int getPoolSize() Returns the number of worker threads that have started but not yet terminated. The result returned by this method may differ from getParallelism() when threads are created to maintain parallelism when others are cooperatively blocked.

2
қосылды

Сіз ForkJoin қолданбасында қолданушы ретінде қолдануға таң қаласыз. мұнда ең жақсы Үйреншікті аласыз.

Форк/Биржаның логикасы өте қарапайым: (1) әр үлкен тапсырманы бөлек (арқан)   кішігірім тапсырмаларға; (2) әр тапсырманы бөлек ағынға өңдеу   (егер қажет болса, оларды кішігірім міндеттерге бөліп); (3) қосылыңыз   нәтижелер.
1
қосылды