Многопоточность (поток сознания)

Модная несколько лет назад тема “а давайте разместим игровой цикл в двух потоках – графика отдельно, физика отдельно” постепенно сходит на нет. И слава богу. По целому ряду причин. Во-первых, двухпроцессорными машинами уже никого не удивить, скоро никто не будет удивляться процессорам, в которых количество ядер будет измеряться двузначными числами. А во-вторых (и это более важно), ручное распределение задач по ядрам имеет определенную степень осмысленности только для простых задач, где такое распределение действительно может принести определенный перформанс. Но чем сложнее становится код, тем сложнее: а) держать в голове все выполняемые приложением задачи, б) иметь действительно эффективную реализацию многопоточности, в которой треды будут выполнять вычисления, а не ожидать друг друга.

При дальнейшем усложнении приложения можно получить и совсем дурацкую ситуацию – вручную распараллеленное приложение будет работать медленнее, чем точно такое же, но выполняемое в один поток. Ситуация сродни программированию на асемблере – известно, что ручной ассемблерной реализацией на синтетическом примере можно “выжать” еще 10-15% производительности из кода против С. Но если писать большую программу на ассемблере, то это займет гигантское количество времени и ресурсов, а результат вряд ли будет производительнее, чем высокоуровневая программа.
То же самое происходит и здесь. Ручное распараллеливание синтетического примера действительно может дать хороший прирост производительности. Ручное распараллеливание крупной программы почти наверняка потребует гигантских усилий и может принести замедление вместо ожидаемого ускорения.

OpenMP далеко не панацея. Если говорить про паралеллизацию внутри рассчетных функций, то накладные расходы на вход в параллельный расчет могут перекрывать весь буст. Да и в зависимости от структуры кода и специфики вычислений далеко не факт, что удастся найти достаточное количество мест, где применить параллелизацию. Если использовать OpenMP с внутренними функциональными вызовами, то надо хорошо представлять себе, какие вызовы и ручные синхронизации надо засовывать во вложенные функции. Фактически, получаем тот же самое ручное распараллеливание, разница только в том, что N тредов созданы заранее (накладные расходы на CreateThread высоки), они спят на эвенте, и включаются, как только есть openmp scope. По окончании засыпают снова. ТВВ тоже не панацея – это почти то же самое распределение задач, чуть более автоматизированное, которое заставляет держать программиста в голове огромное количество данных про выполняемые задачи, используемые подсистемы и т.п.

Попробуем расширить концепцию многопоточности (это будет очень похоже на PS3, но никто нам не запрещает расширить “сферу применения”). В первую очередь надо абстрагироваться от того, что у нас один многоядерный комп. Пусть лучше будет много одноядерных. Их “общая память” (в смысле, если это один многоядерный комп) – это определенный бонус, но даже если это разные компьютеры со связью по TCP/IP – это не так страшно. Если компьютеров много, то и параллельные вычисления, и распределенные вычисления, и мультиплеер – все они хорошо ложатся под это определение.

Что меняется, если принять такой концепт? Меняется подход к организации вычислений. Каждый “компьютер” (или тред) выполняют задачи. Отдельная задача имеет:

  • входные параметры.
  • выходные данные.
  • константные данные, которые могут требоваться в процессе вычислений.

Вход и выход может приходить по TCP/IP или браться из оперативной памяти. С константными данными – все еще проще – они могут закешироваться на локальном компьютере (если это распределенные вычисления) или даже полностью безопасно браться из общей памяти (на одном многоядерном компьютере).

Задачи именованные, каждая {категория} задач имеет собственное описание, которое позволяет определить следующее:

  • порядок исполнения задач (либо с помощью приоритетов, либо с помощью dependencies before/after, как это было реализовано в RedKey).
  • использование константных и неконстантных внешних данных. Константные внешние данные могут безопасно использоваться любым количеством задач, неконстантные определяют последовательность выполняемых операций. Очевидно, что менеджер должен заниматься правильной “укладкой” задач в конвеер.
  • использование (эксклюзивное, и не очень) внешнего middleware, ресурсов компьютера и т.п.

Фактически проблему хочется свести к тому, что конкретный программист реализует задачи, исполняемые (с точки зрения взаимодействия с внешними данными) в однопоточной среде. Количество объектов синхронизации в основных задачах при этом сводится к нулю, в используемых библиотеках (если они не thread safe) и в самом менеджере синхронизация, ессно, остается.

Что получаем в качестве бонуса:

  • действующие приложения – это один большой “task: do all”.
  • возможность гибко параллелизовать исполняемую программу либо на любое количество процессоров, либо на любое количество компьютеров в “облаке” (разумеется, при достаточно распределенной системе задач и гибких связях).
  • возможность реализовывать runtime профилирование и оптимизацию задач с точки зрения наиболее грамотного распределения их по ядрам (с учетом замеряемого времени выполнения), за счет информации об используемых данных – оптимизировать доступы в память (в многопроцессорной машине – для использования константных данных для максимального количества задач ежесекундно, в облаке – для минимизации кешей и пересылок на компьютерах).
  • возможность отлаживать программу специальными перемешиваниями задач – для контроля правильности описания.
  • И многое многое другое.

Что за язык будет описывать такую систему – я пока не знаю, если кто знает – поделитесь ссылками. Придуманная связка language + external definitions работают не очень хорошо и требуют завидной аккуратности. Нужно, чтобы система задач была интегрирована в сам язык. Ключевым описанием в котором становится не function/procedure foo(), а task do_something с описаниями зависимостей и взаимосвязей.

  • look4awhile

    F#, plinq. a potom moi noviy pet-project
    davay golosom?

  • http://www.creative-assembly.com.au/ Loyso

    > Нужно, чтобы система задач была интегрирована в сам язык.
    Нужно чтобы язык и его object-system были интегрированы с системами создания контента. А иначе действительно руками замучаешься.