Re[4]: эффективная реализация thread pool
От: barney  
Дата: 21.05.18 17:18
Оценка:
К>
К>второй поток                                               исполнитель work1

К>second_task...
К>second_task...
К>wait(THE_FUTURE)
К>   another_task...
К>   another_task...
К>   wait(something_else)                                    work1...
К>      third_task...                                        THE_PROMISE.set_value
К>      third_task...
К>   awaited something_else - и вернёмся в another_task
К>   another_task...          а отнюдь не в second_task
К>   another_task...
К>awaited THE_FUTURE!
К>second_task...
К>


вот. в этом и проблема. нужно как то таски абстрагировать от потоков.
т.е если в левом потоке second_task повис на ожидании — воркер со стеком надо как то "закрыть"
и если там уже исполняется another_task то promise.set должен не прерывать стек первого потока,
а в новом потоке "раскрыть" second_task с его стеком.
не представляю, как это сделать вручную.
как вариант — в воркерах вообще запретить блокироваться по wait.
и любые зависимые переменные — вводить в новый воркер параметром

auto worker1 = []() -> int { return do_long_calc(); }
auto worker2 = [](int future_result){ use(future_result); }

Pool::dispatch(worker1).then(worker2); // здесь пул должен сам увидеть что worker1 возвращает значение,
которое нужно будет сохранить и запулить второму воркеру.
Re[5]: эффективная реализация thread pool
От: Кодт Россия  
Дата: 21.05.18 17:59
Оценка:
Здравствуйте, barney, Вы писали:

К>>(иллюстрация бешеной латентности из-за рекурсии)


B>вот. в этом и проблема. нужно как то таски абстрагировать от потоков.

B>т.е если в левом потоке second_task повис на ожидании — воркер со стеком надо как то "закрыть"
B>и если там уже исполняется another_task то promise.set должен не прерывать стек первого потока,
B>а в новом потоке "раскрыть" second_task с его стеком.
B>не представляю, как это сделать вручную.
B>как вариант — в воркерах вообще запретить блокироваться по wait.
B>и любые зависимые переменные — вводить в новый воркер параметром

B>auto worker1 = []() -> int { return do_long_calc(); }

B>auto worker2 = [](int future_result){ use(future_result); }

B>Pool::dispatch(worker1).then(worker2); // здесь пул должен сам увидеть что worker1 возвращает значение,

B>которое нужно будет сохранить и запулить второму воркеру.

Ну да, сделать бесстековые сопрограммы, примерно так, как в яваскрипте. Или какое-нибудь пи-исчисление.
Каждая функция должна внутри не зависеть / не зависать от заданий, выполняющихся в этом тредпуле.
Перекуём баги на фичи!
Re[6]: эффективная реализация thread pool
От: reversecode google
Дата: 21.05.18 18:02
Оценка:
К>Ну да, сделать бесстековые сопрограммы, примерно так, как в яваскрипте. Или какое-нибудь пи-исчисление.
К>Каждая функция должна внутри не зависеть / не зависать от заданий, выполняющихся в этом тредпуле.

зачем читать топик
http://rsdn.org/forum/cpp/7147121.1
Автор: reversecode
Дата: 17.05.18
Re[4]: эффективная реализация thread pool
От: Кодт Россия  
Дата: 21.05.18 18:12
Оценка:
Здравствуйте, AndrewJD, Вы писали:

AJD>future::then позволяет указать колбэк, который вызвовится когда результат будет готов. 'future::then' есть в boost, facebook folly и будет в следующем стандарте.


А сопрограммы будут в следующем стандарте?
Перетащат из буста?
Перекуём баги на фичи!
Re[5]: эффективная реализация thread pool
От: reversecode google
Дата: 21.05.18 18:22
Оценка:
К>А сопрограммы будут в следующем стандарте?
К>Перетащат из буста?

только стеклесс
Re[7]: эффективная реализация thread pool
От: Кодт Россия  
Дата: 22.05.18 16:38
Оценка:
Здравствуйте, reversecode, Вы писали:

R>зачем читать топик

R>http://rsdn.org/forum/cpp/7147121.1
Автор: reversecode
Дата: 17.05.18


Не "читать", а слушать коротенький доклад на 43 минуты, из которого примерно полчаса мотивационной речи.

