Сторонники FP утверждают, что параллелизм прост, потому что их парадигма избегает изменчивого состояния. Я не понимаю
Представьте, что мы создаем многопользовательское сканирование подземелий (roguelike), используя FP, где мы подчеркиваем чистые функции и неизменные структуры данных. Мы создаем подземелья, состоящие из комнат, коридоров, героев, монстров и добычи. Наш мир фактически является графом объектов и их взаимосвязей. Поскольку вещи меняются, наше представление о мире изменяется, чтобы отразить эти изменения. Наш герой убивает крысу, подбирает короткий меч и т. Д.
Для меня мир (текущая реальность) несет в себе эту идею состояния, и мне не хватает того, как FP преодолевает это. Когда наш герой начинает действовать, функции изменяют состояние мира. Похоже, что каждое решение (ИИ или человек) должно основываться на состоянии мира, как оно есть в настоящем. Где бы мы допустили параллелизм? У нас не может быть нескольких процессов, одновременно изменяющих состояние мира, чтобы один процесс не основывал свои результаты на каком-то просроченном состоянии. Мне кажется, что все управление должно происходить в одном цикле управления, поэтому мы всегда обрабатываем текущее состояние, представленное нашим текущим графом объектов мира.
Ясно, что существуют ситуации, идеально подходящие для параллелизма (т. Е. При обработке изолированных задач, состояния которых не зависят друг от друга).
Я не вижу, насколько параллелизм полезен в моем примере, и это может быть проблемой. Я могу как-то искажать претензию.
Может ли кто-то лучше представить это требование?
источник
Ответы:
Я постараюсь намекнуть на ответ. Это не ответ, а только вводная иллюстрация. Ответ @ jk указывает на реальную вещь, молнии.
Представьте, что у вас есть неизменная древовидная структура. Вы хотите изменить один узел, вставив дочерний элемент. В результате вы получите совершенно новое дерево.
Но большая часть нового дерева точно такая же, как старое дерево. Умная реализация будет повторно использовать большинство фрагментов дерева, маршрутизируя указатели вокруг измененного узла:
Книга Окасаки полна таких примеров.
Поэтому я полагаю, что вы могли разумно изменять небольшие части игрового мира при каждом движении (подбирать монету) и фактически изменять только небольшие части структуры данных вашего мира (ячейка, в которой была подобрана монета). Части, которые принадлежат только прошлым состояниям, будут вовремя убраны мусором.
Это, вероятно, требует некоторого рассмотрения при проектировании структуры мира игры с данными соответствующим образом. К сожалению, я не эксперт в этих вопросах. Определенно, это должна быть другая матрица NxM, которую можно использовать в качестве изменяемой структуры данных. Вероятно, он должен состоять из более мелких частей (коридоры «отдельные ячейки»), которые указывают друг на друга, как это делают узлы дерева.
источник
Ответ 9000 - это половина ответа, постоянные структуры данных позволяют повторно использовать неизмененные части.
Вы, возможно, уже думаете: «Эй, что если я захочу изменить корень дерева?» как и в приведенном примере, теперь это означает изменение всех узлов. Здесь на помощь приходят молнии . Они позволяют элементу в фокусе быть измененным в O (1), и фокус может быть перемещен куда угодно в структуре.
Другой момент, связанный с молниями, заключается в том, что молния существует практически для любого типа данных, который вы хотите.
источник
Программы с функциональным стилем создают множество возможностей использовать параллелизм. Каждый раз, когда вы преобразуете или фильтруете или агрегируете коллекцию, и все является чистым или неизменным, существует возможность ускорения операции за счет параллелизма.
Например, предположим, что вы выполняете решения ИИ независимо друг от друга и в произвольном порядке. Они не сменяются, они все принимают решение одновременно, и тогда мир движется вперед. Код может выглядеть так:
У вас есть функция для вычисления того, что будет делать монстр с данным состоянием мира, и применение его к каждому монстру как часть вычисления следующего состояния мира. Это естественно для функционального языка, и компилятор может выполнять шаг «применить его к каждому монстру» параллельно.
На императивном языке вы, скорее всего, будете перебирать каждого монстра, применяя его эффекты к миру. Так проще сделать, потому что вы не хотите иметь дело с клонированием или сложным псевдонимом. В этом случае компилятор не может выполнять вычисления монстров параллельно, потому что ранние решения монстров влияют на более поздние решения монстров.
источник
Прослушивание нескольких переговоров Rich Хики - это один , в частности - облегчено мое замешательство. В одном он указал, что это нормально, что параллельные процессы могут не иметь самого текущего состояния. Мне нужно было это услышать. У меня были проблемы с перевариванием, потому что программы на самом деле были бы в порядке, если бы решения основывались на снимках мира, которые были заменены более новыми. Я продолжал задаваться вопросом, как параллельная FP обошла проблему принятия решений на основе старого состояния.
В банковском приложении мы никогда не хотели бы основывать решение на снимке состояния, которое с тех пор было заменено более новым (произошло снятие).
Такой параллелизм прост, потому что парадигма FP избегает изменчивого состояния - это техническое утверждение, которое не пытается ничего сказать о логических достоинствах принятия решений на потенциально старом состоянии. FP все еще в конечном счете моделирует изменение состояния. Там нет обойти это.
источник
Я хотел бы рассказать об этом общем вопросе как о человеке, который является функциональным неофитом, но в течение многих лет испытывал побочные эффекты, и хотел бы смягчить их по разным причинам, включая более простые (или особенно «более безопасные», меньше ошибок ") параллелизм. Когда я смотрю на своих функциональных коллег и то, что они делают, трава кажется немного зеленее и пахнет лучше, по крайней мере, в этом отношении.
Последовательные алгоритмы
Тем не менее, в вашем конкретном примере, если ваша проблема носит последовательный характер и B не может быть выполнен до тех пор, пока A не закончится, концептуально вы не сможете запустить A и B параллельно, несмотря ни на что. Вы должны найти способ разорвать зависимость порядка, как в своем ответе, на основе выполнения параллельных ходов, используя старое игровое состояние, или использовать структуру данных, которая позволяет независимо изменять ее части, чтобы устранить зависимость порядка, как предложено в других ответах. или что-то в этом роде. Но есть определенная доля концептуальных проблем дизайна, таких как эта, когда вы не можете просто так просто многопоточить все, потому что вещи неизменны. Некоторые вещи будут носить последовательный характер, пока вы не найдете какой-нибудь умный способ разорвать зависимость от порядка, если это вообще возможно.
Более простой параллелизм
Тем не менее, есть много случаев , когда мы не в состоянии распараллеливания программ , которые включают побочные эффекты в местах , которые потенциально могли бы значительно улучшить производительность просто из - за возможности того, что он может не быть потокобезопасным. Один из случаев, когда устранение изменяемого состояния (или, более конкретно, внешних побочных эффектов) очень помогает, как я вижу, состоит в том, что он превращает «может быть, а может и не быть потокобезопасным» в «определенно потокобезопасный» .
Чтобы сделать это утверждение более конкретным, рассмотрим, что я даю вам задачу реализовать функцию сортировки в C, которая принимает компаратор и использует его для сортировки массива элементов. Он должен быть довольно обобщенным, но я дам вам легкое предположение, что он будет использоваться против входных данных такого масштаба (миллионы элементов или более), что, несомненно, будет выгодно всегда использовать многопоточную реализацию. Можете ли вы многопоточность вашей функции сортировки?
Проблема в том, что вы не можете этого сделать, потому что компараторы, вызываемые вашей функцией сортировки, могутвызывать побочные эффекты, если вы не знаете, как они реализованы (или, по крайней мере, документированы) для всех возможных случаев, что невозможно без дегенерализации функции. Компаратор может сделать что-то отвратительное, например модифицировать глобальную переменную внутри неатомарным способом. 99,9999% компараторов могут этого не делать, но мы все еще не можем многопоточность этой обобщенной функции просто из-за 0,00001% случаев, которые могут вызывать побочные эффекты. В результате вам, возможно, придется предложить как однопоточную, так и многопоточную функцию сортировки и передать ответственность программистам, использующим ее, чтобы решить, какую из них использовать, основываясь на безопасности потоков. И люди могут все еще использовать однопоточную версию и упустить возможности многопоточности, потому что они также могут быть не уверены, является ли компаратор потокобезопасным,
Существует целый ряд умственных способностей, которые можно задействовать, просто рационализируя безопасность потоков, не создавая повсюду блокировок, которые могут исчезнуть, если бы у нас были твердые гарантии того, что функции не будут вызывать побочных эффектов ни сейчас, ни в будущем. И есть страх: практический страх, потому что любой, кто должен был отладить состояние гонки несколько слишком много раз, вероятно, будет колебаться из-за многопоточности всего, что не может быть на 110% уверенным в потоке и останется таковым. Даже для самого параноика (с которым я, вероятно, по крайней мере граничу), чистая функция обеспечивает то чувство облегчения и уверенности, что мы можем безопасно вызывать его параллельно.
И это один из основных случаев, когда я считаю очень полезным, если вы можете получить твердую гарантию, что такие функции являются поточно-ориентированными, что вы получаете с чисто функциональными языками. Другой заключается в том, что функциональные языки часто способствуют созданию функций, в первую очередь свободных от побочных эффектов. Например, они могут предоставить вам постоянные структуры данных, где достаточно эффективно вводить массивную структуру данных, а затем выводить совершенно новую, только небольшую часть которой изменили по сравнению с оригиналом, не касаясь оригинала. Те, кто работает без таких структур данных, могут захотеть изменить их напрямую и потерять некоторую безопасность потоков.
Побочные эффекты
Тем не менее, я не согласен с одной частью при всем уважении к моим функциональным друзьям (которые я считаю супер крутой):
Это не обязательно неизменность, которая делает параллелизм настолько практичным, насколько я понимаю. Это функции, которые не вызывают побочных эффектов. Если функция вводит массив для сортировки, копирует его, а затем изменяет копию для сортировки ее содержимого и выводит копию, она все еще так же поточно-ориентированная, как и та, которая работает с некоторым типом неизменяемого массива, даже если вы передаете тот же самый ввод массив к нему из нескольких потоков. Поэтому я думаю, что для изменяемых типов все еще есть место для создания кода, очень дружественного к параллелизму, хотя есть много дополнительных преимуществ для неизменяемых типов, включая постоянные структуры данных, которые я использую не столько для их неизменяемых свойств, сколько для избавьтесь от необходимости глубокого копирования всего, чтобы создавать функции без побочных эффектов.
И часто бывает непросто сделать функции свободными от побочных эффектов в виде перетасовки и копирования некоторых дополнительных данных, возможно, дополнительного уровня косвенности, и, возможно, некоторого GC на части постоянной структуры данных, но я смотрю на одного из моих приятелей, у которого есть 32-ядерная машина, и я думаю, что обмен, вероятно, того стоит, если мы сможем более уверенно делать больше вещей параллельно.
источник