<?xml version='1.0' encoding='UTF-8'?><rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearchrss/1.0/" xmlns:blogger="http://schemas.google.com/blogger/2008" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0" version="2.0"><channel><atom:id>tag:blogger.com,1999:blog-7754674872362895099</atom:id><lastBuildDate>Mon, 04 Nov 2024 19:06:03 +0000</lastBuildDate><category>programming</category><category>C++</category><category>boost</category><category>templates</category><category>asio</category><category>exceptions</category><category>lua</category><category>жизнь</category><category>C#</category><category>codejam</category><category>fp</category><category>fsharp</category><category>work</category><category>.NET</category><category>OOP</category><category>blog</category><category>erlang</category><category>google</category><category>holly wars</category><category>music</category><category>thoughts</category><category>thread-safety</category><category>unit-test</category><category>web</category><category>win32</category><category>windows</category><title>Очень серьезный блог</title><description></description><link>http://evgeny-lazin.blogspot.com/</link><managingEditor>noreply@blogger.com (Anonymous)</managingEditor><generator>Blogger</generator><openSearch:totalResults>77</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7754674872362895099.post-6871867374467045208</guid><pubDate>Fri, 19 Dec 2014 22:37:00 +0000</pubDate><atom:updated>2014-12-20T01:37:47.483+03:00</atom:updated><title>Some obvious things about asynchronous network I/O</title><description>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
Async IO and async API is a two different things that often confused. First is what we usually want, second is what we doomed to deal with all the time. But these are not the same thing. You can achieve asynchronicity using only synchronous API but at the same time you can fail to do this using asynchronous calls. This can be illustrated with this boost.asio example:&lt;br /&gt;
&lt;script src=&quot;https://gist.github.com/Lazin/d315715e0abf47f01d94.js&quot;&gt;&lt;/script&gt;
&lt;a href=&quot;http://www.boost.org/doc/libs/1_55_0/doc/html/boost_asio/example/cpp11/echo/async_tcp_echo_server.cpp&quot; target=&quot;_blank&quot;&gt;link to source&lt;/a&gt;&lt;br /&gt;
It is obvious (for somebody who knows boost.asio API) that this code uses async I/O, `async_read_some` and `async_write` is asynchronous calls. This is a part of the server code. Server reads some data from socket asynchronously first and then it asynchronously writes response to the socket. All input and all output in this program is non-blocking but anyway - this server is synchronous because server can&#39;t write data to the socket until it reads something from it!&lt;br /&gt;
&lt;br /&gt;
Yes, this is echo server, it works that way, but this pattern can be found in many &quot;asynchronous&quot; applications. One example - RPC system. You &quot;call&quot; method and your RPC library wraps arguments in a packet and sends it to RPC server. Now server can perform some processing and return error code sending another packet back. In this case no matter what API you use - synchronous or asynchronous, interaction between single client and server will be synchronous anyway.&lt;br /&gt;
&lt;br /&gt;
The worst thing is that performance of such system will be limited by the network latency and not by the network bandwidth. Because each RPC call will result in network round-trip.&lt;br /&gt;
&lt;br /&gt;
So, what&#39;s the conclusion?&lt;br /&gt;
&lt;ol style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;Don&#39;t be fulled by `async` buzzword, pay attention to system interaction (protocols), not to API being used to implement that interaction.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Design your protocol in such a way that can utilize high network bandwidth.&lt;/li&gt;
&lt;li&gt;And finally - do the crazy things! For example, you can perform your RPC calls without waiting for responses assuming that no error was occurred but if this is not the case - you can rollback changes that was made under the wrong assumptions. Or if you know that client will request some data from server with 99.(9)% probability, you can send this data without waiting.&lt;/li&gt;
&lt;/ol&gt;
&lt;span class=&quot;fullpost&quot;&gt;&lt;/span&gt;&lt;/div&gt;
</description><link>http://evgeny-lazin.blogspot.com/2014/12/some-obvious-things-about-asynchronous.html</link><author>noreply@blogger.com (Anonymous)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7754674872362895099.post-6851720479197983118</guid><pubDate>Wed, 26 Nov 2014 19:01:00 +0000</pubDate><atom:updated>2014-11-26T22:02:58.920+03:00</atom:updated><title>C++ Myths Debunking (part 1)</title><description>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
It is well known that small object allocation in C ++ is slow. This is a quote from Andrey Alexandrescu book “Modern C++ Design”:&lt;br /&gt;
&lt;blockquote class=&quot;tr_bq&quot;&gt;
For occult reasons, the default allocator is notoriously slow. A possible reason is that it is usually implemented as a thin wrapper around the C heap allocator (malloc/realloc/free). The C heap allocator is not focused on optimizing small chunk allocations.&lt;/blockquote&gt;
&lt;blockquote&gt;
…&lt;br /&gt;
In addition to being slow, the genericity of the default C++ allocator makes it very space inefficient for small objects. The default allocator manages a pool of memory, and such management often requires some extra memory. Usually, the bookkeeping memory amounts to a few extra bytes (4 to 32) for each block allocated with new. If you allocate 1024-byte blocks, the per-block space overhead is insignificant (0.4% to 3%). If you allocate 8-byte objects, the per-object overhead becomes 50% to 400%, a figure big enough to make you worry if you allocate many such small objects.&lt;/blockquote&gt;
Book states that memory allocation is in fact slow and more of that, it states that allocation of small objects using malloc and new can cause high memory fragmentation. As far as I understand this is a common knowledge beyond C++ programmers. Many of us believes that fancy allocators and manual memory management is a Good Thing. Maybe this was true when book was released first (more than ten years ago) but not now! Let’s check the facts.&lt;br /&gt;
&lt;br /&gt;
I’ve created this gist to show the state of things - &lt;a href=&quot;https://gist.github.com/Lazin/6dee931149cc16c3a237&quot;&gt;link&lt;/a&gt;. This code allocates one million small objects using simple segregated storage (boost.pool) frees memory and then allocates another million of small objects using jemalloc. Time and memory usage is tracked. Result can be surprising - &lt;a href=&quot;https://gist.github.com/Lazin/798f4c194797212f3f3f&quot;&gt;link&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
First - malloc is slower than memory pool but not drastically. On my machine it’s five times slower than memory pool if deallocation time was taken into account and only three times slower if it wasn’t (this is relevant for some applications). Second - using jemalloc to allocate memory for small objects actually saves some space! Memory pool have used 20Mb of RAM and jemalloc have managed to fit everything into 16Mb.&lt;br /&gt;
&lt;br /&gt;
This isn’t surprising because jemalloc &lt;a href=&quot;https://www.facebook.com/notes/facebook-engineering/scalable-memory-allocation-using-jemalloc/480222803919&quot;&gt;implements simple segregated storage under the hood&lt;/a&gt;. It manages memory better than most of the fancy handwritten memory allocation schemes on Earth. It is a better option than custom allocator most of the time because it’s stable, fast and it can give you some feedback. It can be beaten by some custom allocation scheme in synthetic tests but not in practice. &lt;br /&gt;
&lt;br /&gt;
And finally you can always switch different allocators (jemalloc/tcmalloc/whatsoever) using LD_PRELOAD. This is not the case with custom hand-coded allocators - if you make mistake you can’t fix it without rewriting your code.&lt;/div&gt;
</description><link>http://evgeny-lazin.blogspot.com/2014/11/c-myths-debunking-part-1.html</link><author>noreply@blogger.com (Anonymous)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7754674872362895099.post-2384680453486240869</guid><pubDate>Sat, 01 Nov 2014 08:36:00 +0000</pubDate><atom:updated>2014-11-01T11:39:54.186+03:00</atom:updated><title></title><description>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
Недавно я наконец зарелизил &lt;a href=&quot;https://github.com/akumuli/Akumuli&quot; target=&quot;_blank&quot;&gt;akumuli&lt;/a&gt;. Все что планировалось - сделано.Дальше я планирую улучшить компрессию для real значений, избавиться от зависимости из библиотеки boost, оставить только header-only библиотеки оттуда, чтобы упростить deployment. Ну и наконец допилить враппер для Golang.&lt;br /&gt;
&lt;br /&gt;
Теперь о том, что будет дальше. Если кто-то внимательно изучил принцип работы моего движка для хранения данных, то этот человек мог заметить, что он организован не совсем так, как это обычно бывает. Обычно, такие вещи оптимизируются для запросов, возвращающих один временной ряд, чтобы иметь возможность быстро построить по нему график. Это здорово, но это могут делать graphite, influxedb, open tsdb, kairosdb, seriesly и тд. Но это не то что делает time series БД, по сути все вышеперечисленные решения, они не time-series а metrics databases. Они нужны в основном для devops применений, что хорошо, так как количество таких применений растет, но вот делать yet another metrics storage мне не очень интересно.&lt;br /&gt;
&lt;br /&gt;
Akumuli не может быстро вернуть один временной ряд, это все выливается в полное сканирование тома. Это все именно так и задумывалось, потому что я пытаюсь построить TSDB, которая не предназначена для построения графиков, вместо этого, она должна уметь делать similarity search. Мой поинт в том, что чем больше time-series данных вы собираете, тем более бесполезными становятся обычные способы работы с ними ну и тем более бесполезным становится построение графиков по ним. Для того, чтобы работать с такими объемами информации, нужно уметь их эффективно майнить. Именно это akumuli и научится делать в ближайшем будущем. Для mining-а этих данных не нужны ни point-запросы ни способность быстро извлечь одну серию, для этого нужно уметь быстро строить индексы, а для этого нужны уметь быстро сканировать все данные.&lt;br /&gt;
&lt;br /&gt;
Можно коротко описать принцип работы TSDB для майнинга следующим образом:&lt;br /&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;Собираем данные и записываем их на диск.&lt;/li&gt;
&lt;li&gt;Делаем из длинных серий короткие с помощью sliding window. &lt;/li&gt;
&lt;li&gt;Делаем dimensionality reduction, есть много методов основанных на преобразовании Фурье, wavelet transform и даже преобразовании в текст.&lt;/li&gt;
&lt;li&gt;Индексируем то что получилось с помощью R-tree, VA-files или чего-нибудь еще.&lt;/li&gt;
&lt;li&gt;Выполняем запросы с погрешностью используя полученные индексы, если нужно - читаем оригинальные данные.&lt;/li&gt;
&lt;/ul&gt;
Это все желательно уметь делать не на одной машине а на нескольких.&lt;/div&gt;
</description><link>http://evgeny-lazin.blogspot.com/2014/11/akumuli.html</link><author>noreply@blogger.com (Anonymous)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7754674872362895099.post-3305558670390369931</guid><pubDate>Fri, 01 Aug 2014 22:20:00 +0000</pubDate><atom:updated>2014-08-02T02:20:31.341+04:00</atom:updated><title></title><description>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
Небольшой отчет о статусе akumuli. Итак, я реализовал всю основную функциональность.&lt;br /&gt;
Запись - работает, пропускную способность по записи я особо изощренно не тестировал, на моем старом ноутбуке, akumuli может прожевать порядка 3х миллионов insert-ов в секунду (и прочитать их потом назад, что немаловажно). На нормальном железе - заметно больше. Это все благодаря тому, что akumuli трансформирует случайную запись в последовательную запись, с которой у современных железок все очень хорошо и замечательно. В будущем производительность будет ниже, так как ей придется немного пожертвовать ради повышения отказоустойчивости. До сих пор не реализованы некоторые важные вещи, например синхронизация чтения и записи при перезаписи старых данных.&lt;br /&gt;
Поиск - тоже работает. Он требует оптимизаций (оптимизация с помощью mincore еще не реализована), но корректно ищет все что нужно. Возможно стоит реализовать простой планировщик запросов, например, на основе алгоритма FSCAN.&lt;br /&gt;
Я серьезно переработал in-memory индекс для данных, не вышедших за пределы sliding window. Теперь он основан не на B-tree, а на алгоритме patience sort. Подробно с алгоритмом можно &lt;a href=&quot;http://en.wikipedia.org/wiki/Patience_sorting&quot; target=&quot;_blank&quot;&gt;ознакомиться в википедии&lt;/a&gt;. Если коротко, то все работает примерно так - в начале у нас есть пустой массив, элементами которого являются sorted runs - отсортированные в порядке увеличения меток времени массивы. Когда записывается первый элемент, в этот массив добавляется единственный sorted run из одного, только что добавленного, элемента. Далее, при записи следующего элемента, мы смотрим на его метку времени и, если она больше или равна метке времени предыдущего элемента, то этот элемент добавляется в тот же sorted run, если она меньше - то создается еще один sorted run. Далее, процесс повторяется для каждого последующего элемента, в результате чего, мы получаем набор отсортированных массивов. Их не может быть очень много, так как глубина записи ограничена sliding window. Эти массивы и есть индекс. Когда нужно слить всю информацию на диск (данные вышли за пределы sliding window), они сливаются с помощью простого k-way merge-а, очень быстро и эффективно. В этих массивах можно легко искать простым бинарным поиском, запись в такой индекс происходит быстрее чем запись в b-tree, даже на самых неудобных данных.&lt;br /&gt;
В ближайшее время я планирую реализовать синхронизацию между чтением и записью и оптимизировать поиск. После этого, возможно, у меня дойдут руки до whitepaper-а по системе.&lt;br /&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;span class=&quot;fullpost&quot;&gt;&lt;/span&gt;&lt;/div&gt;
</description><link>http://evgeny-lazin.blogspot.com/2014/08/akumuli.html</link><author>noreply@blogger.com (Anonymous)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7754674872362895099.post-3414257809591046809</guid><pubDate>Wed, 11 Jun 2014 20:14:00 +0000</pubDate><atom:updated>2014-06-12T00:16:24.187+04:00</atom:updated><title></title><description>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
&lt;span class=&quot;fullpost&quot;&gt;Как вы возможно помните, я пишу хранилище для временных рядов - akumuli. В данный момент я занят самим &quot;движком&quot; для хранения данных, однако планирую и frontend, который будет уметь записывать и считывать данные по сети. Для этого, мне нужен какой-нибудь механизм сериализации, само собой - он должен быть быстрым и эффективным, но самое главное - он должен быть безопасным. В идеале - сервер должен уметь торчать в интернет, с оговорками, вроде ограничения количества сообщений от одного клиента в time-frame.&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;fullpost&quot;&gt;И тут все очень плохо. Очень многие библиотеки сериализации проектировались без учета требований безопасности и позволяют положить сервис одним сообщением. За примерами далеко ходить не надо, вот, например, один товарищ с RSDN написал библиотеку сериализации - YAS (Yet Another Serializer) для С++. Это header only библиотека, умеющая сериализовать как стандартные контейнеры, так и контейнеры из boost и Qt. Можно также сериализовать пользовательские типы, интерфейс похож на boost::serialization. Библиотека YAS заявлена как drop-in replacement (correct me if I wrong) для boost::serialization, что хорошо, и работает заметно быстрее оного, что тоже хорошо. Что не хорошо, так это возможность уронить сервис одним сообщением:&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;fullpost&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;script src=&quot;https://gist.github.com/Lazin/e731baee468ed01d4050.js&quot;&gt;&lt;/script&gt;
&lt;span class=&quot;fullpost&quot;&gt;&lt;br /&gt;&lt;/span&gt;
Внимательно смотрим на 13-ю строку. Несложно догадаться, что нам достаточно передать вместо длины списка очень большое число, чтобы сервис упал или ушел в своп, при этом элементы списка можно не передавать вовсе! Вызов list.resize попытается создать нужное количество элементов, сделав столько аллокаций, сколько мы ему скажем, причем в выделенные участки памяти он будет писать, а значит память реально будет выделяться системой. При этом YAS не позволяет задать максимальный размер сообщения и ограничить максимальную длину списка. Этот фокус можно повторить для других типов, поддерживаемых этой библиотекой - deque, stable_vector из boost, может еще что-то.&lt;br /&gt;
&lt;br /&gt;
Можно подумать, что это проблема только одной библиотеки, но на самом деле, такая фича у библиотек сериализации встречается часто (при том, что это самая очевидная ошибка из всех, что они могут сделать). Вот, например, &lt;a href=&quot;https://github.com/USCiLab/cereal&quot; target=&quot;_blank&quot;&gt;cereal&lt;/a&gt; - второй по счету результат в выдаче на github по запросу serialization для языка С++.&lt;br /&gt;
&lt;br /&gt;
&lt;script src=&quot;https://gist.github.com/Lazin/18c10ae0ac1a48a882d3.js&quot;&gt;&lt;/script&gt;Очень похоже, не правда ли? Я проверил, там нигде размер не проверяется.&lt;br /&gt;
А теперь внимание, простой вопрос - если настолько простая дыра в безопасности лежит на виду в проекте, у которого больше 300 лайков на github и за которым следят больше сорока человек, то сколько таких дыр будет в проекте попроще? :)&lt;br /&gt;
К слову, C++ реализация protocol buffers ничем подобным не страдает, там можно ограничить максимальный размер сообщения сверху (64Мб по умолчанию, но можно задать свое значение), а максимальная длина их base-128 variant-ов ограничена 64-мя битами.&lt;br /&gt;
&lt;br /&gt;
Бывают примеры и посложнее, вот, например &lt;a href=&quot;https://github.com/msgpack/msgpack-go/blob/master/unpack.go&quot; target=&quot;_blank&quot;&gt;реализация message pack для Go&lt;/a&gt;, там, на 179-й строке есть функция unpack, которая вызывает сама себя рекурсивно, в зависимости от того, что встретит в потоке данных. Это нужно для того, чтобы парсить всякие вложенные структуры данных, вроде массива строк, т.е. глубина рекурсии зависит от входящих данных! Можно очень легко создать такое сообщение, которое заставит эту функцию вызвать себя очень много раз подряд и сожрать очень много памяти под стек, вообще сколько угодно памяти (в Go не получится переполнение стека из-за особенностей рантайма, но стек расти все равно будет). Если бы это было написано не на Go, а скажем, на Java, мы бы могли получить переполнение стека куда быстрее, чем сожрать всю память :)&lt;br /&gt;
&lt;br /&gt;
В общем, нужно писать хороший и безопасный код и не писать плохой, а также стараться использовать поверенные решения и анализировать сторонний код, используемый в вашем приложении. Спасибо за внимание! :)&lt;/div&gt;
</description><link>http://evgeny-lazin.blogspot.com/2014/06/akumuli.html</link><author>noreply@blogger.com (Anonymous)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7754674872362895099.post-413227126614614569</guid><pubDate>Wed, 28 May 2014 22:07:00 +0000</pubDate><atom:updated>2014-05-29T02:07:50.152+04:00</atom:updated><title>Про юнит-тесты</title><description>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
В последнее время модно ругать юнит-тесты, по разным причинам. Сложность поддержки в актуальном состоянии системы юнит-тестов, отсутствие 100% гарантии корректности кода в случае, если тесты проходят, использование интеграционных тестов, очень многий код сложно тестировать с помощью юнит-тестов, существование практики написания тестов тупо ради покрытия и тд.&lt;br /&gt;
Я не собираюсь быть адвокатом какой-либо из сторон спора, вместо этого, я хочу показать, что юнит-тесты - вещь простая и естественная, вытекающая из нормальной практики программирования.&lt;br /&gt;
Итак, когда опытный программист пишет код функции с нетривиальной логикой, он думает в том числе и в терминах инвариантов. Практически любой код содержит кучу инвариантов (инварианты циклов, инварианты состояния объектов и тд) и желание зафиксировать их в коде - вполне естественно. Обычно, это делается с помощью assert-ов. Assert-ом удобно проверять, например, что счетчик ожидающих обработки элементов, после выполнения цикла, будет равен нулю, либо, что указатель находится внутри массива, перед выбрасыванием исключения можно проверять - не останется ли объект после этого в невалидном состоянии, и тд и тп.&lt;br /&gt;
Но более сложные инварианты с помощью assert-а проверить проблематично. Здесь уже программисты часто прибегают к условной компиляции, для того, чтобы вставить дополнительные проверки в отладочный билд.&lt;br /&gt;
Все эти проверки засоряют код, который помимо основной логики и логики обработки ошибок, содержит теперь и кучу отладочного кода, который заботливо вырезается компилятором в релизной версии. Если написать много такого кода, то обязательно появится следующая идея - &quot;а почему бы не вынести все это в отдельное место, чтобы оно не мозолило глаза&quot; и появляются юнит тесты. Вот и все :) &lt;br /&gt;
&lt;span class=&quot;fullpost&quot;&gt;&lt;/span&gt;&lt;/div&gt;
</description><link>http://evgeny-lazin.blogspot.com/2014/05/blog-post.html</link><author>noreply@blogger.com (Anonymous)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7754674872362895099.post-7558290328788226143</guid><pubDate>Tue, 25 Feb 2014 22:24:00 +0000</pubDate><atom:updated>2014-02-26T02:32:53.733+04:00</atom:updated><title>Akumuli: поиск и выборка данных</title><description>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
Итак, в прошлом посте мы выяснили, что данные на диске хранятся очень просто - в больших, плоских файлах, отсортированными по возрастанию. Осталось только научиться в них искать. Самое очевидное решение - бинарный поиск. Представим для простоты, что мы ищем конкретные пары значений: метка времени +&amp;nbsp;идентификатор и не занимаемся поиском диапазонов, выборкой срезов и тд, для простоты.&lt;br /&gt;
&lt;div&gt;
&lt;br /&gt;
&lt;div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;
Все плохо&lt;/h3&gt;
&lt;div&gt;
Допустим, мы храним простые, 4х байтовые значения, к которым &lt;a href=&quot;http://github.com/Lazin/Akumuli&quot; target=&quot;_blank&quot;&gt;akumuli &lt;/a&gt;добавит 20 байт заголовка - идентификатор параметра и метку времени. Том у нас имеет размер 4Гб, бинарный поиск делает log2(N) итераций в худшем случае, отсюда: &lt;a href=&quot;http://www.wolframalpha.com/input/?i=log2%284GB+%2F+24B%29&quot; target=&quot;_blank&quot;&gt;log2(4GB/24B) = 27&lt;/a&gt;. Это значит, что нам потребуется до 27-ми итераций бинарного поиска. Причем первые итераций эдак 25, будут приводить к hard page fault (я использую отображаемые в память файлы для поиска), если поиск выполняется в первый раз. Если сравнить это с B-tree, для которого нам потребуется загрузить в худшем случае пять страниц (если размер страницы - 4КБ), то сразу станет понятно, почему так никто не делает. Бинарный поиск не является cache oblivious алгоритмом и будет работать не эффективно.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;
Поиск решения&lt;/h3&gt;
&lt;div&gt;
К счастью, мы можем использовать специфику данных. Источники time series данных, очень часто бывают периодическими, например, это могут быть датчики, передающие показания с определенной частотой. Не обязательно, чтобы каждый источник был периодическим, так как параметров много, можно с высокой долей вероятности ожидать, что информация будет записываться примерно с одинаковой скоростью. А это как раз тот случай, когда можно использовать &lt;a href=&quot;https://en.wikipedia.org/wiki/Interpolation_search&quot; target=&quot;_blank&quot;&gt;интерполирующий поиск&lt;/a&gt;. Принцип работы этого алгоритма крайне прост: мы знаем максимальную и минимальную метки времени, а также количество элементов в томе, мы делаем предположение о том, что метки времени всех данных распределены равномерно на этом промежутке времени, исходя из этого, мы можем приблизительно определить, где в томе может находиться искомое значение.&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Интерполирующий поиск имеет сложность O(log log N), что уже сильно лучше бинарного поиска и близко к B-tree. В случае периодических источников, нам потребуется загрузить ровно столько же страниц, сколько в случае B-tree с размером страницы в 4КБ (выкладки пожалуй не буду приводить, но я считал, правда!). Но это нельзя считать решением, так как в реальности, даже с периодическими источниками можно получить неравномерное распределение, например в случае, если на какое-то время легла сеть и мы ничего не получали. В случае click-stream-ов мы будем наблюдать всякие суточные ритмы и тд. В общем, в реальности распределение может быть неравномерным. В этом случае, интерполирующий поиск будет ошибаться и делать больше итераций чем нужно (потенциально, даже больше чем бинарный поиск). Поэтому, мой алгоритм поиска делает ровно пять шагов интерполирующего поиска, а затем, откатывается на бинарный поиск. Почему именно пять? Это ровно столько, сколько нужно для того, чтобы найти результат в случае равномерного распределения.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;
Улучшения и оптимизации&lt;/h3&gt;
&lt;div&gt;
Этим все не ограничивается. Алгоритм поиска старается на каждом этапе уменьшить область поиска. В самом начале область поиска равна всему тому, но на каждой итерации интерполирующего поиска одна из границ сдвигается ближе к искомому элементу. В случае, если область поиска сузилась до одной страницы, алгоритм откатывается на бинарный поиск, так как чем меньше масштаб, тем сильнее сказывается неравномерность распределения данных по меткам времени. Интерполирующий поиск старается сместить обе границы, если произошел overshoot, то на следующей итерации он постарается сделать undershoot. Это позволяет быстрее уменьшать область поиска.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Помимо этого, я планирую учитывать состояние страниц виртуальной памяти при поиске. Так как том мапится в память, одни страницы на момент поиска могут быть уже загружены с диска, а другие - еще нет. Мы можем получить эту информацию от операционной системы (системный вызов mincore в linux, в windows не помню как, но это тоже возможно). Во время поиска, мы можем использовать эту информацию для того, чтобы избежать page fault-ов, обращаясь только к загруженным в память страницам. Алгоритм поиска позволяет это делать, интерполирующий поиск может проверить не тот элемент, адрес которого он вычислил, а тот, который находится в ближайшей загруженной странице памяти. Бинарный поиск может проверить элемент не точно в середине области поиска, а ближайший из загруженных. Естественно, иногда все же придется обращаться к страницам, отсутствующим в памяти.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;
Open problem&lt;/h3&gt;
&lt;div&gt;
Описанные улучшения не решают проблемы неравномерного распределения данных. Есть множество статей, описывающих разные решения этой проблемы. Как правило они предлагают поддерживать какую-либо структуру данных в памяти для ускорения интерполирующего поиска. Что конкретно нужно реализовать в akumuli я еще не решил. Возможно я буду поддерживать эту информацию непосредственно в томе, а может быть наоборот - буду собирать эти данные во время выполнения поиска и кэшировать - я еще не знаю. Это решение нужно принимать, основываясь на каких-то эмпирических данных, а для того, чтобы их получить, нужно реализовать все вышеперечисленное. Так или иначе, поиск, это то, что можно улучшать бесконечно.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Пока что, я ожидаю, что описанный мной алгоритм будет работать достаточно хорошо, как минимум, не хуже чем не специализированные решения. Накопленный опыт позволяет на это надеяться. В случае же попадания в sweet spot - работа с периодическими источниками - поиск должен работать просто фантастически быстро.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
</description><link>http://evgeny-lazin.blogspot.com/2014/02/akumuli_26.html</link><author>noreply@blogger.com (Anonymous)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7754674872362895099.post-3748552570687831151</guid><pubDate>Mon, 24 Feb 2014 22:19:00 +0000</pubDate><atom:updated>2014-02-25T02:27:08.814+04:00</atom:updated><title>Akumuli: запись и хранение данных</title><description>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
Сегодня я попробую рассказать о том, как &lt;a href=&quot;https://github.com/Lazin/Akumuli&quot; target=&quot;_blank&quot;&gt;akumuli &lt;/a&gt;записывает на диск 100500 сообщений каждую секунду, но начну с небольшого лирического отступления.&lt;br /&gt;
&lt;div&gt;
&lt;br /&gt;
&lt;div&gt;
Как известно, данные на диске можно хранить в различных B-деревьях, это такая структура данных, которая позволяет искать по ключу за логарифмическое время, но в тоже время - читая минимальное количество страниц памяти с диска. Для хранения time series данных, умные люди очень давно придумали&lt;a href=&quot;http://www.cs.ucr.edu/~tsotras/cs236/W14/tempDB-survey.pdf&quot; target=&quot;_blank&quot;&gt; TSB tree и некоторые другие структуры данных на основе B-tree&lt;/a&gt;.&lt;span class=&quot;fullpost&quot;&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
Изначально, я планировал реализовать свой проект на основе TSB-tree, это вполне возможно и мне кажется, это и есть самый правильный дизайн. Но я попытался создать небольшой прототип на питоне и понял, что это не так просто, как кажется. Особенно, если хочется чтобы библиотека использовала фиксированное количество места на диске. Так как это персональный проект и я не могу тратить на него много времени, я решил отказаться от реализации TSB-tree, ведь помимо описанной мной проблемы тут есть проблемы синхронизации, проблемы целостности/восстановления данных, так как структура достаточно сложная.&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Я ввел одно ограничение, которое в принципе можно обойти, но которое очень упрощает жизнь - ограничил late writes. Это означает, что библиотека не позволяет записывать сильно устаревшие данные, размер окна записи задается в конфигурации, а также, может меняться динамически. В случае, если нагрузка слишком большая, окно записи может уменьшаться, снижая нагрузку. Это ограничение позволило мне использовать более простую структуру для хранения данных.&lt;br /&gt;
&lt;br /&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;
Persistent storage&lt;/h3&gt;
&lt;/div&gt;
&lt;div&gt;
Итак, данные в akumuli хранятся в томах, размер каждого тома - 4Гб. Все тома создаются заранее, при создании хранилища, и образуют циклический список. В любой момент времени мы можем писать только в один том. При этом, метки времени в соседних томах могут пересекаться. На высоком уровне, алгоритм записи выглядит очень просто - мы пишем в открытый том до тех пор, пока он не заполнится, затем, открываем следующий и пишем в него. Если в следующем томе есть данные - они теряются. Вы уже наверное поняли, что глубина хранения данных определяется размером хранилища, новые данные просто перезатирают самые старые, также как в rrd-tool. Это осмысленное решение а не недостаток дизайна, оно не позволяет задавать глубину хранения для каждого параметра в отдельности, но зато, позволяет писать софт, который работает предсказуемо, не падая от нехватки места на диске.&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Внутри тома все тоже устроено достаточно просто. Том, по сути, очень похож на узел B-tree, но только очень большой. Вначале тома располагается header с метаданными (количество добавленных элементов и тд), далее следует массив смещений, оставшееся место занято непосредственно данными. Каждый элемент данных начинается с заголовка - метки времени и идентификатора параметра, за которым следуют пользовательские данные переменной длины.&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4dPx96y1gHEJTWM3Uf5d4TYQA9Dv_MhnTWUhuxHwdf2z026TCu14XKSj_WgC55EAknFHivpY5IfYiwcTh9MnOXoY1KBxJ2s46sr4UKnjCJzzRZTYxZ9fNxzNL2Pznr_fYq8_0EwhXxh8/s1600/indirection-vector.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4dPx96y1gHEJTWM3Uf5d4TYQA9Dv_MhnTWUhuxHwdf2z026TCu14XKSj_WgC55EAknFHivpY5IfYiwcTh9MnOXoY1KBxJ2s46sr4UKnjCJzzRZTYxZ9fNxzNL2Pznr_fYq8_0EwhXxh8/s1600/indirection-vector.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
Данные записываются начиная с конца тома, в обратном направлении. На изображении, смещения увеличиваются слева направо, при этом элемент данных &quot;А&quot; был добавлен первым, &quot;В&quot; - вторым, а &quot;С&quot; - третьим. В массив смещений записываются смещения элементов данных (как неожиданно!). Причем смещения, как раз записываются в прямом порядке.&lt;br /&gt;
На изображении, элемент массива с индексом 0 содержит смещение элемента &quot;А&quot; и был добавлен первым. Запись в том заканчивается тогда, когда массив смещений и данные встречаются, т.е. между последним добавленным элементом массива данных и последним смещением не достаточно места для добавления следующего элемента данных.&lt;br /&gt;
&lt;br /&gt;
Такая схема позволяет, во первых, хранить данные переменной длины, во вторых, записывать данные в том очень быстро (запись линеаризуется, мы используем пропускную способность дисков по максимуму) и самое главное, эта схема позволяет очень быстро сортировать данные, для этого достаточно отсортировать массив смещений. Тот факт, что данные в томе могут быть отсортированы в порядке, отличном от порядка добавления также отражен на рисунке (элементы массива 1 и 2). Помимо этого, данная схема позволяет легко вводить избыточность, которая нужна для эффективного поиска редко обновляющихся данных. Можно добавить смещение старого элемента еще раз, чтобы алгоритму поиска не нужно было сканировать том глубоко (показано пунктирной линией).&lt;br /&gt;
&lt;br /&gt;
Эта схема позволяет также хранить вместе с пользовательскими данными всякую вспомогательную информацию, сводки (rollups), хинты для алгоритма поиска и прочие метаданные.&lt;br /&gt;
&lt;br /&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;
In-memory cache&lt;/h3&gt;
&lt;/div&gt;
&lt;div&gt;
Самая главная проблема здесь - как правильно выбрать момент для сортировки данных в томе? Можно сортировать понемногу при добавлении каждого элемента, можно подождать, когда какие-то данные станут достаточно старыми (выйдут за пределы окна записи и станут неизменяемыми) и сортировать этот диапазон массива только после этого. Можно сделать еще лучше и не сортировать данные вообще никогда, вместо этого, записывать &amp;nbsp;смещения сначала в кэш, в оперативной памяти и постепенно сливать смещения самых старых элементов на диск.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Кэш в памяти у меня построен на основе B-tree (&lt;a href=&quot;https://code.google.com/p/cpp-btree/&quot; target=&quot;_blank&quot;&gt;реализация гугла&lt;/a&gt;), в качестве ключа используется кортеж из метки времени и ид-ра параметра, значение - смещение элемента в томе. Б-деревья выбраны не случайно, time-series данные имеют одну особенность, метка времени как правило возрастает, это значит, что данные в B-tree добавляются почти всегда в порядке возрастания, а это sweet spot для B-tree. Режим, в котором вставка в B-tree выполняется очень быстро.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Кэш организован следующим образом, данные хранятся в bucket-ах (штука, которая содержит внутри себя дерево и кое какую метаинформацию). Каждый такой bucket отвечает за небольшой интервал времени, кратный глубине окна записи, эти интервалы не пересекаются. Bucket-ы объединены в список, в хронологическом порядке. Устаревшие bucket-ы, запись в которые уже запрещена (они вышли за границу окна записи), извлекаются из конца списка по очереди, их содержимое перебирается в порядке возрастания и получившаяся последовательность смещений записывается в соответствующий сектор массива смещений тома. Future write приводит к созданию нового bucket-а (на самом деле не созданию, а извлечению готового из пула, zero allocation).&lt;br /&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;
Concurrency&lt;/h3&gt;
&lt;div&gt;
В кэш можно писать параллельно из нескольких потоков, чем больше потоков пишут в один bucket, тем больше lock contention и тем все медленнее. Чтобы решить эту проблему, bucket содержит не одно дерево, а несколько, по количеству процессоров/ядер. Каждый поток сначала выбирает свой экземпляр B-tree из bucket-а, лочит его, а затем - пишет в него. Это уменьшает contention и улучшает cache locality, в общем, все работает лучше. При сохранении последовательности смещений на диск, последовательности, полученные от отдельных деревьев сливаются в одну, а уже потом - записываются в том.&lt;br /&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;
Comming soon&lt;/h3&gt;
&lt;div&gt;
В следующей статье я постараюсь описать то, как выполняется поиск.&lt;/div&gt;
&lt;/div&gt;
</description><link>http://evgeny-lazin.blogspot.com/2014/02/akumuli.html</link><author>noreply@blogger.com (Anonymous)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4dPx96y1gHEJTWM3Uf5d4TYQA9Dv_MhnTWUhuxHwdf2z026TCu14XKSj_WgC55EAknFHivpY5IfYiwcTh9MnOXoY1KBxJ2s46sr4UKnjCJzzRZTYxZ9fNxzNL2Pznr_fYq8_0EwhXxh8/s72-c/indirection-vector.png" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7754674872362895099.post-3024632708522918760</guid><pubDate>Sun, 23 Feb 2014 23:07:00 +0000</pubDate><atom:updated>2014-02-24T03:07:58.420+04:00</atom:updated><title></title><description>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
Несколько месяцев назад, я начал работать над своим собственным &lt;a href=&quot;http://akumuli.org/&quot;&gt;open source проектом&lt;/a&gt;. Попробую рассказать почему я это все начал и чего хорошего хочу сделать.&lt;div&gt;
&lt;span class=&quot;fullpost&quot;&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div&gt;
Итак, все началось с того, что я не смог найти time-series БД, которая бы являлась продуктом с открытым исходным кодом и при этом нормально работала. &lt;i&gt;Time-series данные, это любые данные снабженные меткой времени и идентификатором параметра (aka идентификатор метрики, aka идентификатор источника). Параметры могут соответствовать, например, разным сенсорам, разным измеряемым величинам, разным пользователям в click stream-е и тд.&lt;/i&gt;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Главная проблема time-series данных в том, что их всегда очень много. Представьте себе большой датацентр, в котором работают 10 000 машин, на каждой из которых специальный демон десять раз в секунду измеряет количество свободной памяти, загрузку CPU и отправляет это все в БД. Казалось бы, десять раз в секунду это не очень много, но это уже 100k операций записи в секунду, и это не пик, это sustained write rate, для данных, не помещающихся в память. А что, если потребуется измерять значения параметров не десять раз в секунду, а сто?&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Самое известное решение этой проблемы - rrd-tool, де факто стандарт во многих областях, имеет жутко неэффективный формат хранения данных с огромным количеством недостатков. Для того, чтобы понять как плох rrd-tool (но в то же время хорош, для определенных применений), нужно понять как он хранит данные, я не буду вдаваться в подробности, скажу лишь, что точность хранения меток времени там ограничена, количество параметров там также ограничено, чем их больше, тем медленнее все работает. Запись в rrd файл это множество random writes. В общем, rrd подходит для чего-нибудь небольшого и не требовательного.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Представитель принципиально другого класса систем - open tsdb (и 100500 подражателей) тоже не слишком хорош, на мой взгляд. Во первых, оно зависит от hadoop и hbase. Hbase используется для хранения данных. Поэтому, open tsdb нельзя использовать в качестве embedded БД. Если вы пишете софт, который работает на каком нибудь промышленном ПК, собирающем данные от каких-нибудь датчиков, то вы open tsdb использовать не сможете. Помимо этого, open tsdb округляет метки времени. Для мониторинга серверов (задача, для которой open tsdb создавалась) это подходит. Для других применений - не всегда.&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Самый главный недостаток всех этих систем - они игнорируют специфику данных. Как правило, они формируют некий ключ, комбинируя идентификатор параметра и метку времени, затем этот ключ используется для записи (в hbase, cassandra, leveldb etc). Для того, чтобы найти это значение, нужно использовать точно такой же ключ. По сути, эти БД работают с точечными данными. Отсюда все эти округления меток времени и тд. Главная задача той же open tsdb - построить сводки (rollups), а не поиск значения параметра X в момент времени Y.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
В настоящей time series БД, операция записи создает не точку, а линию. Если мы записали значение параметра в момент времени T0, а затем ищем его значение в момент времени T1, причем T1 &amp;gt; T0, то мы должны найти ранее записанное значение. Это логично, ведь между моментами времени T0 и T1 значение параметра не менялось. К сожалению, большинству time series баз данных это неведомо.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
В общем, я пришел к выводу, о необходимости создания специализированного бэкенда для таких данных. LevelDB, HBase и им подобные - не решают всех проблем. Собственно, я собираюсь заполнить данный пробел, создав быстрый и в тоже время &quot;правильный&quot; backend.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Цели пока такие:&lt;/div&gt;
&lt;div&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;Способность выдавать порядка миллиона операций записи в секунду на моем ноутбуке.&lt;/li&gt;
&lt;li&gt;Использование фиксированного количества места на жестком диске (как rrd-tool).&lt;/li&gt;
&lt;li&gt;Кэширование наиболее актуальных данных в памяти.&lt;/li&gt;
&lt;li&gt;Хитрый алгоритм поиска, который я придумал, но еще не реализовал :)&lt;/li&gt;
&lt;/ul&gt;
Первые две цели уже достигнуты, остальное - в процессе. В ближайшее время я постараюсь описать более подробно архитектуру и алгоритмы, в том виде, в котором это все существует сейчас.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Behold!&lt;/div&gt;
&lt;/div&gt;
</description><link>http://evgeny-lazin.blogspot.com/2014/02/open-source.html</link><author>noreply@blogger.com (Anonymous)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7754674872362895099.post-6842853729112540204</guid><pubDate>Sun, 14 Apr 2013 10:21:00 +0000</pubDate><atom:updated>2013-04-14T14:21:05.361+04:00</atom:updated><title></title><description>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
Деление на ноль, это отличная тема для троллинга, между прочим:&lt;br /&gt;
&lt;blockquote class=&quot;tr_bq&quot;&gt;
Есть такой стандарт — IEEE754, это стандарт на floating point вычисления. Согласно этому стандарту, при делении числа на 0, получается либо +, либо — бесконечность. Но это было сделано не потому что 1/0 = бесконечности, а для того, тобы упростить жизнь программистам. Начнем с того, что в этом стандарте существуют 3 нуля — 0, –0 и +0. Два последних получаются при underflow, при underflow нам не хватает точности для того, чтобы представить число, мы можем сохранить только знак. &lt;br /&gt;&lt;br /&gt;Если теперь представить какое–нибудь вычисление, в котором какое–нибудь число делится на постоянно уменьшающееся значение, то при достаточном количестве итераций мы получим underflow, то есть, по сути — ноль. Если бы в FP вычислениях, при делении на ноль получалось бы NaN, как того требует здравый смысл, то мы получили бы NaN вместо результата вычисления. Но вместо этого мы получим Inf, что в данном случае верно и правильно, мало того, мы получим правильный знак у Inf, в зависимости от того, с какой стороны произошел underflow, мы получим либо +Inf либо –Inf, bingo! &lt;br /&gt;&lt;br /&gt;И теперь внимание — большинство делений на 0 в реальных программах происходят именно в такой ситуации, как я описал — ноль получается в результате underflow, а не нормальных вычислений. Вычисления с плавающей точкой — это аппроксимация, они априори не точны. В данном случае, разработчики стандарта пожертвовали точностью в угоду корректности. Но из–за этого 90% программистов считают что 1/0 должно быть равно бесконечности :)&lt;/blockquote&gt;
Читатель, учись делить на ноль правильно! &lt;br /&gt;
&lt;span class=&quot;fullpost&quot;&gt;