И я конечно понимаю, на гитхабе лежит самодокументирующийся код, бери-читай-используй...
Но...
Перекуём баги на фичи!
Re[8]: эффективная реализация thread pool
От: barney  
Дата: 22.05.18 16:48
Оценка:
К>Не "читать", а слушать коротенький доклад на 43 минуты, из которого примерно полчаса мотивационной речи.
К>И я конечно понимаю, на гитхабе лежит самодокументирующийся код, бери-читай-используй...
К>Но...

Вот вот,
да и код rethinkdb — дико запутанный ад, с кучей платформенных зависимостей, захардкоженных с помощью #ifdef в препроцессоре

Вот полюбуйтесь
  адский ад
asm(
#if defined(__i386__) || defined(__x86_64__) || defined(__arm__) || defined (__s390x__) || defined (__powerpc64__)
// We keep architecture-specific code interleaved in order to enforce commonality.
#if defined(__x86_64__)
#if defined(__LP64__) || defined(__LLP64__)
// Pointers are of the right size
#else
// Having non-native-sized pointers makes things very messy.
#error "Non-native pointer size."
#endif
#endif // defined(__x86_64__)
".text\n"
"_lightweight_swapcontext:\n"

#if defined(__i386__)
    /* `current_pointer_out` is in `4(%ebp)`. `dest_pointer` is in `8(%ebp)`. */
#elif defined(__x86_64__)
    /* `current_pointer_out` is in `%rdi`. `dest_pointer` is in `%rsi`. */
#elif defined(__arm__)
    /* `current_pointer_out` is in `r0`. `dest_pointer` is in `r1` */
#elif defined(__s390x_)
    /* `current_pointer_out` is in `%r2`. `dest_pointer` is in `%r3`. */
#elif defined(__powerpc64__)
    /* `current_pointer_out` is in `r3`. `dest_pointer` is in `r4` */
#endif

    // Save preserved registers.
#if defined(__i386__)
    // Preserve esi, edi, ebx, and ebp. The return address is already on the stack.
    "push %esi\n"
    "push %edi\n"
    "push %ebx\n"
    "push %ebp\n"
#elif defined(__x86_64__)
    // Preserve r12-r15, rbx, and rbp. The return address is already on the stack.
    "pushq %r12\n"
    "pushq %r13\n"
    "pushq %r14\n"
    "pushq %r15\n"
    "pushq %rbx\n"
    "pushq %rbp\n"
#elif defined(__arm__)
    // Preserve r4-r12 and the return address (r14). For consistency with x86 r12 is
    // pushed first, followed by r14 and then r4-r11.
    "push {r12}\n"
    "push {r14}\n"
    "push {r4-r11}\n"
#elif defined(__arm64__) || defined(__aarch64__)
    // Preserve d8-d15 + x19-x29 and the return address (x30).
    // Note: x30 is stored twice due to alignment requirements
    "sub sp, sp, #0xb0\n"
    "stp d8,  d9,  [sp, #0x00]\n"
    "stp d10, d11, [sp, #0x10]\n"
    "stp d12, d13, [sp, #0x20]\n"
    "stp d14, d15, [sp, #0x30]\n"
    "stp x19, x20, [sp, #0x40]\n"
    "stp x21, x22, [sp, #0x50]\n"
    "stp x23, x24, [sp, #0x60]\n"
    "stp x25, x26, [sp, #0x70]\n"
    "stp x27, x28, [sp, #0x80]\n"
    "stp x29, x30, [sp, #0x90]\n"
    "str x30, [sp, #0xa0]\n"
#elif defined(__s390x__)
    // Preserve r6-r13, the return address (r14), and f8-f15.
    "aghi %r15, -136\n"
    "stmg %r6, %r14, 64(%r15)\n"
    "std %f8, 0(%r15)\n"
    "std %f9, 8(%r15)\n"
    "std %f10, 16(%r15)\n"
    "std %f11, 24(%r15)\n"
    "std %f12, 32(%r15)\n"
    "std %f13, 40(%r15)\n"
    "std %f14, 48(%r15)\n"
    "std %f15, 56(%r15)\n"
#elif defined(__powerpc64__)
    "addi 1, 1, -(21*8)\n"
    "std 2, (8*0)(1)\n"
    "std 14, (8*1)(1)\n"
    "std 15, (8*2)(1)\n"
    "std 16, (8*3)(1)\n"
    "std 17, (8*4)(1)\n"
    "std 18, (8*5)(1)\n"
    "std 19, (8*6)(1)\n"
    "std 20, (8*7)(1)\n"
    "std 21, (8*8)(1)\n"
    "std 22, (8*9)(1)\n"
    "std 23, (8*10)(1)\n"
    "std 24, (8*11)(1)\n"
    "std 25, (8*12)(1)\n"
    "std 26, (8*13)(1)\n"
    "std 27, (8*14)(1)\n"
    "std 28, (8*15)(1)\n"
    "std 29, (8*16)(1)\n"
    "std 30, (8*17)(1)\n"
    "std 31, (8*18)(1)\n"
    "mfcr 0\n"
    "std 0, (8*19)(1)\n"
    "mflr 0\n"
    "std 0, (8*20)(1)\n"
#endif

    /* Save old stack pointer. */
#if defined(__i386__)
    /* i386 passes arguments on the stack. We add ((number of things pushed)+1)*(sizeof(void*)) to esp in order to get the first argument. */
    "mov 20(%esp), %ecx\n"
    /* We then copy the stack pointer into the space indicated by the first argument. */
    "mov %esp, (%ecx)\n"
#elif defined(__x86_64__)
    /* On amd64, the first argument comes from rdi. */
    "movq %rsp, (%rdi)\n"
#elif defined(__arm__)
    /* On ARM, the first argument is in `r0`. `r13` is the stack pointer. */
    "str r13, [r0]\n"
#elif defined(__arm64__) || defined(__aarch64__)
    /* On ARM64, the first argument is in `x0`. `sp` is the stack pointer and `x4` is a scratch register. */
    "mov x4, sp\n"
    "str x4, [x0]\n"
#elif defined(__s390x__)
    /* On s390x, the first argument is in r2. r15 is the stack pointer. */
    "stg %r15, 0(%r2)\n"
#elif defined(__powerpc64__)
    "std  1, 0(3)\n"
#endif

    /* Load the new stack pointer and the preserved registers. */
#if defined(__i386__)
    /* i386 passes arguments on the stack. We add ((number of things pushed)+1)*(sizeof(void*)) to esp in order to get the first argument. */
    "mov 24(%esp), %esi\n"
    /* We then copy the second argument to be the new stack pointer. */
    "mov %esi, %esp\n"
#elif defined(__x86_64__)
    /* On amd64, the second argument comes from rsi. */
    "movq %rsi, %rsp\n"
#elif defined(__arm__)
    /* On ARM, the second argument is in `r1` */
    "mov r13, r1\n"
#elif defined(__arm64__) || defined(__aarch64__)
    /* On ARM64, the second argument is in `x1` */
    "mov sp, x1\n"
#elif defined(__s390x__)
    /* On s390x, the second argument is in r3 */
    "lgr %r15, %r3\n"
#elif defined(__powerpc64__)
    "mr 1, 4\n"
#endif

#if defined(__i386__)
    "pop %ebp\n"
    "pop %ebx\n"
    "pop %edi\n"
    "pop %esi\n"
#elif defined(__x86_64__)
    "popq %rbp\n"
    "popq %rbx\n"
    "popq %r15\n"
    "popq %r14\n"
    "popq %r13\n"
    "popq %r12\n"
#elif defined(__arm__)
    "pop {r4-r11}\n"
    "pop {r14}\n"
    "pop {r12}\n"
#elif defined(__arm64__) || defined(__aarch64__)
    "ldp d8,  d9,  [sp, #0x00]\n"
    "ldp d10, d11, [sp, #0x10]\n"
    "ldp d12, d13, [sp, #0x20]\n"
    "ldp d14, d15, [sp, #0x30]\n"
    "ldp x19, x20, [sp, #0x40]\n"
    "ldp x21, x22, [sp, #0x50]\n"
    "ldp x23, x24, [sp, #0x60]\n"
    "ldp x25, x26, [sp, #0x70]\n"
    "ldp x27, x28, [sp, #0x80]\n"
    "ldp x29, x30, [sp, #0x90]\n"
    "ldr x4, [sp, #0xa0]\n"
    "add sp, sp, #0xb0\n"
#elif defined(__s390x__)
    "lmg %r6, %r14, 64(%r15)\n"
    "ld %f8, 0(%r15)\n"
    "ld %f9, 8(%r15)\n"
    "ld %f10, 16(%r15)\n"
    "ld %f11, 24(%r15)\n"
    "ld %f12, 32(%r15)\n"
    "ld %f13, 40(%r15)\n"
    "ld %f14, 48(%r15)\n"
    "ld %f15, 56(%r15)\n"
    "aghi %r15, 136\n"
#elif defined(__powerpc64__)
    "ld 2, (8*0)(1)\n"
    "ld 14, (8*1)(1)\n"
    "ld 15, (8*2)(1)\n"
    "ld 16, (8*3)(1)\n"
    "ld 17, (8*4)(1)\n"
    "ld 18, (8*5)(1)\n"
    "ld 19, (8*6)(1)\n"
    "ld 20, (8*7)(1)\n"
    "ld 21, (8*8)(1)\n"
    "ld 22, (8*9)(1)\n"
    "ld 23, (8*10)(1)\n"
    "ld 24, (8*11)(1)\n"
    "ld 25, (8*12)(1)\n"
    "ld 26, (8*13)(1)\n"
    "ld 27, (8*14)(1)\n"
    "ld 28, (8*15)(1)\n"
    "ld 29, (8*16)(1)\n"
    "ld 30, (8*17)(1)\n"
    "ld 31, (8*18)(1)\n"
    "ld 0, (8*19)(1)\n"
    "mtcr 0\n"
    "ld 0, (8*20)(1)\n"
    "mtlr 0\n"
    "addi 1, 1, (8*21)\n"
#endif

#if defined(__i386__) || defined(__x86_64__)
    /* The following ret should return to the address set with
    `artificial_stack_t()` or with the previous `lightweight_swapcontext`. The
    instruction pointer is saved on the stack from the previous call (or
    initialized with `artificial_stack_t()`). */
    "ret\n"
#elif defined(__arm__)
    /* Above, we popped `LR` (`r14`) off the stack, so the bx instruction will
    jump to the correct return address. */
    "bx r14\n"
#elif defined(__arm64__) || defined(__aarch64__)
    /* Above, we stored the `x30` the return address in a variable register `x4` so the ret instruction will
    return it to jump. */
    "ret x4\n"
#elif defined(__s390x__)
    /* Above, we popped the return address (r14) off the stack. */
    "br %r14\n"
#elif defined(__powerpc64__)
    "blr\n"
#endif

#else
#error "Unsupported architecture."
#endif
);


