<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:atom="https://www.w3.org/2005/Atom" xmlns:sy="https://purl.org/rss/1.0/modules/syndication/" xmlns:dc="https://purl.org/dc/elements/1.1/" xmlns:content="https://purl.org/rss/1.0/modules/content/" xmlns:wfw="https://wellformedweb.org/CommentAPI/" xmlns:slash="https://purl.org/rss/1.0/modules/slash/"
  version="2.0">
  <channel>
    <title>Русский блог по разработке для HPC</title>
    <atom:link
      href="https://docs.microsoft.com/archive/blogs/ru-hpc/feed.xml"
      rel="self"
      type="application/rss+xml" />
    <link>https://docs.microsoft.com/archive/blogs/ru-hpc/feed.xml</link>
    <description>Технологии создания параллельных алгоритмов и распределенных систем. Новости из мира HPC.</description>
    <lastBuildDate>Wed, 17 Apr 2019 03:25:01 GMT</lastBuildDate>
    <language>en-US</language>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>1</sy:updateFrequency>
    <item>
      <title>Взаимодействие MATLAB и HPC Server 2008</title>
      <link>https://docs.microsoft.com/archive/blogs/ru-hpc/matlab-hpc-server-2008</link>
      <pubDate>Sun, 11 Apr 2010 09:08:28 GMT</pubDate>
      <dc:creator><![CDATA[Garry Trinder]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/ru-hpc/2010/04/11/matlab-hpc-server-2008/</guid>
      <description><![CDATA[Как известно, Windows HPC Server 2008 предоставляет широкие возможности для интеграции с наиболее...]]></description>
      <content:encoded><![CDATA[<p>Как известно, Windows HPC Server 2008 предоставляет широкие возможности для интеграции с наиболее популярными вычислительными пакетами. После не всегда приятных опытов общения с нативным Job Manager от MATLAB, во время которых без какой-либо логики или причины worker-ы на узлах зависали либо теряли подключение к Job Manager на головном узле, было решено предпринять попытки связать MATLAB с Job Manager из HPC Pack 2008 (кроме этого, за пределы данной статьи было решено вынести взаимодействие MATLAB с популярной системой очередей Torque). </p>  <h4>Подготовка кластера к работе с MATLAB</h4>  <p>На все узлы кластера необходимо установить клиент MATLAB той же версии и в ту же папку (это критично), что и на головном узле. После чего, если мы имеем уже настроенный HPC Cluster Manager из пакета HPC Pack 2008, можно запустить с помощью его функциональной кнопки Run Command следующие команды на всех узлах:</p>  <p>&quot;c:\Program Files\MATLAB\R2009b\toolbox\distcomp\bin\mdce&quot; install</p>  <p>&quot;c:\Program Files\MATLAB\R2009b\toolbox\distcomp\bin\mdce&quot; start</p>  <p>Всё, Distributing Computing Centre, необходимый для работы MATLAB на кластере, установлен как сервис и запущен на всех узлах. Разумеется, что это надо поставить в автозагрузку либо написать скрипт.</p>  <p>Подготовка к работе закончена. Перейдём к головному узлу.</p>  <h4>Работа на головном узле</h4>  <p><a href="https://msdnshared.blob.core.windows.net/media/TNBlogsFS/BlogFileStorage/blogs_msdn/ru-hpc/WindowsLiveWriter/MATLABHPCServer2008_10CA5/clip_image001_2.jpg" original-url="https://blogs.msdn.com/blogfiles/ru-hpc/WindowsLiveWriter/MATLABHPCServer2008_10CA5/clip_image001_2.jpg"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="clip_image001" border="0" alt="clip_image001" src="https://msdnshared.blob.core.windows.net/media/TNBlogsFS/BlogFileStorage/blogs_msdn/ru-hpc/WindowsLiveWriter/MATLABHPCServer2008_10CA5/clip_image001_thumb.jpg" original-url="https://blogs.msdn.com/blogfiles/ru-hpc/WindowsLiveWriter/MATLABHPCServer2008_10CA5/clip_image001_thumb.jpg" width="424" height="256" /></a></p>  <p>Рис.1. Рабочий интерфейс MATLAB R2009</p>  <p>После запуска MATLAB необходимо вызвать Configurations Manager (вкладка Parallel=&gt;Manage Configurations)</p>  <p><a href="https://msdnshared.blob.core.windows.net/media/TNBlogsFS/BlogFileStorage/blogs_msdn/ru-hpc/WindowsLiveWriter/MATLABHPCServer2008_10CA5/clip_image003_2.jpg" original-url="https://blogs.msdn.com/blogfiles/ru-hpc/WindowsLiveWriter/MATLABHPCServer2008_10CA5/clip_image003_2.jpg"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="clip_image003" border="0" alt="clip_image003" src="https://msdnshared.blob.core.windows.net/media/TNBlogsFS/BlogFileStorage/blogs_msdn/ru-hpc/WindowsLiveWriter/MATLABHPCServer2008_10CA5/clip_image003_thumb.jpg" original-url="https://blogs.msdn.com/blogfiles/ru-hpc/WindowsLiveWriter/MATLABHPCServer2008_10CA5/clip_image003_thumb.jpg" width="329" height="314" /></a></p>  <p>Рис.2. Parallel Configurations Manager</p>  <p>В случае необходимости можно проверить новую конфигурацию нажатием на Start Validation – набор встроенных тестов. Однако прохождение всех тестов не гарантирует то, что задача будет выполняться (в частности, замечалось, что тесты проходят даже в случае, если несколько узлов помечены в HPC Cluster Manager как Unreachable).</p>  <p>File=&gt;</p>  <p><a href="https://msdnshared.blob.core.windows.net/media/TNBlogsFS/BlogFileStorage/blogs_msdn/ru-hpc/WindowsLiveWriter/MATLABHPCServer2008_10CA5/clip_image005_2.jpg" original-url="https://blogs.msdn.com/blogfiles/ru-hpc/WindowsLiveWriter/MATLABHPCServer2008_10CA5/clip_image005_2.jpg"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="clip_image005" border="0" alt="clip_image005" src="https://msdnshared.blob.core.windows.net/media/TNBlogsFS/BlogFileStorage/blogs_msdn/ru-hpc/WindowsLiveWriter/MATLABHPCServer2008_10CA5/clip_image005_thumb.jpg" original-url="https://blogs.msdn.com/blogfiles/ru-hpc/WindowsLiveWriter/MATLABHPCServer2008_10CA5/clip_image005_thumb.jpg" width="331" height="343" /></a></p>  <p>Рис.3. Вкладка создания новой конфигурации</p>  <p>После нажатия на CCS (Compute Cluster Server) будет предложено настроить данную конфигурацию. На скриншоте приведена работающая конфигурация. </p>  <p>Root Directory Of Matlab … - папка, в которой находится Matlab на узлах.</p>  <p>Number of workers… - сколько worker-ов может использовать конфигурация.</p>  <p>Directory where job… - в случае, если выполняются кластерные вычисления, необходимо создать папку, доступную всем узлам. Туда будет складываться весь массив данных, сгенерированный за время вычисления задачи.</p>  <p>HPC Server scheduler hostname – имя головного узла.</p>  <p>Cluster Version – версия ОС.</p>  <p>XML File… и Name of the job template… - «болванка» для создания задач.</p>  <p>Workers run in SOA mode for distributed jobs (UseSOAJobSubmission) – использование одной из последних фич в HPC Server 2008, а именно, выполнение задач в режиме SOA. </p>  <p><a href="https://msdnshared.blob.core.windows.net/media/TNBlogsFS/BlogFileStorage/blogs_msdn/ru-hpc/WindowsLiveWriter/MATLABHPCServer2008_10CA5/clip_image007_2.jpg" original-url="https://blogs.msdn.com/blogfiles/ru-hpc/WindowsLiveWriter/MATLABHPCServer2008_10CA5/clip_image007_2.jpg"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="clip_image007" border="0" alt="clip_image007" src="https://msdnshared.blob.core.windows.net/media/TNBlogsFS/BlogFileStorage/blogs_msdn/ru-hpc/WindowsLiveWriter/MATLABHPCServer2008_10CA5/clip_image007_thumb.jpg" original-url="https://blogs.msdn.com/blogfiles/ru-hpc/WindowsLiveWriter/MATLABHPCServer2008_10CA5/clip_image007_thumb.jpg" width="351" height="335" /></a></p>  <p>Рис.4. Конфигурация</p>  <p>На следующих вкладках (Jobs и Tasks) можно указать дополнительные опции, которые чаще всего прописываются вручную в программном коде задач (MinimumNumberOfWorkers и др.)</p>  <p>На этом конфигурация закончена.&#160; Таким образом, всего лишь за несколько простых шагов мы подготовили MATLAB для работы с Windows HPC Server 2008.</p>]]></content:encoded>
    </item>
    <item>
      <title>SIMD</title>
      <link>https://docs.microsoft.com/archive/blogs/ru-hpc/simd</link>
      <pubDate>Fri, 01 Jan 2010 11:58:48 GMT</pubDate>
      <dc:creator><![CDATA[Garry Trinder]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/ru-hpc/2010/01/01/simd/</guid>
      <description><![CDATA[Среди моделей параллельного программирования одной из самых известных является SIMD (Single...]]></description>
      <content:encoded><![CDATA[<p>Среди моделей параллельного программирования одной из самых известных является <a href="https://en.wikipedia.org/wiki/SIMD">SIMD</a> (Single Instruction Multiple Data) – концепция, которая существует уже несколько десятилетий. Для подходящих задач она обладает гигантским потенциалом, но, к сожалению, совсем неестественной моделью программирования, которая сложна для разработки и не очень практична для промышленного применения.</p>  <p>В этом посте остановимся на классической модели SIMD, проанализируем ее сильные и слабые стороны, а также вскользь рассмотрим ее аппаратные реализации. Позже также поговорим о расширениях SIMD, приносящих теоретическую красоту в жертву практичности, об особенностях реализации таких расширений в современных SIMD-устройствах, а также упомянем API и средства разработки для таких устройств.</p>  <p><b>Принципы SIMD</b></p>  <p><b></b></p>  <p>Главная идея SIMD заключается в том, что алгоритм (или его часть, занимающая существенно много времени) представляется в виде <i>набора инструкций</i>, применяющихся <i>независимо</i> к каждому элементу <i>линейного однородного источника данных</i>. Идеальное SIMD-устройство каждый такт выполняет по одной инструкции из набора для каждого элемента данных.</p>  <p>Независимость вычислений для каждого элемента – важнейшее условие, которое гарантирует возможность параллельного исполнения алгоритма. Таким образом, SIMD эффективно раскрывает <a href="https://en.wikipedia.org/wiki/Data_parallelism">параллелизм по данным</a>, присутствующий в программе. Яркий пример алгоритма, который можно выразить в стиле SIMD – цикл вычисления суммы векторов, выполняющий один и тот же набор инструкций (сложение) для каждого элемента линейного однородного набора данных (двух массивов координат векторов). </p>  <p>Практическая применимость SIMD обеспечивается реализующим его железом. Так как главная идея заключается в одновременной обработке как можно большего количества <i>данных</i>, то мы отказываемся от <a href="https://en.wikipedia.org/wiki/Superscalar">суперскалярного конвейера</a> с <a href="https://en.wikipedia.org/wiki/Branch_predictor">предсказателем ветвлений</a> и <a href="https://en.wikipedia.org/wiki/Out-of-order_execution">спекулятивным выполнением инструкций</a>, которые используются для одновременной обработки как можно большего числа <i>инструкций</i>. Такой подход, с одной стороны, освобождает транзисторы и место на пластине для чистой вычислительной мощи, а, с другой стороны, упрощает исполнительные модули. Все вместе это позволяет эффективно наращивать количество и частоту исполнительных модулей.</p>  <p>Это делает SIMD-устройства настолько производительными, что на сегодняшний день они являются быстрейшими среди всех вычислительных устройств по теоретическому максимуму количества выполняемых за такт операций. В этом смысле вычислительная мощь новейших видеокарт на порядки превосходит способности современных процессоров.</p>  <p><b>Анализ принципов SIMD и практические следствия</b></p>  <p><b></b></p>  <p>При использовании SIMD один и тот же код выполняется сразу для нескольких элементов данных – этим такой подход схож с параллелизмом потоков операционной системы. </p>  <p>Но, в отличие от традиционного подхода, параллельно исполняющиеся вычисления выполняются абсолютно синхронно, то есть за каждый такт для всех элементов данных выполняется одна и та же инструкция (это ограничение называется <i><a href="https://en.wikipedia.org/wiki/Lockstep_(computing)">lockstep</a><a name="e.nu"></a></i>). Lockstep делает модель SIMD гораздо менее гибкой, чем модель потоков ОС, ведь разные потоки ОС могут в один и тот же момент времени выполнять абсолютно разные вычисления (за что и причисляются к категории <a href="https://en.wikipedia.org/wiki/MIMD">MIMD</a><a name="q35m"></a> – Multiple Instructions Multiple Data). </p>  <p>С другой стороны, так как одна и та же программа разделена между несколькими исполняющими устройствами (вследствие принципа lockstep), то <i>потоки предельно легковесны:</i> в отличие от потоков операционной системы, им вообще не нужен контекст (сравните с 1 Mb (по умолчанию) <a href="https://bishop-it.ru/2009/04/потоки-и-память/">стека потока для Windows XP</a><a name="xfll"></a>). Соответственно, типичный сценарий использования потоков ОС содержит единицы и десятки потоков, а современные видеокарты работают с единицами и десятками <i>тысяч </i>потоков.</p>  <p>Также злой гений lockstep накладывает на SIMD-программы еще одно ограничение. Алгоритмы с расходящимся потоком управления (<i>divergent</i> <i>control</i> <i>flow</i>) не могут быть исполнены в lockstep и поэтому неприменимы к SIMD. Таким образом, запрещается использовать ветвления и динамическую диспетчеризацию вызовов: например, виртуальные методы. Впрочем, дальше мы увидим, что современные реализации SIMD ослабляют это ограничение изящными обходными маневрами.</p>  <p>SIMD-устройства содержат значительное количество однородных исполнительных модулей. Поэтому обрабатываемые каждый такт данные должны быть однородными и выравненными относительно количества и вычислительной емкости модулей. В классической SIMD-модели устройство работает только с <i>линейно упорядоченными данными</i>, откуда имеем два важных следствия (см. <a href="https://en.wikipedia.org/wiki/Vectored_I/O">Vectorized I/O</a><a name="ug0g"></a>):</p>  <p>1. Перед запуском вычислений программист должен вручную упорядочить обрабатываемые данные, которые могут быть произвольно размещены в адресном пространстве приложения, и линейно разместить их в памяти устройства (эта процедура называется <i>gather</i>).</p>  <p>2. После окончания вычислений программист должен вручную переместить линейно упорядоченные результаты обратно в адресное пространство приложения (эта процедура называется <i>scatter</i>).</p>  <p><b>Аппаратные реализации SIMD</b></p>  <p><b></b></p>  <p>Пример реализации SIMD – набор инструкций <a href="https://en.wikipedia.org/wiki/Streaming_SIMD_Extensions">SSE</a>, который предоставляет несколько 128-битных регистров и ряд работающих с ними команд. SSE-команды выполняют одну и ту же операцию над несколькими элементами данных: например, ADDSS поэлементно складывает данные в двух SSE-регистрах, а CMPSS производит поэлементное сравнение. Вследствие размера регистров, превосходящего как целочисленные регистры (32 или 64 бита), так и регистры сопроцессора (80 бит), SSE обеспечивает одновременную обработку либо четырех вещественных чисел одинарной точности (32 бита), либо двух вещественных чисел двойной точности (64 бита), либо нескольких целых чисел – последних может быть от двух до шестнадцати, в зависимости от размера операндов. Соответственно, SSE – <i>векторная </i>реализация SIMD.</p>  <p>Альтернативный подход характерен для <a href="https://s09.idav.ucdavis.edu/talks/02_kayvonf_gpuArchTalk09.pdf">современных видеокарт</a> – в них присутствуют и могут одновременно работать сотни одинаковых <i>скалярных</i> исполнителей, которые умеют выполнять сложение и умножение вещественных чисел одинарной точности, а также несколько десятков одинаковых исполнителей, которые к тому же умеют вычислять <a href="https://ru.wikipedia.org/wiki/Трансцендентная_функция">трансцендентные функции</a><a name="u:8s"></a> – экспоненты, логарифмы, тригонометрию. Кроме того, аппаратный планировщик умеет чередовать логические потоки и таким образом параллельно выполнять на порядки больше потоков, чем физически присутствует исполнительных модулей. Так мы получаем сотни <a href="https://en.wikipedia.org/wiki/FLOPS">флопов</a> за такт и тысячи параллельно выполняющихся потоков.</p>  <p><b>Применение SIMD в практических задачах</b></p>  <p><b></b></p>  <p>Как показано выше, применимость SIMD ограничивается циклами с ограниченно динамическим потоком управления. Стоит отметить, что даже не всякий цикл представим в нужном виде, а только тот, в котором итерации не имеют <a href="https://en.wikipedia.org/wiki/Data_dependency">зависимости по данным</a>. Например, при вычислении <a href="https://ru.wikipedia.org/wiki/Числа_Фибоначчи">чисел Фибоначчи</a><a name="lbph"></a> по рекуррентной формуле каждая итерация использует результаты предыдущей, поэтому такой цикл нельзя преобразовать к виду SIMD.</p>  <p>Однако иногда удается преобразовать алгоритм к эквивалентному виду, который уже можно выразить в терминах SIMD. Например, сложение массива чисел в наивной реализации не преобразуется к нужному виду, ибо между итерациями цикла есть <a href="https://en.wikipedia.org/wiki/Dependence_analysis#Output_dependence">write-after-write зависимость</a> по переменной-аккумулятору. Если же мы разобьем массив на последовательные фрагменты, то каждый из этих фрагментов можно просуммировать параллельно, а получившийся массив сумм снова разбить на фрагменты и снова просуммировать, <a href="https://gpgpu.org/static/asplos2008/ASPLOS08-5-advanced-data-parallel-programming.pdf">и так далее</a>.</p>  <p>Естественным образом подходят для SIMD алгоритмы компьютерной графики, обработки сигналов, изображений и видео, численного решения уравнений и систем уравнений, описывающих процессы в промышленности. Также в погоне за гигантскими вычислительными мощностями, которые доступны SIMD-программам, регулярно предлагаются SIMD-версии изначально неподходящих для SIMD алгоритмов, в том числе и алгоритмов общего назначения: сортировок, сканов, редукций и т.п.</p>  <p><b>Что дальше?</b></p>  <p><b></b></p>  <p>В последующем посте рассмотрим <a href="https://en.wikipedia.org/wiki/Stream_processing">stream-модель</a> – пример data-centric подхода к SIMD, естественно вытекающего из характера многих практических данных. Также поговорим об отступлениях от классической модели, делающих ее более приспособленной к практическому применению.</p>  <p><b>Материалы для чтения по темам следующих постов</b></p>  <p>1.<a href="https://en.wikipedia.org/wiki/Stream_processing">Stream processing</a>, Wikipedia, the free encyclopedia.</p>  <p>2.<a href="https://s09.idav.ucdavis.edu/talks/02_kayvonf_gpuArchTalk09.pdf">Running Code at a Teraflop: Overview of GPU Architecture</a>, SIGGRAPH 2009: Beyond Programmable Shading, Kayvon Fatahalian, Stanford University.</p>  <p>3. <a href="https://www.mathematik.tu-dortmund.de/~goeddeke/arcs2008/C1_CUDA.pdf">Introduction to CUDA, CUDA performance, CUDA tool chain</a>, ARCS 2008 GPGPU and CUDA Tutorials, Simon Green, NVIDIA.</p>  <p>4. <a href="https://www.nada.kth.se/~tomaso/Stream2008/M1.pdf">AMD Stream SDK: Introduction and Overview</a>, PDC/AMD workshop on GPU programming 2008, Praveen Bhaniramka, AMD.</p>  <p>5. <a href="https://www.nada.kth.se/~tomaso/Stream2008/M7.pdf">AMD Stream SDK: GPU Hardware and Performance</a>, PDC/AMD workshop on GPU programming 2008, Praveen Bhaniramka, AMD.</p>]]></content:encoded>
    </item>
    <item>
      <title>Работа с MS-MPI в среде Visual Studio и Windows HPC Server</title>
      <link>https://docs.microsoft.com/archive/blogs/ru-hpc/ms-mpi-visual-studio-windows-hpc-server</link>
      <pubDate>Mon, 28 Dec 2009 09:45:42 GMT</pubDate>
      <dc:creator><![CDATA[Garry Trinder]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/ru-hpc/2009/12/28/ms-mpi-visual-studio-windows-hpc-server/</guid>
      <description><![CDATA[Этот пост будет посвящен различным аспектам работы с наиболее популярной библиотекой для разработки...]]></description>
      <content:encoded><![CDATA[<p>Этот пост будет посвящен различным аспектам работы с наиболее популярной библиотекой для разработки параллельных программ – MS-MPI.</p>  <p>Для того, чтобы компилировать свои программы, написанные на C/C++/C# с использованием MPI и Visual Studio 2008, необходимо сначала скачать дистрибутив HPC Pack SDK 2008, содержащий все необходимые библиотеки:</p>  <h3>- <a href="https://www.microsoft.com/downloads/details.aspx?FamilyID=12887DA1-9410-4A59-B903-693116BFD30E&amp;displaylang=en">HPC Pack SDK 2008</a></h3>  <p>Аналогично нижеописанному алгоритму производится настройка проекта для MPICH2, за исключением других путей к библиотекам и mpiexec. Настройка проекта и запуск параллельных программ ничем не отличается и в 10-й версии Visual Studio.</p>  <p>В данной инструкции компьютер, на котором осуществляется компиляция и запуск программы, имеет доменное имя hnwindows.</p>  <p>1 – Создать проект C++    <br /><a href="https://msdnshared.blob.core.windows.net/media/TNBlogsFS/BlogFileStorage/blogs_msdn/risman/WindowsLiveWriter/MSMPIwithVisualStudio2008_F3C8/MPI03_2.jpg" original-url="https://blogs.msdn.com/blogfiles/risman/WindowsLiveWriter/MSMPIwithVisualStudio2008_F3C8/MPI03_2.jpg"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="clip_image001" border="0" alt="clip_image001" src="https://msdnshared.blob.core.windows.net/media/TNBlogsFS/BlogFileStorage/blogs_msdn/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image001_f4761337-a2fd-409a-990f-c7a897bf49e2.jpg" original-url="https://blogs.msdn.com/blogfiles/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image001_f4761337-a2fd-409a-990f-c7a897bf49e2.jpg" width="644" height="463" /></a>     <br />(В русской версии Visual C++=&gt;Общие, «Пустой проект»)</p>  <p>Открыть свойства проекта.</p>  <p>2 – Поменять значение поля Subsystem в Linker=&gt;System на CONSOLE(/SUBSYSTEM:CONSOLE) (для русской версии VS: Компоновщик=&gt;Система=&gt;Подсистема)    <br /><a href="https://msdnshared.blob.core.windows.net/media/TNBlogsFS/BlogFileStorage/blogs_msdn/risman/WindowsLiveWriter/MSMPIwithVisualStudio2008_F3C8/MPI04_2.jpg" original-url="https://blogs.msdn.com/blogfiles/risman/WindowsLiveWriter/MSMPIwithVisualStudio2008_F3C8/MPI04_2.jpg"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="clip_image002" border="0" alt="clip_image002" src="https://msdnshared.blob.core.windows.net/media/TNBlogsFS/BlogFileStorage/blogs_msdn/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image002_0936de7b-5b29-42e9-be14-2339fa639d8a.jpg" original-url="https://blogs.msdn.com/blogfiles/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image002_0936de7b-5b29-42e9-be14-2339fa639d8a.jpg" width="644" height="444" /></a>     <br />3 – Добавить к компоновщику (Linker) путь к дополнительным библиотекам (в русской версии VS необходимо зайти на вкладку Общие)     <br /><a href="https://msdnshared.blob.core.windows.net/media/TNBlogsFS/BlogFileStorage/blogs_msdn/risman/WindowsLiveWriter/MSMPIwithVisualStudio2008_F3C8/MPI05_2.jpg" original-url="https://blogs.msdn.com/blogfiles/risman/WindowsLiveWriter/MSMPIwithVisualStudio2008_F3C8/MPI05_2.jpg"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="clip_image003" border="0" alt="clip_image003" src="https://msdnshared.blob.core.windows.net/media/TNBlogsFS/BlogFileStorage/blogs_msdn/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image003_3665de0a-7deb-4f63-b9be-b236413fd62f.jpg" original-url="https://blogs.msdn.com/blogfiles/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image003_3665de0a-7deb-4f63-b9be-b236413fd62f.jpg" width="577" height="424" /></a>     <br />4 – Добавить к компоновщику (Linker) дополнительные зависимости . Вписать <b>msmpi</b><b>.</b><b>lib</b> <b>Ws2_32.lib</b>. В данной программе используется дополнительная зависимость Ws2_32.lib, данная библиотека используется для сетевого взаимодействия, в других проектах может быть и не задействована.     <br /><a href="https://msdnshared.blob.core.windows.net/media/TNBlogsFS/BlogFileStorage/blogs_msdn/risman/WindowsLiveWriter/MSMPIwithVisualStudio2008_F3C8/MPI06_2.jpg" original-url="https://blogs.msdn.com/blogfiles/risman/WindowsLiveWriter/MSMPIwithVisualStudio2008_F3C8/MPI06_2.jpg"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="clip_image004" border="0" alt="clip_image004" src="https://msdnshared.blob.core.windows.net/media/TNBlogsFS/BlogFileStorage/blogs_msdn/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image004_35b8be29-0a5e-403d-907e-75f40b2f2efe.jpg" original-url="https://blogs.msdn.com/blogfiles/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image004_35b8be29-0a5e-403d-907e-75f40b2f2efe.jpg" width="619" height="424" /></a>     <br />5 – добавить файл C++ к проекту (в данной инструкции мы будем использовать программу Hello World), нажать правой кнопкой мыши на названии проекта, Add=&gt;Add an element (для русской версии Добавить=&gt;Создать элемент), где выбрать C++ File: </p>  <pre class="csharpcode">#include &lt;iostream&gt;

#include &lt;iomanip&gt;

#include &lt;fstream&gt;

#include &lt;cstdlib&gt;

#include &lt;ctime&gt;

#include &lt;cassert&gt;

#include &lt;math.h&gt;

#include &lt;windows.h&gt;

#include &lt;mpi.h&gt;

<span class="kwrd">using</span> <span class="kwrd">namespace</span>::std; 

<span class="kwrd">int</span> myRank; 

<span class="kwrd">int</span> numProcs; 

<span class="kwrd">char</span> host[256]; 

<span class="kwrd">int</span> main(<span class="kwrd">int</span> argc, <span class="kwrd">char</span>* argv[]) 

{ 

  MPI_Init(&amp;argc, &amp;argv); 

  MPI_Comm_size(MPI_COMM_WORLD, &amp;numProcs); <span class="rem">// number of processes involved in run </span>

  MPI_Comm_rank(MPI_COMM_WORLD, &amp;myRank); <span class="rem">// my process id: 0 &lt;= myRank &lt; numProcs </span>

  gethostname(host, <span class="kwrd">sizeof</span>(host)/<span class="kwrd">sizeof</span>(host[0])); <span class="rem">// machine we are running on </span>

  cout &lt;&lt; <span class="str">&quot;Process &quot;</span> &lt;&lt; myRank &lt;&lt; <span class="str">&quot; of &quot;</span> &lt;&lt; numProcs &lt;&lt; <span class="str">&quot; is running on '&quot;</span> &lt;&lt; host &lt;&lt; <span class="str">&quot;'.&quot;</span> &lt;&lt; endl; 

  MPI_Finalize(); 
  
<span class="kwrd">  return</span> 0; 

}</pre>
<style type="text/css">




.csharpcode, .csharpcode pre
{
	font-size: small;
	color: black;
	font-family: consolas, "Courier New", courier, monospace;
	background-color: #ffffff;
	/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt 
{
	background-color: #f4f4f4;
	width: 100%;
	margin: 0em;
}
.csharpcode .lnum { color: #606060; }</style>

<p>6 – Вызвать свойства проекта, появится новая вкладка C/C++. Добавить к путям компилятора (Compiler) пути для дополнительных файлов (для русской версии: дополнительные каталоги включения): 
  <br /><a href="https://msdnshared.blob.core.windows.net/media/TNBlogsFS/BlogFileStorage/blogs_msdn/risman/WindowsLiveWriter/MSMPIwithVisualStudio2008_F3C8/MPI07_2.jpg" original-url="https://blogs.msdn.com/blogfiles/risman/WindowsLiveWriter/MSMPIwithVisualStudio2008_F3C8/MPI07_2.jpg"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="clip_image005" border="0" alt="clip_image005" src="https://msdnshared.blob.core.windows.net/media/TNBlogsFS/BlogFileStorage/blogs_msdn/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image005_99e85339-1f42-4807-871c-19a8138f81cb.jpg" original-url="https://blogs.msdn.com/blogfiles/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image005_99e85339-1f42-4807-871c-19a8138f81cb.jpg" width="644" height="444" /></a></p>

<p>7 – Для того, чтобы узлы успешно запустили программу, необходимо скомпилировать нашу программу со всеми зависимостями. Для этого значение «Использование MFC» необходимо поменять на «Использовать MFC в статической библиотеке».</p>

<p>8 – Скомпилировать программу. Build project (в русской версии Построить &lt;имя проекта&gt;).</p>

<p>Бинарный файл, необходимый для запуска, расположен в Start=&gt;Documents=&gt;Documents=&gt;Visual Studio 2008=&gt;Projects=&gt;&lt;имя проекта&gt;=&gt;Debug=&gt;&lt;имя С++-файла&gt;.exe</p>

<p>Необходимо обратить внимание, что, в случае, если проект «собирается» без указания «MFC как статическая библиотека», размер бинарного файла резко отличается. В случае с данной программой – без статической MFC около 28 кб, с – около 600 кб. </p>

<p>Необходимо также отметить, что, если необходимы многопроцессорные расчёты, то «собирать» MFC в статическом виде не требуется – все необходимые библиотеки есть на том компьютере, на котором происходит разработка. В случае же запуска на кластере MFC в статическом виде просто необходимо, так как на узлах могут отсутствовать необходимые библиотеки.</p>

<p>Для того, чтобы программа была доступна всем узлам, необходимо поместить бинарный файл в каталог, доступный по сети всем узлам кластера. </p>

<h2><a name="_Toc249430028">Запуск </a>MPI-программы в различных режимах</h2>

<p>Запустим либо cmd, либо Start=&gt;Programs=&gt;HPC Pack 2008=&gt;HPC Power Shell 
  <br /><b>mpiexec –n 10 \\hnwindows\share\MyMPIProject.exe </b></p>

<p>Результат:</p>

<p><a href="https://msdnshared.blob.core.windows.net/media/TNBlogsFS/BlogFileStorage/blogs_msdn/risman/WindowsLiveWriter/MSMPIwithVisualStudio2008_F3C8/MPI08_2.jpg" original-url="https://blogs.msdn.com/blogfiles/risman/WindowsLiveWriter/MSMPIwithVisualStudio2008_F3C8/MPI08_2.jpg"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="clip_image008" border="0" alt="clip_image008" src="https://msdnshared.blob.core.windows.net/media/TNBlogsFS/BlogFileStorage/blogs_msdn/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image008_40dc74ca-f595-44cc-a778-0c2fa82e66e9.jpg" original-url="https://blogs.msdn.com/blogfiles/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image008_40dc74ca-f595-44cc-a778-0c2fa82e66e9.jpg" width="644" height="332" /></a></p>

<p>Использование программы mpiexec (полный список опций можно вызвать mpiexec /help2)</p>

<p>mpiexec [options] executable [args] [ : [options] exe [args] : ... ]</p>

<p>mpiexec -configfile &lt;file name&gt;</p>

<p>-n &lt;количество процессов&gt;</p>

<p>-env &lt;название переменной&gt; &lt;значение переменной&gt;</p>

<p>-wdir &lt;директория, в которой хранятся файлы&gt;</p>

<p>-hosts n host1 [m1] host2 [m2] ... hostn [mn] [список компьютеров в случае использования кластера, синтаксис: -hosts 1 node-12 2 node-13 3 node-14 и др)</p>

<p>-cores &lt;количество ядер, которое нужно задействовать, на каждом процессоре&gt;</p>

<p>В случае успешного добавления должна появиться надпись:</p>

<p><i>Job has been submitted. ID: [].</i></p>

<p>Примеры ручного добавления задач:</p>

<p><i>mpiexec</i><i> -</i><i>n</i><i> 4 </i><i>pi</i><i>.</i><i>exe</i><i></i></p>

<p><i></i><i>mpiexec -hosts 1 server1 master : -n 8 worker</i></p>

<h4><a name="_Toc249430029">Ручной запуск параллельной задачи на кластере</a></h4>

<p><i>job submit /numcores:16 mpiexec \\hnwindows\share\MyMPIProject.exe</i></p>

<p>запустить задачу на 16 ядрах</p>

<p><i>job submit /parametric:6-12:3 mpiexec <a href="file:///\\hnwindows\share\MyMPIProject.exe">\\hnwindows\share\MyMPIProject.exe</a></i><i> *</i></p>

<p>запустить параметрическую задачу, при этом: 6-12 – диапазон изменяемых значений, 3 – шаг между ними, * - место подстановки значений. Соответственно, данная команда запустит три задачи:</p>

<p>MyMPIProject 6</p>

<p>MyMPIProject 9</p>

<p>MyMPIProject 12</p>

<p><a href="https://msdnshared.blob.core.windows.net/media/TNBlogsFS/BlogFileStorage/blogs_msdn/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image010_2.jpg" original-url="https://blogs.msdn.com/blogfiles/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image010_2.jpg"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="clip_image010" border="0" alt="clip_image010" src="https://msdnshared.blob.core.windows.net/media/TNBlogsFS/BlogFileStorage/blogs_msdn/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image010_thumb.jpg" original-url="https://blogs.msdn.com/blogfiles/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image010_thumb.jpg" width="644" height="405" /></a></p>

<h4><a name="_Toc249430030">Запуск задач с помощью </a>Cluster Manager</h4>

<p>Для успешных расчётов на кластере необходимо, чтобы файл программы и все входные данные были доступны <b>всем</b> узлам. В данном случае (и в любом другом) наиболее оптимально использовать файлы, лежащие в сети, куда имеют доступ все узлы (включая главный узел).</p>

<p>CM находится в Start=&gt;Programs=&gt;HPC Pack 2008=&gt;HPC Cluster Manager. После запуска необходимо выбрать Job Management в левом нижнем углу, после чего откроется консоль управления задачами. Все задачи разделены на «все», «мои», каждый вид подразделяется на «Настраиваемые в данный момент», «Выполняющиеся», «Закончившиеся», «Закончившиеся с ошибкой», «Отменённые».</p>

<p>Для добавления задач необходимо использовать консоль справа Actions, подменю Job Submission. В зависимости от тип задачи, можно выбрать new Job, new Single-Task Job, new Parametric Sweep job</p>

<p>New Job: диалог для создания задачи, в которой может быть несколько подзадач. Из необходимых для выбор полей отметим Job resources: может быть «ядро», «сокет», «узел». Use assigned resourses exclusively for this job указывает менеджеру задач, что выбранные ресурсы будут заняты данной задачей эксклюзивно.</p>

<p><a href="https://msdnshared.blob.core.windows.net/media/TNBlogsFS/BlogFileStorage/blogs_msdn/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image012_2.jpg" original-url="https://blogs.msdn.com/blogfiles/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image012_2.jpg"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="clip_image012" border="0" alt="clip_image012" src="https://msdnshared.blob.core.windows.net/media/TNBlogsFS/BlogFileStorage/blogs_msdn/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image012_thumb.jpg" original-url="https://blogs.msdn.com/blogfiles/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image012_thumb.jpg" width="644" height="386" /></a></p>

<p>Следующая вкладка Task List, отвечает за добавление подзадач:</p>

<p><a href="https://msdnshared.blob.core.windows.net/media/TNBlogsFS/BlogFileStorage/blogs_msdn/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image014_2.jpg" original-url="https://blogs.msdn.com/blogfiles/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image014_2.jpg"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="clip_image014" border="0" alt="clip_image014" src="https://msdnshared.blob.core.windows.net/media/TNBlogsFS/BlogFileStorage/blogs_msdn/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image014_thumb.jpg" original-url="https://blogs.msdn.com/blogfiles/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image014_thumb.jpg" width="423" height="484" /></a></p>

<p>Добавить можно Basic Task, Parametric Task, Task From File.</p>

<p><a href="https://msdnshared.blob.core.windows.net/media/TNBlogsFS/BlogFileStorage/blogs_msdn/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image016_2.jpg" original-url="https://blogs.msdn.com/blogfiles/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image016_2.jpg"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="clip_image016" border="0" alt="clip_image016" src="https://msdnshared.blob.core.windows.net/media/TNBlogsFS/BlogFileStorage/blogs_msdn/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image016_thumb.jpg" original-url="https://blogs.msdn.com/blogfiles/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image016_thumb.jpg" width="378" height="484" /></a></p>

<p>Command line: как необходимо запускать задачу. </p>

<p><i>mpiexec <a href="file:///\\hnwindows\share\myMPIProject.exe">\\hnwindows\share\myMPIProject.exe</a> *</i></p>

<p><i>(.</i><i>exe</i><i> можно опустить).</i></p>

<p>Working directory: рабочая директория, относительно которой будут определяться абсолютные пути для standard input/output/error.</p>

<p>Standard input: файл с входными данными </p>

<p>Standard output: файл для выходных данных</p>

<p>Standard error: файл для ошибок выполнения.</p>

<p>После нажатия Submit, если всё было заполнено верно, задача появится в вкладке Active:</p>

<p><a href="https://msdnshared.blob.core.windows.net/media/TNBlogsFS/BlogFileStorage/blogs_msdn/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image018_2.jpg" original-url="https://blogs.msdn.com/blogfiles/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image018_2.jpg"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="clip_image018" border="0" alt="clip_image018" src="https://msdnshared.blob.core.windows.net/media/TNBlogsFS/BlogFileStorage/blogs_msdn/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image018_thumb.jpg" original-url="https://blogs.msdn.com/blogfiles/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image018_thumb.jpg" width="644" height="405" /></a> 

  <br />После окончания расчётов задача сменит статус либо на Finished:</p>

<p><a href="https://msdnshared.blob.core.windows.net/media/TNBlogsFS/BlogFileStorage/blogs_msdn/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image020_2.jpg" original-url="https://blogs.msdn.com/blogfiles/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image020_2.jpg"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="clip_image020" border="0" alt="clip_image020" src="https://msdnshared.blob.core.windows.net/media/TNBlogsFS/BlogFileStorage/blogs_msdn/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image020_thumb.jpg" original-url="https://blogs.msdn.com/blogfiles/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image020_thumb.jpg" width="644" height="405" /></a></p>

<p>Поскольку не был указан файл для выходных данных, мы можем посмотреть их, два раза нажав на свойствах задачи в окне внизу.</p>

<p>Пример запуска параметрической задачи из командной строки:</p>

<p><i>Job submit /parametric:6-12:1 /jobname:”MyParametricSweep” mpiexec <a href="file:///\\hnwindows\share\MyMPIProject.exe">\\hnwindows\share\MyMPIProject.exe</a></i><i> *</i></p>

<p><a href="https://msdnshared.blob.core.windows.net/media/TNBlogsFS/BlogFileStorage/blogs_msdn/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image022_2.jpg" original-url="https://blogs.msdn.com/blogfiles/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image022_2.jpg"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="clip_image022" border="0" alt="clip_image022" src="https://msdnshared.blob.core.windows.net/media/TNBlogsFS/BlogFileStorage/blogs_msdn/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image022_thumb.jpg" original-url="https://blogs.msdn.com/blogfiles/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image022_thumb.jpg" width="644" height="405" /></a></p>

<h4><a name="_Toc249430031">Интеграция</a> Visual Studio и mpiexec.</h4>

<p>Есть также дополнительный способ интеграции Visual Studio и mpiexec, нестандартный, но, тем не менее, позволяющий оперировать кластерными вычислениями непосредственно из Visual Studio, без запуска HPC Cluster Manager, mpiexec и т.д.</p>

<p>Так, в подменю Tools находится раздел Other tools (в русской версии Сервис=&gt;Внешние инструменты).</p>

<p><a href="https://msdnshared.blob.core.windows.net/media/TNBlogsFS/BlogFileStorage/blogs_msdn/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image024_2.jpg" original-url="https://blogs.msdn.com/blogfiles/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image024_2.jpg"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="clip_image024" border="0" alt="clip_image024" src="https://msdnshared.blob.core.windows.net/media/TNBlogsFS/BlogFileStorage/blogs_msdn/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image024_thumb.jpg" original-url="https://blogs.msdn.com/blogfiles/ru-hpc/WindowsLiveWriter/MSMPIVisualStudio_109DD/clip_image024_thumb.jpg" width="488" height="484" /></a></p>

<p>Command – путь к mpiexec от необходимой реализации MPI. В аргументах необходимо указать путь к проекту ($(TargetPath)), в Initial Directory – директорию проекта.</p>

<p>Из полезных дополнительных опций следует отметить use output window – использовать консоль для вывода результатов.</p>

<p>Таким образом, мы получаем полный доступ к функциональности mpiexec и других утилит, поставляемых с HPC Pack 2008. В качестве пример приведу настройку External tools для добавления и расчёта задачи на кластере в HPC Job Manager:</p>

<p>Command: C:\Program Files\Microsoft HPC Pack\Bin\job.exe</p>

<p>Arguments: submit /numcores:16 mpiexec <a href="file:///\\hnwindows\share\MyMPIProject.exe">\\hnwindows\share\MyMPIProject.exe</a></p>

<p>Разумеется, данный пример не иллюстрирует всех возможностей конфигурации External Tools. Данный инструмент достаточно мощный, позволяющий эффективно связывать среду разработки с непосредственно кластером (кроме этого, можно добиться такой конфигурации, что с workstation можно отправлять задачи на расчёт на удалённый headnode кластера), и данному аспекту будет посвящён отдельный пост. </p>]]></content:encoded>
    </item>
    <item>
      <title>Транзакционная память – вторая часть</title>
      <link>https://docs.microsoft.com/archive/blogs/ru-hpc/53</link>
      <pubDate>Sun, 06 Dec 2009 02:56:00 GMT</pubDate>
      <dc:creator><![CDATA[Garry Trinder]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/ru-hpc/2009/12/06/53/</guid>
      <description><![CDATA[В прошлом посте мы рассмотрели концепцию транзакционной памяти - средство высокоуровневой...]]></description>
      <content:encoded><![CDATA[<p>В <a href="https://blogs.msdn.com/ru-hpc/archive/2009/12/03/9931925.aspx">прошлом посте</a> мы рассмотрели концепцию транзакционной памяти - средство высокоуровневой организации работы параллельно выполняющихся задач. Рассмотрев основы операционной семантики, во многом совпадающие с семантикой транзакций, мы не упомянули несколько интересных расширений, предложенных в работе <a href="https://research.microsoft.com/en-us/um/people/simonpj/Papers/STM/stm.pdf">Composable Memory Transactions</a> и воплощенных в языке Concurrent Haskell. О них и пойдет речь сегодня.</p>  <p><strong>Оператор retry</strong></p>  <p>Цель введения транзакционной памяти - предоставить высокоуровневую замену блокировкам, но согласование обращений к разделяемой памяти отнюдь не исчерпывает все использования блокировок. Еще один важный аспект - <em>объекты синхронизации, которые используются для оповещения о событиях и блокирующего ожидания таких событий</em>. Чтобы быть последовательной, методология транзакционной памяти должна предоставлять аналогичную высокоуровневую концепцию, которую можно использовать для ожидания событий.</p>  <p>Одним из первых предложений в этом направлении было введение <a href="https://research.microsoft.com/en-us/um/people/tharris/papers/2003-oopsla.pdf">условных критических областей</a> (conditional critical regions, CCR) - аннотация транзакций некоторым условием, без правдивости которого транзакция не начинает выполнение. Этот подход работает, но не очень изящен, ибо требует создания новой транзакции даже тогда, когда транзакция уже запущена - то есть, только ради того, чтобы выразить семантику условного ожидания. </p>  <p>Современный взгляд на решение этой проблемы заключается в введении оператора retry, который сигнализирует о необходимости перезапуска транзакции - комбинация этого оператора с условным оператором предоставляет конструкт ожидания некоторого события и во многих случаях делает явную сигнализацию события избыточной. Ниже приведено сравнение реализаций блокирующей коллекции ограниченной емкости, аналогичной коллекции BlockingCollection&lt;T&gt; из .NET Framework 4.0 (реализация, основанная на примитивах синхронизации намеренно упрощена в целях краткости).</p>  <table border="0" cellspacing="0" cellpadding="2" width="707"><tbody>     <tr>       <td valign="top" width="385">         <p>Блокирующая коллекция</p>          <p>(реализована при помощи примитивов синхронизации)</p>       </td>        <td valign="top" width="320">         <p>Блокирующая коллекция</p>          <p>(реализована при помощи оператора retry)</p>       </td>     </tr>      <tr>       <td valign="top" width="385">         <div class="csharpcode">           <pre class="alt"><span class="kwrd">public</span> <span class="kwrd">class</span> BlockingCollection&lt;T&gt;</pre>

          <pre>{</pre>

          <pre class="alt">    <span class="kwrd">private</span> Object _lock = <span class="kwrd">new</span> Object();</pre>

          <pre>    </pre>

          <pre class="alt">    <span class="kwrd">public</span> <span class="kwrd">void</span> Put(T item)</pre>

          <pre>    {</pre>

          <pre class="alt">        <span class="kwrd">while</span> (Monitor.Wait(_lock, Timeout.Inifinite))</pre>

          <pre>        {</pre>

          <pre class="alt">            <span class="kwrd">try</span> </pre>

          <pre>            {</pre>

          <pre class="alt">                Monitor.Enter(_lock);</pre>

          <pre>                <span class="kwrd">if</span> (_impl.Count == _maxSize) <span class="kwrd">continue</span>;</pre>

          <pre class="alt">                _impl.Enqueue(item);</pre>

          <pre>            }</pre>

          <pre class="alt">            <span class="kwrd">finally</span></pre>

          <pre>            {</pre>

          <pre class="alt">                Monitor.Pulse(_lock);</pre>

          <pre>            }</pre>

          <pre class="alt">        } </pre>

          <pre>    }</pre>

          <pre class="alt">&#160;</pre>

          <pre>    <span class="kwrd">public</span> T Take()</pre>

          <pre class="alt">    {</pre>

          <pre>        <span class="kwrd">while</span> (Monitor.Wait(_lock, Timeout.Inifinite))</pre>

          <pre class="alt">        {</pre>

          <pre>            <span class="kwrd">try</span> </pre>

          <pre class="alt">            {</pre>

          <pre>                Monitor.Enter(_lock);</pre>

          <pre class="alt">                <span class="kwrd">if</span> (_impl.Count == 0) <span class="kwrd">continue</span>;</pre>

          <pre>                <span class="kwrd">return</span> _impl.Dequeue();</pre>

          <pre class="alt">            }</pre>

          <pre>            <span class="kwrd">finally</span></pre>

          <pre class="alt">            {</pre>

          <pre>                Monitor.Pulse(_lock);</pre>

          <pre class="alt">            }</pre>

          <pre>        }        </pre>

          <pre class="alt">    }</pre>

          <pre>}</pre>
        </div>
        <style type="text/css">
.csharpcode, .csharpcode pre
{
	font-size: small;
	color: black;
	font-family: consolas, "Courier New", courier, monospace;
	background-color: #ffffff;
	/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt 
{
	background-color: #f4f4f4;
	width: 100%;
	margin: 0em;
}
.csharpcode .lnum { color: #606060; }</style></td>

      <td valign="top" width="320">
        <div class="csharpcode">
          <pre class="alt"><span class="kwrd">public</span> <span class="kwrd">class</span> BlockingCollection&lt;T&gt;</pre>

          <pre>{</pre>

          <pre class="alt">    <span class="kwrd">public</span> <span class="kwrd">void</span> Put(T item)</pre>

          <pre>    {</pre>

          <pre class="alt">        atomic</pre>

          <pre>        {</pre>

          <pre class="alt">            <span class="kwrd">if</span> (_impl.Count == _maxSize) retry;</pre>

          <pre>            _impl.Enqueue(item);</pre>

          <pre class="alt">        }</pre>

          <pre>    }</pre>

          <pre class="alt">&#160;</pre>

          <pre>    <span class="kwrd">public</span> T Take()</pre>

          <pre class="alt">    {</pre>

          <pre>        atomic</pre>

          <pre class="alt">        {</pre>

          <pre>            <span class="kwrd">if</span> (_impl.Count == 0) retry;</pre>

          <pre class="alt">            <span class="kwrd">return</span> _impl.Dequeue();</pre>

          <pre>        }</pre>

          <pre class="alt">    }</pre>

          <pre>}</pre>
        </div>
        <style type="text/css">
.csharpcode, .csharpcode pre
{
	font-size: small;
	color: black;
	font-family: consolas, "Courier New", courier, monospace;
	background-color: #ffffff;
	/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt 
{
	background-color: #f4f4f4;
	width: 100%;
	margin: 0em;
}
.csharpcode .lnum { color: #606060; }</style></td>
    </tr>
  </tbody></table>

<p></p>

<p>Важно отметить, что <em>реализация оператора retry совсем не обязана в цикле перезапускать транзакцию</em>, занимая процессорное время и впустую растрачивая энергию - для корректной работы достаточно перезапускать транзакцию только лишь в случае, когда один из элементов данных, прочитанных транзакцией до выполнения retry, изменился. Например, для метода Take в примере выше система исполнения, обнаружив, что поток вызвал retry, приостановит его до того момента, пока не изменится ссылка на объект _impl или поле Count объекта _impl (это возможно, так как типичная реализация STM контролирует все операции с данными приложения - см. раздел &quot;Детали реализации&quot; <a href="https://blogs.msdn.com/ru-hpc/archive/2009/12/03/9931925.aspx">предыдущего поста</a>).</p>

<p>Для полноты изложения отметим особенность операционной семантики оператора retry в контексте вложенных транзакций. Как упоминалось в <a href="https://blogs.msdn.com/ru-hpc/archive/2009/12/03/9931925.aspx">предыдущем посте</a> в разделе &quot;Операционная семантика&quot;, вложенность транзакций проявляется только лишь в откате, но не в фиксации. Тот же самый принцип верен и здесь - если вложенная транзакция вызывает retry, то сигнал о перезапуске получает вся транзакция целиком, то есть retry работает как выброс необрабатываемого исключения (с поправкой на то, что, как указано выше, система исполнения может не обрабатывать retry немедленно, а подождать наилучшего момента).</p>

<p><strong>Комбинатор orElse</strong></p>

<p>В дополнении к оператору retry, который позволяет последовательно комбинировать блокирующие транзакции (например, два подряд идущих вызова Take, которые поочередно получают элементы из различных коллекций), можно ввести оператор orElse, который позволяет комбинировать альтернативные транзакции. </p>

<p>Этот бинарный оператор вначале выполняет транзакцию в левой части. При успешном завершении левой части комбинатор завершает свою работу, а в случае, если эта транзакция вызвала retry, то вместо обычного ожидания она будет отменена, и будет запущена транзакция в правой части. В случае вызова retry и транзакцией в правой части, retry распространится выше по дереву транзакций. Продолжая аналогию - если retry можно рассматривать как выброс исключения, то orElse представляет собой нечто вроде блока catch (с поправкой на то, что и retry, и orElse могут обрабатываются отложенно).</p>

<p>Оставляя в стороне красивые математические свойства этого комбинатора (то, что множество транзакций является относительно него моноидом), рассмотрим его применение в преобразовании между блокирующими и неблокирующими вызовами на примере выше:</p>

<div class="csharpcode">
  <pre class="alt"><span class="kwrd">public</span> <span class="kwrd">bool</span> NonBlockingTake(<span class="kwrd">out</span> T item)</pre>

  <pre>{</pre>

  <pre class="alt">    atomic</pre>

  <pre>    {</pre>

  <pre class="alt">        item = Take();</pre>

  <pre>        <span class="kwrd">return</span> <span class="kwrd">true</span>; </pre>

  <pre class="alt">    }</pre>

  <pre>    ||</pre>

  <pre class="alt">    atomic</pre>

  <pre>    {</pre>

  <pre class="alt">        item = <span class="kwrd">null</span>;</pre>

  <pre>        <span class="kwrd">return</span> <span class="kwrd">false</span>;</pre>

  <pre class="alt">    }</pre>

  <pre>}</pre>
</div>
<style type="text/css">
.csharpcode, .csharpcode pre
{
	font-size: small;
	color: black;
	font-family: consolas, "Courier New", courier, monospace;
	background-color: #ffffff;
	/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt 
{
	background-color: #f4f4f4;
	width: 100%;
	margin: 0em;
}
.csharpcode .lnum { color: #606060; }</style>

<p>Если в очереди есть элементы, то этот метод работает предсказуемо - возвращает последний элемент в out-параметре. Если же в очереди нет элементов, то транзакция, объявленная в методе Take, выполнит оператор retry, который распространится и на родительскую транзакцию, объявленную в методе NonBlockingTake, в результате чего будет выполнена правая часть комбинатора orElse и будет возвращен false.</p>]]></content:encoded>
    </item>
    <item>
      <title>Транзакционная память – первая часть</title>
      <link>https://docs.microsoft.com/archive/blogs/ru-hpc/63</link>
      <pubDate>Thu, 03 Dec 2009 04:12:32 GMT</pubDate>
      <dc:creator><![CDATA[Garry Trinder]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/ru-hpc/2009/12/03/63/</guid>
      <description><![CDATA[В прошлом посте о программной модели актеров мы рассмотрели альтернативу традиционной методике...]]></description>
      <content:encoded><![CDATA[<p>В прошлом посте о <a href="https://blogs.msdn.com/ru-hpc/archive/2009/11/25/9928475.aspx">программной модели актеров</a> мы рассмотрели альтернативу традиционной методике параллельного программирования при помощи потоков операционной системы. Модель актеров подразумевает разбиение программы на набор актеров - независимых сущностей, которые общаются друг с другом только при помощи передачи сообщений. В такой модели исключены самые неприглядные стороны традиционной параллельной разработки - гонки за разделяемыми данными и взаимные блокировки.</p>  <p>Но так же, как и <a href="https://blogs.msdn.com/ru-hpc/archive/2009/11/18/9923854.aspx">функциональная чистота</a>, модель актеров не всегда применима - как с точки зрения производительности (в силу вносимых издержек на копирование данных), так и с точки зрения удобства написания программы (иногда все же не избежать разделяемой структуры данных, например, очереди поступающих сообщений в планировщике актеров). Поэтому использование разделяемой памяти в параллельном программировании имеет большое практическое значение.</p>  <p>Как уже обсуждалось ранее, традиционная модель параллельного программирования слишком низкоуровнева, чтобы быть удобной, поэтому рассмотрим еще одну альтернативу - <a href="https://en.wikipedia.org/wiki/Software_transactional_memory">программную транзакционную память</a> (software transactional memory, STM), которую далее будем называть просто &quot;транзакционная память&quot;.</p>  <p><strong>Транзакционная память</strong></p>  <p>В системах управления базами данных транзакции издавна служат общепринятым средством организации корректного совместного доступа большого числа задач к разделяемому ресурсу, но применительно к управлению памятью история транзакций начинается с 1986 года, когда Том Найт опубликовал свою работу &quot;<a href="https://citeseerx.ist.psu.edu/search?q=%22An+Architecture+for+Mostly+Functional+Languages.%22">An architecture for mostly functional languages</a>&quot;. Изначально из-за больших накладных расходов предполагалось реализовывать технику транзакционной памяти при помощи аппаратной поддержки, но, начиная с середины 90-х, активно развивается исследование полностью программных реализаций, о которых мы и поговорим сегодня. За более подробной информацией об аппаратных реализациях транзакционной памяти любознательного читателя мы отправляем к статье исследователей из лаборатории Sun Microsystems: &quot;<a href="https://research.sun.com/scalable/pubs/ASPLOS2009-RockHTM.pdf">Early Experience with a Commercial Hardware Transactional Memory Implementation</a>&quot;.</p>  <p>Как и в случае с транзакциями СУБД, клиенты, осуществляющие транзакции STM, освобождаются от необходимости явно синхронизировать свою работу с другим задачами в системе. Код, манипулирующий разделяемой памятью, обрамляется в блок atomic {} (или аналогичную конструкцию), а система исполнения обеспечивает свойства <a href="https://en.wikipedia.org/wiki/ACID">ACID</a> - атомарность, согласованность, изоляцию и долговечность (последнее свойство обеспечивается в том смысле, который адекватен работе с оперативной памятью).</p>  <p>Вот как, например, при помощи STM проверяется целостность двунаправленного телефонного справочника, реализованного при помощи двух словарей (с небольшими изменениями пример взят из документа &quot;<a href="https://download.microsoft.com/download/9/5/6/9560741A-EEFC-4C02-822C-BB0AFE860E31/STM_User_Guide.pdf">.NET Framework 4 Beta 1 enabled to use Software Transactional Memory (STM.NET Version 1.0), Programmers’ Guide</a>&quot;):</p>  <table border="0" cellspacing="0" cellpadding="2" width="727"><tbody>     <tr>       <td valign="top" width="402">         <p>Проверка целостности справочника</p>          <p>(реализована с использованием блокировок)</p>       </td>        <td valign="top" width="323">         <p>Проверка целостности справочника</p>          <p>(реализована при помощи STM)</p>       </td>     </tr>      <tr>       <td valign="top" width="402">         <div class="csharpcode">           <div class="csharpcode">             <pre class="alt"><span class="kwrd">bool</span> ValidateMapping(<span class="kwrd">string</span> name, <span class="kwrd">string</span> phone) </pre>

            <pre>{</pre>

            <pre class="alt">    <span class="kwrd">bool</span> result1, result2; </pre>

            <pre>    <span class="kwrd">string</span> name2, phone2; </pre>

            <pre class="alt">&#160;</pre>

            <pre>    <span class="kwrd">const</span> <span class="kwrd">int</span> MaxOptReadOps = 16; </pre>

            <pre class="alt">    <span class="kwrd">int</span> backoffIterations = 1;</pre>

            <pre>    <span class="kwrd">int</span> v1, v2; </pre>

            <pre class="alt">    <span class="kwrd">int</span> optReadOps = 0;</pre>

            <pre>&#160;</pre>

            <pre class="alt">    <span class="kwrd">while</span> (<span class="kwrd">true</span>) </pre>

            <pre>    { </pre>

            <pre class="alt">        <span class="kwrd">if</span> (optReadOps &lt;= maxOptReadOps) </pre>

            <pre>        { </pre>

            <pre class="alt">            <span class="rem">// До некоторого порога используем</span></pre>

            <pre>            <span class="rem">// оптимистическую блокировку</span></pre>

            <pre class="alt">            v1 = version; </pre>

            <pre>            result1 = name2phone.TryGetValue(name, </pre>

            <pre class="alt">                               <span class="kwrd">out</span> outPhone); </pre>

            <pre>            result2 = phone2name.TryGetValue(phone, </pre>

            <pre class="alt">                               <span class="kwrd">out</span> outName); </pre>

            <pre>&#160;</pre>

            <pre class="alt">            v2 = version; </pre>

            <pre>            <span class="kwrd">if</span>( (v1 != v2) || ((v1 &amp; 1) != 0)) </pre>

            <pre class="alt">            { </pre>

            <pre>                <span class="rem">// Кто-то успел обновить словарь</span></pre>

            <pre class="alt">                <span class="rem">// Ждем некоторое время</span></pre>

            <pre>                <span class="rem">// перед следующей попыткой</span></pre>

            <pre class="alt">                var t = backoffIterations &lt;&lt; optReadOps;</pre>

            <pre>                Thread.SpinWait(t); </pre>

            <pre class="alt">                optReadOps++; </pre>

            <pre>            } </pre>

            <pre class="alt">            <span class="kwrd">else</span> </pre>

            <pre>            { </pre>

            <pre class="alt">                <span class="kwrd">break</span>; </pre>

            <pre>            } </pre>

            <pre class="alt">        } </pre>

            <pre>        <span class="kwrd">else</span> </pre>

            <pre class="alt">        { </pre>

            <pre>            <span class="rem">// Мы несколько раз пробовали</span></pre>

            <pre class="alt">            <span class="rem">// оптимистическую блокировку,</span></pre>

            <pre>            <span class="rem">// но нас все время прерывали</span></pre>

            <pre class="alt">            <span class="rem">// Время переключиться в режим</span></pre>

            <pre>            <span class="rem">// пессимистической блокировки</span></pre>

            <pre class="alt">            rwls.EnterReadLock(); </pre>

            <pre>            <span class="kwrd">try</span> </pre>

            <pre class="alt">            {</pre>

            <pre>                result1 = name2phone.TryGetValue(name, </pre>

            <pre class="alt">                                   <span class="kwrd">out</span> outPhone); </pre>

            <pre>                result2 = phone2name.TryGetValue(phone, </pre>

            <pre class="alt">                                   <span class="kwrd">out</span> outName); </pre>

            <pre>            } </pre>

            <pre class="alt">            <span class="kwrd">finally</span> </pre>

            <pre>            { </pre>

            <pre class="alt">                rwls.ExitReadLock(); </pre>

            <pre>            } </pre>

            <pre class="alt">            <span class="kwrd">break</span>; </pre>

            <pre>    } </pre>

            <pre class="alt">&#160;</pre>

            <pre>    <span class="rem">// Проверяем результаты</span></pre>

            <pre class="alt">}</pre>
          </div>
          <style type="text/css">


.csharpcode, .csharpcode pre
{
	font-size: small;
	color: black;
	font-family: consolas, "Courier New", courier, monospace;
	background-color: #ffffff;
	/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt 
{
	background-color: #f4f4f4;
	width: 100%;
	margin: 0em;
}
.csharpcode .lnum { color: #606060; }</style><style type="text/css">


.csharpcode, .csharpcode pre
{
	font-size: small;
	color: black;
	font-family: consolas, "Courier New", courier, monospace;
	background-color: #ffffff;
	/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt 
{
	background-color: #f4f4f4;
	width: 100%;
	margin: 0em;
}
.csharpcode .lnum { color: #606060; }</style></div>
      </td>

      <td valign="top" width="323">
        <div class="csharpcode">
          <pre class="alt"><span class="kwrd">bool</span> ValidateMapping(<span class="kwrd">string</span> name, <span class="kwrd">string</span> phone) </pre>

          <pre>{</pre>

          <pre class="alt">    <span class="kwrd">string</span> outPhone = <span class="kwrd">null</span>; </pre>

          <pre>    <span class="kwrd">string</span> outName = <span class="kwrd">null</span>; </pre>

          <pre class="alt">&#160;</pre>

          <pre>    atomic </pre>

          <pre class="alt">    { </pre>

          <pre>        outPhone = name2phone.TryGetValue(name);</pre>

          <pre class="alt">        outName = phone2name.TryGetValue(phone); </pre>

          <pre>    } </pre>

          <pre class="alt">&#160;</pre>

          <div class="csharpcode">
            <div class="csharpcode">
              <pre>    <span class="rem">// Проверяем результаты</span></pre>
            </div>
          </div>

          <pre class="alt">}</pre>
        </div>
        <style type="text/css">


.csharpcode, .csharpcode pre
{
	font-size: small;
	color: black;
	font-family: consolas, "Courier New", courier, monospace;
	background-color: #ffffff;
	/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt 
{
	background-color: #f4f4f4;
	width: 100%;
	margin: 0em;
}
.csharpcode .lnum { color: #606060; }</style><style type="text/css">


.csharpcode, .csharpcode pre
{
	font-size: small;
	color: black;
	font-family: consolas, "Courier New", courier, monospace;
	background-color: #ffffff;
	/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt 
{
	background-color: #f4f4f4;
	width: 100%;
	margin: 0em;
}
.csharpcode .lnum { color: #606060; }</style></td>
    </tr>
  </tbody></table>

<p>На примере мы видим, что STM решает фундаментальную проблему традиционного параллельного программирования, которая заключается в том, что <em>блокировки не компонуются</em>. Так, в примере слева, чтобы скомпоновать потокобезопасные реализации словарей, программисту необходимо приложить нетривиальные усилия, а также знать детали реализации этих словарей (например, знать о наличии поля версии и иметь к нему доступ). В отличие от блокировок <em>транзакции в памяти прекрасно компонуются</em> - потокобезопасные реализации в примере справа (тоже написанные при помощи STM) могут быть корректно объединены в более крупную транзакцию.</p>

<p>Таким образом, одно из важнейших свойств транзакционной памяти заключается в том, что она не только избавляет программиста от необходимости реализовывать блокировки и балансировать между слишком мелкой и слишком крупной гранулярностью, но и делает возможным <em>повторное использование потокобезопасного кода</em>.</p>

<p><strong>Операционная семантика</strong></p>

<p>Основы операционной семантики транзакционной памяти во многом совпадают с интуитивным представлением о транзакциях, которое можно получить из опыта использования СУБД:</p>

<ol>
  <li>Для каждой транзакции система исполнения обеспечивает ACID-свойства. </li>

  <li>Если транзакция противоречит некоторой транзакции, зафиксированной в процессе своего выполнения (то есть, читает или пишет измененный элемент данных), то транзакция откатывается - результаты ее действий отменяются, и она может быть перезапущена (чаще всего системы STM автоматически перезапускают транзакцию в случае конфликтов). </li>

  <li>Если необработанное исключение покидает пределы транзакции, то транзакция откатывается и не перезапускается. </li>

  <li>Вложенные транзакции фиксируются вместе с родительской транзакцией (то есть, относительно фиксации дерево транзакций выпрямляется в список), но откатываются по отдельности (например, исключение выброшенное вложенной транзакцией, но обработанное в родительской, отменит только вложенную). </li>
</ol>

<p>Отметим, что этим семантика транзакционной памяти не ограничивается и допускает несколько полезных расширений, но на сегодня мы отложим их рассмотрение до <a href="https://blogs.msdn.com/ru-hpc/archive/2009/12/06/9933123.aspx">следующего поста</a>.</p>

<p><strong>Детали реализации</strong></p>

<p>Для того, чтобы магия STM стала возможной, и мы могли писать параллельный код кратко и выразительно, <em>необходима сложная система исполнения</em>. Кстати, в этом техника STM очень похожа на технику сборки мусора (garbage collection, GC) - они обе значительно упрощают программу, обе могут быть разрабатываемы узкой группой специалистов независимо от приложений, их использующих и, к сожалению, обе ухудшают производительность приложения, но о последнем позже. Сейчас рассмотрим детали реализации типичной системы исполнения STM.</p>

<p><em>Для каждой транзакции система выделяет приватную область памяти</em>, недоступную другим транзакциями, в которой транзакция хранит результаты своей работы (это обеспечивает изоляцию). Таким образом, до фиксации все записи элементов данных идут исключительно в приватную область памяти, а фиксация заключается в том, что действия, совершенные транзакцией, атомарным образом записываются в публичную память (это обеспечивает согласованность). Долговечность транзакции достигается в том смысле, что после фиксации никакие ошибки в программе не могут привести к полному или частичному откату транзакции.</p>

<p><em>Корректность фиксирования в STM обычно достигается оптимистическим способом</em> - в процессе выполнения транзакции используемые ей данные не блокируются, а фиксация транзакции разрешается, если и только если на момент фиксирования состояние элементов данных, которые были прочитаны или записаны транзакцией, совпадают с состояниями на момент начала транзакции. В противном случае транзакция перезапускается - ее приватная область памяти очищается и исполнение начинается сначала. </p>

<p>Таким образом, необходимым условием работы типичной системы STM является <em>слежение за используемыми в приложении данными</em> - оно может быть реализовано при помощи <a href="https://dejavu.codeplex.com/">вручную объявленных оберток</a>, а может быть полностью неявным благодаря <a href="https://msdn.microsoft.com/en-us/devlabs/ee334183.aspx">модифицированной виртуальной машине</a>.</p>

<p><strong>Проблемы работы с разделяемой памятью</strong></p>

<p>Транзакционная память полностью исключает взаимные блокировки, но с доступом к разделяемой памяти связаны и другие трудности, например, гонки за данными и неоптимальность планирования. Рассмотрим этот вопрос более подробно.</p>

<p>Оптимистичность фиксации в STM делает невозможной взаимные блокировки, но допускает возникновение их антиподов - <a href="https://en.wikipedia.org/wiki/Deadlock#Livelock"><em>лайвлоков</em></a> (livelocks). Лайвлоки могут возникать, когда несколько транзакций одновременно меняют один и тот же элемент данных, вследствие того, что многие реализации STM в целях оптимизации задолго до фиксации определяют тот факт, что транзакции конфликтуют и отправляют их на перезапуск. Для разрешения таких ситуаций используются вариации алгоритма <a href="https://en.wikipedia.org/wiki/Exponential_backoff">экспоненциальных пауз перед повторным запуском</a> (exponential backoff).</p>

<p>Еще один возможный тип проблем совместного доступа к разделяемой памяти, которому подвержены системы STM - <a href="https://en.wikipedia.org/wiki/Priority_inversion"><em>инверсия приоритетов</em></a> (priority inversion). Инверсия приоритетов может возникать из-за предельной оптимистичности алгоритма фиксации, описанного выше. Так, &quot;неудачная&quot; или очень большая задача может быть постоянно отправляема на перезапуск из-за того, что за время каждого ее исполнения находится другая задача, которая успевает поменять используемые исходной задачей данные и зафиксировать свои изменения. Подобные проблемы обычно разрешаются присваиванием задаче ранга, который определяется по эвристической формуле на основе количества неудачных попыток выполнениям и размера задачи. В случае, если ранг превосходит некий заданный порог, выполнение задачи начинает гарантироваться пессимистическими алгоритмами (блокировками).</p>

<p><strong>Недостатки транзакционной памяти</strong></p>

<p>Как и практически все методологии параллельного программирования, STM имеет как плюсы, так и минусы. Достоинства транзакционной памяти мы в деталях обсудили выше - в этом разделе поговорим о недостатках.</p>

<p>STM основана на том, что с любой точки исполнения транзакции могут быть отменены и корректно выполнены заново, то есть <em>STM полагается на отсутствие в транзакциях побочных эффектов</em>. Но, в реальной жизни зачастую приходится сталкиваться с логикой так называемого awkward squad - вводом-выводом, исключениями и так далее. Эта же логика иногда необходима и в транзакциях. Один из вариантов - запретить побочные эффекты внутри транзакций, но он слишком ограничивающий, чтобы удовлетворять все потребности программистов. С другой стороны, хороших альтернатив нет, и есть лишь ряд полумер, каждая из которых требует вмешательства программиста и подходит лишь для конкретного класса ситуаций, но не покрывает все возможные случаи изменчивой логики: 1) отложенные вызовы побочных эффектов, 2) написанные вручную компенсаторы, которые могут отменять результаты побочных эффектов того или иного типа, 3)написанные вручную альтернативы побочным эффектам, выполняемые, когда изменчивая логика вызывается из транзакции.</p>

<p>Далее, так как важный сценарий возможного использования STM - применение к унаследованному коду, встает вопрос о <em>сосуществовании транзакционного и нетранзакционного способов доступа к разделяемой памяти.</em> На обычный код не распространяется слежение системы исполнения STM за памятью, поэтому он легко может нарушить согласованность транзакций. В этом случае, равно как и в предыдущем, запретительные меры слишком строги, чтобы быть практичными, а среди альтернатив нельзя выбрать однозначно хорошую. В качестве одного из решений можно предложить ручную аннотацию критичных фрагментов унаследованного кода (<a href="https://download.microsoft.com/download/9/5/6/9560741A-EEFC-4C02-822C-BB0AFE860E31/STM_User_Guide.pdf">.NET Framework 4 Beta 1 enabled to use Software Transactional Memory (STM.NET Version 1.0) Programmers’ Guide</a>, раздел 6 &quot;Atomic Compatibility Contracts&quot;).</p>

<p>Наконец, рассмотрим самый важный пункт критики транзакционной памяти - <em>производительность</em>. Кроме очевидных издержек на поддержку теневых копий элементов данных в приватных областях памяти, типичная реализация STM, описанная выше, имеет еще несколько особенностей, которые дальше ухудшают производительность решения. Во-первых, для чтения содержимого объекта необходимо производить дополнительные косвенные обращения - проверить, был ли изменен объект во время транзакции (первое), и, если да, то прочитать содержимое из приватной области (возможно, второе). В обычной реализации чтение адреса объекта, а также дополнительные косвенные обращения, производимые STM, навряд ли будут обладать пространственной локальностью, а это значительно снижает эффективность процессорного кэша. Во-вторых, вследствие неблокирующей природы типичных реализаций STM может произойти перенасыщение системы одновременно выполняющимися транзакциями, что приведет к очень неэффективному использованию процессора. Возможными решениями здесь являются: 1) использование аппаратной поддержки STM, 2) <a href="https://enfranchisedmind.com/blog/posts/the-problem-with-stm-your-languages-still-suck/">отказ от изменчивости данных</a>, что избавит от необходимости хранить теневые копии объектов, 3) <a href="https://berkeley.intel-research.net/rennals/rennals/pubs/052RobEnnals.pdf">замена оптимистической схемы работы автоматическими блокировками</a>, что избавит от необходимости в приватной области памяти для транзакций и не допустит перенасыщение системы задачами.</p>

<p>Непосредственно связана в вопросом производительности проблема <em>гранулярности слежения за данными приложения</em>. Здесь мы имеем типичную дилемму блокировок - слишком мелкозернистые блокировки непрактичны, потому что приводят к большим издержкам, слишком крупнозернистые непрактичны, потому что приводят к ложным конфликтам. Например, рассмотрим подход, выбранный авторами библиотеки <a href="https://msdn.microsoft.com/en-us/devlabs/ee334183.aspx">STM.NET</a> - структурировать изменения на уровне отдельных объектов (<a href="https://download.microsoft.com/download/9/5/6/9560741A-EEFC-4C02-822C-BB0AFE860E31/STM_User_Guide.pdf">.NET Framework 4 Beta 1 enabled to use Software Transactional Memory (STM.NET Version 1.0) Programmers’ Guide</a>, раздел 8.1 &quot;Consistency of Aborted Transactions&quot;). С одной стороны, он обладает лучшей производительностью по сравнению со слежением за каждым машинным словом, но, с другой стороны, порождает ложные конфликты между транзакциями, которые меняют разные поля одного и того же объекта.</p>

<p><strong>Заключение</strong></p>

<p>Транзакционная память - одна из самых противоречивых техник в параллельном программировании. В отличие от функционального программирования, преимуществами которого можно воспользоваться, лишь приняв новую методологию мышления и переписав существующие программы, STM позволяет раскрыть параллелизм уровня задач здесь и сейчас - для программ, работающих с разделяемой памятью. </p>

<p>Однако на практике реализации STM сталкиваются с многочисленными трудностями, не последней из которых является значительное ухудшение производительности (которое, справедливости ради, зачастую компенсируется значительно улучшенной масштабируемостью). Впрочем, пока что нет концептуальных опровержений STM, и для каждого недостатка предложен ряд практичных решений, поэтому исследования транзакционной памяти - одни из самых активных в современном параллельном программировании.</p>

<p>Из практических реализаций STM для платформы .NET можно отметить библиотеку <a href="https://weblogs.asp.net/ralfw/archive/tags/Software+Transactional+Memory/default.aspx">NSTM</a>, требующую явное создание оберток данных приложения, и проект <a href="https://msdn.microsoft.com/en-us/devlabs/ee334183.aspx">STM.NET</a>, который следит за данными неявно при помощи модифицированных CLR и JIT. Оба эти подхода имеют свои достоинства и недостатки, обсуждение которых выходит за рамки данного поста.</p>

<p><strong>Полезные материалы (на английском)</strong></p>

<p>1. &quot;<a href="https://research.microsoft.com/en-us/um/people/tharris/papers/2003-oopsla.pdf">Language Support for Lightweight Transactions</a>&quot;, Tim Harris, Keir Fraser, 2003</p>

<p>2. &quot;<a href="https://research.microsoft.com/en-us/um/people/simonpj/Papers/STM/stm.pdf">Composable Memory Transactions</a>&quot;, Tim Harris, Simon Marlow, Simon Peyton Jones, Maurice Herlihy, 2006</p>

<p>3. &quot;<a href="https://download.microsoft.com/download/9/5/6/9560741A-EEFC-4C02-822C-BB0AFE860E31/STM_User_Guide.pdf">.NET Framework 4 Beta 1 enabled to use Software Transactional Memory (STM.NET Version 1.0), Programmers’ Guide</a>&quot;, The STM.NET Team, Microsoft Research, 2009</p>

<p>4. &quot;<a href="https://blogs.msdn.com/brandonwerner/archive/2009/01/14/software-transactional-memory-debunked.aspx">Software Transactional Memory: Debunked?</a>&quot;, Brandon Werner, 2009</p>

<p>5. &quot;<a href="https://enfranchisedmind.com/blog/posts/the-problem-with-stm-your-languages-still-suck/">The problem with STM: your languages still suck</a>&quot;, Brian Hurt, 2009</p>

<p>6. &quot;<a href="https://berkeley.intel-research.net/rennals/rennals/pubs/052RobEnnals.pdf">Software Transactional Memory Should Not Be Obstruction-Free</a>&quot;, Rob Ennals, 2006</p>

<p>7. &quot;<a href="https://www.cs.rochester.edu/u/scott/papers/2006_TRANSACT_RSTM.pdf">Lowering the Overhead of Nonblocking Software Transactional Memory</a>&quot;, Virendra J. Marathe et al., 2006</p>

<p>8. &quot;<a href="https://research.sun.com/scalable/pubs/ASPLOS2009-RockHTM.pdf">Early Experience with a Commercial Hardware Transactional Memory Implementation</a>&quot;, Dave Dice, Yossi Lev, Mark Moir, Dan Nussbaum, 2009</p>]]></content:encoded>
    </item>
    <item>
      <title>Программная модель актеров</title>
      <link>https://docs.microsoft.com/archive/blogs/ru-hpc/73</link>
      <pubDate>Wed, 25 Nov 2009 02:30:06 GMT</pubDate>
      <dc:creator><![CDATA[Garry Trinder]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/ru-hpc/2009/11/25/73/</guid>
      <description><![CDATA[Несмотря на повсеместное распространение и эксклюзивную поддержку на многих платформах, модель...]]></description>
      <content:encoded><![CDATA[<p>Несмотря на повсеместное распространение и эксклюзивную поддержку на многих платформах, модель параллелизма, основанная на потоках операционной системы, имеет ряд принципиальных недостатков, которые усложняют написание корректных и масштабируемых параллельных программ.</p>  <p>Во-первых, программисту доступен лишь самый низкий уровень абстракции для общения между параллельно выполняющимися задачами - разделяемое состояние (shared state). В такой модели дополнительно к работе со данными каждая задача должна прилагать усилия по координации этой работы с другими задачами. Главная проблема здесь в том, что программисту необходимо очень детально описать логику работы и координации, что в свою очередь создает <a href="https://en.wikipedia.org/wiki/Semantic_gap">семантический разрыв</a> между человеческим и компьютерным представлениями программы. Этот разрыв снижает эффективность выражения мыслей, ухудшает читаемость кода и в итоге приводит к ошибкам в программе (гонки за данными (contentions, races), взаимные блокировки (deadlocks), плохая масштабируемость).</p>  <p>Во-вторых, в операционных системах Windows <a href="https://blogs.msdn.com/ru-hpc/archive/2009/08/19/clr.aspx">слишком велики</a> издержки на запуск потока ОС, что делает непрактичным или невозможным разбиение программы на большое количества параллельно исполняющихся элементов. Но значительное количество программ (веб-приложения, обработчики систем реального времени и т.п.), наоборот, нуждаются в одновременном запуске как можно большего числа исполнителей. Поэтому, и здесь мы наблюдаем семантический разрыв, который заставляет программиста вручную поддерживать отображение логических потоков на потоки физические. Это приводит к таким сложнейшим решениям, как <a href="https://blogs.msdn.com/cbrumme/archive/2004/02/21/77595.aspx">модель файберов SQL Server</a>, в которых каждая упущенная мелочь может иметь катастрофические последствия.</p>  <p>Поводя промежуточный итог, можно заметить, что <i>в силу своей низкоуровневости модель потоков не слишком хорошо подходит для программирования человеком</i> - отсюда вытекают проблемы, с которыми обычно сталкиваются разработчики при написании параллельных программ. Один из выходов из этой ситуации - использование высокоуровневых моделей, которые позволяют человеческое понимание программы более просто выразить в программном коде. Одной из таких моделей является модель актеров.</p>  <p><b>Модель</b><b> </b><b>актеров</b></p>  <p>В 1973 году Карл Хьюитт с коллегами выпустили работу &quot;<a href="https://dli.iiit.ac.in/ijcai/IJCAI-73/PDF/027B.pdf">A Universal Modular Actor Formalism for Artificial Intelligence</a>&quot;, в которой представили резюме многолетней работы исследователей из MIT Artificial Intelligence Laboratory - &quot;a modular ACTOR architecture and definitional method for artificial intelligence that is conceptually based on a single kind of object: actors [or, if you will, virtual processors, activation frames, or streams] &quot;. </p>  <p>В такой архитектуре вычисления реализуются набором актеров, каждый из которых:</p>  <ol>   <li>Имеет идентификатор, по которому он может быть опознан и адресован другими актерами. </li>    <li>Умеет общаться с другими актерами путем посылки и получения сообщений, причем для набора отправленных сообщений гарантируется только сам факт их доставки адресатам, но не порядок их получения. </li>    <li>Реализует свое поведение в реакциях на поступающие сообщения. </li>    <li>Имеет недоступное для внешнего мира состояние, которое может влиять на его поведение. </li>    <li>В ответ на некоторое сообщение может выполнить произвольную комбинацию следующих действий: а) изменить свое состояние, б) изменить логику обработки последующих сообщений, в) послать одно или несколько асинхронных сообщений, г) создать одного или нескольких новых актеров, д) завершить свою работу. </li> </ol>  <p>Как можно видеть, модель актеров очень похожа на <a href="https://bitworking.org/news/116/Alan-Kay-on-the-meaning-of-Object-Oriented">модель объектов</a> в объектно-ориентированном программировании: а) программа представляется в виде набора однородных взаимодействущих сущностей, б) состояние каждой сущности инкапсулировано внутри и доступно только теми способами, которые разрешит владелец, в) в процессе выполнения программы поведение сущностей может меняться.</p>  <p>Оставляя за кадром <a href="https://en.wikipedia.org/wiki/Actor_model#Formal_systems">математические теории</a>, с помощью которых можно формальным образом делать заключения о системах актеров, рассмотрим модель актеров как ценную абстракцию разработки параллельных программ.</p>  <p><b>Практическая реализация</b></p>  <p>На практике актер реализуется в виде сущности, у которой есть <i>состояние</i>, <i>поведение</i> и <i>почтовый ящик </i>(mailbox) - асинхронная очередь необработанных сообщений (обычно почтовый ящик поддерживается библиотекой исполнения, а программист работает с ним неявно при помощи конструктов посылки и получения сообщений - в некоторых языках даже есть специальные операторы send и receive).</p>  <p>Программа в модели актеров представляется в виде набора параллельно работающих сущностей. Так как потенциально таких сущностей может быть очень много, то используются техники планирования на пользовательском уровне (user-mode scheduling), которые заключаются в том, что запуском актеров на исполнение управляет не операционная система, а среда исполнения (runtime). В операционных системах Windows такой подход можно реализовать при помощи <a href="https://en.wikipedia.org/wiki/Fiber_(computer_science)">файберов</a> и <a href="https://msdn.microsoft.com/en-us/library/dd627187(VS.85).aspx">UMS API</a>. </p>  <p>Упрощенно типичный алгоритм планировщика среды исполнения можно описать следующим образом: процессорное время делится на кванты, которые по очереди раздаются актерам в системе; в случае, если актер исчерпал свой квант времени, или же, если обнаруживается, что после обработки очередного сообщения почтовый ящик актера пуст, планировщик приостанавливает текущего актера и запускает на исполнение следующего.</p>  <p>Модель актеров можно реализовать на любом популярном языке программирования, но, как и в случае с <a href="https://blogs.msdn.com/ru-hpc/archive/2009/11/15/9922606.aspx">функциями высшего порядка</a>, определенные возможности языка и стандартной библиотеки могут сделать использование этой методики более удобным. Из таких возможностей можно отметить:</p>  <ol>   <li>Сопоставление с шаблоном (pattern matching) для удобной фильтрации сообщений. </li>    <li>Объявление собственных операторов для наглядной записи шаблонов коммуникации между актерами (например, в F# можно определить операторы &quot;&lt;--&quot; и &quot;&lt;-&gt;&quot; для, соответственно, посылки сообщения и посылки с последующим ожиданием ответа). </li>    <li>Поддержку сопрограмм для облегчения реализации планировщика (например, в C# можно использовать операторы yield return и yield break для возвращения управления планировщику с возможностью последующего возобновления исполнения с места остановки). </li>    <li>Наличие асинхронных API типичных операций вроде работы с файлами и сетью для предотвращения блокировки всей системы одним-единственным актером, ожидающим результата длительного синхронного вызова. </li> </ol>  <p>На платформе .NET преимуществами модели актеров можно воспользоваться при помощи специального языка программирования <a href="https://download.microsoft.com/download/B/D/5/BD51FFB2-C777-43B0-AC24-BDE3C88E231F/Axum%20Programmers%20Guide.pdf">Axum</a>, а также на привычных <a href="https://code.google.com/p/retlang/">C#</a> и <a href="https://codebetter.com/blogs/matthew.podwysocki/archive/2009/05/28/actors-in-f-the-bounded-buffer-problem.aspx">F#</a> (в случае F# будет гораздо меньше синтаксического шума, который прячется за встроенными в язык монадами и сопоставлению по шаблону).</p>  <p><b>Пример использования</b></p>  <p>В качестве примера использования модели актеров рассмотрим разработку онлайнового аукциона, на котором могут распродаваться большое количество лотов, на каждый из которых могут делать ставки покупатели. Для упрощения задачи представим упрощенные функциональные требования: 1) при добавлении нового лота начинается аукцион, который длится 24 часа, 2) пользователи заходят в программу, получают список лотов, могут делать ставки на некоторые из них, 3) по завершении аукциона лот получает пользователь, сделавший наибольшую ставку.</p>  <p><b>Аукцион - типичное решение</b></p>  <p>Одним из типичных подходов к решению этой задачи будет разработка клиент-серверного приложения при помощи стандартного стека средств платформы .NET - WPF на клиентской стороне, LINQ to SQL/Entity Framework на стороне сервера и WCF для общения между компонентами приложения. Рассмотрим типичную реализацию сервера, заострив внимание на пропускной способности решения и абстрагировавшись от других деталей, например, от авторизации/аутентификации. Серверный API выражается двумя методами - Lot[] GetAll(), bool Bid(Guid lotId, decimal amount). Метод GetAll обращается к базе данных и выбирает оттуда все записи в таблице Lot, при помощи OR/M средства преобразуя их в объекты и отправляя клиенту. Метод Bid запрашивает текущую ставку на лот с идентификатором lotId у базы данных и, если ставка меньше, чем предложенная пользователем, метод обновляет поле highestBid в записи лота и сохраняет изменения в базу.</p>  <p>Чтобы полностью задействовать все ядра сервера, мы запускаем сразу несколько потоков, которые будут принимать и обрабатывать запросы от клиентов. Теперь мы должны позаботиться о синхронизации разделяемых между ними данных - ведь сразу несколько потоков могут попытаться записать ставку для одного и того же лота. Поэтому мы вводим протокол оптимистического блокирования - в соответствии с ним метод Bid перед сохранением обновленного объекта Lot в базу проверяет время предыдущего обновления записи в базе, и, если оно не совпадает с таковым у объекта в памяти, метод перезапускается.</p>  <p>После обкатки программы в тестовом режиме мы замечаем, что временами пропускная способность сервера недостаточна, и пользователи жалуются на медленную работу системы. После сеанса профилировки обнаруживается проблема - слишком много времени в методе Bid тратится на ожидание ответа от базы данных, из-за чего вместо обработки очередных запросов пользователей потоки простаивают. Для решения этой проблемы мы вводим кэш лотов, который делает ненужным обращение к базе данных в самом начале выполнения метода Bid. Но, так как наше приложение многопоточно, мы должны дополнительно реализовать протокол синхронизации доступа к кэшу, например, при помощи блокировки.</p>  <p>Р��зюмируя, можно отметить, что в финальной реализации метод Bid представляет собой неразделимую смесь трех алгоритмов - бизнес-логики, оптимистичного блокирования и синхронизации доступа к кэшу, причем последние два являются вспомогательным кодом и непосредственно не реализуют функциональные требования.</p>  <p><b>Аукцион - решение с помощью актеров</b></p>  <p>Как описано выше, в модели актеров программа представляется в виде набора однородных взаимодействующих сущностей. Руководствуясь этим принципом, можно разделить систему следующим образом - в каждом клиентском модуле работает по одному актеру, которые посылают сообщения на сервер, на котором каждый лот в базе данных представлен отдельным актером.</p>  <p>Анализируя такой дизайн, можно заметить, что для него нехарактерны проблемы с масштабируемостью - так как актеров в системе много, то нагрузку по обработке ими сообщений от клиентских актеров среда исполнения сможет эффективно распределить на типичное количество процессоров. Кроме того, становится ненужным оптимистическое блокирование лотов, так как обработку каждого конкретного лота осуществляет единственный актер, запросы к которому поступают не одновременно, а последовательно - через почтовый ящик. Также, значительно упрощается кэширование - для него не надо создавать глобальную для сервера хэш-таблицу, а достаточно просто хранить в состоянии каждого актера текущую ставку на его лот. По тем же причинам, по которым не нужно оптимистическое блокирование, здесь также не нужна и синхронизация доступа к кэшу.</p>  <p>Кроме того, с помощью актеров мы можем провести еще более тонкую настройку пропускной способности сервера. Для предыдущего подхода кэширование позволило уменьшить количество обращений к базе данных, но каждый поток-обработчик все равно вынужден ждать результатов сохранения обновленного лота. В модели актеров мы можем делегировать работу с базой данных отдельной группе актеров-персисторов. Актер-обработчик, выполнив свою работу, далее посылает сообщение группе персисторов, после чего один из актеров этой группы сохраняет результаты в базу данных и посылает ответ клиенту, а также извещает обработчика в случае, если произошел сбой. В процессе исполнения программы можно балансировать между количеством обработчиков и персисторов, сравнивая длину очереди необработанных сообщений той или иной группы и сообразно добавляя или удаляя актеров.</p>  <p>Наконец, в модели актеров несложно реализовать истечение времени аукциона - вместо отдельного потока, который бы синхронизировался с обработчиками, мы перекладываем решение на планировщик системы исполнения, алгоритм работы которого дополняется новым правилом: &quot;перед передачей актеру очередного сообщения проверить время его жизни и, если оно превосходит 24 часа, то, не обрабатывая текущее сообщение, завершить аукцион по текущему лоту и разослать результаты заинтересованным клиентам&quot;.</p>  <p><b>Заключение</b></p>  <p>Модель актеров - высокоуровневый подход для создания параллельных приложений, обладающий большим потенциалом для решения практических задач. С точки зрения прикладного программирования, этот подход построен на следующих базовых принципах:</p>  <ul>   <li>Наличие легковесных процессов, не привязанных к потокам ОС. </li>    <li>Возможность асинхронной передачи сообщений между процессами. </li>    <li>Отсутствие разделямых данных между процессами и, следовательно, невозможность гонок и взаимных блокировок. </li> </ul>  <p>Как мы видели на примере, параллельные программы, разработанные с использованием актеров, удобны в написании и сопровождении, а также принципиально не содержат проблем с гонками и взаимными блокировками, которые характерны для традиционной методологии. Здесь можно провести аналогию с <a href="https://blogs.msdn.com/ru-hpc/archive/2009/11/18/9923854.aspx">функциональной чистотой</a> - за счет записи в специальном виде программа получает свойства, полезные и для человека, и для компьютера.</p>  <p>Наконец, модель актеров отлично подходит для создания распределенных систем, с помощью примитивов посылки и принятия сообщений абстрагируя программиста от необходимости обнаруживать удаленные процессы, сериализовывать данные, выполнять RPC-вызовы и контролировать их надежность. Таким образом устроен язык программирования <a href="https://en.wikipedia.org/wiki/Erlang_(programming_language)">Erlang</a>, который стал де-факто стандартом для разработки распределенных систем с высокими требованиями по нагрузке и доступности.</p>  <p><b>Полезные</b><b> </b><b>материалы</b><b> (</b><b>на</b><b> </b><b>английском</b><b>)</b></p>  <p>1. &quot;<a href="https://codebetter.com/blogs/matthew.podwysocki/archive/2009/05/12/axum-introduction-and-ping-pong-example.aspx">Axum – Introduction and Ping Pong Example</a>&quot;, Matthew Podwysocki, 2009</p>  <p>2. &quot;<a href="https://codebetter.com/blogs/matthew.podwysocki/archive/2009/05/15/axum-ping-pong-with-dataflow-networks.aspx">Axum – Ping Pong with Dataflow Networks</a>&quot;, Matthew Podwysocki, 2009</p>  <p>3. &quot;<a href="https://codebetter.com/blogs/matthew.podwysocki/archive/2009/05/20/f-actors-revisited.aspx">F# Actors Revisited</a>&quot;, Matthew Podwysocki, 2009</p>  <p>4. &quot;<a href="https://codebetter.com/blogs/matthew.podwysocki/archive/2009/09/25/pondering-axum-f.aspx">Pondering Axum + F#</a>&quot;, Matthew Podwysocki, 2009</p>  <p>5. &quot;<a href="https://codebetter.com/blogs/matthew.podwysocki/archive/2009/05/28/actors-in-f-the-bounded-buffer-problem.aspx">Actors in F# – The Bounded Buffer Problem</a>&quot;, Matthew Podwysocki, 2009</p>  <p>6. &quot;<a href="https://blogs.msdn.com/maestroteam/archive/2009/02/27/we-haven-t-forgotten-about-other-models-honest.aspx">We haven’t forgotten about other models – honest!</a>&quot;, Josh Phillips, Axum development team, 2009</p>  <p>7. &quot;<a href="https://lamp.epfl.ch/~phaller/doc/haller07actorsunify.pdf">Actors that Unify Threads and Events</a>&quot;, Philipp Haller and Martin Odersky, 2007</p>  <p>8. &quot;<a href="https://ruben.savanne.be/articles/concurrency-in-erlang-scala">Concurrency in Erlang &amp; Scala: The Actor Model</a>&quot;, Ruben Vermeersch, 2009</p>  <p>9. &quot;<a href="https://www.valuedlessons.com/2008/06/message-passing-conccurrency-actor.html">Message Passing Conccurrency (Actor Model) in Python</a>&quot;, &quot;Valued Lessons&quot; blog, 2008</p>]]></content:encoded>
    </item>
    <item>
      <title>Windows HPC Server 2008 R2 Beta 1 доступен</title>
      <link>https://docs.microsoft.com/archive/blogs/ru-hpc/windows-hpc-server-2008-r2-beta-1</link>
      <pubDate>Mon, 23 Nov 2009 09:03:45 GMT</pubDate>
      <dc:creator><![CDATA[Garry Trinder]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/ru-hpc/2009/11/23/windows-hpc-server-2008-r2-beta-1/</guid>
      <description><![CDATA[Новая версия продукта, продолжающего линейку Windows HPC Server , была аннонсирована на конференции...]]></description>
      <content:encoded><![CDATA[<p>Новая версия продукта, продолжающего линейку Windows HPC Server , была аннонсирована на конференции <a href="https://sc09.supercomputing.org/" target="_blank">Supercomputing 2009.</a> Стать участником программы тестирования и получить всю необходимую информацию о продукте, а также сам продукт можно через сервис Microsoft Connect по <a href="https://connect.microsoft.com/HPC/content/content.aspx?ContentID=6923" target="_blank">этой</a> ссылке.</p> <p><a href="https://technet.microsoft.com/en-us/library/ee783541(WS.10).aspx" target="_blank">Улучшения</a> коснулись всех ключевых компонентов системы, но особо хочется отметить очень интересную возможность использовать узлы кластера для вычисления пользовательских (UDF) функций Excel 2010. Документация, а также видео, демонстрирующее&nbsp; все новые возможности Windows HPC Server 2008 R2 доступны <a href="https://technet.microsoft.com/en-us/library/ee783547(WS.10).aspx" target="_blank">здесь</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Чистота функций</title>
      <link>https://docs.microsoft.com/archive/blogs/ru-hpc/93</link>
      <pubDate>Tue, 17 Nov 2009 13:01:04 GMT</pubDate>
      <dc:creator><![CDATA[Garry Trinder]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/ru-hpc/2009/11/17/93/</guid>
      <description><![CDATA[Наряду с функциями высшего порядка еще одной отличительной чертой функционального программирования...]]></description>
      <content:encoded><![CDATA[<p>Наряду с <a href="https://blogs.msdn.com/ru-hpc/archive/2009/11/15/9922606.aspx">функциями высшего порядка</a> еще одной отличительной чертой функционального программирования является трепетное отношение к <a href="https://en.wikipedia.org/wiki/Side_effect_(computer_science)">побочным эффектам</a> (side effects). &quot;<a href="https://homepages.inf.ed.ac.uk/wadler/papers/essence/essence.ps">Should I be pure or impure?</a>&quot; - этот вопрос волнует не одно поколение функциональных программистов, и сегодня мы рассмотрим аргументы в пользу обеих сторон, а выбор предоставим вам.</p>  <p><strong>Чистота функций (functional purity)</strong></p>  <p>Функция называется <em>чистой</em> (pure), если 1) для одного и того же набора аргументов она всегда возвращает один и тот же результат, 2) единственный результат работы функции - ее возвращаемое значение. Антонимом этого понятия является <em>изменчивость</em> (impurity). Примеры:</p>  <ul>   <li>Оператор сложения целых чисел, синус, да и вообще все детерминированные математические функции являются чистыми. </li>    <li>Функция получения текущего времени, генератор случайных чисел не являются чистыми, так их два последовательных вызова практически всегда возвращают разные значения, нарушая условие №1. </li>    <li>Любая работа с пользователем и любой ввод-вывод не являются чистыми, так как нарушают условие №2, порождая побочные эффекты. </li>    <li>Оператор присваивания не является чистым, так как нарушает условие №2, изменяя состояние приложения. </li> </ul>  <p>Если это ваше первое знакомство с чистотой функций, то вы наверняка недоумеваете о том, как же программировать на чистых языках (т.е. в которых нельзя присвоить переменной значение или общаться с внешним миром). Оказывается, практическая полезность таких языков весьма высока, но требует другого подхода к программированию, который весьма часто оказывается даже более удобным. Кроме того, чистые программы имеют ряд полезных свойств, недоступных обычным программам - об этом чуть ниже.</p>  <p><strong>Программирование без изменчивости</strong></p>  <p>Дилемма между чистотой и изменчивостью чаще всего возникает тогда, когда между двумя частями алгоритма - P1 и P2 есть зависимость, то есть P2 использует результаты работы P1 (существуют и более сложные случаи зависимостей, но для краткости мы опустим их анализ). Для разрешения такой ситуации императивное программирование предлагает переменные - изменяемое состояние, которое используется в обеих частях алгоритма (mutable shared state). Функциональный подход заключается в выделении зависимой части в отдельную функцию и передаче ей измененного состояния в аргументах (как мы убедились <a href="https://blogs.msdn.com/ru-hpc/archive/2009/11/15/9922606.aspx">в прошлом посте</a>, благодаря функциональной композиции даже небольшие фрагменты программы можно выносить в независимые, повторно используемые функции).</p>  <p>Рассмотрим пример - <em>наивное</em> сложение чисел некоторого конечного списка. Это итеративный алгоритм, который перебирает список от начала до конца, на каждом шаге накапливая частичную сумму. Очевидно, между соседними итерациями цикла есть зависимость, так как та итерация, которая будет выполняться позже, использует результат работы предыдущей итерации - частичную сумму. Ниже приведены качественно разные варианты реализации алгоритма (если вы не знакомы с F#, то в объеме, необходимом для понимания кода, приведенного ниже, его можно освоить, прочитав <a href="https://lorgonblog.spaces.live.com/blog/cns!701679AD17B6D310!1325.entry?sa=721984245">ознакомительный пост</a>; реализации приведены лишь с иллюстративной целью - в базовых библиотеках обоих языков есть встроенные функции, которые можно использовать для достижения нужного результата):</p>  <table border="0" cellspacing="0" cellpadding="2" width="592"><tbody>     <tr>       <td valign="top" width="295">         <p>Суммирование элементов списка (C#, изменчивый подход)</p>       </td>        <td valign="top" width="295">         <p>Суммирование элементов списка (F#, чистый подход)</p>       </td>     </tr>      <tr>       <td valign="top" width="295">         <div class="csharpcode">           <pre class="alt"><span class="kwrd">int</span> SumAll(IEnumerable&lt;<span class="kwrd">int</span>&gt; list)</pre>

          <pre>{</pre>

          <pre class="alt">    var sum = 0;</pre>

          <pre>    <span class="kwrd">foreach</span> (var element <span class="kwrd">in</span> list)</pre>

          <pre class="alt">        sum += element;</pre>

          <pre>&#160;</pre>

          <pre class="alt">    <span class="kwrd">return</span> sum;</pre>

          <pre>}</pre>
        </div>
        <style type="text/css">



.csharpcode, .csharpcode pre
{
	font-size: small;
	color: black;
	font-family: consolas, "Courier New", courier, monospace;
	background-color: #ffffff;
	/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt 
{
	background-color: #f4f4f4;
	width: 100%;
	margin: 0em;
}
.csharpcode .lnum { color: #606060; }</style></td>

      <td valign="top" width="295">
        <div class="csharpcode">
          <pre class="alt">let sumAll list = </pre>

          <pre>    let rec fold f acc list = </pre>

          <pre class="alt">        match list with</pre>

          <pre>        | h::t -&gt; fold f (f acc h) t</pre>

          <pre class="alt">        | [] -&gt; acc</pre>

          <pre>    fold (+) 0 list</pre>
        </div>
        <style type="text/css">



.csharpcode, .csharpcode pre
{
	font-size: small;
	color: black;
	font-family: consolas, "Courier New", courier, monospace;
	background-color: #ffffff;
	/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt 
{
	background-color: #f4f4f4;
	width: 100%;
	margin: 0em;
}
.csharpcode .lnum { color: #606060; }</style></td>
    </tr>
  </tbody></table>

<p>Выше мы рассмотрели только самый простой пример выражения изменчивого алгоритма в чистом виде, существует много других, которые выходят за рамки этого поста - представление переменной в виде, возможно, бесконечного списка значений (вообще рекурсивные структуры данных и, в частности, списки очень важны для ФП), инкапсуляция изменчивого состояния в монадах (чтобы не таскать за собой большое число параметров) и так далее.</p>

<p><strong>Полезные свойства чистых программ</strong></p>

<p>Самое важное достоинство чистых программ - то, что их становится возможным анализировать при помощи математического аппарата, то есть выполнять детерминированный анализ и делать 100% верные заключения. Это помогает как человеку (разработчику), так и компьютеру (компилятору, исполнителю).</p>

<p><em>Человеку</em> чистота помогает тем, что программа ведет себя предсказуемо, а это, в свою очередь, упрощает отладку и тестирование приложения. В отладчике достаточно найти стек-фрейм, в котором данные начали отличаться от правильных, и именно в нем и будет заключена ошибка, так как ни последовательность исполняемых шагов, ни внешнее состояние, ни отладочные просмотры не могли повлиять на работу программы. Юнит-тестирование становится проще, так как каждому тесту гарантирована полнота охвата тестируемого аспекта приложения.</p>

<p><em>Компьютеру</em> чистота и <a href="https://en.wikipedia.org/wiki/Referential_transparency_(computer_science)">ссылочная прозрачность</a> помогают тем, что можно применять автоматические рассуждения для анализа различных аспектов программы. Разрабатывая программу в чистом стиле, мы явно указываем зависимости между фрагментами нашего алгоритма, что делает возможным математически обоснованными оптимизирующие переупорядочение, распараллеливание и кэширование фрагментов. Еще из интересных особенностей применения компьютерных рассуждений можно отметить облегчение реализации live updates, обновлений программы прямо во время работы (как, например, это сделано в Erlang).</p>

<p><strong>Недостатки чистых программ</strong></p>

<p>Соответственно определению, можно отметить самый главный недостаток чистых программ - они не поддерживают концепцию внешнего мира, т.е. файлов, баз данных, взаимодействия с пользователем. Это - принципиально неустранимое ограничение, но при помощи таких конструкций, как <a href="https://en.wikipedia.org/wiki/Monad_(functional_programming)">монады</a>, можно с высокой точностью отделить изменчивый код от основной программы. Этим возможности монад не исчерпываются, поэтому их мы рассмотрим в отдельном посте.</p>

<p>Следующий недостаток непосредственно вытекает из неизменности состояния - массивы и структуры, представляющие сущности предметной области, создаются &quot;на один раз&quot; и, при необходимости быть измененными, пересоздаются заново. Так, например, упоминаемая в <a href="https://blogs.msdn.com/ru-hpc/archive/2009/11/12/9921249.aspx">одном из предыдущих постов</a> быстрая сортировка на языке Haskell: qsort (x:xs) = qsort(filter (&lt; x) xs) ++ [x] ++ qsort(filter (&gt;= x) xs) требует больше памяти, чем ее in-place эквивалент на языке C. Вот почему так сложилось, что исторически впервые сборщик мусора появился в функциональных языках - в императивных программах объекты обычно долгоживущие, ибо передаются по ссылке, а в функциональных языках большой процент кода обычно написан в чистом стиле (что, как показано выше, подразумевает более частую аллокацию/деаллокацию структур), поэтому явное управление памятью оказывается слишком неудобным.</p>

<p>Чаще всего получается, что чистая реализация алгоритма по крайней мере не уступает императивным эквивалентам по скорости написания и сложности поддержки, но иногда бывает, что императивная версия оказывается удобнее для записи и сопровождения. Эту тему отлично раскрыл Мэтью Подвысоцки в своем посте &quot;<a href="https://weblogs.asp.net/podwysocki/archive/2009/05/05/functionally-implementing-intersperse.aspx">Functionally Implementing Intersperse</a>&quot;</p>

<p><strong>Заключение</strong></p>

<p>Чистота - замечательное свойство программ, которое делает гораздо более удобной работу с кодом как для программиста, так и для компьютера. Но достигается она дорогой ценой - во-первых, в чистых фрагментах программы ограничено общение с внешним миром (файлы, базы данных, пользовательский интерфейс), во-вторых, чистые программы почти всегда проигрывают своим императивным эквивалентам в скорости и требованиям к памяти, а, в-третьих, не всегда чистая запись алгоритма удобна для написания и поддержки.</p>

<p>Поэтому наилучшим решением на практике будет взвесить достоинства и недостатки чистого стиля для каждого из фрагментов программы и выбрать для каждого фрагмента стиль сообразный практичности его применения. Например, код чтения ввода пользователя или общения с базой данных нужно написать императивно, а сложные вычислительные алгоритмы обычно полезно реализовать в чистом виде.</p>

<p><strong>Материалы для чтения</strong></p>

<p>В последующем посте мы поговорим о <a href="https://en.wikipedia.org/wiki/Evaluation_strategy">стратегиях исполнения кода</a> - отложенной (<a href="https://en.wikipedia.org/wiki/Lazy_evaluation">lazy</a>) и жадной (<a href="https://en.wikipedia.org/wiki/Eager_evaluation">eager</a>), а также об их особенностях. Этим мы завершим описание триады концепций, составляющих базис организации и композиции функционального кода. В дальнейших постах о теории ФП мы рассмотрим более сложные конструкты и начнем с <a href="https://en.wikipedia.org/wiki/Continuation">продолжений</a>.</p>

<p>Работу с данными (функциональные структуры данных, применимость списков, сопоставление с образцом/активные образцы) мы намеренно оставляем за кадром, чтобы не выходить за рамки этого блога, поэтому по этой теме мы лишь дадим лишь ссылки на полезные документы:</p>

<p>1. &quot;<a href="https://www.cs.cmu.edu/~rwh/theses/okasaki.pdf">Purely Functional Data Structures</a>&quot;, Chris Okasaki, 1996 </p>

<p>2. &quot;<a href="https://en.wikipedia.org/wiki/List_comprehension">List comprehension</a>&quot;, from Wikipedia, the free encyclopedia </p>

<p>3. &quot;<a href="https://en.wikipedia.org/wiki/Pattern_matching">Pattern matching</a>&quot;, from Wikipedia, the free encyclopedia </p>

<p>4. &quot;<a href="https://blogs.msdn.com/dsyme/attachment/2044281.ashx">Extensible Pattern Matching via a Lightweight Language</a>&quot;, Don Syme et al., 2007 </p>]]></content:encoded>
    </item>
    <item>
      <title>Windows HPC Server 2008: обзор управления системой</title>
      <link>https://docs.microsoft.com/archive/blogs/ru-hpc/windows-hpc-server-2008</link>
      <pubDate>Mon, 16 Nov 2009 08:36:00 GMT</pubDate>
      <dc:creator><![CDATA[Garry Trinder]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/ru-hpc/2009/11/16/windows-hpc-server-2008/</guid>
      <description><![CDATA[Иногда наблюдается ситуация, когда у исследователей есть большой интерес к запуску различного рода...]]></description>
      <content:encoded><![CDATA[<P>Иногда наблюдается ситуация, когда у исследователей есть большой интерес к запуску различного рода расчетных задач на Windows HPC Server 2008, но при этом в академических учреждениях отсутствуют специалисты, способные помочь с развертыванием HPC кластера. В этом случае на помощь приходит либо Microsoft, с бесплатными тренингами по развертыванию кластера для своих клиентов, либо, если есть большое желание изучить возможности HPC Server самому в режиме “evaluation”, развертывание происходит своими силами.</P> <P>Специально для второго случая мы постараемся выкладывать максимальное количество доступных материалов на русском, посвященных управлению кластером. Ниже приведена ссылка на один из доступных документов. </P> <P> <P> </P> <DIV style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:8eb9d37f-1541-4f29-b6f4-1eea890d4876:da3e44e5-5923-47f8-8963-46a3d9d95396" class="wlWriterEditableSmartContent"><DIV style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:8eb9d37f-1541-4f29-b6f4-1eea890d4876:da3e44e5-5923-47f8-8963-46a3d9d95396" class="wlWriterSmartContent"> <P><A title="SystemManagementOverview" href="https://cid-071de1a44b77aa63.skydrive.live.com/self.aspx/HPC%20RU/WindowsHPCServer2008%5E_SystemManagementOverview%5E_ru%5E_edit%5E_TD%5E_lred%20%5E52%5E6.doc" mce_href="https://cid-071de1a44b77aa63.skydrive.live.com/self.aspx/HPC%20RU/WindowsHPCServer2008%5E_SystemManagementOverview%5E_ru%5E_edit%5E_TD%5E_lred%20%5E52%5E6.doc" target="_blank">SystemManagementOverview</A> – просто скачайте документ по ссылке со SkyDrive.</P></DIV>]]></content:encoded>
    </item>
    <item>
      <title>Функции высшего порядка</title>
      <link>https://docs.microsoft.com/archive/blogs/ru-hpc/113</link>
      <pubDate>Sun, 15 Nov 2009 01:41:37 GMT</pubDate>
      <dc:creator><![CDATA[Garry Trinder]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/ru-hpc/2009/11/15/113/</guid>
      <description><![CDATA[В этом посте мы продолжим знакомство с функциональным программированием и начнем рассмотрение...]]></description>
      <content:encoded><![CDATA[<p>В этом посте мы <a href="https://blogs.msdn.com/ru-hpc/archive/2009/11/12/9921249.aspx">продолжим</a> знакомство с функциональным программированием и начнем рассмотрение базовых концепций, которые обуславливают его сильные стороны и являются причиной его недостатков. Сегодня поговорим о функциях высшего порядка.</p>  <p><strong>Функции высшего порядка (high-order functions)</strong></p>  <p><em>Функцией высшего порядка (ФВП)</em> называется функция, которая принимает в качестве параметра другую функцию или возвращает функцию в качестве результата. Например, <a href="https://blogs.msdn.com/ru-hpc/archive/2009/11/12/9921249.aspx">ранее</a> мы рассматривали реализацию быстрой сортировки на языке Haskell. В ней шаг рекурсии выражался следующей строчкой: qsort (x:xs) = qsort(filter (&lt; x) xs) ++ [x] ++ qsort(filter (&gt;= x) xs). В этом коде функция filter в качестве параметра принимает функцию - критерий фильтрации. Поэтому filter является функцией высшего порядка. Другими примерами типичных ФВП являются <a href="https://en.wikipedia.org/wiki/Map_(higher-order_function)">отображения</a>, <a href="https://en.wikipedia.org/wiki/Fold_(higher-order_function)">аккумуляторы</a> и <a href="https://en.wikipedia.org/wiki/Convolution_(computer_science)">свертки</a>.</p>  <p>Ценностью ФВП является то, что они делают возможной <a href="https://blogs.msdn.com/ru-hpc/archive/2009/11/12/9921249.aspx">функциональную композицию</a>, которая позволяет выделить независимые части из подпрограмм, которые в императивном программировании являются минимальной единицей структуризации кода. Благодаря этому: 1) общую структуру конкретной подпрограммы становится легче анализировать, 2) каждый из меньших фрагментов становится легче охватить мысленным взглядом, 3) существуют способы повторно использовать фрагменты между подпрограммами.</p>  <p><strong>Практический пример</strong></p>  <p>В классической статье &quot;<a href="https://www.math.chalmers.se/~rjmh/Papers/whyfp.pdf">Why Functional Programming Matters</a>&quot; Джон Хьюз приводит ряд примеров, которые показывают мощь функциональной композиции. Чтобы дополнить картину, мы приведем еще одну иллюстрацию - сравним императивную и функциональную реализации обработки <a href="https://en.wikipedia.org/wiki/Abstract_syntax_tree">абстрактного синтаксического дерева</a>. Для краткости примера рассмотрим простой AST, состоящий из нодов Literal (объявление литерала) и Apply (применение функции).</p>  <p>Решение, <a href="https://msdn.microsoft.com/en-us/library/bb882521.aspx">предлагаемое на MSDN</a>, основывается на паттерне Visitor. В рамках этого паттерна мы реализуем класс (далее будем называть его “визитор”), который для каждого типа нода содержит по одному методу, в котором задана логика обработки нода и последующего обхода его детей. Иллюстрацией этого подхода является базовый класс для визиторов (реализация упрощена в целях краткости):</p>  <div class="csharpcode">   <pre class="alt"><span class="kwrd">public</span> <span class="kwrd">abstract</span> <span class="kwrd">class</span> AbstractVisitor</pre>

  <pre>{</pre>

  <pre class="alt">    <span class="kwrd">protected</span> Expression Visit(Expression exp)</pre>

  <pre>    {</pre>

  <pre class="alt">        <span class="kwrd">switch</span>(exp.NodeType)</pre>

  <pre>        {</pre>

  <pre class="alt">            <span class="kwrd">case</span> ExpressionType.Literal:</pre>

  <pre>                <span class="kwrd">return</span> VisitLiteral((LiteralExpression)exp);</pre>

  <pre class="alt">&#160;</pre>

  <pre>            <span class="kwrd">case</span> ExpressionType.Apply:</pre>

  <pre class="alt">                <span class="kwrd">return</span> VisitApply((ApplyExpression)exp);</pre>

  <pre>&#160;</pre>

  <pre class="alt">            <span class="kwrd">default</span>:</pre>

  <pre>                <span class="kwrd">throw</span> <span class="kwrd">new</span> ArgumentException(String.Format(<span class="str">&quot;Unexpected node '{0}' of type '{1}'&quot;</span>,</pre>

  <pre class="alt">                    exp, exp.NodeType));</pre>

  <pre>        }</pre>

  <pre class="alt">    }</pre>

  <pre>&#160;</pre>

  <pre class="alt">    <span class="kwrd">protected</span> <span class="kwrd">virtual</span> LiteralExpression VisitLiteral(LiteralExpression le)</pre>

  <pre>    {</pre>

  <pre class="alt">        <span class="kwrd">return</span> le;</pre>

  <pre>    }</pre>

  <pre class="alt">&#160;</pre>

  <pre>    <span class="kwrd">protected</span> <span class="kwrd">virtual</span> ApplyExpression VisitApply(ApplyExpression ae)</pre>

  <pre class="alt">    {</pre>

  <pre>        var args = ae.Args.Select(arg =&gt; Visit(arg));</pre>

  <pre class="alt">        <span class="kwrd">return</span> Expression.Apply(ae.Function, args);</pre>

  <pre>    }</pre>

  <pre class="alt">}</pre>
</div>
<style type="text/css">






.csharpcode, .csharpcode pre
{
	font-size: small;
	color: black;
	font-family: consolas, "Courier New", courier, monospace;
	background-color: #ffffff;
	/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt 
{
	background-color: #f4f4f4;
	width: 100%;
	margin: 0em;
}
.csharpcode .lnum { color: #606060; }</style>

<p>Функциональный подход к решению задачи представляет собой <a href="https://en.wikipedia.org/wiki/Catamorphism">катаморфизм</a> - ФВП, которая содержит логику обхода АСТ и принимает в качестве параметра N различных функций, реализующих обработку соответствующих типов нодов. Рассмотрим реализацию катаморфизма на языке F# (если вы не знакомы с F#, то в объеме, необходимом для понимания кода, приведенного ниже, его можно освоить, прочитав <a href="https://lorgonblog.spaces.live.com/blog/cns!701679AD17B6D310!1325.entry?sa=721984245">ознакомительный пост</a>; реализация предельно упрощена в целях краткости, о ее возможных усовершенствованиях можно почитать в <a href="https://en.wikipedia.org/wiki/Catamorphism#Detailed_example">серии статей Брайана МакНамары</a>).</p>

<div class="csharpcode">
  <pre class="alt">let fold ast literalF applyF = </pre>

  <pre>    let rec Iter ast =</pre>

  <pre class="alt">        match ast with </pre>

  <pre>        | Literal(value) -&gt; literalF value</pre>

  <pre class="alt">        | Apply(name, args) -&gt; applyF name (Seq.map Iter args)</pre>

  <pre>    Iter ast</pre>
</div>
<style type="text/css">






.csharpcode, .csharpcode pre
{
	font-size: small;
	color: black;
	font-family: consolas, "Courier New", courier, monospace;
	background-color: #ffffff;
	/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt 
{
	background-color: #f4f4f4;
	width: 100%;
	margin: 0em;
}
.csharpcode .lnum { color: #606060; }</style>

<p><strong>Анализ примера</strong></p>

<p>Сразу же можно заметить, что конкретному визитору, который перекрывает некоторый метод VisitXXX, необходимо будет воспроизвести логику обхода детей обрабатываемого узла, в то время как при использовании функционального подхода работа с нодом четко разделяется на логику fold (обход) и логику literalF/applyF (обработки). Здесь мы наблюдаем функциональную композицию в действии - монолитный для императивной версии код мы смогли разделить на две части, каждая из которых может использоваться отдельно. </p>

<p>Рассматриваемый пример позволяет сделать еще одно наблюдение, аналогичное предыдущему - сравнить объектную и функциональную композицию. На первый взгляд подходы идентичны - абстрактному классу соответствует fold, а методам VisitXXX соответствуют функции-параметры. Но при ближайшем рассмотрении можно заметить, что в отличие от методов, жестко связанных с классом, в котором они объявлены, функции обработки literalF и applyF никак не привязаны к fold. Например, в объектно-ориентированной модели не получится скомбинировать логику двух визиторов без внесения дублирования.</p>

<p>Кроме того, в <a href="https://lorgonblog.spaces.live.com/blog/cns!701679AD17B6D310!250.entry?_c=BlogPart">одной из последних статей</a> серии Брайана МакНамары про катаморфизмы можно увидеть еще один аспект функциональной декомпозиции – благодаря тому, что катаморфизм становится отложенным (<a href="https://en.wikipedia.org/wiki/Lazy_evaluation">lazy</a>), у нас появляется возможность извне обрывать рекурсивное углубление в структуру данных, то есть вместо логики обхода (fold) и N обработчиков (literalF, applyF) наш алгоритм декомпозируется на обходчик, N контроллеров и N обработчиков. Конечно же, такие возможности нужны не всегда, но отметьте, насколько мелкозернистой в этом случае оказывается функциональная декомпозиция по сравнению с декомпозицией, предоставляемой ИП и ООП. </p>

<p><strong>Заключение</strong></p>

<p>Функции высшего порядка - ценная абстракция функционального программирования, благодаря которой программу можно с высокой точностью разделить на независимые фрагменты. На практике это приводит к более быстрому написанию, лучшей читаемости и сопровождаемости кода.</p>

<p>В контексте параллельного программирования функциональная композиция полезна тем, что в последовательной императивной программе она выделяет структурные части, которые можно автоматически проанализировать и, возможно, выполнить параллельно.</p>

<p>Завершаясь, стоит отметить, что доступны ФВП в любом языке, в котором есть понятие &quot;указатель на функцию&quot;, поэтому теоретически преимуществами функциональной композиции можно воспользоваться практически всегда. Другое дело, что для удобства использования ФВП необходима встроенная в язык поддержка как минимум четырех концепций - <a href="https://en.wikipedia.org/wiki/Closure_(computer_science)">замыканий</a>, <a href="https://en.wikipedia.org/wiki/Type_polymorphism">полиморфизма типов</a>, <a href="https://en.wikipedia.org/wiki/Type_inference">выведения типов</a> и <a href="https://msdn.microsoft.com/en-us/library/bb397687.aspx">удобной формы записи функций</a>. Эти возможности лишь недавно пришли в популярные языки программирования - например, C# получил их только в третьей версии, а в Java на момент написания статьи их нет.</p>

<p><strong>Материалы для чтения по темам следующих постов</strong></p>

<p>Темой следующего поста о базовых концепциях функционального программирования будет предмет священной войны теоретиков - <a href="https://en.wikipedia.org/wiki/Pure_function">чистота функций</a>. Мы рассмотрим необходимость изменяемого состояния в программах, возможности по его инкапсуляции, а также преимущества и недостатки альтернативных взглядов на проблему. Кроме документа по ссылке выше рекомендуем к прочтению:</p>

<p>1. &quot;<a href="https://en.wikipedia.org/wiki/Referential_transparency_(computer_science)">Referential transparency</a>&quot;, from Wikipedia, the free encyclopedia</p>

<p>2. &quot;<a href="https://www.cs.berkeley.edu/~finifter/pure-ccs08.pdf">Verifiable Functional Purity in Java</a>&quot;, Matthew Finifter et al., 2008</p>

<p>3. &quot;<a href="https://www.leading-edge-dev.de/?p=358">Code Contracts #5: Method purity</a>&quot;, Matthias Jauernig, 2009</p>

<p>Для ценителей функционального программирования у нас есть еще одна отличная статья - лекция Джона Бэкуса &quot;<a href="https://www.stanford.edu/class/cs242/readings/backus.pdf">Can Programming Be Liberated from the von Neumann Style? A Functional Style and Its Algebra of Programs</a>&quot;, прочитанная им на получении награды <a href="https://en.wikipedia.org/wiki/ACM_Turing_Award">ACM Turing Award</a>.</p>]]></content:encoded>
    </item>
  </channel>
</rss>