&lt;/span&gt;&lt;/div&gt;
</description><link>http://evgeny-lazin.blogspot.com/2013/04/ieee754-floating-point.html</link><author>noreply@blogger.com (Anonymous)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7754674872362895099.post-7038741001569587956</guid><pubDate>Sun, 20 Jan 2013 12:04:00 +0000</pubDate><atom:updated>2013-01-20T16:04:10.770+04:00</atom:updated><title>Restricted Transactional Memory в Haswell</title><description>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
Пожалуй я не сильно ошибусь, сказав что существует всего два механизма управления изменениями - пессимистичный и оптимистичный. Первый мы уже давно используем в своих программах в виде всевозможных мьютексов и семафоров. Второй механизм, до недавнего времени, применялся в различных СУБД.&lt;br /&gt;
Software transactional memory (STM) - реализация второго механизма управления изменениями, по сути это просто транзакции в коде. Вы помечаете участок кода, который должен выполняться в рамках одной транзакции. Во время выполнения, система запоминает все что вы читаете и записываете (поддерживает read set и write set). В случае, если произошел конфликт, несколько транзакций попытались изменить одни и те же переменные, происходит откат транзакции, система возвращается в исходное состояние, после чего транзакция выполняется повторно.&lt;br /&gt;
&lt;br /&gt;
Существует множество реализаций STM на разных языках и платформах, тем не менее, это по прежнему экзотика. Про железные реализации я вообще молчу, не случившийся Rock и BlueGene/Q для суперкомпьютера IBM, но есть и повод для оптимизма.&lt;br /&gt;
Примерно год назад, Intel анонсировали новый процессор - Haswell, который будет поддерживать набор инструкций Transactional Synchronization Extensions (TSX). Restricted Transactional Memory (RTM) - это часть TSX, добавляющая поддержку транзакционной памяти. Как видно из названия - поддержку ограниченную.&lt;br /&gt;
&lt;br /&gt;
Для программиста, RTM это четыре новых инструкции - XBEGIN, XEND, XABORT и XTEST.&lt;br /&gt;
XBEGIN - начинает транзакцию, XEND - ее фиксирует, XABORT - откатывает. Инструкция XTEST позволяет узнать, находимся мы сейчас в транзакции, или нет. В Visual Studio 2012 есть интринсики, с помощью которых можно удобно использовать эти инструкции. Называются они соответственно - _xbegin, _xend, _xabort и _xtest.&lt;br /&gt;
&lt;br /&gt;
Работает это следующим образом, вы вызываете ф-ю _xbegin, которая возвращает статус транзакции. В случае, если ф-я вернула _XBEGIN_STARTED - транзакция была начата, в противном случае - произошел откат транзакции по какой-либо причине. В случае отката транзакции, управление возвращается в ее начало, то есть в _xbegin() и в этом случае, _xbegin вернет статус, отличный от _XBEGIN_STARTED. В конце транзакции вы должны вызвать ф-ю _xend, в этом случае произойдет фиксация транзакции. Ф-я _xabort прерывает выполняющуюся транзакцию, управление вернется в _xbegin. Разные биты статуса _xbegin позволяют определить причину отката транзакции, был это _xabort, конфликт записи или что-нибудь еще.&lt;br /&gt;
&lt;br /&gt;
Но на самом деле все далеко не так радужно, как может показаться. Недаром в названии есть слово `Restricted`, существуют значительные ограничения на код, который может выполняться в транзакциях. Во первых, в транзакции можно выполнять только простые загрузки и сохранения, даже простое переключение контекста прервет транзакцию. Во вторых, размер write-set и read-set ограничен размером L1 кэша, если вы попытаетесь переписать в транзакции мегабайт памяти - ничего не получится. В третьих, RTM работает на уровне линий кэша, поэтому здесь возможен false sharing, в том случае, если разные переменные попадают на одну кэш линию.&lt;br /&gt;
&lt;br /&gt;
Именно по этому, Intel не гарантирует, что транзакция вообще завершится когда-нибудь. Поэтому, код, использующий транзакции, должен следить за тем, сколько раз и по какой причине транзакция откатилась. Реализация должна предусматривать fallback механизм, например, если наша транзакция откатилась N раз, мы можем попытаться захватить соответствующую блокировку и выполнить тот же самый код без транзакции, используя эксклюзивный доступ к данным.&lt;br /&gt;
&lt;br /&gt;
По этим причинам, код, выполняемый в транзакции, должен быть коротким, он не должен пытаться делать I/O или что-нибудь кроме простых загрузок и сохранений в память, он должен изменять и читать как можно меньше данных. Именно по этому, люди, ждущие от RTM чего-то похожего на STM из Haskell будут разочарованы.&lt;br /&gt;
На мой взгляд, RTM подходит для создания различных lock-free структур данных. Если без RTM написать lock-free очередь - было делом нетривиальным, то с RTM все станет намного проще. Вместо того, чтобы ломать себе голову над тем, как с помощью CAS реализовать ту или иную операцию, достаточно просто обернуть ее в транзакцию. Я уже молчу о более сложных структурах данных.&lt;br /&gt;
&lt;br /&gt;
Для иллюстрации вышесказанного, я написал простой, lock-free двух-связный список. Элементы можно добавлять и удалять из любого конца списка.&lt;br /&gt;
&lt;br/&gt;
&lt;script src=&quot;https://gist.github.com/4542229.js&quot;&gt;&lt;/script&gt;