Просто, я вот, вовсе не уверен что это заведется на всех платформах — ios, android, mac, linux, win
Да, пацаны круты — взяли и запилили корутины с переключением контекста,
но было бы интересно c++11 / posix решение без платформенных ассемблеров и жести
Re[9]: эффективная реализация thread pool
От: reversecode google
Дата: 22.05.18 17:53
Оценка:
да вперед, я посмотрю как вы сделаете переключение стека без ассемблера
стекфул корутины в бусте не зря на асме сделали, а не на longjump
но вы предпочитаете разбить лоб, сломать ногу, потерять руку и время, но самостоятельно доказать что это не возможно
Re[8]: эффективная реализация thread pool
От: reversecode google
Дата: 22.05.18 17:57
Оценка:
К>Не "читать", а слушать коротенький доклад на 43 минуты, из которого примерно полчаса мотивационной речи.

ну да, вы еще скажите что заходя в интернет у вас везде вплывает навязчивая реклама ...
я вот никакой коротенькой речи даже не увидел

К>И я конечно понимаю, на гитхабе лежит самодокументирующийся код, бери-читай-используй...

К>Но...

услышать это от какого то новичка я бы еще понял, но синьеру с не первым годом в С++ разобраться в трех строчках кода ...
Re[9]: эффективная реализация thread pool
От: Кодт Россия  
Дата: 22.05.18 22:21
Оценка:
Здравствуйте, reversecode, Вы писали:

