LINUX.ORG.RU

Реализация дерева зависимостей по данным для многопоточных приложений на C

 , ,


0

2

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

Например если надо посчитать (sin((a+b+c+d)*(e+f+g)))^((a+b+c+d)+(h+i+j)) то можно построить такое дерево:

              (_^_)
               ^ ^
               | |
          +----+ +---+
          |          |
      sin(_)         |
          ^          |
          |          |
        (_*_)      (_+_)
         ^ ^        ^ ^
         | |        | |
    +----+ +-+------+ |
    |        |        |
 (e+f+g) (a+b+c+d) (h+i+j)

Как я себе это представляю:

Надо строить направленный ациклический граф, в каждой вершине храним указатель на следующие вершины(в примере у (a+b+c+d) две следующих вершины: (_*_) и (_+_)); счетчик числа вершин, указывающих на эту вершину, указатель на функцию, которую надо вызвать; массив указателей на данные, которые в эту функцию надо передавать; указатель на область памяти, куда надо записать результат выполнения функции.

Вначале нужно создать очередь, в которую помещаем указатели на те вершины, на которые никакие другие вершины не указывают. Запускаем некоторое количество тредов. По завершени обработки задачи, каждый тред декрементирует счетчик числа вершин в тех вершинах, на которые указывает та вершина, задача из которой была завершена этим тредом(например по завершении (a+b+c+d) надо декрементировать счетчик у вершин (_*_) и (_+_)). Если оказывается что после декрементирования счетчика в только одной вершине оказался 0, тред немедленно приступает к выполнению задачи из той вершины. Если в двух и более счетчиках в вершинах оказался 0, тред добавляет в очередь указатели на все такие вершины, кроме одной, и выполняет задание из той «одной» вершины, которая не добавлена в очередь. Иначе он берет задачу из очереди. Если в очереди ничего нет, тред выставляет особое значение в некоторой переменной, означающее что этот тред бездействует, после чего этот тред сам себе шлет сигнал SIGSTOP. И надо сделать так, чтобы при добавлении задач в очередь, проверялось наличие бездействующих тредов, и слать им сигнал SIGCONT.

Как подобная модель многопоточности называется? Есть ли уже готовые реализации подобного для C или других языков программирования? Через вызов clone() и всякие там CAS спинлоки такое реализовать нетрудно, как мне кажется. Поверх этого можно еще сделать реактивное программирование.

★★★★★

Последнее исправление: SZT (всего исправлений: 1)
Ответ на: комментарий от anonymous

В кеше может быть только их кусок, вот в рамках этого куска и на множественном доступе кеш уже работает. В данном контексте этого нет.

У кэша бывает неск уровней. Еще есть регистры. Еще сама память по факту имеет неск уровней. Еще м.б. своп, и еще м.б. сетевые диски прикрученные по NFS/sshfs и т.д.

Оч простой пример - данные лежат рядом и при загрузке попадают в кэш автоматом, или данные лежат настолько далеко, что часть данных при загрузке приходится тянуть из свопа или с сетевого диска. Разница в скорости доступа будет?

AIv ★★★★★
()
Ответ на: комментарий от true_admin

Он мне ниче не ответил;-)

Че то я раньше об этом не задумывался, но как то таких проблем не было. ИМНО Рома как обычно несколько радикально настроен;-)

AIv ★★★★★
()
Ответ на: комментарий от AIv

У кэша бывает неск уровней. Еще есть регистры. Еще сама память по факту имеет неск уровней. Еще м.б. своп, и еще м.б. сетевые диски прикрученные по NFS/sshfs и т.д.

Это понятно.

Я про 2 разных понятия, которые обзывают локальностью. Есть локальность в рамках паттерна множественного доступа к частям, которые можно хранить «локально», т.е. если нам надо прочитать что-то 2 раза, то между этими 2-мя чтениями из кеша наши данные не должны быть вытеснены.

Это можно сделать множеством способом, но самый простой - просто не читать с офсетом больше длинны кеша(любо чего-либо ещё). Этот паттерн и зназывается «локальным».

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

Оч простой пример - данные лежат рядом и при загрузке попадают в кэш автоматом

Данные лежать не рядом, а в пределах. Это надо понимать.

//000###ram#####fff///1000####swap####1fff///

Данные по адресу 0xfff и 0x1000 тоже лежат рядом, но из этого ничего не следует.

Разница в скорости доступа будет?

Будет, но формулировка мутная.

Задача подразумеваем чтение всех своих данных и если они не влезают в память, то мы что-то будем читать не из памяти.

Можно использовать монопольно всю память под кеш наших данных и тогда читать с диска надо будет только кусок не поместившийся в память.

В целом я только про мутность формулировок. Только их упомянули - уже никто не знает, а что же они конкретно значат и как их применить к данной задаче.

anonymous
()
Ответ на: комментарий от AIv

Он мне ниче не ответил;-)

Он пишет в dmesg

anonymous
()
Ответ на: комментарий от SZT

Ухудшил я производительность или улучшил, это зависит от того, общий у ядер кэш или нет.

Это не имеет значения, т.к. у тебя в связи с «прыжками» по адресам будет больше загрузок линий по L1D, и уж тем более - больше накладных расходов на поддержание когерентности если ты что-то модифицируешь

frame ★★★
()
Ответ на: комментарий от frame

В условии вообще никак не обозначено, в какой вычислительной среде это все будет работать. Может там NUMA как на этой https://upload.wikimedia.org/wikipedia/ru/8/8c/NUMA.gif картинке, и я дроблю массив на куски, раздаю их разным «памятям» и их уже там запускаю. Или может у меня 4 компьютера соединенные локалкой, со своей ОЗУ и проч, и в каждом по одноядерному процессору. И раздавать им эти куски массива через MPI

SZT ★★★★★
() автор топика
Последнее исправление: SZT (всего исправлений: 2)

Для создания потока нужно как минимум выделить страницу для стека, это сильно дороже вызова функции. Не знаю сколько в тактах, но для таких мелочей однозначно не стоит.

anonymous
()
Ответ на: комментарий от anonymous

Для создания потока нужно как минимум выделить страницу для стека

И зачем же его «создавать»?

anonymous
()
Ответ на: комментарий от SZT

Может там NUMA как на этой https://upload.wikimedia.org/wikipedia/ru/8/8c/NUMA.gif картинке, и я дроблю массив на куски, раздаю их разным «памятям» и их уже там запускаю.

Если процессорная шина между ядрами не скрывает накладных расходов на синхронизацию через каждые 4 байта, то NUMA и подавно

frame ★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.