Это всего лишь proof of concept, не более. Здесь не реализован fallback-механизм, поэтому в случае ошибки в коде, он может зациклиться. Помимо этого, данный код не будет работать на обычных процессорах. Он будет падать по `undefined instruction`. Запустить его можно только в эмуляторе:&lt;br /&gt;
&lt;blockquote class=&quot;tr_bq&quot;&gt;
&lt;span style=&quot;font-family: Courier New, Courier, monospace;&quot;&gt;&lt;b&gt;sde -hsw -rtm-mode full --  appname.exe&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
На данный момент сложно судить о производительности. Я очень надеюсь на то, что RTM будет позволять писать код, который будет работать быстрее, чем аналогичный код, построенный на CAS. На это можно надеяться, так как транзакции все пишут в кэш, а для обнаружения конфликтов записи используется cache coherency протокол, который есть и сейчас. Насколько я понял, все операции записи внутри транзакции - неблокирующие, в отличии от xchg.&lt;br /&gt;
&lt;br /&gt;
Ссылки:&lt;br /&gt;
&lt;a href=&quot;http://software.intel.com/en-us/blogs/2012/11/06/exploring-intel-transactional-synchronization-extensions-with-intel-software&quot;&gt;Exploring Intel® Transactional Synchronization Extensions with Intel® Software Development Emulator&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;http://software.intel.com/en-us/articles/intel-software-development-emulator&quot;&gt;Intel® Software Development Emulator&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;http://software.intel.com/sites/default/files/m/a/b/3/4/d/41604-319433-012a.pdf&quot;&gt;Intel® Architecture Instruction Set Extensions Programming Reference (Chapter 8)&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;span class=&quot;fullpost&quot;&gt;&lt;/span&gt;&lt;/div&gt;
</description><link>http://evgeny-lazin.blogspot.com/2013/01/restricted-transactional-memory-haswell.html</link><author>noreply@blogger.com (Anonymous)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7754674872362895099.post-4951476947973748873</guid><pubDate>Wed, 13 Jun 2012 10:20:00 +0000</pubDate><atom:updated>2012-06-13T14:20:30.908+04:00</atom:updated><title>Singly resizable array</title><description>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
Тема сегодняшнего поста - динамические массивы. Динамический массив, это одна из наиболее часто используемых структур данных. Рассмотрим простой динамический массив, имеющий две операции - добавление элемента в конец массива и поиск элемента массива по индексу. Наивный алгоритм, последовательно добавляющий множество элементов в массив, каждый раз полностью копируя его содержимое, будет иметь квадратичную сложность.&lt;br /&gt;
&lt;br /&gt;
Естественно, операцию push_back в реальной жизни реализуют с помощью метода удвоения. Алгоритм, последовательно добавляющий множество элементов в массив методом удвоения, будет иметь линейную, амортизированую сложность. По памяти все тоже неплохо, в любой момент времени, наш динамический массив будет иметь capacity &amp;lt;= N*2, где N - количество элементов массива, а capacity - его вместительность.&lt;br /&gt;
&lt;div&gt;
&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;
К сожалению, в реальности, все несколько хуже. Во первых, во время удвоения нам нужно выделить память под новый массив и скопировать в него содержимое старого, следовательно, расходы по памяти составят 3*N. Во вторых, постоянное пересоздание массива приводит к фрагментации памяти, так как для выделения памяти под массив при очередном удвоении размера, менеджер памяти не может использовать память, освобожденную динамическим массивом ранее. Именно по этому, во многих реализациях, массив увеличивают не в два раза, а в меньшее число раз, например в полтора. В третьих, данный алгоритм, даже имея сложность O(N), имеет достаточно большую коснтанту, так как периодически перезаписывает большие участки памяти.&lt;br /&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Существует множество способов решения этих проблем. Один из этих способов очень похож на метод удвоения: вместо того, чтобы хранить элеметы в одном большом массиве, можно хранить их в последовательности массивов меньшего размера, при этом первый подмассив должен хранить один элемент, а каждый следующий - в два раза больше чем предыдущий. Для поиска i-го элемента массива нам нужно выполнить две операции: определить позицию старшего бита индекса - k и вычислить значение b, равное значению индекса без старшего бита. При этом значение k будет номером подмассива, а b - индексом элемента в этом подмассиве. Алгоритм добавления элемента в такой динамический массив достаточно очевиден, поэтому я его не буду описывать. Данный подход решает две проблемы из трех. Он не переписывает кучу памяти и не вызывает фрагментацию, но он по прежнему приводит к потерям памяти порядка O(N). В простыне кода в конце поста, данный алгоритм реализован в классе ResizableArray.&lt;br /&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Существует еще один алгоритм, позволяющий решить все три проблемы, он хорошо описан в этой статье -&amp;nbsp;&lt;a href=&quot;http://www.cs.uwaterloo.ca/research/tr/1999/09/CS-99-09.pdf&quot;&gt;http://www.cs.uwaterloo.ca/research/tr/1999/09/CS-99-09.pdf&lt;/a&gt;. Это по сути дальнейшее развитие предыдущего алгоритма, подмассивы(суперблоки) мы делим на множество блоков, каждый из которых имеет размер Sqrt(N), где N - размер суперблока, причем в каждом суперблоке у нас будет Squrt(N) блоков. Подобная схема позволяет сократить потери памяти до O(Sqrt(N)), при этом операции добавления и поиска элемента по индексу, будут иметь сложность O(1), как и прежде. Данный алгоритм реализован в классе ResizableArrayV2.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;
Класс ResizableArray примерно в 2.5 раза быстрее нежели std::vector при добавлении большого количества элементов, в 3.5 раза медленнее вектора при чтении и имеет ровно такие же потери памяти. Класс ResizableArrayV2 примерно в полтора раза быстрее вектора при добавлении элементов, в 4.5 раза медленнее при чтении, но зато он обеспечивает очень низкий, по сравнению с вектором, уровень потерь памяти. Нужно также добавить, что код практически не оптимизирован (я только цикл для вычисления индекса старшего бита равзернул), так-что ситуация может быть несколько лучше.&lt;/div&gt;
&lt;div&gt;
Вот собственно сам код (ни разу не production качества, имейте ввиду):&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;script src=&quot;https://gist.github.com/2923114.js&quot;&gt;
 
