Теоретически реализовать «кражу работы» несложно. Вам нужен набор очередей, содержащих задачи, которые работают, выполняя комбинацию вычислений и генерируя другие задачи, чтобы выполнять больше работы. И вам нужен атомарный доступ к очередям, чтобы помещать вновь созданные задачи в эти очереди. Наконец, вам нужна процедура, которую каждая задача вызывает в конце, чтобы найти больше работы для потока, выполняющего задачу; этой процедуре необходимо заглядывать в рабочие очереди, чтобы найти работу.
Большинство таких систем похищения работы предполагают, что существует небольшое количество потоков (обычно поддерживаемых реальными ядрами процессора), и что существует ровно одна рабочая очередь на каждый поток. Затем вы сначала пытаетесь украсть работу из собственной очереди, а если она пуста, пытаетесь украсть у других. Что становится сложным, так это знать, в какие очереди искать; последовательное сканирование их для работы довольно дорого и может создать огромное количество конфликтов между потоками, ищущими работу.
Пока что это все довольно общие вещи с одним двумя основными исключениями: 1) контексты переключения (например, установка регистров контекста процессора, таких как «стек») не могут быть указаны в чистом C или C ++. Вы можете решить эту проблему, согласившись написать часть вашего пакета в машинном коде, специфичном для целевой платформы. 2) Атомарный доступ к очередям для мультипроцессора не может быть выполнен исключительно на C или C ++ (игнорируя алгоритм Деккера), поэтому вам нужно будет кодировать те, которые используют примитивы синхронизации языка ассемблера, такие как X86 LOCK XCH или Compare and Swap. Теперь код, участвующий в обновлении очереди после получения безопасного доступа, не очень сложен, и вы могли бы легко написать это в нескольких строках C.
Однако я думаю, вы обнаружите, что попытки кодировать такой пакет на C и C ++ со смешанным ассемблером все еще довольно неэффективны, и в конечном итоге вы все равно будете кодировать все это на ассемблере. Все, что осталось, - это точки входа, совместимые с C / C ++: -}
Я сделал это для нашего языка параллельного программирования PARLANSE, который предлагает идею произвольно большого количества параллельных вычисления живут и взаимодействуют (синхронизируются) в любой момент. Он реализован за кулисами на X86 ровно с одним потоком на процессор, и реализация полностью на ассемблере. Код для кражи работы, вероятно, составляет всего 1000 строк, и это сложный код, потому что вы хотите, чтобы он был чрезвычайно быстрым в случае отсутствия конкуренции.
Настоящая ложка дегтя для C и C ++ заключается в том, что, когда вы создаете задачу, представляющую работу, сколько места в стеке вы назначаете? Последовательные программы C / C ++ избегают этого вопроса, просто перераспределяя огромные объемы (например, 10 МБ) одного линейного стека, и никого не заботит, сколько из этого пространства стека тратится впустую. Но если вы можете создать тысячи задач и заставить их работать в определенный момент, вы не сможете разумно выделить 10 МБ для каждой из них. Итак, теперь вам нужно либо статически определить, сколько места в стеке потребуется для задачи (по Тьюрингу), либо вам нужно будет выделить фрагменты стека (например, для каждого вызова функции), чего не делают широко доступные компиляторы C / C ++. (например, тот, который вы, вероятно, используете). Последний выход - ограничить создание задачи несколькими сотнями в любой момент и мультиплексировать несколько сотен действительно огромных стеков среди текущих задач. Вы не можете сделать последнее, если задачи могут заблокировать / приостановить состояние, потому что вы столкнетесь с вашим порогом. Таким образом, вы можете сделать это, только если задачи только выполняют вычисления. Это кажется довольно серьезным ограничением.
Для PARLANSE мы создали компилятор, который размещает записи активации в куче для каждого вызова функции.
person
Ira Baxter
schedule
31.01.2010