Skip to content

Commit

Permalink
2013-09-06.2
Browse files Browse the repository at this point in the history
  • Loading branch information
qnikst committed Sep 6, 2013
1 parent 3d82bb8 commit 4df27ae
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 44 deletions.
32 changes: 18 additions & 14 deletions posts/2013-09-06-serialization-p1.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@
<h1>Сериализация структур данных в Haskell.<br /> Часть 1: типы данных <br /><small><strong>September 6, 2013</strong></small></h1>
</div>

<div style="float:right; width:200px">
<strong>Updates:</strong><br /> 2013-09-06: исправлены ошибки в тексте (спасибо qrilka)
</div>

<p><em>У меня тут в черновиках завалялась старый пост про сериализацию стуктур данных в haskell, доводить её до ума и продолжать мне немного лень, но если будет интересно то могу продолжать. Изначально серия статей предполагалась одновременно с доведением до ума пакета <a href="https://github.com/qnikst/strictput">strictput</a></em></p>
<p>Этим постом я хочу начать небольшую серию статей про сериализацию данных в Haskell. и описание библиотек их реализующих.</p>
<h2 id="типы-данных">Типы данных</h2>
Expand All @@ -45,20 +49,20 @@ <h2 id="типы-данных">Типы данных</h2>
<li>Взаимодействие с внешним рантаймом, например FFI;</li>
<li>Взаимодействие с внешним миром (IPC, передача в сеть и т.д.)</li>
</ol>
<p>Разница между этими задачами заключается в том, что в первом случае, данные находятся в пространстве одного процесса (группы процессов), а во втором в разных и там будет произведено их копирование (существуют варианты, при которых копирование произведено не будет, но их мы оставим в стороне). В связи с этим структуры данных для варинта 1 не должны быть перемещены GC при сборке мусора, поскольку сериализация главным образом интересна нам по для второго сценария, то мы не будем посвящать много времени рассмотрению непереносимых (pinned) структур, а будем только отмечать данное свойство.</p>
<p>Разница между этими задачами заключается в том, что в первом случае, данные находятся в пространстве одного процесса (группы процессов), а во втором в разных, и там будет произведено их копирование (существуют варианты, при которых копирование произведено не будет, но их мы оставим в стороне). В связи с этим структуры данных для варинта 1 не должны быть перемещены GC при сборке мусора, поскольку сериализация главным образом интересна нам для второго сценария, то мы не будем посвящать много времени рассмотрению непереносимых (pinned) структур, а будем только отмечать данное свойство.</p>
<h3 id="c-интерфейс">‘C’ интерфейс</h3>
<p>При взаимодействии с файлами или сетью для передачи бинарных данных в C принятно использовать пару указатель размер данных, которая описывает откуда брать данные и сколько. Существуют более интересные варианты для векторной записи (семейство функций read/writev), в этом случае используется массив структур:</p>
<p>При взаимодействии с файлами или сетью для передачи бинарных данных в C принятно использовать пару указатель, размер данных, которая описывает откуда брать данные и сколько. Существуют более интересные варианты для векторной записи (семейство функций read/writev), в этом случае используется массив структур:</p>
<p>struct iovec { void *iov_base; size_t iov_len; }</p>
<p>в некоторых типах данных можно использовать данный подход, что позволит повысить их производительность, но об этом будет сказано отдельно.</p>
<p>Данный интерфейс может быть изображен в haskell как (# Addr#,CSize #) но крайне не рекомендуется так делать, поскольку это сильно усложнит работу с данными, при этом не принося выгоды, поскольку во многих случаях компилятор может преобразовать структуру к виду аналогичному данному.</p>
<p>Для данных, которые являются строковыми в C часто ещё использются null-terminated строки</p>
<h4 id="типы-haskell-соотвествующие-c-интерфейсу">Типы haskell соотвествующие C интерфейсу</h4>
<p>В haskell существуют следующие типы соответсующие указателям в C:</p>
<h5 id="addr">Addr#</h5>
<p>Addr# - unlifted unpacked тип данных являющийся указателем. Стоит напомнить, что данный тип является строгим по построению. С помощью данного типа можно обращаться напрямую к данным внутри примитивных частей программы. Функции для работы есть в GHC.Prim (пакет ghc-prim) и Control.Primitive (пакет primitive). Обычно использовние Addr# не нужно, поскольку компилятор умеет приводить приводить работу к данному типу (за это отвечает анализатор строгости).</p>
<p>Addr# - unlifted unpacked тип данных являющийся указателем. Стоит напомнить, что данный тип является строгим по построению. С помощью данного типа можно обращаться напрямую к данным внутри примитивных частей программы. Функции для работы с этим типом есть в GHC.Prim (пакет ghc-prim) и Control.Primitive (пакет primitive). Обычно использовние Addr# не нужно, поскольку компилятор умеет приводить приводить работу к данному типу (за это отвечает анализатор строгости).</p>
<h5 id="ptr">Ptr</h5>
<pre class="sourceCode haskell"><code class="sourceCode haskell"><span class="kw">data</span> <span class="dt">Ptr</span> a <span class="fu">=</span> <span class="dt">Ptr</span> (<span class="dt">Addr</span><span class="fu">#</span>)</code></pre>
<p>Типизированная обертка над Addr (lifted, unpacked by default (изменится в ghc 7.8)), с помощью данного типа данных можно работать с указателями в “обычном” haskell коде. В подавляющем большинстве случаев ленивость и boxing убираются компилятором. Данный тип не “держит” содержимое на которое указывает и не защищает его от сборки GC.</p>
<p>Типизированная обертка над Addr (lifted, unpacked по умолчанию (изменится в ghc 7.8)), с помощью данного типа данных можно работать с указателями в “обычном” haskell коде. В подавляющем большинстве случаев ленивость и boxing убираются компилятором. Данный тип не “держит” содержимое на которое указывает и не защищает его от сборки GC.</p>
<p>Функции для работы с указателями предоставляются модулем ‘Foreign.Ptr’</p>
<h5 id="foreignptr">ForeignPtr</h5>
<pre class="sourceCode haskell"><code class="sourceCode haskell"><span class="kw">data</span> <span class="dt">ForeignPtr</span> <span class="fu">=</span> <span class="dt">ForeignPtr</span> <span class="dt">Addr</span><span class="fu">#</span> <span class="dt">ForeignPtrConents</span></code></pre>
Expand All @@ -80,7 +84,7 @@ <h4 id="cstringcstring-len">CString/CString Len</h4>
</ul>
<h4 id="string">String</h4>
<pre class="sourceCode haskell"><code class="sourceCode haskell"> <span class="kw">type</span> <span class="dt">String</span> <span class="fu">=</span> [<span class="dt">Char</span>]</code></pre>
<p>Очень широко используемый тип данных для внутреннего представления тектовых строк в виде потока (Stream/Lazy-Cons-List). Данный тип предоставляется базовой библиотекой Prelude и поэтому весьма часто плохоиспользуется (missused). Поскольку структура данных является ленивой, то возможно создание бесконечных, и цикличеких списков, а так же итеративное построение списка.</p>
<p>Очень широко используемый тип данных для внутреннего представления текcтовых строк в виде потока (Stream/Lazy-Cons-List). Данный тип предоставляется базовой библиотекой Prelude и поэтому весьма часто используется не по назначению. Поскольку структура данных является ленивой, то возможно создание бесконечных и цикличеких списков, а так же итеративное построение списка.</p>
<p>Характеристики:</p>
<ul>
<li>ленивый</li>
Expand All @@ -91,10 +95,10 @@ <h4 id="string">String</h4>
<li>высокий overhead по памяти для хранения списка (x+8+2)*n+8</li>
<li>очень низкая скорость работы</li>
<li>внутренний (не pinned тип)</li>
<li>build/foldr deforestation</li>
<li>IO библиотека существует, но все методы являются ленивыми, что подвергается жесткой критике, рекомендуется не использовать LazyIO, а исполььзовать итеративные библиотеки такие как (pipes, conduit, itertee)</li>
<li><a href="http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.51.646">build/foldr deforestation</a></li>
<li>IO библиотека существует, но все методы являются ленивыми, что подвергается жесткой критике, рекомендуется использовать не ленивый ввод-вывод, а итеративные библиотеки такие как pipes, conduit, или iteratee.</li>
</ul>
<p>Build/forldr deforestation позволяет не создавать списки в том случае, если это не необходимо, например в случаях если списки играют роль “клея” между операциями.</p>
<p>Build/forldr deforestation позволяет не создавать списки в том случае, если это без этого можно обойтись, например, в случаях, если списки играют роль “клея” между операциями,.</p>
<p>Операции над структурой данных:</p>
<ul>
<li>size: O(N) /N прыжков по памяти/ const по памяти</li>
Expand All @@ -105,7 +109,7 @@ <h4 id="string">String</h4>
<p>Все операции изменяющие структуру данных пересоздают структуру слева от списка.</p>
<h4 id="word8">[Word8]</h4>
<pre class="sourceCode haskell"><code class="sourceCode haskell"> <span class="kw">data</span> [] a <span class="fu">=</span> [] <span class="fu">|</span> a <span class="fu">:</span> [a]</code></pre>
<p>Аналог String для бинарных типов данных, к данному типу применяемы все пункты относящиеся к String, кроме того, что нету функционала работы с IO.</p>
<p>Аналог String для бинарных типов данных, к данному типу применяемы все пункты относящиеся к String, кроме того, что нету возможностей работы с вводом выводом.</p>
<h4 id="text">Text</h4>
<p>Современный внутренний тип для предствления строковых данных. Однако рассмотрение данного типа выходит за рамки поста.</p>
<h4 id="bytestring">ByteString</h4>
Expand All @@ -119,19 +123,19 @@ <h4 id="bytestring">ByteString</h4>
<p>То, что каждый из блоков данных содержит указатель на начало выделенной памяти позволяет обходиться без дополнительного копирования структуры удаляя её в том случае, если на неё никто не ссылается.</p>
<h4 id="lazy-bytestring">Lazy ByteString</h4>
<p>data ByteString = Empty | Chunk !Internal.ByteString ByteString</p>
<p>Как можно увидеть данная структура изоморфна списку байтстрок, однако есть существенное отличие, внутри каждого чанка существует не произвольная строка, а строка определенного размера. (64k)</p>
<p>Как можно увидеть данная структура изоморфна списку байтстрок, однако есть существенное отличие: внутри каждого чанка существует не произвольная строка, а строка определенного размера. (64k)</p>
<h3 id="перевод-типов-данных-друг-в-друга">Перевод типов данных друг в друга</h3>
<p>Для того, чтобы показать как перечисленные типы данных переходят друг в друга я привел абсолютно невоспринимаемую картинку: (кто хочет може предложить мне более адекватный вариант).</p>
<p>Для того, чтобы показать как перечисленные типы данных переходят друг в друга, в конце поста я привел абсолютно невоспринимаемую картинку (кто хочет може предложить мне более адекватный вариант).</p>
<p>При переводе типов из одного в другой нужно следить за следующими вещами:</p>
<ol style="list-style-type: decimal">
<li>алгоритмическая сложность операции;</li>
<li>размер выделяемых структур данных;</li>
<li>энергичность/ленивость операции;</li>
<li>безопасность операции.</li>
</ol>
<p>С первым пунктом все важно, второй тоже важен так как агрессивное использование памяти существенно изменить поведение программы относительно ожидаемого. То же относится и к ленивости, в этом случае нужно понимать, какие плюсы идут от ленивости и не может ли произойти каскадное “форсирование” вычислений в тот момент когда этого допускать нельзя.</p>
<p>С безопасностью операций дело обстоит интереснее, в том случае если мы передаем выделенную память вне рантайма Haskell, мы не можем гарантировать чистоту данной структуры так как внешнее окружениме может изменять память как хочет. Обычно для сохранения чистоты в рантайм передается не сама структура, а её полная копия операции ‘useAs*’ в этом случае нужно понимать, что данный подход приводит к увеличению сложности и нагрузки на память.</p>
<p><img src="../images/posts/parser-1/1.png" /></p>
<p>С первым пунктом все важно, второй тоже важен, так как агрессивное использование памяти существенно может изменить поведение программы относительно ожидаемого. То же относится и к ленивости, в этом случае нужно понимать, какие плюсы идут от ленивости, и не может ли произойти каскадное “форсирование” вычислений в тот момент, когда этого допускать нельзя.</p>
<p>С безопасностью операций дело обстоит интереснее, в том случае если мы передаем выделенную память вне рантайма Haskell, мы не можем гарантировать чистоту данной структуры, так как внешнее окружениме может изменять память как хочет. Обычно для сохранения чистоты в рантайм передается не сама структура, а её копия ‘useAs*’. В этом случае нужно понимать, что данный подход приводит к увеличению сложности алгоритма и дополнительной нагрузке на память.</p>
<p>Картинка кликабельна: <a href="../images/posts/parser-1/1.png" target="_blank"><img src="../images/posts/parser-1/1.png" /></a></p>
<hr />
<div id="sociallinks" class="pull-left">
<strong>Share on:</strong>
Expand Down
Loading

0 comments on commit 4df27ae

Please sign in to comment.