&lt;/script&gt;&lt;/div&gt;</description><link>http://evgeny-lazin.blogspot.com/2012/06/singly-resizable-array.html</link><author>noreply@blogger.com (Anonymous)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7754674872362895099.post-893226055402051711</guid><pubDate>Fri, 01 Jun 2012 09:09:00 +0000</pubDate><atom:updated>2012-06-01T13:12:27.178+04:00</atom:updated><title></title><description>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
Производительность, это очень сложно. Существует огромное количество факторов, не имеющих прямого отношения к логике работы программы, которые следует учесть, для того, чтобы приложение эффективно использовало ресурсы процессора.&lt;br /&gt;
Вот наглядный пример:&lt;span class=&quot;fullpost&quot;&gt;&lt;/span&gt;&lt;br /&gt;
&lt;script src=&quot;https://gist.github.com/2850441.js&quot;&gt; &lt;/script&gt;
&lt;div&gt;
Код, использующий один счетчик и InterlockedIncrement работает, в среднем, в три раза медленнее нежели код, исползующий класс ConcurrentCounter. Почему это происходит, должно быть достаточно очевидно. InterlockedIncrement блокирует шину памяти на время выполнения, т.е. по сути, все потоки получают доступ к переменной по очереди, синхронно.&lt;br /&gt;
&lt;br /&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;За какой асбракцией не пряталась бы инструкция lock xchg, знать что именно происходит на низком уровне все равно придется.&lt;/li&gt;
&lt;li&gt;Так же придется знать что такое TLB, write buffer, как происходит инвалидация кэша, что такое false sharing и тд.&lt;/li&gt;
&lt;li&gt;Загрузка процессора под 100% вовсе не означает что он используется эффективно.&lt;/li&gt;
&lt;li&gt;Хотелось бы все эти заботы переложить на плечи разработчиков библитек и инструменов разработки.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;</description><link>http://evgeny-lazin.blogspot.com/2012/06/blog-post.html</link><author>noreply@blogger.com (Anonymous)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7754674872362895099.post-8852751459251579261</guid><pubDate>Tue, 15 Nov 2011 19:32:00 +0000</pubDate><atom:updated>2011-11-16T13:18:22.214+04:00</atom:updated><title>Threaded vs Event driven</title><description>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
Как правило, при создании сервера перед нами глобально стоит всегда один и тот же выбор - threaded или event driven.&lt;span class=&quot;fullpost&quot;&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div&gt;
Многопоточный (threaded) сервер - проще в реализации, на каждое клиентское соединение создается отдельный поток, весь ввод/вывод через это соединение происходит в данном потоке, синхронно. Преимущество данного подхода - относительная простота реализации, недостаток - плохая масштабируемость с ростом числа одновременных соединений (запускать&amp;nbsp;много потоков - плохо).&lt;br /&gt;
Event driven сервер использует механизм IOCP (если речь идет о windows), вся логика обработки запросов выполняется асинхронно, в обработчиках событий. Преимущество данного подхода состоит в том, что клиентское соединение больше не привязано к потоку. Недостатки - сложность разработки, логика распределена по множеству обработчиков событий и сложность отладки. Понимать логику работы event driven сервера не всегда просто, отсюда все остальные сложности.&lt;/div&gt;
&lt;div&gt;
Именно поэтому, зачастую стоит выбрать threaded архитектуру, главное понять в каких случаях это можно сделать, а в каких - нет.&lt;br /&gt;
&lt;br /&gt;
Итак, введем следующие обозначения:&lt;/div&gt;
&lt;div&gt;
T - пропускная способность сервера, количество запросов в секунду.&lt;/div&gt;
&lt;div&gt;
C - время, затрачиваемое процессором на выполнение запроса (CPU time).&lt;/div&gt;
&lt;div&gt;
I - время, затрачиваемое потоком на синхронное выполнение операций ввода/вывода (I/O time).&lt;/div&gt;
&lt;div&gt;
N - количество процессоров.&lt;/div&gt;
&lt;div&gt;
M - количество потоков.&lt;br /&gt;
Я считаю что мы пишем сервер, который выполняет запросы, каждый запрос выполняется фиксированное время, часть времени тратится на выполнение операций ввода вывода - I, а часть на обработку - C.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Максимальная пропускная способность event driven сервера - T = N/C. Это должно быть интуитивно понятно, пропускная способность максимальна тогда, когда процессор загружен обработкой запросов на 100%.&lt;br /&gt;
Пропускная способность многопоточного сервера - T = M/(I + C). Последняя формула справедлива только в том случае, если процессор загружен не полностью, иначе, увеличение M не будет приводить к увеличению пропускной способности сервера. Если эта зависимость кажется вам не очевидной, представьте что у нас есть однопоточный сервер (М = 1), его пропускная способность должна быть обратно пропорциональна сумме I + C, так как весь ввод/вывод выполняется синхронно и блокирует поток.&lt;br /&gt;
Наша задача состоит в том, что-бы определить такое количество потоков, при котором пропускная способность многопоточного сервера является максимальной. Очевидно, она будет равна максимально пропускной способности event driven сервера:&lt;/div&gt;
&lt;blockquote class=&quot;tr_bq&quot;&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: inherit;&quot;&gt;T = N/C = M/(I + C);&lt;br /&gt;M = N (I/C + 1);&lt;/span&gt;&lt;/blockquote&gt;
&lt;div&gt;
Как это можно использовать на практике? Очень просто, допустим, выполнение запроса у нас занимает 1мкс, а операции ввода вывода - 1мс. При N = 1, подставляем значения в формулу - M = 0.001/0.000001 + 1 = 1001, ровно столько потоков нам нужно для того, чтобы полностью загрузить один процессор. Очевидно, что в данном случае лучше использовать event driven архитектуру.&lt;/div&gt;
&lt;div&gt;
Другой пример, время обработки С = 100мкс, время выполнения ввода/вывода I = 1мс, M = 11. В данном случае можно обойтись threaded архитектурой сервера.&lt;br /&gt;
&lt;br /&gt;
У вас может возникнуть следующий вопрос, что будет, если во втором примере к серверу подключится множество клиентов, намного больше&amp;nbsp;одиннадцати&amp;nbsp;и не лучше ли выбрать в этом случае event driven подход? Ответ - а черт его знает, в режиме перегрузки обе архитектуры работают плохо, в рамках event driven подхода будет увеличиваться среднее время обработки отдельного запроса и объем используемой памяти. Ровно тоже самое будет происходить с threaded сервером, который запустит слишком много потоков. Возможно, event driven сервер будет работать в режиме сильной перегрузки немного лучше, но это вовсе не обязательно и вообще зависит от конкретной реализации.&lt;/div&gt;
&lt;/div&gt;</description><link>http://evgeny-lazin.blogspot.com/2011/11/threaded-vs-event-driven.html</link><author>noreply@blogger.com (Anonymous)</author><thr:total>1</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7754674872362895099.post-2458403310601998777</guid><pubDate>Sun, 02 Oct 2011 16:15:00 +0000</pubDate><atom:updated>2011-10-03T09:54:27.888+04:00</atom:updated><title>Disruptor</title><description>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
Продолжая &lt;a href=&quot;http://evgeny-lazin.blogspot.com/2011/10/blog-post.html&quot;&gt;разговор об очередях&lt;/a&gt;.&amp;nbsp;Не так давно, Мартин Фаулер опубликовал статью с описанием архитектуры &lt;a href=&quot;http://martinfowler.com/articles/lmax.html&quot;&gt;LMAX&lt;/a&gt;, это что-то вроде трейдинговой платформы, которая обрабатывает очень много мелких сообщений. Как мы уже знаем, сообщения можно обрабатывать с помощью конвейера, но обычные очереди сообщений оказывается не всегда хорошо подходят для этой цели.&lt;br /&gt;
&lt;span class=&quot;fullpost&quot;&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div&gt;
Представим себе некую сложную систему из очередей и потоков, у нас есть N потоков, которые связаны N + 1 очередями. Пускай очереди реализованы на основе массивов. У нас есть множество массивов, каждое сообщение N + 1 раз записывается в массив и N + 1 раз читается из массива, при этом происходит N + 1 изменений переменных head и tail разных очередей. Даже если все очереди, это SPSC очереди, это несколько избыточно. Для решения этой проблемы, разработчики LMAX создали библиотеку &lt;a href=&quot;http://code.google.com/p/disruptor/&quot;&gt;Disruptor&lt;/a&gt; для java. Помимо этого, уже существуют .NET и С++ порты, примеры в этой статье будут использовать .NET порт.&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhwbO4tY_yoQ9X8MIoTZBe2zmgH2Nenwoals52NOY02bJyzC9otpQOmKppip0xvr03aM87MRd6raAHJgn3yF6seN3Rg8hvS4PgxLccvCrQyc7dVHMFPTh1tstYzo7AcOZOMm2ONr_W8BsA/s1600/paint+%25287%2529.jpg&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;strike&gt;&lt;img border=&quot;0&quot; height=&quot;240&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhwbO4tY_yoQ9X8MIoTZBe2zmgH2Nenwoals52NOY02bJyzC9otpQOmKppip0xvr03aM87MRd6raAHJgn3yF6seN3Rg8hvS4PgxLccvCrQyc7dVHMFPTh1tstYzo7AcOZOMm2ONr_W8BsA/s320/paint+%25287%2529.jpg&quot; width=&quot;320&quot; /&gt;&lt;/strike&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;В общем, disruptor это один большой кольцевой буфер, с помощью которого можно заменить целый огород из очередей. Во время создания, кольцевому буферу передается его размер и фабрика для создания объектов. Помимо этого, буфер можно немного настроить, он может использовать разные механизмы ожидания, блокирующий, yield (передает управление другому потоку) и spin wait. Буфер заполняется созданными фабрикой объектами, которые из него никогда не удаляются.&lt;br /&gt;
&lt;br /&gt;
&lt;pre style=&quot;background-attachment: initial; background-clip: initial; background-color: white; background-image: initial; background-origin: initial;&quot;&gt;&lt;span style=&quot;font-family: Consolas; font-size: 15px;&quot;&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;  var&lt;/span&gt;&amp;nbsp;ringBuffer&amp;nbsp;=&amp;nbsp;&lt;span style=&quot;font-weight: bold;&quot;&gt;new&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;RingBuffer&lt;/span&gt;&amp;lt;&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;ValueEntry&lt;/span&gt;&amp;gt;(
    ()&amp;nbsp;=&amp;gt;&amp;nbsp;&lt;span style=&quot;font-weight: bold;&quot;&gt;new&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;ValueEntry&lt;/span&gt;(),
    100&amp;nbsp;*&amp;nbsp;1024,
&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;    ClaimStrategyFactory&lt;/span&gt;.&lt;span style=&quot;color: #555555; font-weight: bold;&quot;&gt;ClaimStrategyOption&lt;/span&gt;.SingleThreaded,
    &lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;WaitStrategyFactory&lt;/span&gt;.&lt;span style=&quot;color: #555555; font-weight: bold;&quot;&gt;WaitStrategyOption&lt;/span&gt;.Yielding);&lt;/span&gt;
&lt;/pre&gt;
&lt;span style=&quot;font-family: inherit;&quot;&gt;Далее, пользователь библиотеки может создать множество потребителей, причем потребители могут работать как параллельно, при этом каждое сообщение будет обработано всеми параллельно включенными потребителями, а так же последовательно. У вас не получится сделать многие вещи, например, создать цикл или сделать так, чтобы сообщения обрабатывались только одним из параллельных потребителей. &lt;/span&gt;&lt;br /&gt;
&lt;pre style=&quot;background-attachment: initial; background-clip: initial; background-color: white; background-image: initial; background-origin: initial; background-position: initial initial; background-repeat: initial initial; font-family: Consolas; font-size: 15px;&quot;&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;  var&lt;/span&gt;&amp;nbsp;cons&amp;nbsp;=&amp;nbsp;&lt;span style=&quot;font-weight: bold;&quot;&gt;new&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;ValueConsumer&lt;/span&gt;(COUNT);
  ringBuffer&lt;/pre&gt;
&lt;pre style=&quot;background-attachment: initial; background-clip: initial; background-color: white; background-image: initial; background-origin: initial;&quot;&gt;&lt;span style=&quot;font-family: Consolas; font-size: 15px;&quot;&gt;    .ConsumeWith(&lt;span style=&quot;font-weight: bold;&quot;&gt;new&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;Incrementer&lt;/span&gt;())
    .Then(&lt;span style=&quot;font-weight: bold;&quot;&gt;new&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;Incrementer&lt;/span&gt;(),&amp;nbsp;&lt;span style=&quot;font-weight: bold;&quot;&gt;new&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;Decrementer&lt;/span&gt;())
    .Then(cons);&lt;/span&gt;
&lt;/pre&gt;
&lt;div&gt;
&lt;span style=&quot;font-family: inherit;&quot;&gt;В данном примере мы создаем конвейер из трех этапов, на пером этапе каждое сообщение будет обработано одним потребителем Incrementer, на втором двумя(Incrementer и Decrementer), и на третьем - одним потребителем типа ValueConsumer. При этом сообщение останется в массиве после того, как все потребители его обработают, в дальнейшем его перезапишет производитель. Потребители, это объекты, реализующие интерфейс IBatchHandler&amp;lt;T&amp;gt;:&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;pre style=&quot;background-attachment: initial; background-clip: initial; background-color: white; background-image: initial; background-origin: initial; background-position: initial initial; background-repeat: initial initial; font-family: Consolas; font-size: 15px;&quot;&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;  class&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;Incrementer&lt;/span&gt;&amp;nbsp;:&amp;nbsp;&lt;span style=&quot;color: #5c5c5c; font-weight: bold;&quot;&gt;IBatchHandler&lt;/span&gt;&amp;lt;&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;ValueEntry&lt;/span&gt;&amp;gt;
  {
&lt;span style=&quot;font-weight: bold;&quot;&gt;    public&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;font-weight: bold;&quot;&gt;void&lt;/span&gt;&amp;nbsp;OnAvailable(&lt;span style=&quot;font-weight: bold;&quot;&gt;long&lt;/span&gt;&amp;nbsp;sequence,&amp;nbsp;&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;ValueEntry&lt;/span&gt;&amp;nbsp;data)&amp;nbsp;{
      data.Value++;
    }&lt;/pre&gt;
&lt;pre style=&quot;background-attachment: initial; background-clip: initial; background-color: white; background-image: initial; background-origin: initial; background-position: initial initial; background-repeat: initial initial; font-family: Consolas; font-size: 15px;&quot;&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;    public&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;font-weight: bold;&quot;&gt;void&lt;/span&gt;&amp;nbsp;OnEndOfBatch()&amp;nbsp;{
    }
  }&lt;/pre&gt;
&lt;span style=&quot;font-family: inherit;&quot;&gt;Хочу обратить внимание на то, что потребитель изменяет параметр data, параметр data, это элемент кольцевого буфера, sequence - номер сообщения. После этого, пользователь библиотеки должен создать producer barrier - объект, с помощью которого он будет добавлять новые сообщения. Producer barrier может быть многопоточным, тогда внутри себя он будет использовать атомарные операции, либо однопоточным, тогда он будет работать немного быстрее в случае, если вызывается из одного потока.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;
&lt;pre style=&quot;background-attachment: initial; background-clip: initial; background-color: white; background-image: initial; background-origin: initial;&quot;&gt;&lt;span style=&quot;font-family: Consolas; font-size: 15px;&quot;&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;  var&lt;/span&gt;&amp;nbsp;pbarrier&amp;nbsp;=&amp;nbsp;ringBuffer.CreateProducerBarrier();
  ringBuffer.StartConsumers();&lt;/span&gt;
&lt;/pre&gt;
После того, как вы все это сделали, нужно вызвать метод StartConsumers, который запустит по одному потоку на каждого потребителя.&amp;nbsp;&lt;span style=&quot;font-family: inherit;&quot;&gt;Добавление нового сообщения выглядит следующим образом:&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;
&lt;pre style=&quot;background-attachment: initial; background-clip: initial; background-color: white; background-image: initial; background-origin: initial; background-position: initial initial; background-repeat: initial initial; font-family: Consolas; font-size: 15px;&quot;&gt;  &lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;ValueEntry&lt;/span&gt;&amp;nbsp;data;
&lt;span style=&quot;font-weight: bold;&quot;&gt;  var&lt;/span&gt;&amp;nbsp;seq&amp;nbsp;=&amp;nbsp;barrier.NextEntry(&lt;span style=&quot;font-weight: bold;&quot;&gt;out&lt;/span&gt;&amp;nbsp;data);
  data.Value&amp;nbsp;=&amp;nbsp;i;
  barrier.Commit(seq);&lt;/pre&gt;
&lt;br /&gt;
мы должны в первую очередь вызвать метод NextEntry, который выделит в массиве место под наш новый элемент. При этом не происходит выделение памяти, элемент массива был создан заранее. Барьер использует claim factory для получения номера очередного сообщения (параметр claim factory конструктора) именно его и возвращает метод NextEntry. Далее, нужно изменить объект, ссылку на который мы получили вызвав NextEntry, например записав в него наше сообщение и затем, вызвать метод Commit, передав в него номер сообщения.&lt;br /&gt;Такой хитрый метод записи позволяет добавлять элементы в буфер сразу из нескольких потоков без блокировок и синхронизации.&lt;span style=&quot;font-family: inherit;&quot;&gt;Далее, будут вызываться методы OnAvailable всех потребителей в том порядке, который мы задали во время конфигурирования буфера. В этот метод также передается номер обрабатываемого сообщения, который можно использовать например для того, что-бы организовать обработку сообщений в round-robin манере, несколькими параллельными consumer-ами.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
Обработчики содержат внутри себя свою текущую позицию и специальный объект, который создается с помощью wait factory, с помощью которого он может ждать появления доступных для обработки сообщений. Также, все обработчики знают о своих зависимостях и после обработки очередного сообщения &quot;сигналят&quot; следующему обработчику. Текущая позиция обработчика изменяется только из одного потока - потока этого обработчика, при этом генерируется write barrier. Перед тем как прочитать элемент, обработчик должен быть &quot;разбужен&quot; producer barrier-ом или другим обработчиком, он должен прочитать значение текущей позиции разбудившего (генерируется read barrier) и решить, сколько сообщений он может прочитать. Затем, для каждого из доступных сообщений будет вызван метод OnAvailable, а затем OnEndOfBatch.&lt;/div&gt;
&lt;div&gt;
OnEndOfBatch - позволяет обрабатывать сообщения не по оному а пачками, т.н. batch processing. Если ваш обработчик выполняет какой либо I/O, то лучше в методе OnAvailable формировать буфер для отправки(либо просто запомнить номера первого и последнего сообщений между вызовами OnEndOfBatch), а в OnEndOfBatch - непосредственно выполнять I/O.&lt;/div&gt;
&lt;div&gt;
Итак, disruptor может быть очень полезен, хотя-бы тем, что избавляет от необходимости городить и поддерживать огород из множества потоков и очередей вручную. Ну и как приятный бонус, это все очень быстро работает :)&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Ну и в конце - код который я написал что-бы &quot;поиграться&quot; с этой библиотекой, может кому нибудь пригодится:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;pre style=&quot;background-attachment: initial; background-clip: initial; background-color: white; background-image: initial; background-origin: initial; background-position: initial initial; background-repeat: initial initial; font-family: Consolas; font-size: 15px;&quot;&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;using&lt;/span&gt;&amp;nbsp;System;
&lt;span style=&quot;font-weight: bold;&quot;&gt;using&lt;/span&gt;&amp;nbsp;System.Collections.Generic;
&lt;span style=&quot;font-weight: bold;&quot;&gt;using&lt;/span&gt;&amp;nbsp;System.Linq;
&lt;span style=&quot;font-weight: bold;&quot;&gt;using&lt;/span&gt;&amp;nbsp;System.Threading;
&lt;span style=&quot;font-weight: bold;&quot;&gt;using&lt;/span&gt;&amp;nbsp;System.Diagnostics;
&lt;span style=&quot;font-weight: bold;&quot;&gt;using&lt;/span&gt;&amp;nbsp;System.Collections.Concurrent;
&lt;span style=&quot;font-weight: bold;&quot;&gt;using&lt;/span&gt;&amp;nbsp;Disruptor;
 