R>услышать это от какого то новичка я бы еще понял, но синьеру с не первым годом в С++ разобраться в трех строчках кода ...


В которых трёх строчках кода из тех нескольки тысяч?
Просто вот рыться в библиотеке и реконструировать её дизайн и "что хотел сделать автор" (и явно автор не хотел сделать документацию) — могу, но без удовольствия.
Перекуём баги на фичи!
Re[10]: эффективная реализация thread pool
От: barney  
Дата: 23.05.18 02:02
Оценка:
R>да вперед, я посмотрю как вы сделаете переключение стека без ассемблера
R>стекфул корутины в бусте не зря на асме сделали, а не на longjump

а, удачи, берите себе в проект эту хрень из тысячи файлов, с platform specific кодом для 5 архитектур.
выйдет новый Интел, на котором все поломается, или очередной какой либо ARM128 — вам потом и поддерживать это всё,
если автор забьет.
завязывать себя на такое "решение" — зачем, чтобы усложнить себе жизнь?
для начала, поднять + оттестировать на имеющийся уже андроид с его 10000+ устройствами и сотнями разных SoC — удачи )

R>но вы предпочитаете разбить лоб, сломать ногу, потерять руку и время,


фууу, ну и помойный бред...
что то у тебя с головой явно не то, раз такие фантазии
Re[10]: эффективная реализация thread pool
От: reversecode google
Дата: 23.05.18 04:15
Оценка:
в тех которые в папке конкуренси
Re[11]: эффективная реализация thread pool
От: reversecode google
Дата: 23.05.18 04:20
Оценка:
вы упоротый ? где я вам сказал "бери" ?
я вам показал решение где это реализовано и можно изучить как оно устроено
но я вижу вы большой теоретик исследователь в теме "выпить стакан воды с закрытым ртом"
варианты вы уже почти все перебрали,
— залить через нос
— клизьмой через...
— подсказываю, еще внутривенно