&lt;span style=&quot;font-weight: bold;&quot;&gt;namespace&lt;/span&gt;&amp;nbsp;QueueTest
{
	&lt;span style=&quot;font-weight: bold;&quot;&gt;class&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;ValueEntry&lt;/span&gt;
	{
		&lt;span style=&quot;font-weight: bold;&quot;&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;font-weight: bold;&quot;&gt;int&lt;/span&gt;&amp;nbsp;Value&amp;nbsp;{&amp;nbsp;&lt;span style=&quot;font-weight: bold;&quot;&gt;get&lt;/span&gt;;&amp;nbsp;&lt;span style=&quot;font-weight: bold;&quot;&gt;set&lt;/span&gt;;&amp;nbsp;}
	}
 
	&lt;span style=&quot;font-weight: bold;&quot;&gt;class&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;Incrementer&lt;/span&gt;&amp;nbsp;:&amp;nbsp;&lt;span style=&quot;color: #5c5c5c; font-weight: bold;&quot;&gt;IBatchHandler&lt;/span&gt;&amp;lt;&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;ValueEntry&lt;/span&gt;&amp;gt;
	{
		&lt;span style=&quot;font-weight: bold;&quot;&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;font-weight: bold;&quot;&gt;void&lt;/span&gt;&amp;nbsp;OnAvailable(&lt;span style=&quot;font-weight: bold;&quot;&gt;long&lt;/span&gt;&amp;nbsp;sequence,&amp;nbsp;&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;ValueEntry&lt;/span&gt;&amp;nbsp;data)&amp;nbsp;{
			data.Value++;
		}
 
		&lt;span style=&quot;font-weight: bold;&quot;&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;font-weight: bold;&quot;&gt;void&lt;/span&gt;&amp;nbsp;OnEndOfBatch()&amp;nbsp;{
		}
	}
 
	&lt;span style=&quot;font-weight: bold;&quot;&gt;class&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;Decrementer&lt;/span&gt;&amp;nbsp;:&amp;nbsp;&lt;span style=&quot;color: #5c5c5c; font-weight: bold;&quot;&gt;IBatchHandler&lt;/span&gt;&amp;lt;&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;ValueEntry&lt;/span&gt;&amp;gt;
	{
		&lt;span style=&quot;font-weight: bold;&quot;&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;font-weight: bold;&quot;&gt;void&lt;/span&gt;&amp;nbsp;OnAvailable(&lt;span style=&quot;font-weight: bold;&quot;&gt;long&lt;/span&gt;&amp;nbsp;sequence,&amp;nbsp;&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;ValueEntry&lt;/span&gt;&amp;nbsp;data)&amp;nbsp;{
			data.Value--;
		}
 
		&lt;span style=&quot;font-weight: bold;&quot;&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;font-weight: bold;&quot;&gt;void&lt;/span&gt;&amp;nbsp;OnEndOfBatch()&amp;nbsp;{
		}
	}
 
	&lt;span style=&quot;font-weight: bold;&quot;&gt;class&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;ValueProducer&lt;/span&gt;
	{
		&lt;span style=&quot;font-weight: bold;&quot;&gt;int&lt;/span&gt;&amp;nbsp;count;
		&lt;span style=&quot;color: #5c5c5c; font-weight: bold;&quot;&gt;IProducerBarrier&lt;/span&gt;&amp;lt;&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;ValueEntry&lt;/span&gt;&amp;gt;&amp;nbsp;barrier;
 
		&lt;span style=&quot;font-weight: bold;&quot;&gt;public&lt;/span&gt;&amp;nbsp;ValueProducer(&lt;span style=&quot;font-weight: bold;&quot;&gt;int&lt;/span&gt;&amp;nbsp;num,&amp;nbsp;&lt;span style=&quot;color: #5c5c5c; font-weight: bold;&quot;&gt;IProducerBarrier&lt;/span&gt;&amp;lt;&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;ValueEntry&lt;/span&gt;&amp;gt;&amp;nbsp;pbarrier)&amp;nbsp;{
			count&amp;nbsp;=&amp;nbsp;num;
			barrier&amp;nbsp;=&amp;nbsp;pbarrier;
		}
 
		&lt;span style=&quot;font-weight: bold;&quot;&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;font-weight: bold;&quot;&gt;void&lt;/span&gt;&amp;nbsp;Run()&amp;nbsp;{
			&lt;span style=&quot;font-weight: bold;&quot;&gt;for&lt;/span&gt;&amp;nbsp;(&lt;span style=&quot;font-weight: bold;&quot;&gt;int&lt;/span&gt;&amp;nbsp;i&amp;nbsp;=&amp;nbsp;0;&amp;nbsp;i&amp;nbsp;&amp;lt;&amp;nbsp;count;&amp;nbsp;i++)&amp;nbsp;{
				&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;ValueEntry&lt;/span&gt;&amp;nbsp;data;
				&lt;span style=&quot;font-weight: bold;&quot;&gt;var&lt;/span&gt;&amp;nbsp;seq&amp;nbsp;=&amp;nbsp;barrier.NextEntry(&lt;span style=&quot;font-weight: bold;&quot;&gt;out&lt;/span&gt;&amp;nbsp;data);
				data.Value&amp;nbsp;=&amp;nbsp;i;
				barrier.Commit(seq);
			}
		}
	}
 
	&lt;span style=&quot;font-weight: bold;&quot;&gt;class&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;ValueConsumer&lt;/span&gt;&amp;nbsp;:&amp;nbsp;&lt;span style=&quot;color: #5c5c5c; font-weight: bold;&quot;&gt;IBatchHandler&lt;/span&gt;&amp;lt;&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;ValueEntry&lt;/span&gt;&amp;gt;
	{
		&lt;span style=&quot;font-weight: bold;&quot;&gt;int&lt;/span&gt;&amp;nbsp;iter&amp;nbsp;=&amp;nbsp;0;
		&lt;span style=&quot;font-weight: bold;&quot;&gt;int&lt;/span&gt;&amp;nbsp;count;
		&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;AutoResetEvent&lt;/span&gt;&amp;nbsp;evt;
 
		&lt;span style=&quot;font-weight: bold;&quot;&gt;public&lt;/span&gt;&amp;nbsp;ValueConsumer(&lt;span style=&quot;font-weight: bold;&quot;&gt;int&lt;/span&gt;&amp;nbsp;cnt)&amp;nbsp;{
			count&amp;nbsp;=&amp;nbsp;cnt;
			evt&amp;nbsp;=&amp;nbsp;&lt;span style=&quot;font-weight: bold;&quot;&gt;new&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;AutoResetEvent&lt;/span&gt;(&lt;span style=&quot;font-weight: bold;&quot;&gt;false&lt;/span&gt;);
		}
 
		&lt;span style=&quot;font-weight: bold;&quot;&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;font-weight: bold;&quot;&gt;void&lt;/span&gt;&amp;nbsp;OnAvailable(&lt;span style=&quot;font-weight: bold;&quot;&gt;long&lt;/span&gt;&amp;nbsp;sequence,&amp;nbsp;&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;ValueEntry&lt;/span&gt;&amp;nbsp;data)&amp;nbsp;{
			&lt;span style=&quot;font-weight: bold;&quot;&gt;if&lt;/span&gt;&amp;nbsp;(sequence&amp;nbsp;==&amp;nbsp;count&amp;nbsp;-&amp;nbsp;1)
				evt.Set();
			&lt;span style=&quot;font-weight: bold;&quot;&gt;if&lt;/span&gt;&amp;nbsp;(data.Value&amp;nbsp;!=&amp;nbsp;sequence&amp;nbsp;+&amp;nbsp;1)
				&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;Console&lt;/span&gt;.WriteLine(&lt;span style=&quot;color: #676767;&quot;&gt;&quot;Error&amp;nbsp;at&amp;nbsp;{0}&quot;&lt;/span&gt;,&amp;nbsp;iter&amp;nbsp;-&amp;nbsp;1);
		}
 
		&lt;span style=&quot;font-weight: bold;&quot;&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;font-weight: bold;&quot;&gt;void&lt;/span&gt;&amp;nbsp;OnEndOfBatch()&amp;nbsp;{
		}
 
		&lt;span style=&quot;font-weight: bold;&quot;&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;font-weight: bold;&quot;&gt;void&lt;/span&gt;&amp;nbsp;WaitForAll()&amp;nbsp;{
			evt.WaitOne();
		}
	}
 
	&lt;span style=&quot;font-weight: bold;&quot;&gt;class&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;Program&lt;/span&gt;
	{
		&lt;span style=&quot;font-weight: bold;&quot;&gt;const&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;font-weight: bold;&quot;&gt;int&lt;/span&gt;&amp;nbsp;COUNT&amp;nbsp;=&amp;nbsp;100&amp;nbsp;*&amp;nbsp;1000;
		&lt;span style=&quot;font-weight: bold;&quot;&gt;static&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;font-weight: bold;&quot;&gt;void&lt;/span&gt;&amp;nbsp;Main(&lt;span style=&quot;font-weight: bold;&quot;&gt;string&lt;/span&gt;[]&amp;nbsp;args)&amp;nbsp;{
			&lt;span style=&quot;font-weight: bold;&quot;&gt;var&lt;/span&gt;&amp;nbsp;ringBuffer&amp;nbsp;=&amp;nbsp;&lt;span style=&quot;font-weight: bold;&quot;&gt;new&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;RingBuffer&lt;/span&gt;&amp;lt;&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;ValueEntry&lt;/span&gt;&amp;gt;(
				()&amp;nbsp;=&amp;gt;&amp;nbsp;&lt;span style=&quot;font-weight: bold;&quot;&gt;new&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;ValueEntry&lt;/span&gt;(),
				100&amp;nbsp;*&amp;nbsp;1024,
				&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;ClaimStrategyFactory&lt;/span&gt;.&lt;span style=&quot;color: #555555; font-weight: bold;&quot;&gt;ClaimStrategyOption&lt;/span&gt;.SingleThreaded,
				&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;WaitStrategyFactory&lt;/span&gt;.&lt;span style=&quot;color: #555555; font-weight: bold;&quot;&gt;WaitStrategyOption&lt;/span&gt;.Yielding);
 
			&lt;span style=&quot;font-weight: bold;&quot;&gt;var&lt;/span&gt;&amp;nbsp;cons&amp;nbsp;=&amp;nbsp;&lt;span style=&quot;font-weight: bold;&quot;&gt;new&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;ValueConsumer&lt;/span&gt;(COUNT);
			ringBuffer
				.ConsumeWith(&lt;span style=&quot;font-weight: bold;&quot;&gt;new&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;Incrementer&lt;/span&gt;())
				.Then(&lt;span style=&quot;font-weight: bold;&quot;&gt;new&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;Incrementer&lt;/span&gt;(),&amp;nbsp;&lt;span style=&quot;font-weight: bold;&quot;&gt;new&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;Decrementer&lt;/span&gt;())
				.Then(cons);
			&lt;span style=&quot;font-weight: bold;&quot;&gt;var&lt;/span&gt;&amp;nbsp;pbarrier&amp;nbsp;=&amp;nbsp;ringBuffer.CreateProducerBarrier();
			&lt;span style=&quot;font-weight: bold;&quot;&gt;var&lt;/span&gt;&amp;nbsp;prod&amp;nbsp;=&amp;nbsp;&lt;span style=&quot;font-weight: bold;&quot;&gt;new&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;ValueProducer&lt;/span&gt;(COUNT,&amp;nbsp;pbarrier);
			ringBuffer.StartConsumers();
			&lt;span style=&quot;font-weight: bold;&quot;&gt;var&lt;/span&gt;&amp;nbsp;sw&amp;nbsp;=&amp;nbsp;&lt;span style=&quot;font-weight: bold;&quot;&gt;new&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;Stopwatch&lt;/span&gt;();
			sw.Start();
			prod.Run();
			cons.WaitForAll();
			sw.Stop();
			&lt;span style=&quot;color: #484848; font-weight: bold;&quot;&gt;Console&lt;/span&gt;.WriteLine(&lt;span style=&quot;color: #676767;&quot;&gt;&quot;Disruptor:&amp;nbsp;{0}ms&quot;&lt;/span&gt;,&amp;nbsp;sw.ElapsedMilliseconds);
		}
	}
}
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
</description><link>http://evgeny-lazin.blogspot.com/2011/10/disruptor.html</link><author>noreply@blogger.com (Anonymous)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhwbO4tY_yoQ9X8MIoTZBe2zmgH2Nenwoals52NOY02bJyzC9otpQOmKppip0xvr03aM87MRd6raAHJgn3yF6seN3Rg8hvS4PgxLccvCrQyc7dVHMFPTh1tstYzo7AcOZOMm2ONr_W8BsA/s72-c/paint+%25287%2529.jpg" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7754674872362895099.post-3359606576096584418</guid><pubDate>Sun, 02 Oct 2011 14:58:00 +0000</pubDate><atom:updated>2011-10-02T20:16:48.613+04:00</atom:updated><title>Очереди сообщений</title><description>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
Я хотел написать пост о библиотеке disruptor, начал писать введение и, совершенно случайно, написал целый пост :)&lt;br /&gt;
Итак, очереди бывают разными, бывают очереди, с помощью которых реализуют обход графа в ширину, а бывают такие очереди, с помощью которых можно передавать сообщения от одного потока к другому, этот пост именно о них.&lt;br /&gt;
Для начала договоримся, что очередь, это структура данных, поддерживающая две операции - enqueue и dequeue, добавление и извлечение элемента данных из очереди. Элементы извлекаются из очереди в FIFO порядке.&lt;br /&gt;
Когда речь заходит о распараллеливании каких либо вычислений, то многим на ум сразу приходит параллелизм на уровне данных, то бишь одну половину массива мы обрабатываем в одном потоке, а вторую в другом, в простейшем случае. Либо как нибудь еще разбиваем задачу на независимые части, обрабатываем их на разных процессорах и затем объединяем результаты.&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
Однако это возможно не всегда, некоторые вычисления представляют собой набор последовательных операций, в этом случае, мы не можем обрабатывать один элемент данных параллельно. Но, если нам нужно выполнять обработку множества последовательных элементов данных, то можно использовать &lt;a href=&quot;http://en.wikipedia.org/wiki/Pipeline_(software)&quot;&gt;принцип конвейера&lt;/a&gt;. Мы выполняем обработку первого элемента данных на первом процессоре, затем продолжаем обработку на втором процессоре, затем на третьем и тд. При этом, когда мы выполняем, первый этап обработки для одного сообщения, мы можем выполнять второй этап обработки предыдущего и третий этап обработки пред-предыдущего сообщений и тд.,&amp;nbsp;&lt;u&gt;параллельно&lt;/u&gt;.&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj3ZIQiqAgTuI0d2m5ntMtDaYqWo7Xw9abDKYd-icysbPW81KqsVFb3cLekd6ROqkKaUqK1m867kqqddb5_vVCVkKiyBiAdU3MoQxqPhv5vatfQHcC8O2vZDe7z8ii-ru_N-DK2aUlQx8Q/s1600/paint+%25283%2529.jpg&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;240&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj3ZIQiqAgTuI0d2m5ntMtDaYqWo7Xw9abDKYd-icysbPW81KqsVFb3cLekd6ROqkKaUqK1m867kqqddb5_vVCVkKiyBiAdU3MoQxqPhv5vatfQHcC8O2vZDe7z8ii-ru_N-DK2aUlQx8Q/s320/paint+%25283%2529.jpg&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
В итоге, мы сможем одновременно обрабатывать столько сообщений, сколько у нас есть независимых операций.&lt;br /&gt;
В жизни все несколько сложнее, операции выполняются с разной скоростью, поэтому, для объединения разных этапов обработки в цепочку используются очереди. Мы кладем сообщение в первую очередь, поток, выполняющий первый этап обработки вытаскивает его оттуда, выполняет обработку и кладет результат в свою выходную очередь сообщений, которая в то же время, является входной для второго этапа обработки. И так далее.&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhV39XZ5hyRb6nwZSjkIk5KbWmzMW8dtmccG3LO7JV4QnhqbsI3B3AGg9zOSFZczAvRuz3f3xAfACOHfX6d6oIfczBbB5y7-5BIMHZNqkmUzvImLeZHQ6-lCVzV6xiSWHZfJ11nd-zEz90/s1600/paint+%25281%2529.jpg&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;300&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhV39XZ5hyRb6nwZSjkIk5KbWmzMW8dtmccG3LO7JV4QnhqbsI3B3AGg9zOSFZczAvRuz3f3xAfACOHfX6d6oIfczBbB5y7-5BIMHZNqkmUzvImLeZHQ6-lCVzV6xiSWHZfJ11nd-zEz90/s400/paint+%25281%2529.jpg&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
Мы можем использовать на одном из этапов обработки несколько потоков, если эта операция требует больше вычислительных ресурсов. Для этого мы должны на стороне производителя в &lt;a href=&quot;http://ru.wikipedia.org/wiki/Round-robin_(%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC)&quot;&gt;round-robin&lt;/a&gt; порядке выбрать очередь, в которую будем записывать, а на стороне потребителя, в том же самом порядке выбрать очередь из которой сообщение будет извлечено и помещено в следующую очередь(это если нам нужно сохранить порядок сообщений, если не нужно, то можно сделать проще). Для этого нам нужны два счетчика, изначально их значения должны быть равны, прежде чем добавлять элемент, мы вычисляем индекс очереди как i mod N, где i - значение счетчика, N - количество очередей, добавляем элемент - queues[i mod N].enqueue(X), после этого мы увеличиваем i на единицу.&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEix7qvEE8xAgWHcIsWqzLDzuAdNJWo7tTzZGCQ2NrpsGLSBvkhnuxdq4A0-cKHSGe7LtCTR_QbHmv7s9uq8vjNE-Am6aNCJIh0NLdi_CUviIVL5oULO391sx469sxLBD7uoRmyZSOGt8WE/s1600/paint+%25286%2529.jpg&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;240&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEix7qvEE8xAgWHcIsWqzLDzuAdNJWo7tTzZGCQ2NrpsGLSBvkhnuxdq4A0-cKHSGe7LtCTR_QbHmv7s9uq8vjNE-Am6aNCJIh0NLdi_CUviIVL5oULO391sx469sxLBD7uoRmyZSOGt8WE/s320/paint+%25286%2529.jpg&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
На стороне потребителя нужно извлечь последовательность элементов из множества очередей, алгоритм будет таким же, только вместо enqueue нужно вызвать dequeue.&lt;br /&gt;
&lt;br /&gt;
&lt;div&gt;
Естественно, для решения этой задачи нельзя использовать std::queue, System.Collection.Generics.Queue, java.util.Queue или то, что есть в вашем любимом языке программирования. Эти структуры данных не потокобезопасны, доступ к ним придется синхронизировать, а это означает что два потока не смогут одновременно добавить и извлечь элемент из очереди.&lt;br /&gt;
Помимо этого, операции на стороне потребителя и производителя часто выполняются с разной скоростью, в случае, если производитель работает быстрее чем потребитель, достаточно долго, программа просто вылетит по OOM, так как в очередь будет помещено слишком много элементов.&lt;br /&gt;
Для решения всех этих проблем были созданы специальные очереди. Их можно классифицировать следующим образом: блокирующие - неблокирующие; ограниченные - неограниченные. Так же очереди классифицируют по уровню concurrency - single producer/single consumer(SPSC), single producer/multiple consumers(SPMC), multiple producers/single consumer(MPSC), multiple producers/multiple consumers(MPMC). При этом данные характеристики могут сочетаться произвольным образом, например single producer/multiple consumers очередь может быть ограниченной и неблокирующей одновременно.&lt;br /&gt;
&lt;br /&gt;
Блокирующая очередь, это очередь, операции над которой могут заблокировать вызывающий поток до тех пор, пока состояние очереди не изменится. Например, в случае ограниченной очереди, попытка добавления элемента(enqueue) в переполненную очередь может быть заблокирована до тех пор, пока какой либо другой поток не извлечет элемент и не освободит место для нового элемента. Попытка извлечения элемента из пустой блокирующей очереди так же может заблокировать вызывающий поток до тех пор, пока в очередь не будет добавлен элемент. Обычно, для блокирующей очереди реализуют способ сообщить потоку потребителю, извлекающему сообщения из очереди, что производитель уже завершил работу.&amp;nbsp;Данные свойства делают блокирующие очереди очень простым в использовании инструментом для организации совместной работы множества потоков.&lt;br /&gt;
Неблокирующие очереди, как правило просто возвращают признак того, была операция выполнена, или нет. Их преимущество, перед блокирующими очередями, состоит в том, что они могут быть реализованы без использования мьютексов, семафоров и прочих condintion variables, которые&amp;nbsp;обычно&amp;nbsp;применяются в блокирующих очередях для реализации ожидания.&lt;br /&gt;
&lt;br /&gt;
Очереди также отличаются по внутренней реализации, существуют очереди на основе массивов и на основе списков. Общее у этих реализаций то, что в каждом случае присутствуют два указателя, Tail и Head. Tail - указатель конца списка, к которому добавляются элементы, Head - указатель на начало списка, из которого извлекаются элементы.&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiB9G6nKxB2GAHYxujc1Xi8KQ9NmKS9NaSmlzDMoj5KlJQ8jXoVZ7gvzn2lm6yFcNtrG4lj5C4VFBhOVSYOXHHidyn0k-X8Ayd_W0gpRzBjxtNBC6mnjEtGsqof_IU92sjQEc-Jq7GWFl0/s1600/paint+%25285%2529.jpg&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;300&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiB9G6nKxB2GAHYxujc1Xi8KQ9NmKS9NaSmlzDMoj5KlJQ8jXoVZ7gvzn2lm6yFcNtrG4lj5C4VFBhOVSYOXHHidyn0k-X8Ayd_W0gpRzBjxtNBC6mnjEtGsqof_IU92sjQEc-Jq7GWFl0/s400/paint+%25285%2529.jpg&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;
Таким образом, для добавления и для извлечения элементов из очереди нужно изменить одну из этих переменных и прочитать обе. Так же, очевидно, что на основе массива нельзя построить неограниченную очередь(очередь, в которую можно добавить произвольное количество элементов). Однако, очередь, реализованная на основе списка может быть ограниченной, достаточно поддерживать счетчик элементов очереди. &lt;i&gt;Кстати, автор этого поста видел и гибридный вариант, очередь на основе списка, элементами которой являлись массивы фиксированного размера, это оптимизация, упрощающая жизнь сборщику мусора.&lt;/i&gt;&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;
Теперь о типах очередей с точки зрения concurrency.&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;
SPSC - самая простая и быстрая очередь, она может быть реализована вообще без использования атомарных&amp;nbsp;&lt;i&gt;(команды с префиксом lock, блокирующие шину памяти на время выполнения)&lt;/i&gt; операций, достаточно двух барьеров памяти. Мало того, операции enqueue и dequeue могут быть реализованы как wait free операции &lt;i&gt;(естественно, wait free гарантия здесь условна, так как в случае списка, нужно выделить память под новый элемент, в случае массива wait free&amp;nbsp;гарантия&amp;nbsp;может&amp;nbsp;выполняться&amp;nbsp;только тогда, когда есть место в массиве)&lt;/i&gt;.&lt;/div&gt;
SPMC &amp;nbsp;и MPSC очереди немного сложнее и медленнее, так как в данном случае несколько потоков могут бороться за head либо за tail указатели. По этой причине, данные очереди могут быть реализованы только с применением атомарных операций. Для такой&amp;nbsp;очереди&amp;nbsp;нужна как минимум одна &lt;a href=&quot;http://ru.wikipedia.org/wiki/%D0%A1%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5_%D1%81_%D0%BE%D0%B1%D0%BC%D0%B5%D0%BD%D0%BE%D0%BC&quot;&gt;CAS операция&lt;/a&gt;, с одной из сторон, для MPSC - со стороны производителя, для SPMC - со стороны потребителя.&lt;br /&gt;
MPMC - наиболее универсальная и наименее эффективная очередь. Она требует использования двух атомарных CAS операций, одной при добавлении элементов и одной при извлечении.&lt;br /&gt;
&lt;br /&gt;
Все эти качества нужно учитывать при проектировании системы, выполняющей конвейерную обработку данных. Конвейерная обработка, это своего рода размен, мы увеличиваем общую пропускную способность(количество сообщений в сек.), но уменьшаем латентность(время обработки отдельного сообщения), ведь теперь помимо обработки сообщения, к латентности добавится время выполнения операций добавления и извлечения из всех очередей на пути сообщения. Также, не стоит забывать о балансировке нагрузки, если один из этапов конвейера работает на порядок быстрее чем все остальные, то нет смысла выделять его в отдельный этап и наоборот, если один из этапов выполняется слишком медленно, возможно стоит его разделить на части, либо выполнять его параллельно.&lt;/div&gt;
&lt;/div&gt;
</description><link>http://evgeny-lazin.blogspot.com/2011/10/blog-post.html</link><author>noreply@blogger.com (Anonymous)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj3ZIQiqAgTuI0d2m5ntMtDaYqWo7Xw9abDKYd-icysbPW81KqsVFb3cLekd6ROqkKaUqK1m867kqqddb5_vVCVkKiyBiAdU3MoQxqPhv5vatfQHcC8O2vZDe7z8ii-ru_N-DK2aUlQx8Q/s72-c/paint+%25283%2529.jpg" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7754674872362895099.post-2386889255157652159</guid><pubDate>Wed, 03 Aug 2011 09:16:00 +0000</pubDate><atom:updated>2011-10-02T20:17:33.663+04:00</atom:updated><title></title><description>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
Мне часто доводилось встречать на разных форумах и блогах такое мнение - если вы пишете приложение, критичное к скорости выполнения, то виртуальные функции не для вас. Обычно, в таких случаях советуют применять статический полиморфизм или еще что-то подобное.&lt;br /&gt;
На самом деле, с косвенными вызовами в общем и с виртуальными функциями/методами в частности - все хорошо. Современные процессоры умеют предсказывать ветвления даже в случае непрямого вызова, это может свести на нет все накладные расходы, связанные с вызовом виртуальной функции.&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;span class=&quot;fullpost&quot;&gt;Проблема в другом. Часто, виртуальные функции используют следующим образом: создают массив/список указателей на базовый класс, обходят все элементы массива и вызывают виртуальный метод. Это приводит к тому, что количество L1 кэш промахов заметно увеличивается(виртуальный метод может быть переопределен и при каждом новом вызове процессор, в худшем случае, будет выполнять новый код). Помимо этого, такой подход уменьшает вероятность успешного предсказания ветвления.&lt;br /&gt;
Я написал небольшой тест, демонстрирующий эти эффекты. Сначала, в список добавляются все элементы одного класса, затем другого и тд. После этого, для каждого элемента списка вызывается виртуальный метод. На моей машине это происходит примерно за 300 миллисекунд. Далее, элементы списка перемешиваются случайным образом, что увеличивает время обхода списка с вызовом метода втрое! И в конце я вызываю, в точно таком же цикле, обычный, не виртуальный метод, который делает ровно тоже самое что и виртуальные методы. Это занимает все те же 300 миллисекунд.&amp;nbsp;Делайте выводы :)&lt;br /&gt;
&lt;/span&gt;&lt;br /&gt;
&lt;pre style=&quot;background: white; color: black; font-family: Droid Sans Mono; font-size: 13;&quot;&gt;&lt;span class=&quot;fullpost&quot;&gt;&lt;span class=&quot;fullpost&quot;&gt;&lt;span style=&quot;color: blue;&quot;&gt;using&lt;/span&gt;&amp;nbsp;System;
&lt;span style=&quot;color: blue;&quot;&gt;using&lt;/span&gt;&amp;nbsp;System.Collections.Generic;
&lt;span style=&quot;color: blue;&quot;&gt;using&lt;/span&gt;&amp;nbsp;System.Linq;
&lt;span style=&quot;color: blue;&quot;&gt;using&lt;/span&gt;&amp;nbsp;System.Text;
&lt;span style=&quot;color: blue;&quot;&gt;using&lt;/span&gt;&amp;nbsp;System.Diagnostics;
 