ждем от вас результатов
Re[12]: эффективная реализация thread pool
От: barney  
Дата: 23.05.18 06:39
Оценка:
R>вы упоротый ? где я вам сказал "бери" ?
R>я вам показал решение где это реализовано и можно изучить как оно устроено
R>но я вижу вы большой теоретик исследователь в теме "выпить стакан воды с закрытым ртом"
R>варианты вы уже почти все перебрали,
R>- залить через нос
R>- клизьмой через...
R>- подсказываю, еще внутривенно

божежмой,
за ссылку спасибо, конечно,
но зачем же так негативить? что это за потоки каловых масс
вам же уже объяснили — ветка создана для разбора решений,
и общения по интересам
Re[11]: эффективная реализация thread pool
От: alex_public  
Дата: 24.05.18 02:02
Оценка:
Здравствуйте, barney, Вы писали:

R>>да вперед, я посмотрю как вы сделаете переключение стека без ассемблера

R>>стекфул корутины в бусте не зря на асме сделали, а не на longjump
B>а, удачи, берите себе в проект эту хрень из тысячи файлов, с platform specific кодом для 5 архитектур.
B>выйдет новый Интел, на котором все поломается, или очередной какой либо ARM128 — вам потом и поддерживать это всё,
B>если автор забьет.
B>завязывать себя на такое "решение" — зачем, чтобы усложнить себе жизнь?
B>для начала, поднять + оттестировать на имеющийся уже андроид с его 10000+ устройствами и сотнями разных SoC — удачи )

Это ты уже какую-то чушь пишешь. В библиотеку Boost.Context (которая как раз отвечает за переключение стеков в сопрограммах) входит всего по 3 очень простеньких asm файла на каждую архитектуру процессора (а вот их побольше чем 5, хотя и не сильно). Так что при появление гипотетического arm128 написание версии под него скорее всего займёт меньше одного часа...
Re[12]: эффективная реализация thread pool
От: barney  
Дата: 24.05.18 19:58
Оценка:
_>Это ты уже какую-то чушь пишешь. В библиотеку Boost.Context (которая как раз отвечает за переключение стеков в сопрограммах) входит всего по 3 очень простеньких asm файла на каждую архитектуру процессора (а вот их побольше чем 5, хотя и не сильно). Так что при появление гипотетического arm128 написание версии под него скорее всего займёт меньше одного часа...

Речь была о rethinkdb.

Boost в этом плане интереснее, да и тяготеет становиться стандартом,
но, вроде бы пока что он плохо заводится на Android NDK
Re[13]: эффективная реализация thread pool
От: reversecode google
Дата: 24.05.18 20:22
Оценка:
B>Boost в этом плане интереснее, да и тяготеет становиться стандартом,
B>но, вроде бы пока что он плохо заводится на Android NDK

вы свои фантазии оставьте при себе
яндекс уже на видео рассказал как у них карты на андроиде и айосе на этих корутинах работают
и не только эти сервисы, но это уже умение ресерчить что бы узнать
Re[14]: эффективная реализация thread pool
От: barney  
Дата: 25.05.18 00:44
Оценка: +1
R>вы свои фантазии оставьте при себе
R>яндекс уже на видео рассказал как у них карты на андроиде и айосе на этих корутинах работают
R>и не только эти сервисы, но это уже умение ресерчить что бы узнать

че ты такой важный? на умняке, как академик, вещаешь с таким гонором — "фантазии" "умение ресерчить" — типа, куда ж нам, простым смертным )
в детстве обижали?) как же нелепо это выглядит)))
будь проще, если есть что сказать,
а то пафосу столько, что гляди лопнешь от собственной важности и превосходства )
Re[13]: эффективная реализация thread pool
От: alex_public  
Дата: 26.05.18 00:15
Оценка:
Здравствуйте, barney, Вы писали:

_>>Это ты уже какую-то чушь пишешь. В библиотеку Boost.Context (которая как раз отвечает за переключение стеков в сопрограммах) входит всего по 3 очень простеньких asm файла на каждую архитектуру процессора (а вот их побольше чем 5, хотя и не сильно). Так что при появление гипотетического arm128 написание версии под него скорее всего займёт меньше одного часа...

B>Речь была о rethinkdb.

Причём тут какой-то rethinkdb, если ты писал свой ответ на вполне однозначную фразу "стекфул корутины в бусте не зря на асме сделали, а не на longjump"?

B>Boost в этом плане интереснее, да и тяготеет становиться стандартом,

B>но, вроде бы пока что он плохо заводится на Android NDK

Естественно, что не все библиотеки Boost'a собираются под Андроид (причём это не какие-то баги, а вполне себе заранее заложенная и документированная ситуация). Однако с обсуждаемыми библиотеками (переключения контекстов, сопрограммы и т.п.) никаких проблем нет. И это вполне предсказуемо — откуда возьмутся проблемы у банального ассемблерного кода сохранения/восстановления регистров? Там сложности в основном у библиотек требующих всяческий специфичный системный API...
Re[14]: эффективная реализация thread pool
От: barney  
Дата: 26.05.18 08:26
Оценка:
_>Причём тут какой-то rethinkdb, если ты писал свой ответ на вполне однозначную фразу "стекфул корутины в бусте не зря на асме сделали, а не на longjump"?

...который был, в контексте, ответа на мою фразу и пример аццкого кода rethinkdb, нашпигованного платформенными зависимостями )
там черт ногу сломит...
реверскод?) перелогинься))

_>Естественно, что не все библиотеки Boost'a собираются под Андроид (причём это не какие-то баги, а вполне себе заранее заложенная и документированная ситуация). _>

Однако с обсуждаемыми библиотеками (переключения контекстов, сопрограммы и т.п.) никаких проблем нет.

ясно, хорошо

_> И это вполне предсказуемо — откуда возьмутся проблемы у банального ассемблерного кода сохранения/восстановления регистров?


ясно, сохранение контекста/регистров на платформенных асмах — не проблема для библиотеки

_> Там сложности в основном у библиотек требующих всяческий специфичный системный API...


м... я думал, boost опирается только на c++ std / posix и с андроидом проблема именно в совместимости с NDK
(там, например нет стандартной C библиотеки, там bionic)

про Context / Coroutines
вообще это интересно, вполне можно использовать

Технический практический вопрос:
Как именно сделать некий легковесный пул задач на корутинах?

Я так понимаю, корутины дают легковесное переключение "контекста" т.к оным там является переключение (сохранение/восстановление)
указателя инструкций + текущего стека + регистров.
И это эффективнее вызова планировщика ОС

Плюс, позволяет писать более "синхронный" код, как в лекции про яндекс.карты.

Интересно, как именно сделать "асинхронный фьючерс" и wait() — например, ожидание ввода-вывода, сети, результата работы другой корутины и т.д.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.