&lt;span style=&quot;color: blue;&quot;&gt;namespace&lt;/span&gt;&amp;nbsp;L1CacheMissDemo
{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;class&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #2b91af;&quot;&gt;Program&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;static&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;void&lt;/span&gt;&amp;nbsp;Main(&lt;span style=&quot;color: blue;&quot;&gt;string&lt;/span&gt;[]&amp;nbsp;args)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;var&lt;/span&gt;&amp;nbsp;lst&amp;nbsp;=&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;new&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #2b91af;&quot;&gt;List&lt;/span&gt;&amp;lt;&lt;span style=&quot;color: #2b91af;&quot;&gt;Base&lt;/span&gt;&amp;gt;();
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;for&lt;/span&gt;&amp;nbsp;(&lt;span style=&quot;color: blue;&quot;&gt;int&lt;/span&gt;&amp;nbsp;i&amp;nbsp;=&amp;nbsp;0;&amp;nbsp;i&amp;nbsp;&amp;lt;&amp;nbsp;1000000;&amp;nbsp;i++)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lst.Add(&lt;span style=&quot;color: blue;&quot;&gt;new&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #2b91af;&quot;&gt;Derived1&lt;/span&gt;());
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;for&lt;/span&gt;&amp;nbsp;(&lt;span style=&quot;color: blue;&quot;&gt;int&lt;/span&gt;&amp;nbsp;i&amp;nbsp;=&amp;nbsp;0;&amp;nbsp;i&amp;nbsp;&amp;lt;&amp;nbsp;1000000;&amp;nbsp;i++)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lst.Add(&lt;span style=&quot;color: blue;&quot;&gt;new&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #2b91af;&quot;&gt;Derived2&lt;/span&gt;());
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;for&lt;/span&gt;&amp;nbsp;(&lt;span style=&quot;color: blue;&quot;&gt;int&lt;/span&gt;&amp;nbsp;i&amp;nbsp;=&amp;nbsp;0;&amp;nbsp;i&amp;nbsp;&amp;lt;&amp;nbsp;1000000;&amp;nbsp;i++)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lst.Add(&lt;span style=&quot;color: blue;&quot;&gt;new&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #2b91af;&quot;&gt;Derived3&lt;/span&gt;());
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;for&lt;/span&gt;&amp;nbsp;(&lt;span style=&quot;color: blue;&quot;&gt;int&lt;/span&gt;&amp;nbsp;i&amp;nbsp;=&amp;nbsp;0;&amp;nbsp;i&amp;nbsp;&amp;lt;&amp;nbsp;1000000;&amp;nbsp;i++)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lst.Add(&lt;span style=&quot;color: blue;&quot;&gt;new&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #2b91af;&quot;&gt;Derived4&lt;/span&gt;());
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;var&lt;/span&gt;&amp;nbsp;s&amp;nbsp;=&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;new&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #2b91af;&quot;&gt;Stopwatch&lt;/span&gt;();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;s.Start();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;foreach&lt;/span&gt;&amp;nbsp;(&lt;span style=&quot;color: blue;&quot;&gt;var&lt;/span&gt;&amp;nbsp;x&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;in&lt;/span&gt;&amp;nbsp;lst)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;x.Process();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;s.Stop();
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #2b91af;&quot;&gt;Console&lt;/span&gt;.WriteLine(&lt;span style=&quot;color: #a31515;&quot;&gt;&quot;Case&amp;nbsp;1:&amp;nbsp;{0}&quot;&lt;/span&gt;,&amp;nbsp;s.ElapsedMilliseconds);
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: grey;&quot;&gt;//&amp;nbsp;shuffle&amp;nbsp;elements&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;var&lt;/span&gt;&amp;nbsp;r&amp;nbsp;=&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;new&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #2b91af;&quot;&gt;Random&lt;/span&gt;(&lt;span style=&quot;color: #2b91af;&quot;&gt;Guid&lt;/span&gt;.NewGuid().GetHashCode());
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;for&lt;/span&gt;&amp;nbsp;(&lt;span style=&quot;color: blue;&quot;&gt;int&lt;/span&gt;&amp;nbsp;i&amp;nbsp;=&amp;nbsp;0;&amp;nbsp;i&amp;nbsp;&amp;lt;&amp;nbsp;lst.Count;&amp;nbsp;i++)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;var&lt;/span&gt;&amp;nbsp;j&amp;nbsp;=&amp;nbsp;r.Next(i,&amp;nbsp;lst.Count);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;var&lt;/span&gt;&amp;nbsp;t&amp;nbsp;=&amp;nbsp;lst[i];
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lst[i]&amp;nbsp;=&amp;nbsp;lst[j];
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lst[j]&amp;nbsp;=&amp;nbsp;t;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;s.Restart();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;foreach&lt;/span&gt;&amp;nbsp;(&lt;span style=&quot;color: blue;&quot;&gt;var&lt;/span&gt;&amp;nbsp;x&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;in&lt;/span&gt;&amp;nbsp;lst)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;x.Process();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;s.Stop();
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #2b91af;&quot;&gt;Console&lt;/span&gt;.WriteLine(&lt;span style=&quot;color: #a31515;&quot;&gt;&quot;Case&amp;nbsp;2:&amp;nbsp;{0}&quot;&lt;/span&gt;,&amp;nbsp;s.ElapsedMilliseconds);
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;var&lt;/span&gt;&amp;nbsp;p&amp;nbsp;=&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;new&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #2b91af;&quot;&gt;Program&lt;/span&gt;();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;s.Restart();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;foreach&lt;/span&gt;(&lt;span style=&quot;color: blue;&quot;&gt;var&lt;/span&gt;&amp;nbsp;_&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;in&lt;/span&gt;&amp;nbsp;lst)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;p.Process();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;s.Stop();
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #2b91af;&quot;&gt;Console&lt;/span&gt;.WriteLine(&lt;span style=&quot;color: #a31515;&quot;&gt;&quot;Case&amp;nbsp;3:&amp;nbsp;{0}&quot;&lt;/span&gt;,&amp;nbsp;s.ElapsedMilliseconds);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;void&lt;/span&gt;&amp;nbsp;Process()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;int&lt;/span&gt;&amp;nbsp;k&amp;nbsp;=&amp;nbsp;0;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;for&lt;/span&gt;&amp;nbsp;(&lt;span style=&quot;color: blue;&quot;&gt;int&lt;/span&gt;&amp;nbsp;i&amp;nbsp;=&amp;nbsp;0;&amp;nbsp;i&amp;nbsp;&amp;lt;&amp;nbsp;100;&amp;nbsp;i++)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;k&amp;nbsp;+=&amp;nbsp;i&amp;nbsp;*&amp;nbsp;i;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;abstract&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;class&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #2b91af;&quot;&gt;Base&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;abstract&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;void&lt;/span&gt;&amp;nbsp;Process();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;class&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #2b91af;&quot;&gt;Derived1&lt;/span&gt;&amp;nbsp;:&amp;nbsp;&lt;span style=&quot;color: #2b91af;&quot;&gt;Base&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;override&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;void&lt;/span&gt;&amp;nbsp;Process()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;int&lt;/span&gt;&amp;nbsp;k&amp;nbsp;=&amp;nbsp;0;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;for&lt;/span&gt;&amp;nbsp;(&lt;span style=&quot;color: blue;&quot;&gt;int&lt;/span&gt;&amp;nbsp;i&amp;nbsp;=&amp;nbsp;0;&amp;nbsp;i&amp;nbsp;&amp;lt;&amp;nbsp;100;&amp;nbsp;i++)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;k&amp;nbsp;+=&amp;nbsp;i&amp;nbsp;*&amp;nbsp;i;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;class&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #2b91af;&quot;&gt;Derived2&lt;/span&gt;&amp;nbsp;:&amp;nbsp;&lt;span style=&quot;color: #2b91af;&quot;&gt;Base&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;override&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;void&lt;/span&gt;&amp;nbsp;Process()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;int&lt;/span&gt;&amp;nbsp;k&amp;nbsp;=&amp;nbsp;0;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;for&lt;/span&gt;&amp;nbsp;(&lt;span style=&quot;color: blue;&quot;&gt;int&lt;/span&gt;&amp;nbsp;i&amp;nbsp;=&amp;nbsp;0;&amp;nbsp;i&amp;nbsp;&amp;lt;&amp;nbsp;100;&amp;nbsp;i++)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;k&amp;nbsp;+=&amp;nbsp;i&amp;nbsp;*&amp;nbsp;i;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;class&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #2b91af;&quot;&gt;Derived3&lt;/span&gt;&amp;nbsp;:&amp;nbsp;&lt;span style=&quot;color: #2b91af;&quot;&gt;Base&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;override&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;void&lt;/span&gt;&amp;nbsp;Process()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;int&lt;/span&gt;&amp;nbsp;k&amp;nbsp;=&amp;nbsp;0;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;for&lt;/span&gt;&amp;nbsp;(&lt;span style=&quot;color: blue;&quot;&gt;int&lt;/span&gt;&amp;nbsp;i&amp;nbsp;=&amp;nbsp;0;&amp;nbsp;i&amp;nbsp;&amp;lt;&amp;nbsp;100;&amp;nbsp;i++)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;k&amp;nbsp;+=&amp;nbsp;i&amp;nbsp;*&amp;nbsp;i;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;class&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #2b91af;&quot;&gt;Derived4&lt;/span&gt;&amp;nbsp;:&amp;nbsp;&lt;span style=&quot;color: #2b91af;&quot;&gt;Base&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;override&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;void&lt;/span&gt;&amp;nbsp;Process()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;int&lt;/span&gt;&amp;nbsp;k&amp;nbsp;=&amp;nbsp;0;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: blue;&quot;&gt;for&lt;/span&gt;&amp;nbsp;(&lt;span style=&quot;color: blue;&quot;&gt;int&lt;/span&gt;&amp;nbsp;i&amp;nbsp;=&amp;nbsp;0;&amp;nbsp;i&amp;nbsp;&amp;lt;&amp;nbsp;100;&amp;nbsp;i++)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;k&amp;nbsp;+=&amp;nbsp;i&amp;nbsp;*&amp;nbsp;i;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}
&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;
&lt;/div&gt;
</description><link>http://evgeny-lazin.blogspot.com/2011/08/blog-post.html</link><author>noreply@blogger.com (Anonymous)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7754674872362895099.post-2066627389639176489</guid><pubDate>Sun, 19 Jun 2011 10:18:00 +0000</pubDate><atom:updated>2011-06-19T14:18:42.914+04:00</atom:updated><title></title><description>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;Чтение чужого кода - весьма полезное занятие, можно узнать много интересного, хотя все конечно зависит от того, кем этот код написан. В этом плане, код создателя Clojure -&amp;nbsp;&lt;a href=&quot;http://ru.wikipedia.org/wiki/%D0%A5%D0%B8%D0%BA%D0%BA%D0%B8,_%D0%A0%D0%B8%D1%87%D0%B0%D1%80%D0%B4&quot;&gt;Ричарда Хикки&lt;/a&gt;,&amp;nbsp;просто эльдорадо какое-то :)&lt;span class=&quot;fullpost&quot;&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div&gt;Вот один интересный прием использованный в Clojure - допустим у нас есть объект - массив фиксированной длины содержащий до 32-х элементов. Массив заполнен не полностью, некоторые элементы равны null, некоторые содержат полезные данные. Доступ к элементам осуществляется обычным образом, по индексу.&lt;/div&gt;&lt;div&gt;Ход мыслей обычного программиста - массив небольшой, поэтому можно просто создавать его полностью, часть элементов массива будут раны null, часть заняты полезными данными, код будет проще и будет быстрее работать, так как не нужно вычислять индекс элемента в массиве. Однако Ричард Хикки решил иначе, дело в том, что этот массив - часть &lt;a href=&quot;http://en.wikipedia.org/wiki/Persistent_data_structure&quot;&gt;персистентной структуры данных&lt;/a&gt;, поэтому создавать массив полностью накладно, так как такие массивы будут создаваться очень часто в процессе, называемом path copying.&amp;nbsp;&lt;/div&gt;&lt;div&gt;Вот как это сделано в clojure - объект содержит массив и маску (32 бита). Массив содержит только полезные данные, то есть, если в массиве 4 элемента, и 28 null-ов, то будет создан массив из 4-х элементов. Маска служит индексом, если i-й элемент массива равен null, то i-й бит маски будет сброшен, а в противном случае - установлен.&lt;/div&gt;&lt;div&gt;Самое интересное в этом - поиск элемента в массиве по индексу. Допусти мы хотим получить значение i-го элемента массива. Для этого, нам нужно выполнить сравнение:&lt;/div&gt;&lt;blockquote&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace;&quot;&gt;int bit = 1 &amp;lt;&amp;lt; i;&amp;nbsp;&lt;/span&gt;&lt;/blockquote&gt;&lt;blockquote&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace;&quot;&gt;if (mask &amp;amp; bit == 0) return null;&lt;/span&gt;&lt;/blockquote&gt;&lt;div&gt;в противном случае, элемент не равен null и нам нужно найти его смещение в массиве. Допустим, array - наш массив, он имеет размер - от 0 до &amp;nbsp;32х. Для получения индекса i-го элемента в массиве, мы должны выполнить следующие операции:&lt;/div&gt;&lt;blockquote&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace;&quot;&gt;int index = numberOfSetBits(mask &amp;amp; (bit - 1));&lt;/span&gt;&lt;/blockquote&gt;&lt;blockquote&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace;&quot;&gt;return array[index];&amp;nbsp;&lt;/span&gt;&lt;/blockquote&gt;где numberOfSetBits - функция, возвращающая количество не нулевых битов числа. Это вычисляется за несколько тактов процессора :)&lt;br /&gt;
Представим, что у нас есть массив из 4-х элементов. Допустим, 0-й, 3-й, 6-й и 7-й элементы не равны null. В этом случае, маска будет выглядеть так - 11001001. Младший бит&amp;nbsp;соответствует&amp;nbsp;первому элементу массива, а старший - последнему. Теперь, вычислим индекс последнего элемента массива - i = 7, bit = 1 &amp;lt;&amp;lt; 7 = 10000000. Поскольку bit - степень двойки, то bit - 1 будет равно 1111111. Если мы побитово сравним это значение с маской, то мы получим все те же биты, что и в маске, кроме старшего - 11001001 &amp;amp; 1111111 = 1001001. Теперь, если мы посчитаем биты в этом числе, то получим число не null элементов индекс которых меньше нашего i, а именно 3. Именно это число и будет индексом в нашем массиве, не включающем null значения.&lt;br /&gt;
Все эти битовые операции выполняются за считанные такты процессора и код в целом работает быстрее. Вообще, мне очень нравятся такие&amp;nbsp;не очевидные&amp;nbsp;решения :)&lt;/div&gt;</description><link>http://evgeny-lazin.blogspot.com/2011/06/blog-post.html</link><author>noreply@blogger.com (Anonymous)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7754674872362895099.post-4017756322795044557</guid><pubDate>Sun, 29 May 2011 09:14:00 +0000</pubDate><atom:updated>2011-05-29T13:14:48.957+04:00</atom:updated><title>Рукибыпоотрывал пост</title><description>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;Решил я сегодня попробовать Apache Etch. Это такая штука, которая позволяет описать набор сервисов и сообщений, на специальном IDL, скомпилировать это все специальным компилятором, получив на выходе набор исходников, добавить их в свой проект и наслаждаться жизнью. Этот проект привлек меня тем, что он более функциональный, нежели Thrift, позволяет делать больше. В частности, наследовать сообщения. Ну и Cisco тоже внушает некоторое доверие.&lt;div&gt;Так вот, у меня не заработал простой hello world, который я взял с официального сайта. Не заработал он вот из-за чего:&lt;span class=&quot;fullpost&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj4T6YsH6B3A-SKbBhufreZ2WLnro8u5YdYhW5onajNYl47OfpgwglIUxoZ6aHfLhxALXwVaGad4TAhzMkmC4PxlMcBhZTMXeys1RK4GruKbfAKEfC7Zgr4fJNvmOuj0lyKIywtzeRNhOc/s1600/etch_fail.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj4T6YsH6B3A-SKbBhufreZ2WLnro8u5YdYhW5onajNYl47OfpgwglIUxoZ6aHfLhxALXwVaGad4TAhzMkmC4PxlMcBhZTMXeys1RK4GruKbfAKEfC7Zgr4fJNvmOuj0lyKIywtzeRNhOc/s1600/etch_fail.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;Дело в том, что сообщения об ошибках зависят от локализации, а разработчик Etch использует эти сообщения не для отображения, он строит на них логику! За это нужно отрывать руки по самый локоть, ибо даже в MSDN сказано что так делать нельзя.&amp;nbsp;&lt;/div&gt;&lt;div&gt;В общем, до свиданья apache etch :D&lt;/div&gt;&lt;/div&gt;</description><link>http://evgeny-lazin.blogspot.com/2011/05/blog-post_29.html</link><author>noreply@blogger.com (Anonymous)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj4T6YsH6B3A-SKbBhufreZ2WLnro8u5YdYhW5onajNYl47OfpgwglIUxoZ6aHfLhxALXwVaGad4TAhzMkmC4PxlMcBhZTMXeys1RK4GruKbfAKEfC7Zgr4fJNvmOuj0lyKIywtzeRNhOc/s72-c/etch_fail.png" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7754674872362895099.post-4215435477143102235</guid><pubDate>Mon, 09 May 2011 10:14:00 +0000</pubDate><atom:updated>2011-05-09T14:19:52.035+04:00</atom:updated><title>В интернете кто-то снова не прав!</title><description>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif;&quot;&gt;Точнее, в оплоте создателей стартапов и специалистов по натягиванию шаблонов на wordpress - хабрахабре :)&lt;/span&gt;&lt;span class=&quot;fullpost&quot;&gt;&lt;/span&gt;

&lt;div&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif;&quot;&gt;Некий юзер, взялся переводить статьи о lock-free алгоритмах на великий и могучий, за что ему спасибо конечно. Но к несчастью для себя, он покусился на святое! По своему перевел термин lock-free на русский язык! Идея, на мой взгляд, крайне неудачная, особенно в свете того, что уже давно прижился перевод - &quot;без блокировок&quot;.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif;&quot;&gt;Почему lock-free неправильно переводить как &quot;беззамочный&quot;? Все очень просто, в информатике нет &quot;замков&quot;, зато есть блокировки. Здесь даже говорить не о чем.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif;&quot;&gt;Немного сложнее с &quot;беззахватными&quot; алгоритмами. Под захватом подразумевается захват некоего ресурса, например мьютекса, то есть определенную семантику. Lock-free алгоритм, конечно же не может ничего захватывать.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif;&quot;&gt;Гарантия lock-freredom означает, что в каждый момент времени, один из потоков совершает прогресс. Полезным на практике следствием соблюдения lock-freredom гарантии является то, что мы можем прибить любой из потоков, выполняющих наш lock-free алгоритм и у нас гарантированно не возникнет dead-lock. Если гарантия lock-freedom не соблюдается, то один из потоков может сделать нечто такое, что заставит другие потоки его ждать. Например, захватить мьютекс. Если мы этот поток прибьем, то возникнет dead-lock.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif;&quot;&gt;Так вот, гарантия lock-freedom может не соблюдаться даже при полном отсутствии мьютексов, критических секций и любых других объектов, имеющих такую семантику. Например, приложение может не использовать общие для нескольких потоков данные вообще, посылая, вместо этого сообщения. При этом, взаимная блокировка возникнуть очень даже может. Поток, по каким-то причинам(например, из-за того, что его убили), может не посылать сообщение другому потоку, который это сообщение ждет.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif;&quot;&gt;Поэтому, я считаю термин &quot;беззахватный&quot; крайне неудачным, не отражающим сути.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif;&quot;&gt;

&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif;&quot;&gt;P.S.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif;&quot;&gt;Я несколько раз написал о том, что поток может быть &quot;прибит&quot;. В Windows это может быть сделано функцией TerminateThread, например. Так вот, прибивать потоки, не в коем случае не нужно. Это может привести к непредсказуемым последствиям. Если вы используете эту функцию, то у меня для вас плохие новости!&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Arial, Helvetica, sans-serif;&quot;&gt;За все время работы, мне лишь однажды пришлось столкнуться с этой проблемой. Я написал библиотеку, клиент которой часто вызывал TerminateThread. Это иногда приводило к неприятным последствиям. Поэтому, мне пришлось предоставить этому клиенту lock-free интерфейс.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;</description><link>http://evgeny-lazin.blogspot.com/2011/05/blog-post.html</link><author>noreply@blogger.com (Anonymous)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7754674872362895099.post-2966872092019547685</guid><pubDate>Tue, 26 Apr 2011 18:57:00 +0000</pubDate><atom:updated>2011-04-26T22:57:33.627+04:00</atom:updated><title>GC и латентность</title><description>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgawkoEXIPtYx-ogn39eMvhO5mZQVf1KDubF7ZMsaGG8ucKF_D8yRlB8QcSFo32Zhx3m1DRAlNPafMt1RQMThDeJ8wAoXlrg5B1Jq5QPw7YiGuQ-X8-6MpHbyZOn_2lqQDJHt-GnuRgZLA/s1600/latency.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgawkoEXIPtYx-ogn39eMvhO5mZQVf1KDubF7ZMsaGG8ucKF_D8yRlB8QcSFo32Zhx3m1DRAlNPafMt1RQMThDeJ8wAoXlrg5B1Jq5QPw7YiGuQ-X8-6MpHbyZOn_2lqQDJHt-GnuRgZLA/s1600/latency.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;Пояснение. Это скриншот тестовой утилиты, которая измеряет производительность одного нашего продукта (работающего под .NET Framework 4). По оси абсцисс - номер запроса, по оси ординат - время получения ответа на запрос. Запросы не отличаются друг от друга. В среднем, один запрос выполняется меньше чем за 5&amp;nbsp;миллисекунд. Но один, выполняется в 50 раз медленнее. Это происходит из-за того, что &amp;nbsp;выполнение запроса приостанавливается для того, что-бы сборщик мусора смог сделать свою работу.&lt;/div&gt;&lt;span class=&quot;fullpost&quot;&gt;&lt;/span&gt;&lt;/div&gt;</description><link>http://evgeny-lazin.blogspot.com/2011/04/gc.html</link><author>noreply@blogger.com (Anonymous)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgawkoEXIPtYx-ogn39eMvhO5mZQVf1KDubF7ZMsaGG8ucKF_D8yRlB8QcSFo32Zhx3m1DRAlNPafMt1RQMThDeJ8wAoXlrg5B1Jq5QPw7YiGuQ-X8-6MpHbyZOn_2lqQDJHt-GnuRgZLA/s72-c/latency.png" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7754674872362895099.post-8689641518965929317</guid><pubDate>Sat, 16 Apr 2011 13:28:00 +0000</pubDate><atom:updated>2011-04-16T17:40:44.531+04:00</atom:updated><title></title><description>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;Прочитал вчера вот эту статью: &lt;a href=&quot;http://easy-coding.blogspot.com/2011/04/go.html&quot;&gt;http://easy-coding.blogspot.com/2011/04/go.html&lt;/a&gt;  &lt;br /&gt;
&lt;blockquote&gt;Итак: данная программа берет TAR с исходниками, распаковывает его, и каждый файл прогоняет через компилятор.  Сразу скажу, цель того, что я все это пишу тут, это продемонстрировать (и не более того), как просто и удобно на Go можно писать многопоточные императивные программы.&lt;/blockquote&gt;и не впечатлился. Эта программа реализует простую вещь - Master/Worker pattern. Каких-либо особых преимуществ языка Go в этом нелегком деле я не заметил. Для написания этой программы, язык программирования вообще не важен, он должен позволять запускать потоки и использовать блокирующие очереди, вот и все. Все тоже самое может быть написано на C++, с использованием класса tbb::concurrent_queue, или на C#, с использованием класса BlockingCollection. Код будет выглядеть так же просто.&lt;br /&gt;
Мало того, на шарпе, можно написать как-то так:&lt;br /&gt;
&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace;&quot;&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;color: blue;&quot;&gt;using&lt;/span&gt;(&lt;span class=&quot;Apple-style-span&quot; style=&quot;color: blue;&quot;&gt;var &lt;/span&gt;tar = &lt;span class=&quot;Apple-style-span&quot; style=&quot;color: blue;&quot;&gt;new &lt;/span&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;color: #990000;&quot;&gt;TarReader&lt;/span&gt;(tarname))&amp;nbsp;&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace;&quot;&gt;{&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace;&quot;&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;span class=&quot;Apple-style-span&quot; style=&quot;color: blue;&quot;&gt;var &lt;/span&gt;files = tar.NewReader(tmpdir); &lt;/span&gt;&lt;br /&gt;
&lt;div&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace;&quot;&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;color: #990000;&quot;&gt;&amp;nbsp; &amp;nbsp; Parallel&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace;&quot;&gt;.ForEach(files, file =&amp;gt;&amp;nbsp;&lt;span class=&quot;Apple-style-span&quot; style=&quot;color: #990000;&quot;&gt;Compiler&lt;/span&gt;.Run(file, params));&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: &#39;Courier New&#39;, Courier, monospace;&quot;&gt;}&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
(далее, имена всех классов и методов вымышлены, все совпадения - случайны)&lt;br /&gt;
&amp;nbsp;Допустим, tar - объект, читающий tar архив, метод NewReader(tmpdir) - возвращает IEnumerable&lt;string&gt;, при обходе которого на диске, в каталоге tmpdir, будут создаваться новые файлы, а итератор будет возвращать их имена. При вызове tar.Dispose() - все временные файлы должны быть удалены.&lt;/string&gt;&lt;/div&gt;&lt;div&gt;Допустим, у нас есть класс Compiler, со статическим методом Run, который получает имя файла и набор флагов, в виде строк, запускает компилятор, дожидается когда он отработает и завершает работу. &lt;/div&gt;&lt;div&gt;Вот собственно и все. TPL не будет создавать 100500 потоков, количество выполняемых параллельно задач будет зависеть от количества процессоров. Возиться с очередями и балансировкой нагрузки - не нужно. Обработка ошибок - проще некуда.&lt;/div&gt;&lt;div&gt;Возникает логичный вопрос - зачем этот Go вообще нужен? :)&lt;/div&gt;&lt;/div&gt;</description><link>http://evgeny-lazin.blogspot.com/2011/04/httpeasy-coding.html</link><author>noreply@blogger.com (Anonymous)</author><thr:total>7</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7754674872362895099.post-652574321409311390</guid><pubDate>Tue, 04 Jan 2011 15:42:00 +0000</pubDate><atom:updated>2011-10-01T10:41:17.164+04:00</atom:updated><title></title><description>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
&lt;div style=&quot;background-color: transparent; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;&quot;&gt;
&lt;span style=&quot;white-space: pre-wrap;&quot;&gt;Вдогонку к предыдущему посту. Мы всегда, неосознанно, ищем во всем связи, даже если никакой связи на самом деле нет. Проявляется это по разному, например, многие хоккеисты во время плей-офф не бреются, а тренеры не меняют рубашки :)
Программисты в этом деле - впереди планеты всей, ведь у нас есть специальный инструмент для внесения в код подобных заблуждений - наследование! Если между сущностями есть связь - то их можно объединить в иерархию наследования.
Пожалуй, самый яркий пример подобного заблуждения встречается в литературе, в качестве примера объектно ориентированного программирования - иерархия фигур в векторном графическом редакторе, знаменитый Shape и его наследники - Line, Rectangle и прочие. (Справедливости ради стоит отметить, что автор этих строк, когда-то давно, написал простой векторный граф. редактор используя именно такой подход :D)
Итак, у нас есть базовый абстрактный класс - Shape, у которого есть набор виртуальных методов для: вывода себя на экран/графический контекст; получения bounding box-а объекта; трансформации объекта... Далее, программист должен реализовать классы - наследники Shape, которые специализируют все операции для определенной фигуры.
Плюсы этого подхода - кажущаяся простота кода, вроде все в одном месте, а также повторное использование кода, к примеру, класс Circle может быть наследником класса Ellipse. Минусы несколько менее очевидны: сложность внесения изменений - на каждую новую фигуру нам потребуется реализовать новый класс - наследник Shape и реализовать соответствующие вирт. методы, а если мы хотим добавить новую операцию, для которой нужен еще один вирт. метод, то нам придется реализовать его для каждого существующего класса - наследника Shape; не очень хорошая производительность - у нас целых два уровня косвенности - указатель на объект и виртуальный метод этого объекта. И самый главный недостаток этого подхода - нам не нужно знать о том, что та или иная фигура является окружностью или линией, это не имеет значения, так зачем же нам поддерживать иерархию наследования?
Иногда самый простой и очевидный способ решения проблемы - самый правильный. Однако, в данном случае, наиболее простой и эффективный метод решения достаточно не очевиден и даже контр-интуитивен. Представим, что у нас есть два массива - массив точек и массив метаданных. Массив точек содержит координаты всех точек чертежа: начало и конец каждой линии; начало, конец и контрольные точки каждого спалйна, точки заливки... Массив метаданных должен содержать информацию о том, к чему относится та или иная точка, а также прочую информацию об объекте: начало линии, конец линии, начало сплайна, конец сплайна, изменить цвет линии, изменить цвет заливки и тд. В этом случае, для того, что-бы отобразить все на экране, нам потребуются два индекса/указателя на текущую позицию в каждом из массивов, далее, в цикле мы будем выбирать поочередно метаданные и соответствующие им точки. К примеру, если в текущей позиции массива метаданных находится команда LINE, то, начиная с текущей позиции массива точек, должны быть выбраны две точки - координаты начала и конца линии и, используя текущий цвет фигуры, нарисована линия; если мы встретили в метаданных команду FLOOD_FILL, то из массива точек следует выбрать одну точку и выполнить заливку текущим цветом фона из этой точки.
Минусы этого подхода - ну очень непонятно для поклонников GoF, Грэди Буча и Скотта Мейерса. Плюсы: очень легко изменять код - набор изначальных примитивов очень ограничен и не меняется, операции реализуются для примитивов, не для фигур, один раз; эффективность - нет никаких указателей, смарт-поинтеров, виртуальных методов, многие операции могут быть реализованы очень просто, например, для поворота всего чертежа, достаточно умножить каждую точку массива на соответствующую матрицу поворота.
Конечно, это не означает, что наследование это плохо, или, что следует отказаться от ООП, просто следует помнить о том, что то, что у вас в голове выстраивается в иерархию, на самом деле иерархией может и не быть. Фигуры чертежа состоят из примитивов - линий, сплайнов. То, что из этих линий и сплайнов можно создать более сложные фигуры, которые могут быть объединены в иерархию - может быть совсем не важно для решения задачи.&lt;/span&gt;&lt;/div&gt;
&lt;span class=&quot;fullpost&quot;&gt;&lt;/span&gt;&lt;/div&gt;
</description><link>http://evgeny-lazin.blogspot.com/2011/01/blog-post.html</link><author>noreply@blogger.com (Anonymous)</author><thr:total>6</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7754674872362895099.post-2787080538356365986</guid><pubDate>Sun, 05 Dec 2010 20:20:00 +0000</pubDate><atom:updated>2010-12-05T23:20:29.265+03:00</atom:updated><title></title><description>Я считаю, что люди - иррациональные существа, логика для нас - противоестественна, а рациональному мышлению нужно учиться всю жизнь. На наши решения, очень сильно влияют различные&amp;nbsp;&lt;a href=&quot;http://ru.wikipedia.org/wiki/%D0%A1%D0%BF%D0%B8%D1%81%D0%BE%D0%BA_%D0%BA%D0%BE%D0%B3%D0%BD%D0%B8%D1%82%D0%B8%D0%B2%D0%BD%D1%8B%D1%85_%D0%B8%D1%81%D0%BA%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B9&quot;&gt;когнитивные искажения&lt;/a&gt;. Одно из самых сильных, на мой взгляд - склонность искать подтверждение собственной правоты, вместо того, что-бы рассматривать альтернативы. Любой, особенно неопытный, программист - делает это постоянно. Вместо того, что-бы думать о том, чем плох наш код, искать его недостатки, мы ищем его достоинства и, естественно, находим. Позднее, с недостатками мы все же встречаемся, в процессе тестирования и отладки :)&lt;span class=&quot;fullpost&quot;&gt;&lt;/span&gt;</description><link>http://evgeny-lazin.blogspot.com/2010/12/blog-post.html</link><author>noreply@blogger.com (Anonymous)</author><thr:total>6</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-7754674872362895099.post-4181481255609477830</guid><pubDate>Sun, 18 Jul 2010 06:48:00 +0000</pubDate><atom:updated>2010-07-18T10:48:23.545+04:00</atom:updated><title></title><description>&lt;p&gt;Увидел сегодня следующее:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;a href=&quot;http://www.reddit.com/r/programming/comments/cqn6o/whats_wrong_with_c_from_the_point_of_view_of/c0ui1dw&quot; target=&quot;_blank&quot;&gt;Everyone should program in C++ for a few years so they learn memory management, project compilation strategies, decoupling, and a host of other professional activities that are needed to program in C++. But when they go back to the more convenient languages they will realize how much they enjoy programming again. &lt;/a&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Я в общем-то тоже считаю, что умение программировать на С++ – очень полезный навык. Рано или поздно, C++ программист учится очень рациональному и прагматичному подходу к программированию, или терпит фиаско :)&lt;/p&gt;  </description><link>http://evgeny-lazin.blogspot.com/2010/07/everyone-should-program-in-c-for-few.html</link><author>noreply@blogger.com (Anonymous)</author><thr:total>2</thr:total></item></channel></rss>