...

суббота, 9 января 2016 г.

Разбираемся с синтаксисом шаблонов в Angular2


Многие впервые увидев синтаксис шаблонов Angular2 начинают причитать, мол ужас какой сделали, неужто нельзя было как в Angular1 хотя-бы. Зачем нужно было вводить это разнообразие скобочек, звездочек и прочей ерунды! Однако при ближайшем рассмотрении все становится куда проще, главное не пугаться.

Так как шаблоны в AngularJS являются неотъемлемой его частью, важно разобраться с ними в самом начале знакомства с новой версии этого фреймворка. Заодно обсудим, какие преимущества дает нам данный синтаксис по сравнению с angular 1.x. Причем лучше всего будет рассматривать это на небольших примерах.

Данная статья во многом основана на материалах этих двух статей:

Для того, что бы упростить подачу материала, давайте разберемся. Под AngularJS я буду подразумевать всю ветку Angular 1.x, в то время как под Angular2 — ветку 2.x.

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


В случае простого вывода данных разницы вы не почувствуете, однако, если вы решите передать часть состояния в качестве значения какого-либо атрибутов элементов, можно уже наблюдать интересные решения.

Давайте освежим в памяти как работают биндинги на атрибуты в AngularJS. Для этого мы обычно напрямую пробрасывали выражения (интерполируемые при компиляции шаблона) прямо в атрибут элемента, либо пользовались одной из множества директив. Например ngValue для установки значения свойства value у инпутов.

Приведем пример как это работает в AngularJS:

<input ng-value="expression" />

Так же не забываем что мы можем просто интерполировать результат выражения напрямую в качестве значения аргумента:
<input value="{{expression}}" />

Заметим интересную особенность. Второй вариант многие его избегают, так как можно увидеть промежуточное состояние до того, как angular интерполирует значения. Однако первый вариант использует директивы. То есть для того что бы у нас все было хорошо, красиво и удобно, нам надо сделать по директиве на каждое свойство всех элементов. Согласитесь, не слишком то удобно. Почему бы не добавить какое-то обозначение для атрибута, которое бы говорило ангуляру замэпить значение на него. Причем было бы неплохо что бы синтаксис был валидным. И они добавили, теперь для этого надо всего-лишь обернуть интересующий нас атрибут (любой атрибут) в квадратные скобки — [].
<input [value]="valueExpression" [placeholder]="placeholderExpression" />

Однако, у нас все еще есть возможность мэпить интерполируемое значение, так же как это было в AngularJS:
<input value="{{ valueExpression }}" placeholder="{{ placeholderExpression }}" />

В AngularJS мы могли подписаться на события элементов используя специальные директивы. Так же, как и в случае со свойствами, нам приходится иметь дело с целой кучей возможных событий. И на каждое событие приходилось делать директиву. Пожалуй, самой популярной из таких директив является ngClick:
<button ng-click="doSomething($event)">

Учитывая что мы уже решили такую проблему для свойств элементов, то наверное так же стоит решать и эту проблему? Именно это и сделали! Для того, что бы подписаться на событие, достаточно прописать атрибут используюя следующий синтаксис: (eventName). Таким образом у нас есть возможность подписаться на любое событие генерируемое нашим элементом, при этом без надобности писать отдельную директиву:
<button (click)="doSomething($event)">

Существует распространенное мнение, что двусторонний биндинг это плохо, и что это основной грех ангуляра. Это не совсем так. Проблема с двусторонним биндингом в AnugularJS была в том, что он используется повсеместно, не давая разработчикам альтернативы (возможно ситуация с этим в скором времени изменится).

Все же иногда возникают случаи когда двусторонний биндинг здорово упрощает разработку, особенно в случае форм. Так как же реализован оный в Angular2? Давайте подумаем, как организовать двусторонний биндинг имея односторонний биндинг свойст элементов и биндинг событий:

<input type="text" [value]="firstName" (input)="firstName=$event.target.value" />

Опять же, не очень то удобно. Посему в Angular2 так же есть синтаксический сахар с использованием ngModel. Результат будет идентичен тому, что мы привели выше:
<input type="text" [(ngModel)]="firstName" />

Для передачи данных между элементами в пределах одного шаблона используются локальные переменные. Наиболее близкой аналогией в AngularJS, пожалуй, можно считать доступ к элементам формы по имени через ngForm. Конечно, это не совсем корректное сравнение, так как работает это только за счет директивы ngForm. В Angular2 вы можете использовать ссылку на любой объект или DOM элемент в пределах элемента шаблона и его потомков, используя локальные переменные #.
<video #movieplayer ...>
  <button (click)="movieplayer.play()">
</video>

В данном примере мы можем видеть, как через переменную movieplayer мы можем получить доступ к API медиа элемента прямо в шаблоне.

Помимо символа #, вы так же можете объявлять переменные используя префикс var-. В этом случае вместо #movieplayer мы могли бы записать var-movieplayer.


Символ * вызывает больше всего вопросов. Давайте разберемся, зачем он понадобился. Для осознания причин добавления этого символа, нам стоит вспомнить о таком элементе как template.

Элемент template позволяет нам задекларировать кусочек DOM, который мы можем инициализировать позже, что дает нам более высокую производительность и более рациональное использование ресурсов. Чем-то это похоже на documentFragment в контексте HTML.

Пожалуй, будет проще показать зачем оно надо на примере:

<div style="display:none">
  <img src="path/to/your/image.png" />
</div>

В этом небольшом примере мы можем видеть, что блок скрыт (display:none). Однако браузер все равно будет пытаться загрузить картинку, даже если она не понадобится. Если подобных вещей на странице много, это может пагубно отразиться на общей производительности страницы.

Решением этой проблемы станет использование элемента template.

<template>
  <img src="path/to/your/image.png" />
</template>

В этом случае браузер не будет загружать изображение, пока мы не инициализируем шаблон.

Но вернемся к нашим баранам. Использование символа * перед директивой элемента позволит ангуляру при компиляции обернуть элемент в шаблон. Проще посмотреть на примере:

<hero-detail *ngIf="isActive" [hero]="currentHero"></hero-detail>

Этот шаблон будет трансформирован в такой:
<template [ngIf]="isActive">
  <hero-detail [hero]="currentHero"></hero-detail>
</template>

Теперь должно стать ясно, что этот символ предоставляет синтаксический сахар для достижения более высокой производительности при использовании условных директив вроде ngFor, ngIf и ngSwitch. Логично что нет нужды в создании экземпляра компонента hero-detail пока isActive не является истиной.
Пайпы — это прямой аналог фильтров из AngularJS. В общем и целом синтаксис их применения не особо поменялся:
<p>My birthday is {{ birthday | date:"MM/dd/yy" }} </p>

Зачем понадобилось менять название с уже привычных фильтров на новые пайпы — отдельный вопрос. Сделано это было что бы подчеркнуть новую механику работы фильтров. Теперь это не синхронные фильтры, а асинхронные пайпы (по аналогии с unix pipes).

В AngularJS фильтры запускаются синхронно на каждый $digest цикл. Этого требует механизм отслеживания изменений в AngularJs. В Angular2 же отслеживание изменений учитывает зависимости данных, посему это позволяет оптимизировать целый ряд концепций. Так же появилось разделение на stateful и stateless пайпы (в то время как фильры AngularJS заведомо считались stateful).

Stateless пайпы, как это может быть понятно из названия, не имеют собственного состояния. Это чистые функции. Они выполняются только один раз (или если изменились входящие данные). Большинство пайпов в Angular2 являются stateless пайпами. Это позволяет существенно увеличить производительность.

Stateful пайпы напротив, имеют свое состояние и они выполняются часто в связи с тем что внутренне состояние может поменяться. Примером подобного пайпа является Async. Он получает на вход промис, подписывается на изменения и возвращает заресолвленное значение.

// это не TypeScript, это babel со stage-1, ну так, к сведенью
@Component({
  selector: 'my-hero',
  template: 'Message: {{delayedMessage | async}}',
})
class MyHeroAsyncMessageComponent {
  delayedMessage = new Promise((resolve, reject) => {
    setTimeout(() => resolve('You are my Hero!'), 500);
  });
}
// повелись? Неее, это просто TypeScript без определения типов.

В этом примере компонент my-hero выведет Message: You are my Hero! только после того, как бует заресолвлен промис delayedMessage.

Для того что бы сделать stateful пайпы мы должны явно объявить это в метаданных оного. Иначе Angular2 будет считать его stateless.


В AngularJS мы могли делать обращения к чему угодно совершенно безболезненно, что частенько выливалось в весьма коварные баги и затрудняло отладку. В Angular2 мы наконец будем получать ошибки! Однако подобное решение не всем может придтись по душе без дополнительного сахара.

В Javascript нам частенько приходится проверять наличие каких-либо свойств. Думаю все мы писали что-то подобное:

if (cordova && cordova.plugins && cordova.plugins.notification){
  // use cordova.plugins.notification
}

Делая такие проверки мы конечно же хотим избежать подобного:
TypeError: Cannot read property 'notification' of undefined.

В coffescript для решения этой проблемы был введен Elvis оператор. В Angular2 решили эту проблему используя тот же оператор, но на уровне шаблонов:
<p>Employer: {{employer?.companyName}}</p>

Данная запись означает, что свойство employer опционально, и если оно имеет пустое значение, то остальная часть выражения игнорируется. Если бы мы не воспользовались этим оператором, то в этой ситуации мы бы получили TypeError.

Так же как и в coffescript данный оператор можно использовать сколько угодно раз в рамках одного выражения, например так: a?.b?.c?.d


Разработчики Angular2 проделали огромную работу для того что бы сделать синтаксис шаблонов более гибким и мощным. Да, некоторые вещи требуют немного времени для адаптации, но в целом со временем все это кажется более чем естественным. А главное, не так все страшно как кажется на первый взгляд.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Steam CEG от Valve и с чем его едят. Введение

image
Немного определений

CEG (Custom Executable Generation, рус. Генерация Уникального Исполняемого Файла) — средство защиты от пиратства (как бы парадоксально это не звучало), разработанная великой и ужасной компанией Valve в 2009 году. Изначально планировалось, что новое средство защиты будет использоваться чуть ли не на каждой игре, выпущенной в Steam. Однако, пересмотрев свои приоритеты, компания Valve поняла, что если потенциальный разработчик захочет использовать такое 'счастье' в новоиспеченной игре, то желательно было бы платить и отнюдь не дешевую цену. Поэтому, исторически сложилось, что CEG используется лишь крупными компаниями, которые в состоянии себе это позволить.
Сама суть этой защиты заключается в следующем: как только подходит к концу закачка игры из сервиса цифровой дистрибуции Steam, на персональный компьютер клиента загружается «голый», еще не подписанный исполняемый файл. Затем, на тот же клиентский компьютер, загружается цифровой сертификат и с помощью специальной библиотеки SteamServices.dll (загрузка которой, к слову, производится в TEMP) и специального ключа с AES шифрованием подписывается выше упомянутый исполняемый файл.
Собственно, быстрый смотр защиты

«А какова же тогда практическая ценность такой 'защиты', если она только и делает, что просто подписывает исполняемый файл ?» — спросите Вы. И тут я отвечу — не все так просто как кажется на первый взгляд. Дело в том, что при нанесении цифровой подписи на файл, собирается информация о папках и файлах, которые присутствуют на клиентской системе, а так же некотором железе. В последствии, собранная информация, так же находит свое место в исполняемом файле. И эта информация будет действительно уникальной, так как учитываются следующие составляющие: временной штамп файлов и папок в стиле unix (дата создания, дата последнего изменения), уникальные ключи реестра (а так же проверка, установлен ли клиент Steam на ПК, посредством того же православного реестра), количество файлов в заданной директории, ID процессора, серийный номер жесткого диска. Следует отметить, что последние два — используются лишь в новейших версиях CEG (например, такие игры как: XCOM: Enemy Within, Grid Autosport, DiRT Rally).
Подводные камни

Итак, что же мы имеем — эдакую привязку 'уникального' исполняемого файла к железу и файлам клиентского ПК. Хорошо это или плохо? В общем и целом — хорошая идея и реализация. Минус в этом всем, если, допустим, Вы поехали на дачу к друзьям и прихватили с собой полный бэкап любимой игры с этим самым CEG, сбросив его на переносной носитель и надеясь, что сможете спокойно поиграть. Но тут случилось непредвиденное — у друга не работает Интернет! Зато на ноутбуке установлен Steam. Не беда, Вы делаете восстановление бэкапа вышей игры в оффлайновом режиме Steam, нажимаете кнопку «Играть», но ничего не происходит! А все потому, что железо и прочие составляющие, о которых писалось выше, не совпадают с железом Вашего друга. И без доступа в глобальную сеть, у Вас просто никак не получится запустить игру, так как по понятным причинам сгенерировать новый исполняемый файл невозможно. Или запустить исполняемый файл все таки возможно?
В следующей части статьи пойдет речь о том, возможно ли полностью отучить от CEG исполняемый файл, имея под рукой отладчик и среду разработки программного обеспечения (Visual Studio, например).

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Чей треугольник толще

image

Кроме жанра, игры имеют форму. Это как искусство — есть поэмы и даже романы, а есть стихотворения. Пушкин, например, порой по десятку стихов за неделю в Appstore выкладывал. И мы пашем иной раз не хуже гения. Ай да мы с Пушкиным, ай да сукины сыны!

Одна из игр, написанная намедни, мне показалась достойной внимания широкой математической общественности, которая обожает геометрические головоломки, соревнования и самоиронию.

Постановка задачи


На плоскости задано случайное конечное число точек.
Два игрока ходят по очереди.
За один ход игрок может соединить любые две точки прямой линией.
Линия не должна пересекать другие фигуры.

Цель игры


Если в результате хода образуется треугольник, не содержащий внутри себя других точек — то игрок получает призовое очко.
Игра заканчивется, когда построены все треугольники.
Необходимо набрать очков больше чем соперник.

Модификация игры


Если в результате хода образуется треугольник, не содержащий внутри себя других точек — то игрок добавляет в свой актив площадь образовавшегося треугольника.
Необходимо завоевать большую, чем соперник, площадь.

Особенности геометрической реализации

Для начального расположения точек я обрезаю экран iPhone до прямоугольника 300 на 400 и делаю по краям поля шириной по 10 пикселей.
Расстояние между точками ограничиваю числом не менее 16 пикселей (для удобного захвата пальцем).

Число точек задается случайно в диапазоне от 12 до 30 штук.

При построении запрещаю проходить линии ближе 5 пикселей до любых точек, находящихся на пути.

Особенности интеллекта

В качестве соперника выбран процессор фирмы Apple. Он играет в двух состояниях.
При рейтинге игрока менее 25 баллов, Apple думает на один ход вперед, случайно соединяя точки, если нет варианта с немедленным получением очка.
При рейтинге игрока 25 баллов и выше, соперник думает на 2 хода вперед, проверяя четность полученных после хода возможностей построения треугольников.

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

В процессе игры выработались стандартные трюки, с помощью которых иногда можно выигрывать. Надеюсь, Вы их найдете сами.

Математическую теорию игры не строил, но примитивные варианты раскладов для расположения 4-х точек рассматривал.

В зависимости от выпуклости 4-ех точечного шаблона, при оптимальной игре с обеих сторон игрок, ходящий первым играет либо вничью 1-1, либо выигрывает 2-1.

image
Выпуклый шаблон. Игрок, ходящий первым играет вничью 1-1

image
Невыпуклый шаблон. Игрок, ходящий первым играет выигрывает 2-1

Для 5-ти точечного шаблона, игрок, ходящий первым всегда проигрывает.
image
5-ти точечный выпуклый шаблон. Игрок, ходящий первым всегда проигрывает.

Игра порой чем-то напоминает снукер, когда соперник получает ход и не может построить казалось бы очевидно получающийся треугольник — загораживают другие фигуры.

Для тех, кто заинтересовался — есть небольшое видео. Всех с Новым Годом — он с бонусом!

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

[Перевод] Сколько на самом деле стоит гигабайт трафика? (В самом худшем случае)

Развитие интернета и разнообразных облачных сервисов приводит в том числе и к возникновению интересных вопросов. Еще в 2011 году провайдеры США и Канады (ISP) планировали установить месячный лимит потребления интернет-трафика для пользователей. За превышение этого объема придется платить дополнительно из расчета $1 за гигабайт. Это их желание провоцирует полемику о том, сколько на самом деле стоит (и должен стоить) доступ в интернет?

Основатель проекта HowStuff Works Маршалл Брейн (Marshall Brain) написал в блоге материал с расчетами стоимости доступа в сеть на примере самого дорогого в мире африканского интернета.

Начало


В качестве примера рассмотрим по определению самый дорогой в мире доступ – тот, что получают жители Африки. В этой статье содержатся некоторые интересные данные по этому вопросу. В частности, в материале приведены следующие факты:

В 2011 году был запущен подводный телекоммуникационный кабель в Африку —, в сообщении MTN (Mobile Service Provider) он был назван «самым большим и самым протяженным каналом связи в мире».

Оптоволоконная линия, принадлежащая Западноафриканским кабельным системам, протяженностью 14 тыс. километров, достигает Южноафриканского мыса, соединив провайдеров этого континента напрямую с серверами в Европе. Кабель обеспечивает качественный доступ в сеть для самых до настоящего момента отсталых в этом отношении регионов.

Кабель начинается в Лондоне и идет через 15 точек по западному побережью Африки, сообщает MTN, вложившая $90 млн (63 млн евро) в проект. Вся сумма инвестиций составила $650 млн.


Из текста по ссылке мы узнаем несколько важных моментов:
  • стоимость кабеля – $650 млн;
  • мощность – 5120 гигабайт в секунду;
  • 15 точек доступа по западному побережью континента;
  • протяженность – 14 тыс. километров (это расстояние от Лос-Анджелеса до Нью-Йорка помноженное на три).

Сколько стоит интернет


Мы можем сделать несколько предположений, чтобы рассчитать итоговую стоимость доступа:

Закладываем средства на его амортизацию. Для этого округлим $650 млн до $1 млрд, чтобы покрыть издержки.

Со временем оборудование устареет. При интенсивной эксплуатации и, учитывая развитие технологий, это произойдет через 10 лет.
Инвесторы намерены получить прибыль от этого проекта. Пусть это будет 200% профит от первоначальных вложений, или 20% в год. Таким образом, вложив 1 млрд, инвесторы желали бы в итоге выручить $3 млрд.

Учитывая структуру распределения мощностей и тот факт, что кабель может обеспечивать скорость в 5 терабайт в секунду, или примерно 500 гигабайт/сек, зададимся вопросом: сколько в этой системе будет стоить 1 гигабайт?

Если общая стоимость обеспечения доступа в год составляет $300 млн, если пропускная способность системы равна 500 гигабайт/сек (умножаем на 3600 секунд, 24 часа, 365 дней, получаем 15,7 млрд гигабайт), то цена гигабайта будет: $300 млн/15,7 млрд = 1,9 центов.

1,9 центов в самой дорогой оптоволоконной линии в мире! При этом мы уже заложили прибыль от инвестиций 200%. Но даже, если инвесторов обуревает жадность, и они захотят накинуть к этому еще 100%, получится всего 3,8 центов. Иными словами, сам трафик стоит неприлично дешево. Буквально гроши за один гигабайт. И это, еще раз повторимся, для самой дорогой в мире провайдерской сети.

Отражают ли эти расчеты реальную среднюю стоимость гигабайта трафика? Скорее нет, чем да. Потому что потребление трафика в течение дня неодинаковое. В определенные часы идет его многократное превышение. Если бы пользователи действительно выходили в сеть равномерно на протяжении суток, то реальная стоимость была бы близка к цифре 1,9 центов за гигабайт. В реальности в районе 4 ночи мощности системы простаивают. В другие часы потребление вырастает в разы.

Сегодня больше всего трафика потребляют такие онлайн-сервисы как Netflix и Hulu, и его объемы существенно увеличиваются в вечерние часы. Давайте рассмотрим худший сценарий, при котором онлайн-сервисы – это самая серьезная головная боль провайдеров.

Согласно статье Detailing Netflix's Streaming Costs: Average Movie Costs Five Cents To Deliver, средняя скорость кодирования для видео через Xbox360 составляет примерно 2000 кбит/сек. Это значит, что для просмотра 2-часового фильма потребуется 1,8 Гб трафика. Для видео с более высоким разрешением эта скорость составит 3200 кбит/сек, или 3Гб трафика.

Предположим, что все пользователи смотрят одновременно кино через Netflix. Усложним задачу: пусть по каждой выделенной линии таких пользователей будет несколько, члены семьи смотрят каждый свой фильм. Теперь нам понадобятся примерно 10Мб/сек для каждого пользователя. В течение примерно 3-4 часов каждый вечер. Очевидно, данное обстоятельство перегрузит возможности африканского кабеля, позволяющего проводить 5 терабайт в секунду.

При такой проводимости его мощности рассчитаны на 500 тыс. пользователей. Именно это число должно в итоге окупить вложения в систему в течение 10 лет, или 120 месяцев.

$3 млрд/120 месяцев/500 тыс. пользователей = $50 в качестве оплаты для каждого пользователя в месяц.


Эта цифра учитывает наихудший из возможных раскладов, исходя из реальных объемов потребления трафика, который позволяет каждому из пользователей ежедневно наслаждаться одновременно тремя фильмами в высоком качестве. При том что мощности кабеля это сделать позволяют, и прибыль в итоге составит 200%. Это будет самый дорогой трафик в мире.

Принимая в расчет все эти максимальные параметры (10 Мб/сек для каждого пользователя в течение 6 часов в сутки, 21 Гб в день или 600 Гб в месяц при абонентской плате $50), цена 1 гигабайта будет равна 8,3 цента. Другими словами, за эти копейки можно получать качественный доступ в сеть, учитывающий достойную прибыль для провайдеров.

Давайте представим, что мы живем в рациональном мире, где провайдеры не хотят нагреть пользователей, и все мы стараемся использовать наши ресурсы эффективно. Да, онлайн-сервисы Netflix/Hulu действительно ведут к удорожанию трафика, потому что способствуют перегрузке сетей. Но существует масса возможностей, способных распределять потоки рационально, не допуская перегрузок. Например, в невостребованные часы пользование интернетом можно сделать бесплатным, а в наиболее проблемные брать за гигабайт по 10 центов.

При подобном раскладе многие люди начали бы использовать свой доступ в сеть более разумно: ставить файлы с торрентов на загрузку ночью. Онлайн-сервисы могли бы ввести услуги по предзагрузке фильмов, которые пользователи хотят посмотреть вечером. Люди бы, исходя из экономии, выбирали версии своих любимых телешоу в прайм-тайм, которые бы весили меньше. Появились бы новые приложения, использующие преимущества бесплатного доступа в ночные и утренние часы. Иными словами, рынок самостоятельно бы регулировал рациональное использование трафика на протяжении суток, повышая качество доступа, при этом ведя к снижению его стоимости.

Мечты, мечты…

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Расчет биномиальных коэффициентов с использованием Фурье-преобразований

При решении задач комбинаторики часто возникает необходимость в расчете биномиальные коэффициентов. Бином Ньютона, т.е. разложение image также использует биномиальные коэффициенты. Для их расчета можно использовать формулу, выражающую биномиальный коэффициент через факториалы: image или использовать рекуррентную формулу:image Из бинома Ньютона и рекуррентной формулы ясно, что биномиальные коэффициенты — целые числа.

Одним из методов, позволяющих значительно сократить количество вычислений, является применение Фурье преобразований и дискретных Фурье преобразований.

Наличие большого числа библиотек, реализующих Фурье преобразований (во всевозможных вариантах быстрых версий), делает реализацию алгоритмов не очень сложной задачей для программирования.
Реализованные алгоритмы являются частью библиотеки с открытым исходным кодом FFTTools. Интернет-адрес: http://ift.tt/1JrobeO

Преобразование Фурье функции f вещественной переменной является интегральным и задаётся следующей формулой:

Преобразование Фурье

Разные источники могут давать определения, отличающиеся от приведённого выше выбором коэффициента перед интегралом, а также знака «−» в показателе экспоненты. Но все свойства будут те же, хотя вид некоторых формул может измениться.

Кроме того, существуют разнообразные обобщения данного понятия.

Дискретное преобразование Фурье


Дискретное преобразование Фурье (в англоязычной литературе DFT, Discrete Fourier Transform) — это одно из преобразований Фурье, широко применяемых в алгоритмах цифровой обработки сигналов (его модификации применяются в сжатии звука в MP3, сжатии изображений в JPEG и др.), а также в других областях, связанных с анализом частот в дискретном (к примеру, оцифрованном аналоговом) сигнале. Дискретное преобразование Фурье требует в качестве входа дискретную функцию. Такие функции часто создаются путём дискретизации (выборки значений из непрерывных функций). Дискретные преобразования Фурье помогают решать дифференциальные уравнения в частных производных и выполнять такие операции, как свёртки. Дискретные преобразования Фурье также активно используются в статистике, при анализе временных рядов. Существуют многомерные дискретные преобразования Фурье.

Формулы дискретных преобразований


Прямое преобразование:

image

Обратное преобразование:

image

Дискретное преобразование Фурье является линейным преобразованием, которое переводит вектор временных отсчётов в вектор спектральных отсчётов той же длины. Таким образом преобразование может быть реализовано как умножение симметричной квадратной матрицы на вектор:

image

Свёртка двух функций


Согласно определению, свёрткой двух функций F и G называется функция FхG:

FхG(t) = SUM F(i)*G(j)|i+j=t

Фурье-преобразования для вычисления свёртки


Одним из замечательных свойств преобразований Фурье является возможность быстрого вычисления корреляции двух функций определённых, либо на действительном аргументе (при использовании классической формулы), либо на конечном кольце (при использовании дискретных преобразований).

И хотя подобные свойства присущи многим линейным преобразованиям, для практического применения, для вычисления операции свёртки, согласно данному нами определению, используется формула

FхG = BFT ( FFT(F)*FFT(G) )


Где
  • FFT – операция прямого преобразования Фурье
  • BFT – операция обратного преобразования Фурье

Проверить правильность равенства довольно легко – явно подставив в формулы Фурье-преобразований и сократив получившиеся формулы

Биномиальные коэффициенты


Рассмотрим полином F(x)=1+x и его свёртку с самим собой n раз
Fx..xF = SUM С( i, n-1 )*x^i = BFT ( FFT(F)*...*FFT(F) ) = BFT ( FFT(F)^(n-1) )
То есть биномиальные коэффициенты С( i, n-1 ) могут быть получены из значений коэффициентов полинома (1+x)^(n-1)

Программируем:

using System;
using System.Drawing;
using System.Linq;
using System.Numerics;

namespace FFTTools
{
    public class BinomialBuilder : BuilderBase, IBuilder
    {
        /// <summary>
        ///     Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
        }

        public static void GetLongs(long[] array, long x = 1)
        {
            if (array.Length > 0) array[0] = 1;
            for (var i = 1; i < array.Length; i++)
                for (var j = i; j-- > 0;)
                    array[j + 1] += x*array[j];
        }

        public static void GetDoubles(double[] array, double x = 1.0)
        {
            var complex = new Complex[array.Length];
            if (array.Length > 0) complex[0] = Complex.One;
            if (array.Length > 1) complex[1] = x;
            if (array.Length > 0)
            {
                Fourier(complex, FourierDirection.Forward);
                complex = complex.Select(
                    value => Complex.Pow(value, array.Length - 1)/array.Length).ToArray();
                Fourier(complex, FourierDirection.Backward);
            }
            var index = 0;
            foreach (var value in complex) array[index++] = value.Magnitude;
        }

        public Bitmap ToBitmap(Bitmap source)
        {
            throw new NotImplementedException();
        }
    }
}

Проверяем:

using System;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace FFTTools.UnitTest
{
    [TestClass]
    public class BinomialUnitTest
    {
        [TestMethod]
        public void BinomialTestMethod()
        {
            const int count = 10;
            var doubles = new double[count];
            var longs = new long[count];
            BinomialBuilder.GetLongs(longs);
            BinomialBuilder.GetDoubles(doubles);
            Console.WriteLine(
                string.Join(Environment.NewLine,
                    longs.Zip(doubles, (x, y) => string.Format("{0} - {1} = {2}", x, y, x - y))) +
                Environment.NewLine);
            Assert.IsTrue(doubles.Zip(longs, (x, y) => x - y).All(x => Math.Abs(x) < 0.001));
        }
    }
}

Зачем?


При вычислении с помощью треугольника Паскаля трудоёмкость имеет оценку O(n^2).
При вычислении с помощью быстрых Фурье-преобразований трудоёмкость имеет оценку O(n*log n).

Примечание:


В статье заимствованы фрагменты из статьи Расчет биномиальных коэффициентов на Си (С++)

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Критические ошибки проектирования АСУ ТП и программирования ПЛК

В промышленности внедряются автоматизированные системы управления технологическим процессом (АСУ ТП) на промышленных программируемых логических контроллерах (ПЛК) на объектах модернизации. Вновь поставляемое оборудование, уже по умолчанию содержит АСУ на ПЛК. Но качество проектирования АСУ ТП и программирования ПЛК иногда не соответствует логике и требований к надежной защите управляемого объекта. В этой статье я расскажу о типичной ошибке проектирования и программирования обычного промышленного оборудования.

Введение


Рассмотрим типичный объект, содержащий АСУ на ПЛК в промышленности. В горнообрабатывающей отрасли, на обогатительных фабриках (ОФ) на стадии измельчения полезных ископаемых (руды) применяются различного типа мельницы. Они бывают шаровые, стержневые, вертикальные тонкого измельчения и т.д. Основной функцией данных мельниц является измельчения руды до фракции необходимой в дальнейшем для химического извлечения полезного ископаемого. У такого оборудования есть свои слабые места в процессе эксплуатации. Победитовые коренные подшипники, редуктора и т.д. Они требуют к себе постоянного контроля температуры, наличия смазки и т.д. В случае перегрева или сухого хода АСУ должна отключить агрегат, пока состояние узлов не достигло критической точки. Программные реализации данных защит и блокировок типичны и стандартны для такого рода оборудования.

Какие бывают ошибки?


Давайте рассмотрим две основные ошибки при проектировании и программировании АСУ для оборудования такого типа. Первая ошибка – неправильное проектирование релейной части управления главного привода или критичного механизма. Вторая ошибка – недостаток программы в части обработки фатальных ошибок ПЛК.

Ошибки в схемах.


Рассмотрим случай с релейной частью. На рисунке приведен пример такой ошибки. В схеме показана только часть управления отключения главного привода оборудования.

На первый взгляд обычная релейная схема. Но если присмотреться к ней, то можно определить, что рано или поздно наступит такой момент, когда релейная схема не сможет отключить главный привод в случае возникновения аварийной ситуации. Присмотримся к схеме. Отключение главного привода осуществляется ПЛК дискретным выходом. В данной схеме он релейный, но может быть и транзисторным, суть от этого не поменяется. Так вот, если по какой то причине катушка реле К1 сгорит во время работы оборудования, то при возникновении аварии, контроллер даст сигнал на отключение главного привода, но сигнал дальше сгоревшей катушки реле не пойдет. Но ведь по технологии, при отключении главного привода, требуется и отключение вспомогательного оборудования, в данном случае это маслонасос. Так вод при аварии, маслонасос будет благополучно отключен, а главный привод останется молотить на «сухую». Благо система ко всему еще и оповещение включит, так, что противно кричащий звонок и моргающая красная лампа привлекут к себе внимание обслуживающего персонала и «катастрофы» не произойдет. После этого, местные электрики или КИПовцы, найдут причину сего безобразия, поменяют реле и все станет на свои места, быть может, кто нибудь и задумается, как этого избежать в будущем, но врядли.
Так что в этой схеме реле К1 слабое звено. Что можно сделать чтоб такого не случилось. Элементарно. Сигнал отключения ВВ посадить на нормально-закрытый контакт реле К1, а само реле притягивать во время пуска главного привода и в рабочем состоянии удерживать его притянутым. Кстати, кнопку аварийный стоп, тоже так включать не стоит. Либо контакты кнопки должны непосредственно отключать исполнительный механизм, либо, если таких механизмов несколько, разрывать цепь реле, контакты которого уже отключают исполнительные механизмы. Кстати, такое включение промежуточных реле управления критичными исполнительными механизмами рождает и ошибочную отработку при ошибках программирования ПЛК.

Ошибки программирования ПЛК.


При программировании ПЛК, некоторые программисты допускают ошибки, приводящие к аварийным ситуациям на производстве.
Недавно мне пришлось столкнуться с такой ситуацией. Схема релейной части отключения главного привода была такой как представлено выше. Ошибка при программировании привела к тому, что главный привод работал на «сухую» четыре часа, что привело к перегреву редуктора. В результате редуктор полностью вышел из строя, а это в данном оборудовании дорогостоящий элемент. Что же пошло не так?
При выявлении причины аварии, приведшей к большим материальным затратам, было установлено, что ПЛК перешел в режим «СТОП» по причине срабатывания сторожевого таймера. Соответственно, релейная схема отключила всё вспомогательное оборудование, кроме главного привода. Сторожевой таймер сработал по причине наличия тупиковой ветки в алгоритме, не приводящей к зацикливанию главной функции. А как известно, почти у всех фирм производящих ПЛК, переход ПЛК в режим «СТОП», сопровождается установкой дискретных выходов в безопасное состояние. В данном случае в состояние отключено. В данной АСУ программист совершил две ошибки:
  1. Разветвленный алгоритм имел тупиковую ветку, приведшую к срабатыванию сторожевого таймера.
  2. Обработка исключений в программе не производилась, тем самым ПЛК перешел в режим «СТОП».

Первую ошибку спишем на сложность программы, в которой трудно найти такого вида ошибку.
Вторую ошибку, списать можно только на отсутствие компетенции программиста.
Как известно, многие ПЛК имеют программные модули для отработки различных фатальных ошибок ПЛК. Рассмотрим такие модули на примере ПЛК от фирмы сименс.
Вот небольшой пример такой ошибки.

Здесь программист производит линеаризацию аналогового входа на основе библиотечной функции FC105. В основном цикле по включению бита М0.1 происходит масштабирование аналогового сигнала. Все бы хорошо, но если в ПЛК не загрузить тот самый FC105, то при выполнении данной строчки, ПЛК вывалится в «СТОП SF» если не задать обработчик программных ошибок, так называемый OB121. Если такой обработчик залит в ПЛК, то при таких ошибках индикация SF появится, но ПЛК в режим «СТОП» не уйдет, и продолжит выполнять пользовательскую программу.

Подведем итоги


Релейную схему необходимо проектировать так, что бы в любой аварийной ситуации, будь то технологическая авария или ошибка ПЛК, отключение исполнительных механизмов проводилось в обязательном порядке не зависимо от рода возникновения аварии. Подходить к программированию ПЛК со всей ответственностью, ведь оборудование, которое призвано защитить АСУ ТП от критичных условий эксплуатации, приводящим к разрушению механизмов, намного дороже самой АСУ.
В данной схеме необходимо было использовать следующее включение компонентов релейной схемы.

А в программном модуле OB121, выполнять какие-нибудь действия по архивированию случившегося отказа в ПЛК.

Видео, показывающее поведения ПЛК при программных ошибках и их обработках представлено ниже.

Вывод


Схемное решение и программные реализации таят в себе не редко глубокие ошибки, которые не всегда выявляются на стадии пусковой наладке. В процессе эксплуатации не всегда специалисты предприятия проводят полный проверочный комплекс надежности системы. К тому же обслуживающему персоналу очень часто не хватает квалификации. Будем надеяться, что таких аварийных ситуаций будет ничтожно мало, и они не будут приводить к травмам на производстве.

P.S.
Оставлять просто пустые программные блоки обработки аппаратных или программных ошибок тоже не стоит. В них необходимо выполнять какие либо действия на детектирование таких ошибок или для сбора статистики отказов ПЛК и возможных причин.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

[Перевод] Восход разработчикономики (окончание)

(начало статьи здесь)

Управление рисками при инвестировании в программистские таланты


С другой стороны — этот эффект не имеет названия; возможно, его следовало бы именовать «десятикрадничеством» — плохие разработчики будут стоить вам отнюдь не незначительного ухудшения производительности труда. Нет, по целому ряду трудноуловимых причин они способны катастрофически саботировать сложную программную систему. Плохим разработчиком, построившим угрожающе полную ошибок или из рук вон плохо спроектированную систему, в её основание заложена тикающая бомба замедленного действия, и чтобы разгрести руины после того, как она рванёт, потребуются вложения, тысячекратно превосходящие сэкономленное на старте. Хуже того, работа по разбору завалов потребует привлечения усилий гораздо большего количества хороших разработчиков, и на более длительное время.

В этом моменте разработка программного обеспечения является полной противоположностью другим отраслям — скажем, аэрокосмической, — в которых некомпетентность и грубые ошибки гораздо более очевидны; их легче обнаружить и исправить на ранних этапах, пока стоимость такого исправления ещё не выросла до небес. Отсроченные кризисы (вроде конструкции самолёта, в которой в ходе реальной эксплуатации обнаружился фатальный дефект) также гораздо легче удержать от распространения, изолировать и исправить.

Вызванный «проблемой 2000 года» кризис не был порождением плохого проектирования; его причиной было проектирование прагматичное — что называется, «средней руки». Теперь представьте себе, во что обойдётся починка — на протяжении десятков лет — по-настоящему плохо спроектированных ключевых узлов сегодняшних программных систем. Хуже того, представьте себе, что могут натворить талантливые «десятикратники», перешедшие на Тёмную Сторону (то есть сознательно ставшие «стокрадниками»). Или хорошие парни, оставляющие «чёрные ходы» в разработанных ими системах как страховой полис на случай, если ОАО НПП „ХХХ“ не оплатило разработку этого приложения.

В итоге инвестирование в разработчиков как капитал не заключается всего лишь в найме программистов и последующей сортировке их по уровню таланта. Секрет — в их сортировке и распределении по уровням риска.

Традиционно, когда речь заходит о человеческом капитале, управляющие попросту не задумываются об управлении рисками. Даже если они думают о нём не как о традиционном ресурсе, то в лучшем случае — в терминах твёрдой цены на единицу прибыли. Типичный менеджер по найму будет разговаривать с вами фразами вроде «стоимость найма на эту позицию эквивалентна шести месяцам предлагаемой вами заработной платы».

Такая бухгалтерская логика попросту не работает в отношении программистов.

Но софтверные компании думают по-другому. Они вполне управляются с сортировкой по уровням таланта и риска — как на уровне индустрии в целом, так и на уровнях индивидуальных карьер, — а не на уровне трудовых отношений. Как же им это удаётся?

Жизненный цикл программистского таланта


Прежде всего, крупные софтверные компании вкладываются в большие объёмы кандидатов так рано, как только возможно — предлагая им окружения и средства разработки, что позволяет полностью обойти систему образования и заполучить талант в свои руки на самом раннем этапе его развития.

Затем они удерживают таланты на своём крючке при помощи соревнований, усовершенствованных видов практикумов (типа Google Summer of Code) и прочих механизмов «занятости, но не совсем работы» на всём протяжении обучения в колледже. Далее компании нанимают большинство из них, наблюдают за их производительностью, и сортируют таланты по уровням потенциальной окупаемости и риска.

Конечно, они должны не забывать предлагать великолепные бесплатные буфеты; кроме того, им необходимо предусмотреть соответствующие механизмы для страховки от некомпетентности — чтобы удерживать под контролем хаос, который способны устроить плохие программисты. Они должны вкладываться в технологии (скажем, как Google в Python) с прицелом на таланты, а не на рынок.

Отношения не прекращаются даже когда прекращается трудоустройство. Талантливые программисты настолько ценны, что вы просто вынуждены организовывать открытые экосистемы (основанные на API, доступе к источникам ценной информации вроде картографических данных, и элементах открытых исходных кодов), чтобы сохранить их в своей технологической вселенной даже после их ухода от вас. Бывший программист Microsoft будет по-прежнему ценен для этой компании — если он будет повсюду продвигать мысль, что «надо бы нам приобрести продукты Microsoft».

По мере того, как разработчик матереет, ему становится всё труднее и труднее переключаться с одной технологии на другую — и в какой-то момент он останется «подсевшим» на одну из них — скажем, Java, С++ или Facebook API — на всю оставшуюся жизнь; тогда можно ожидать, что с ней он и состарится. Когда эта технология достигнет своей зрелости, ценность «сидящего» на ней таланта начнёт понижаться — равно как и стоимость его удержания. Буфеты станут победнее, вознаграждение за труд усохнет, отток персонала снизится, и талант уйдёт в закат вместе с технологией. Это в какой-то мере даже мелодраматично: как капитан, скрывающийся в волнах, стоя на мостике своего тонущего судна.

У этого сценария заката жизни программистов и их судов есть пара исключений. По-настоящему талантливые сохраняют в себе вечнозелёной способность перелицевать себя под наисвежайший, находящийся на подъёме набор технологий, когда того ни пожелают. Второе исключение — это случай Большой Ж. Если бомба замедленного действия, забытая некомпетентными разработчиками в древних геологических пластах системы, внезапно рванёт, распространяя дрожь землетрясения сквозь более новые пласты, убелённые сединами ветераны могут внезапно обнаружить свои умения вновь востребованными (мы уже имели возможность наблюдать это в небольшом масштабе перед 2000-м годом, но исправление тогдашнего бардка потребовало не так уж много талантов; хотя можно представить ожидающие нас в будущем более критичные сбои, которые потребуют от вышедших на пенсию героев и героинь вернуться на свои рабочие места, чтобы всё исправить — в стиле фильма "Армагеддон".

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

И вы должны найти способ красиво сплавлять ветеранов на покой — гораздо более вежливо (и более затратно), чем в других отраслях, — ведь они могут оказаться вашим спасательным кругом в случае, если всё начнёт рассыпаться на глазах. Хотя пока это ещё не так очевидно, но по мере того, как поколение беби-бумеров уходит на пенсию (а вместе с ними — и знания о ранних геологических пластах съеденной программным обеспечением планеты), управление рисками, связанными с устаревшим ПО, станет крупным бизнесом. Когда из жизни начнут уходить последние ветераны, всё ещё помнящие старые, как кости мамонта, но всё ещё используемые программны, мы окажемся в ситуации, у которой не имеется исторических прецедентов.

Если вам удастся успешно провернуть всё вышеописанное, то для вас — крупной софтверной компании — вложения, которые вы сделали в 12-летнего школьника, будут приносить доход на протяжении всей его жизни — неважно, будет ли он работать на вас напрямую, или нет. В начале вас будут ожидать исключительно расходы; к 18-летию (или около того) он начнёт приносить вам первые дивиденды; к 30 годам вернёт ваши вложения; обеспечит страховку для бизнес-модели по мере того, как ваша компания достигает зрелости (например, значительная причина, по которой Java по-прежнему актуальна, заключается в том, что в неё по-прежнему вложена уйма талантов), и наконец, обеспечит вас страховкой от неурядиц (кстати, это и есть тот самый «поток», которым вы должны управлять, в отличие от «запаса» игроков на скамейке).

Раз уж мы заговорили об истории, давайте проведём небольшой экскурс для читателей, не знакомых с индустрией программного обеспечения, и всё ещё не понимают, каким эпохальным событием её рождение стало для всей остальной экономики.

Программное обеспечение пожирает мир — сорок третий год подряд


Как недавно отметил Алан Кэй, один из первопроходцев сегодняшней — съеденной программами — планеты, у Интернета нет кнопок «стоп», «завершение работы» или «обратная перемотка». Когда он появился, история, можно сказать, началась с нуля. Программное обеспечение начало подъедать докомпьютерные пласты цивилизации — и откладывать на их место свои.

В один прекрасный день мы оценим чрезвычайную важность происходящего, и заменим маркеры «до н.э / н.э.» на «до э.И. / э.И.» («до эры Интернета / в эру Интернета»), точкой отсчёта которых будет 29 октября 1969 года — день, когда был запущен Интернет (иными словами, я пишу эту статью в 43 году э.И.).

И, кстати, в этой эре будет нулевой год — раз уж за дело взялись программисты.

И тут мы подходим к ныне знаменитому тезису Дэвида Кирпатрика о том, что любая компания сегодня — это софтверная компания.

Мы только начинаем понимать, что программное обеспечение стало центральным для функционирорвания любой компании — вне зависимости от того, что она производит или какие услуги предоставляет.

До сих пор остальная экономика умудрялась выживать на тех талантах, которых индустрия программного обеспечения не смогла всосать в себя (по разным причинам — начиная с нежелания талантов переезжать в Калифорнию, и заканчивая тем фактом, что таланты, оказывается, могут интересоваться чем-то ещё помимо собственно программирования). В большинстве компаний, не связанных напрямую с разработкой ПО, разработчики пока что мирятся со своим статусом низшей касты, несмотря на всё увеличивающуюся их нехватку и всё возрастающую важность их роли.

Но это вот-вот изменится.

По мере того, как каждая компания начнёт реорганизовываться вокруг осознания того, насколько программные системы критичны для её бизнес-модели, по экономике побегут волны изменений. В той мере, в какой прирост количества талантливых программистов не соответствует растущему спросу, отдельные компании — и целые сектора индустрии — начнут схлопываться, будучи не в силах адекватно удовлетворить свои потребности в разработчиках.

Выжившие обнаружат себя во всё возрастающей зависимости от тех, кто вовремя позаботился обеспечить себя значительными залежами программистов. Ваша компания не способна обеспечить свои собственные IT-нужды? Не беда: их обеспечит Microsoft — небесплатно, конечно. Ваши сотрудники — даже не имеющие никакого отношения к ПО, будут, по большому счёту, контролироваться Google, Apple, Facebook и им подобными, устанавливающими планку их ожиданий посредством рынков ПО, обращённых на потребителя (та самая консьюмеризация IT, о которой так долго твердили все, кому ни попадя).

Могут ли индустрии, не связанные с программным обеспечением, вообще оставаться конкуретоспособными? Это непросто, но и не невозможно. Крупнейшей фишкой, которую они могут поставить на кон, являются накопленные ими данные. В борьбе с давлением консьюмеризации IT со стороны ваших сотрудников и Микрософто-размягчением (игра слов: Microsoftening — Microsoft + softening, «размягчение» — прим. перев.) вашей бизнес-модели со стороны IT-отдела, ваше единственное оружие — это доступ к данным, которые не являются программами.

Я напишу об этом как-нибудь в другой раз. Если вы не знаете, как управляться с единственным оружием, которое у вас есть, оно вскоре будет выбито из ваших рук (картографическая, кино-, музыкальная, новостная, издательская и ресторанная индустрии уже ощутили это на своём горьком опыте).

Взгляд со стороны талантов


Никому не нравится быть товаром в мясной лавке — даже деликатесной десятикратной вырезкой. Если (и когда) человек, в то же время являющийся ценным ресурсом, осознаёт свою экономическую роль, начинается сопротивление.

Программисты не дураки. В общем и целом подавляющее большинство схватывает мысль на лету. Невежественные «десятикратники», которых можно было бы передавать из рук в руки как акции или наличность, чрезвычайно редки. Если найдёте такого — воспротивьтесь позыву поэксплуатировать его ради немедленной прибыли, а вместо этого введите его в курс дела. Поверьте, таким путём вы приобретёте нечто более ценное — дружеские отношения с ним.

Естественная реакция, возникающая у хорошего разработчика, осознавшего собственную значимость, — это переход либо к индивидуалистско-наёмническому, либо коллективистско-цеховому образу мыслей.

Индивидуалисты становятся жёсткими переговорщиками, осторожно проверяя свою собственную рыночную цену и постоянно перезаключая договора. Они тщательно инвестируют в поддержание своих навыков на востребованном в настоящее время уровне, и, как могут, противятся вытеснению в отживающую своё часть экосистемы. Такие разработчики стараются хеджировать свои ставки, участвовать в нескольких проектах, и не слишком дистанцироваться от мира открытого ПО. Они выбирают позиции с прицелом на быстрый рост, и оставляют себе возможность сбежать с тонущего корабля с незамаранной репутацией, если чувствуют реальный риск. Неспроста технические таланты-наёмники расхватываются влёт, когда стартап разваливается: срос на CTO, способного успешно свалить вину в провале предприятия на бизнес-менеджмент, огромнен.

Другой тип разработчиков склоняется к цехоподобным структурам, которые служат центрами баланса сил в постоянном перетягивании каната с держателями разработчико-капитала. Правда, этот конфликт выглядит не как типичная классовая борьба, а скорее как громкое хлопанье дверьми: вместо того, чтобы сформировать профсоюз, который взял бы на себя переговоры с управленцами, талантливые разработчики попросту покидают не устраивающую их ситуацию, и подыскивают себе что-нибудь получше при помощи той самой цеховой структуры. Эффективность ограничения мобильности посредством опционов значительно ниже, чем та, которую в своё время обеспечивало русское крепостное право.

В результате мы имеем тонкий баланс сил между разработчиками и владельцами разработчико-капитала, и циклический ток этого капитала от центра к периферии, в ходе которого отдельные разрабочики входят в обращение и выходят из него.

Результатом становися новый тип экономики. Добро пожаловать в мир разработчикономики!

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Программный интернет шлюз для уже не маленькой компании (Shorewall, OpenVPN, OSPF). Часть 2

Представляю вторую статью из серии, ориентированных на «продолжающих» системных администраторов, для опытных я вряд ли открою что-то новое.
В этих статьях мы рассмотрим построение интернет шлюза на linux, позволяющего связать несколько офисов компании, и обеспечить ограниченный доступ в сеть, приоритетзацию трафика (QoS) и простую балансировку нагрузки с резервированием канала между двумя провайдерами.
Конкретно в этой части:
  • Более подробная настройка Shorewall
  • Страшный и не понятный QoS
  • Балансировка нагрузки и резервирование

А в предыдущей части были рассмотрены:

  • Простейшая настройка Shorewall
  • Ужасно сложная настройка dnsmasq
  • Не менее сложная настройка OpenVPN
  • И для многих продолжающих админов нетипичная, динамическая маршрутизация, на примере OSPF

Все описанное ниже справедливо для CentOS 7.1 (и выше, 6 серия тоже подойдет, но с небольшими особенностями)

В прошлый раз мы настроили довольно доверчивый и примитивный режим работы, сейчас мы включим немного здравой паранойи.
Это сделать не сложно, давайте внесем правки в файл:

policy
#
# Shorewall -- /etc/shorewall/policy
#
# For information about entries in this file, type "man shorewall-policy"
#
# The manpage is also online at
# http://ift.tt/1JAPAke
#
###############################################################################
#SOURCE DEST    POLICY          LOG     LIMIT:          CONNLIMIT:
#                               LEVEL   BURST           MASK
$FW     all     REJECT
grn     all     REJECT
tun     all     REJECT
red     all     DROP



Новая конфигурация стала проще, но радоваться рано, файл rules значительно распухнет:
/etc/shorewall/rules
#
# Shorewall -- /etc/shorewall/rules
#
# For information on the settings in this file, type "man shorewall-rules"
#
# The manpage is also online at
# http://ift.tt/1mMAg9J
#
######################################################################################################################################################################################################
#ACTION         SOURCE          DEST            PROTO   DEST    SOURCE          ORIGINAL        RATE            USER/   MARK    CONNLIMIT       TIME            HEADERS         SWITCH          HELPER
#                                                       PORT(S) PORT(S)         DEST            LIMIT           GROUP
?SECTION ALL
?SECTION ESTABLISHED
?SECTION RELATED
?SECTION INVALID
?SECTION UNTRACKED
?SECTION NEW

INCLUDE rules.fw
INCLUDE rules.grn
INCLUDE rules.red
INCLUDE rules.red-dnat
INCLUDE rules.tun



И воспользуемся директивой INCLUDE для разнесения правил по нескольким файлам:
rules.fw
#
# Shorewall -- /etc/shorewall/rules.fw
#
# For information on the settings in this file, type "man shorewall-rules"
#
# The manpage is also online at
# http://ift.tt/1mMAg9J
#
######################################################################################################################################################################################################
#ACTION         SOURCE          DEST            PROTO   DEST    SOURCE          ORIGINAL        RATE            USER/   MARK    CONNLIMIT       TIME            HEADERS         SWITCH          HELPER
#                                                       PORT(S) PORT(S)         DEST            LIMIT           GROUP
DNS(ACCEPT)     $FW             red
Web(ACCEPT)     $FW             red
FTP(ACCEPT)     $FW             red
OpenVPN(ACCEPT) $FW     red
Ping(ACCEPT)    $FW     all
OSPF(ACCEPT)    $FW     tun
SSH(ACCEPT)     $FW             all




rules.grn
#
# Shorewall -- /etc/shorewall/rules.grn
#
# For information on the settings in this file, type "man shorewall-rules"
#
# The manpage is also online at
# http://ift.tt/1mMAg9J
#
######################################################################################################################################################################################################
#ACTION         SOURCE          DEST            PROTO   DEST    SOURCE          ORIGINAL        RATE            USER/   MARK    CONNLIMIT       TIME            HEADERS         SWITCH          HELPER
#                                                       PORT(S) PORT(S)         DEST            LIMIT           GROUP
DNS(ACCEPT)     grn             $FW
Web(ACCEPT)     grn             red
FTP(ACCEPT)     grn             red
Ping(ACCEPT)    grn     all
SSH(ACCEPT)     grn             all     -       -       -       -       s:3/min




rules.red
#
# Shorewall -- /etc/shorewall/rules.red
#
# For information on the settings in this file, type "man shorewall-rules"
#
# The manpage is also online at
# http://ift.tt/1mMAg9J
#
######################################################################################################################################################################################################
#ACTION         SOURCE          DEST            PROTO   DEST    SOURCE          ORIGINAL        RATE            USER/   MARK    CONNLIMIT       TIME            HEADERS         SWITCH          HELPER
#                                                       PORT(S) PORT(S)         DEST            LIMIT           GROUP
OpenVPN(ACCEPT) red     $FW
SSH(ACCEPT)     red             $FW     -       -       -       -       s:3/min




rules.tun
#
# Shorewall -- /etc/shorewall/rules.tun
#
# For information on the settings in this file, type "man shorewall-rules"
#
# The manpage is also online at
# http://ift.tt/1mMAg9J
#
######################################################################################################################################################################################################
#ACTION         SOURCE          DEST            PROTO   DEST    SOURCE          ORIGINAL        RATE            USER/   MARK    CONNLIMIT       TIME            HEADERS         SWITCH          HELPER
#                                                       PORT(S) PORT(S)         DEST            LIMIT           GROUP
Ping(ACCEPT)    tun     $FW
OSPF(ACCEPT)    tun $FW
SSH(ACCEPT)     tun             $FW     -       -       -       -       s:3/min
SSH(ACCEPT)     tun             grn     -       -       -       -       s:3/min




Теперь весь трафик под нашим контролем, пускаем только те протоколы, что хотим. Обратите внимание на правила для SSH, мы ограничили частоту соединений до 3-х в минуту, с каждого ip источника. Но надо быть очень осторожным с этим параметром, неверно указав ключ s: или d:, можно сделать ваш сервис легко поддающимся DDoS атаке. А для Web трафика (да и вообще любого), надо учитывать, что много потенциальных клиентов может сидеть за NAT, и тогда один IP, источник соединений, может генерировать значительное число подключений.
Если нам нужно пробросить порты к внутренним серверам, сделать это не сложно:
rules.red-dnat
#
# Shorewall -- /etc/shorewall/rules.red-dnat
#
# For information on the settings in this file, type "man shorewall-rules"
#
# The manpage is also online at
# http://ift.tt/1mMAg9J
#
######################################################################################################################################################################################################
#ACTION         SOURCE          DEST            PROTO   DEST    SOURCE          ORIGINAL        RATE            USER/   MARK    CONNLIMIT       TIME            HEADERS         SWITCH          HELPER
#                                                       PORT(S) PORT(S)         DEST            LIMIT           GROUP
Web(DNAT)       red     grn:172.16.0.2
#Вариант, если у нас несколько внешних адресов, а мы хотим DNAT только с одного из них:
#Web(DNAT)      red     grn:172.16.0.2  -       -       -       192.168.10.37
#Варианты, все руками:
#DNAT           red                     grn:172.16.0.2  tcp     80,443
#DNAT           red                     grn:172.16.0.2  tcp     80,443  -       192.168.10.37
#Вариант с нестандартными портами:
#DNAT           red                     grn:172.16.0.2:80       tcp     8080


Скажу сразу, и балансировка и резервирование, только лишь настраиваются с помощью Shorewall, сам он не предоставляет никакого функционала по обнаружению пропажи канала или равномерному распределения реальной нагрузки.
Вся настройка сводится к редактированию одного файла:

providers
#
# Shorewall -- /etc/shorewall/providers
#
# For information about entries in this file, type "man shorewall-providers"
#
# For additional information, see http://ift.tt/1JAPB7G
#
############################################################################################
#NAME   NUMBER  MARK    DUPLICATE       INTERFACE       GATEWAY         OPTIONS         COPY
pr1     1       0x10000 -               $IF_RED1        $GW_RED1        fallback=1
pr2     2       0x20000 -               $IF_RED2        $GW_RED2        fallback=4



Если кто помнит, в прошлой статье была секция из «Shorewall.conf». Она нужна как раз для настройки маркировки пакетов и работе с провайдерами. Там мы задали, какие биты из метки пакета, задают идентификатор провайдера, какие просто метки (я такие пока нигде не использую, но Shorewall сам их задействует, для своих нужд), а какие для отслеживания соединений.
Тут мы описали двух провайдеров, задали как маркировать пакеты для них, на каком интерфейсе каждый сидит и какой у кого шлюз.
Вот столбец OPTIONS, надо пояснить немного. Тут ключ fallbaсk заставляет сгенерировать дополнительные правила маршрутизации, на случай если балансировочные не смогут обработать пакет (интерфейс прилег к примеру), а циферка задает вес интерфейса. Чем вес больше, тем чаще в интерфейс будут поступать новые соединения (используется в итоге относительный вес, у кого в пропорции вес выше, туда и трафик чаще, задавать большое значение веса не рекомендуется). Балансировка происходит по принципу roundrobin уже самим ядром и базируется на факте установления соединения по маршруту клиент-сервер (в качестве клиента ваш маршрутизатор, а сервер соответственно, удаленный сервер). При этом маршруты кэшируются на некоторое время, и получается такой эффект: кто-то в локалке, полез к примеру на некий сайт (у которого один IP), трафик пошел через провайдера 1, затем кто-то еще полез туда же, и трафик побежит опять через провайдера 1 (если кэш не успел сбросится). Еще может получится, что у вас не симметричные провайдеры, как в примере, и так повезет, каждое четвертое соединение, попадающие на первого провайдера, окажется и самым «тяжелым», а мы хотели этого избежать… Тут уже простых решений нет, и эта статья вам никак не поможет.
Дав команду:
shorewall show routing


Можно увидеть построенную схему маршрутизации:
Пример маршрутизации
Shorewall 5.0.2.1 Routing at cent1.domain.local - Пт янв  8 23:41:30 MSK 2016


Routing Rules

0:      from all lookup local
999:    from all lookup main
10000:  from all fwmark 0x10000/0xff0000 lookup pr1
10001:  from all fwmark 0x20000/0xff0000 lookup pr2
20000:  from 192.168.10.37 lookup pr1
20000:  from 192.168.10.36 lookup pr2
32765:  from all lookup balance
32767:  from all lookup default

Table balance:


Table default:

default nexthop via 192.168.10.1 dev eth0 weight 1 nexthop via 192.168.10.1 dev eth2 weight 1

Table local:

local 192.168.10.37 dev eth0 proto kernel scope host src 192.168.10.37
local 192.168.10.36 dev eth2 proto kernel scope host src 192.168.10.36
local 172.16.3.1 dev tap0 proto kernel scope host src 172.16.3.1
local 172.16.3.129 dev tap1 proto kernel scope host src 172.16.3.129
local 172.16.248.1 dev lo proto kernel scope host src 172.16.248.1
local 172.16.0.1 dev eth1 proto kernel scope host src 172.16.0.1
local 127.0.0.1 dev lo proto kernel scope host src 127.0.0.1
broadcast 192.168.10.255 dev eth2 proto kernel scope link src 192.168.10.36
broadcast 192.168.10.255 dev eth0 proto kernel scope link src 192.168.10.37
broadcast 192.168.10.0 dev eth2 proto kernel scope link src 192.168.10.36
broadcast 192.168.10.0 dev eth0 proto kernel scope link src 192.168.10.37
broadcast 172.16.3.255 dev tap1 proto kernel scope link src 172.16.3.129
broadcast 172.16.3.128 dev tap1 proto kernel scope link src 172.16.3.129
broadcast 172.16.3.127 dev tap0 proto kernel scope link src 172.16.3.1
broadcast 172.16.3.0 dev tap0 proto kernel scope link src 172.16.3.1
broadcast 172.16.248.1 dev lo proto kernel scope link src 172.16.248.1
broadcast 172.16.1.255 dev eth1 proto kernel scope link src 172.16.0.1
broadcast 172.16.0.0 dev eth1 proto kernel scope link src 172.16.0.1
broadcast 127.255.255.255 dev lo proto kernel scope link src 127.0.0.1
broadcast 127.0.0.0 dev lo proto kernel scope link src 127.0.0.1
local 127.0.0.0/8 dev lo proto kernel scope host src 127.0.0.1

Table main:

192.168.10.1 dev eth2 scope link src 192.168.10.36
172.16.3.1 dev tap0 proto zebra
172.16.3.129 dev tap1 proto zebra
172.16.248.2 via 172.16.3.2 dev tap0 proto zebra metric 13
172.16.12.129 via 172.16.3.2 dev tap0 proto zebra metric 3
172.16.11.1 via 172.16.3.2 dev tap0 proto zebra metric 3
172.16.3.128/25 dev tap1 proto kernel scope link src 172.16.3.129
172.16.3.0/25 dev tap0 proto kernel scope link src 172.16.3.1
192.168.10.0/24 dev eth2 proto kernel scope link src 192.168.10.36 metric 101
192.168.10.0/24 dev eth0 proto kernel scope link src 192.168.10.37 metric 100
172.16.8.0/23 via 172.16.3.2 dev tap0 proto zebra metric 13
172.16.0.0/23 dev eth1 proto kernel scope link src 172.16.0.1 metric 100

Table pr1:

192.168.10.1 dev eth0 scope link src 192.168.10.37
default via 192.168.10.1 dev eth0 src 192.168.10.37

Table pr2:

192.168.10.1 dev eth2 scope link src 192.168.10.36
default via 192.168.10.1 dev eth2 src 192.168.10.36



Но, не все так плохо, и даже так оно работает достойно (к слову сказать, так оно работает у большинства решений, для «честной» балансировки используются весьма не простые решения, которые часто без поддержки на стороне провайдеров (обоих) не работают).
Дополненный файл переменных:
params
#
# Shorewall -- /etc/shorewall/params
#
# Assign any variables that you need here.
#
# It is suggested that variable names begin with an upper case letter
# to distinguish them from variables used internally within the
# Shorewall programs
#
# Example:
#
#       NET_IF=eth0
#       NET_BCAST=130.252.100.255
#       NET_OPTIONS=routefilter,norfc1918
#
# Example (/etc/shorewall/interfaces record):
#
#       net     $NET_IF         $NET_BCAST      $NET_OPTIONS
#
# The result will be the same as if the record had been written
#
#       net     eth0            130.252.100.255 routefilter,norfc1918
#
###############################################################################
IF_RED1=eth0
GW_RED1=192.168.10.1
IF_RED2=eth2
GW_RED2=detect
IF_GRN=eth1
NET_GRN=172.16.0.0/23
IF_TUN=tap+
#LAST LINE -- DO NOT REMOVE



Можно заметить, что у второго провайдера шлюз указан как ключевое слово «detect», которое работает на соединениях с динамической адресацией. Для некоторый случаев (к примеру PPtP), сам Shorewall не может корректно определить шлюз, для чего используется файл с вспомогательным скриптом:
findgw
#
# Shorewall version 4 - Findgw File
#
# /etc/shorewall/findgw
#
# The code in this file is executed when Shorewall is trying to detect the
# gateway through an interface in /etc/shorewall/providers that has GATEWAY
# specified as 'detect'.
#
# The function should echo the IP address of the gateway if it knows what
# it is; the name of the interface is in $1.
#
# See http://ift.tt/1P4Oejr for additional
# information.
#
###############################################################################
LANG='C' nmcli --terse --fields IP6.GATEWAY device show ${1} | cut -f2- -d':' #IPv6
LANG='C' nmcli --terse --fields IP4.GATEWAY device show ${1} | cut -f2- -d':' #IPv4



И дополнит все это скрипт для NetworkManager (более простая версия была в прошлой статье, и она не учитывала, что после поднятия интерфейса, Shorewall не всегда корректно строит политику маршрутизации, поэтому мы для таких интерфейсов просто его перезапускаем).
/etc/NetworkManager/dispatcher.d/30-shorewall.sh
#!/bin/bash
 
IF=$1      # имя сетевого интерфейса, с которым связано событие
STATUS=$2  # новое состояние сетевого интерфейса
function check_prov() {
        PARAM=$(grep -v '^#' /etc/shorewall/params | grep $1 | cut -d '=' -f 1)
        if [ -z "$PARAM" ]; then
                grep -v '^#' /etc/shorewall/providers | grep -q $1
                [[ $? == 0 ]] && shorewall restart
        else
                grep -v '^#' /etc/shorewall/providers | grep -q $PARAM
                [[ $? == 0 ]] && shorewall restart
        fi
}
case $STATUS in
        up)
                # команды выполняемые после установления соединения
                shorewall enable $IF
                shorewall6 enable $IF
                check_prov $IF
        ;;
        down)
                # команды выполняемые после разрыва соединения
                shorewall disable $IF
                shorewall6 disable $IF
                check_prov $IF
        ;;
esac



Но, можно ли направить какой либо трафик по конкретному провайдеру? Ответ Да!
mangle
#
# Shorewall -- /etc/shorewall/mangle
#
# For information about entries in this file, type "man shorewall-mangle"
#
# See http://ift.tt/1JAPAkk for additional information.
# For usage in selecting among multiple ISPs, see
# http://ift.tt/1JAPB7G
#
# See http://ift.tt/1mMAgGT for a detailed description of
# the Netfilter/Shorewall packet marking mechanism.
#
####################################################################################################################################################
#ACTION         SOURCE          DEST            PROTO   DEST    SOURCE  USER    TEST    LENGTH  TOS     CONNBYTES       HELPER  PROBABILITY     DSCP
#                                                       PORT(S) PORT(S)
MARK(0x20000):P 172.16.0.4      0.0.0.0/0!172.16.0.0/12



Тут мы пометили весь трафик от 172.16.0.4 в любую сеть, кроме 172.16.0.0/12, меткой второго провайдера. Условия могут быть и хитрее, надо только учитывать, для трафика сгенерированного на нашем шлюзе, из правила нужно убрать ":P".

Сразу надо пояснить вот что: по настоящему регулировать скорость мы можем только при отправке. Входящий трафик, уже дошел до нашего интерфейса минуя все узкие места, и толкаясь в очередях, за право оказаться у нас на пороге.
Но отчаиваться рано, и есть механизмы, не столь изящные и надежные, как хотелось бы, но решающие эту проблему. В сетях на базе протоколов семейства IP, это решается таким образом:
Источник плавно наращивает скорость отправки, пока ответы о доставке (пакеты ACK в протоколе TCP) вообще приходят или приходят с нормальной задержкой. Если есть потери или растет задержка ACK, скорость снижается. Потом, спустя некоторый промежуток времени, скорость пытаются вновь поднять. И так происходит до окончания передачи.
А как же UDP? А с ним все просто, нет контроля доставки, нет головной боли. Отправил и ОК (это пусть получатель мучается).
Конечно, в чистом виде UDP обычно не используют в сложных задачах по передаче данных. Этот протокол обычно используют как основу, при реализации своего варианта контроля доставки (можно сказать, своих реализаций TCP, в случаях, когда стандартный не устраивает). Поэтому во многих протоколах работающих поверх UDP, контроль доставки тоже есть. Что не отменяет возможности слать непрерывный поток UDP во всю мощь, забивая канал связи цели (тот самый вариант DDoS).
Как же мы можем организовать приоритезацию трафика и выделения полосы пропускания входящего трафика? Ответ есть выше: создать задержку в получении на своей стороне (как следствие, задержку в генерации ACK) или если задержка неприлично высока, пакет отбросить.

В Linux это реализуется созданием псевдо-интерфейса IFB, который как бы встает между физическим интерфейсом и самим шлюзом, пропуская через себя входящий трафик. Трафик входит в физический интерфейс (мы его уже приняли на нем), и тут же отправляется в IFB, который уже регулирует, с какой скоростью, и в каком порядке пропустить этот трафик (или вообще отбросить).
В настройке нам поможет Shorewall (хотя можно и в /etc/modprobe.d прописать):

/etc/shorewall/init
#
# Shorewall -- /etc/shorewall/init
#
# Add commands below that you want to be executed at the beginning of
# a "shorewall start", "shorewall-reload" or "shorewall restart" command.
#
# For additional information, see
# http://ift.tt/1P4Oejr
#
###############################################################################
modprobe ifb numifbs=3
ip link set ifb0 up
ip link set ifb1 up
ip link set ifb2 up



Тут тривиально, создали три псевдо-интерфейса IFB и их подняли.
Далее опишем интерфейсы, на которых мы будем регулировать трафик:
tcdevices
#
# Shorewall -- /etc/shorewall/tcdevices
#
# For information about entries in this file, type "man shorewall-tcdevices"
#
# See http://ift.tt/1JAPAkk for additional information.
#
###############################################################################
#NUMBER:        IN-BANDWITH     OUT-BANDWIDTH   OPTIONS         REDIRECTED
#INTERFACE                                                      INTERFACES
1:$NET_GRN      -               1000mbit        hfsc,classify
2:ifb1          -               1000mbit        hfsc,classify   $NET_GRN
3:$NET_RED1     -               10mbit          hfsc,classify
4:ifb0          -               10mbit          hfsc,classify   $NET_RED1
5:$NET_RED2     -               10mbit          hfsc,classify
6:ifb2          -               10mbit          hfsc,classify   $NET_RED2



Тут мы в явном виде, задаем номера используемых интерфейсов (если не задать, Shorewall пронумерует их в порядке объявления файле), ассоциировали IFB с реальными интерфейсами, задали максимальную исходящую скорость (помним, только её мы и регулируем, и интерфейс ifb по сути это входящая линия) и задали дисциплины классификации и что трафик мы будем именно классифицировать.
Опишем те самые классы:
tcclasses
#
# Shorewall -- /etc/shorewall/tcclasses
#
# For information about entries in this file, type "man shorewall-tcclasses"
#
# See http://ift.tt/1JAPAkk for additional information.
#
###############################################################################
#INTERFACE:CLASS        MARK    RATE:           CEIL    PRIORITY        OPTIONS
#                               DMAX:UMAX
1:1:2                   -       1mbit           3mbit   2               default
1:1:3                   -       256kbit         full    1
2:1:2                   -       1mbit           3mbit   2               default
2:1:3                   -       256kbit         full    1
3:1:2                   -       1mbit           3mbit   2               default
4:1:3                   -       256kbit         full    1
5:1:2                   -       1mbit           3mbit   2               default
5:1:3                   -       256kbit         full    1
6:1:2                   -       1mbit           3mbit   2               default
6:1:3                   -       256kbit         full    1



Пока не будем делать ничего сложного (и оттого интересного и полезного), пока привяжем к каждому интерфейсу (включая IFB) по два класса.
В первом столбце мы ассоциируем интерфейс с классами. ::.
На интерфейсе всегда есть класс 1, который мы по сути, описали в tcdevices.
Далее, пакеты мы не маркировали (при классификации этого и нельзя сделать), и потому столбец использовать не будем, дальше идет самое интересное, минимально гарантированная полоса пропускания, и максимально возможная (не больше чем таковая у класса родителя), для данного класса. Приоритет задаст порядок разрешения спорной ситуации (у кого меньше, тот и пойдет первым, если вышел за приделы гарантированной полосы, и она уже кем-то другим занята полностью). В заключении идут опции, default говорит о том, что если фильтрами ничего не найдено (пакеты не отнесены к классам), то присвоить им класс по умолчанию).
Дальше, так исторически сложилось, что правила классификации реальных интерфейсов, находятся в файле:
mangle
#
# Shorewall -- /etc/shorewall/mangle
#
# For information about entries in this file, type "man shorewall-mangle"
#
# See http://ift.tt/1JAPAkk for additional information.
# For usage in selecting among multiple ISPs, see
# http://ift.tt/1JAPB7G
#
# See http://ift.tt/1mMAgGT for a detailed description of
# the Netfilter/Shorewall packet marking mechanism.
#
####################################################################################################################################################
#ACTION         SOURCE          DEST            PROTO   DEST    SOURCE  USER    TEST    LENGTH  TOS     CONNBYTES       HELPER  PROBABILITY     DSCP
#                                                       PORT(S) PORT(S)
CLASSIFY(1:3)   0.0.0.0/0       0.0.0.0/0       tcp     -       80,443
CLASSIFY(3:3)   0.0.0.0/0       0.0.0.0/0       tcp     80,443



А для виртуальных IFB в:
tcfilters
#
# Shorewall -- /etc/shorewall/tcfilters
#
# For information about entries in this file, type "man shorewall-tcfilters"
#
# See http://ift.tt/1JAPAkk for additional information.
#
########################################################################################################
#INTERFACE:     SOURCE          DEST            PROTO   DEST    SOURCE  TOS             LENGTH  PRIORITY
#CLASS                                                  PORT(S) PORT(S)
2:3             0.0.0.0/0       0.0.0.0/0       tcp     -       80,443
4:3             0.0.0.0/0       0.0.0.0/0       tcp     80,443



В примере выше, я поместил входящий трафик от HTTP(S) сервера в класс с номером 3 на физическом интерфейсе, и на виртуальный, ассоциированный с ним, и исходящий я сделал аналогично, но «развернув порты». Очень внимательно отнеситесь к тому, что соединения часто двунаправленны, и расписывать их классификацию надо отдельно для каждого направления, не зависимо от того, кто инициировал, клиент или сервер.
Именно тут, и начинает трещать крыша. Для понимания поможет картинка (прошу ногами особо не бить, в Visio работать толком не умею).

Резать трафик в итоге можно на разных участках, не применяя IFB, если у вас один провайдер, а сам шлюз, трафик не принимает (не является его получателем). Если провайдеров больше, а шлюз сам активно принимает трафик (он к примеру обслуживает VPN), тогда без IFB выкручиваться не легко.

P.S.
В следующей статье я планирую подробнее остановится на QoS, особенно в свете распространения VoIP технологий. Тема большая, и нужно все хорошенько спланировать. Если вас интересует некий аспект более подробно, пишите запрос в комментарии, я учту пожелания в следующей статье.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

[Перевод] [Перевод] Ассоциативные массивы в языке D

Это перевод статьи «Associative arrays», опубликованной 1 января 2016 года. На мой вкус статья несколько излишне поверхностна и не содержит большого количества подробностей, но она может быть полезной тем, кто знаком с ассоциативными массивами в других языках программирования.

В языке D есть встроенная поддержка ассоциативных массивов, также известных как хэш-таблицы.
Они аналогичны Map в Java или std::unordered_map в C++.

Объявление ассоциативного массива

Чтобы объявить ассоциативный массив, используйте следующий синтаксис:

// Прим. перев.: value — тип значения, key — тип ключа
value[key] myAssociativeArray;

Вставка элементов в ассоциативный массив


Для вставки элементов в ассоциативный массив используйте оператор [].
Ниже приведён пример создания ассоциативного массива квадратов целых чисел от 0 до 10 и вывода их на экран.
import std.stdio;

void main()
{
    int[int] squares; // Объявление

    for (int i = 0; i <= 10; ++i)
        squares[i] = i * i; // Вставка в ассоциативный массив

    writeln(squares);
}

Запустив пример, мы получим следующий вывод:
[0:0, 6:36, 7:49, 2:4, 3:9, 10:100, 1:1, 8:64, 5:25, 4:16, 9:81]
Обратите внимание, что числа не отсортированы — это ожидаемо: ассоциативные массивы внутренне не сортируются.
Примечания:

  • Переназначение существующего ключа заменит значение.
  • Попытка обратиться к несуществующему ключу приведёт к ошибке core.exception.RangeError.

Удаление элементов из ассоциативного массива


Для удаления элементов из ассоциативного массива используйте функцию remove().
aa.remove("hello");

Проверка на существование ключа


Для проверки ключа на существование используйте оператор in, который возвращает указатель на значение. Если ключ не существует, указатель будет null.
int[int] squares;

// ...

int* p = 10 in squares;
// Прим. перев.: Не проверяйте на null операторами сравнения, используйте is
if (p !is null)
    writeln(*p);
else
    writeln("Нет значения.");

Очистка ассоциативного массива


Существует два способа очистить ассоциативный массив:
  1. Пройтись по ключам и удалить их
  2. Отбросить старый массив и создать новый

Способ 1: удаляем ключи

foreach (key; aa.keys)
    aa.remove(key);

Способ 2: создаём новый массив


Чтобы выбросить существующий массив, присвойте ему значение null.
aa = null; // помечено для сборки мусора
aa[1] = 1; // записано по новому адресу в памяти

Свойства


Мы уже знакомы с некоторыми свойствами ассоциативных массивов, например, remove() или keys. Ниже приведены остальные:
Свойство Описание
.sizeof Возвращает размер ссылки на ассоциативный массив. В 32-битных сборках это 4, а в 64-битных сборках 8.
.length Возвращает количество значений в массиве. В отличие от динамических массивов, это свойство только для чтения.
.dup Создаёт ассоциативный массив того же размера и копирует в него содержимое первого массива.
.keys Возвращает динамический массив, элементы которого являются ключами исходного ассоциативного массива.
.values Возвращает динамический массив, элементы которого являются значениями исходного ассоциативного массива.
.rehash На месте реорганизует массив, оптимизируя поиск по нему. rehash полезен, например, когда в программу подгружается таблица символов, по которой впоследствии нужно производить поиск. Возвращает ссылку на реорганизованный массив.
.byKey() Возвращает диапазон, подходящий для перебора с помощью цикла foreach ключей ассоциативного массива.
.byValue() Возвращает диапазон, подходящий для перебора с помощью цикла foreach значений ассоциативного массива.
.byKeyValue() Возвращает диапазон, подходящий для перебора с помощью цикла foreach пар ключ-значение ассоциативного массива. Возвращаемые пары представляются в виде непрозрачного типа со свойствами .key и .value, позволяющими обращаться соответственно к ключу и значению пары.
.get(Key key, lazy Value defVal) Ищет ключ key. Если он существует, возвращает соответствующее ему значение; если не существует, отрабатывает и возвращает значение по умолчанию defVal.

Что ещё почитать


Для более подробной информации (например, чтобы узнать о работе с классами и структурами в ассоциативных массивах) обратитесь к официальной документации на dlang.org.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Низкоуровневая оптимизация параллельных алгоритмов или SIMD в .NET

image

В настоящее время огромное количество задач требует большой производительности систем. Бесконечно увеличивать количество транзисторов на кристалле процессора не позволяют физические ограничения. Геометрические размеры транзисторов нельзя физически уменьшать, так как при превышении возможно допустимых размеров начинают проявляться явления, которые не заметны при больших размерах активных элементов — начинают сильно сказываться квантовые размерные эффекты. Транзисторы начинают работать не как транзисторы.
А закон Мура здесь ни при чем. Это был и остается законом стоимости, а увеличение количества транзисторов на кристалле — это скорее следствие из закона. Таким образом, для того, чтобы увеличивать мощность компьютерных систем приходится искать другие способы. Это использование мультипроцессоров, мультикомпьютеров. Такой подход характеризуется большим количеством процессорных элементов, что приводит к независимому исполнение подзадач на каждом вычислительном устройстве.

Параллельные методы обработки:

Источник параллелизма Ускорение Усилие программиста Популярность
Множество ядер 2х-128х Умеренное Высокая
Множество машин 1х-Бесконечность Умеренно-Высокое Высокая
Векторизация 2х-8х Умеренное Низкая
Графические адаптеры 128х-2048х Высокое Низкая
Сопроцессор 40х-80х Умеренно-высокое Чрезвычайно-низкая

Способов для повышения эффективности систем много и все довольно различны. Одним из таких способов является использование векторных процессоров, которые в разы повышают скорость вычислений. В отличие от скалярных процессоров, которые обрабатывают один элемент данных за одну инструкцию (SISD), векторные процессоры способны за одну инструкцию обрабатывать несколько элементов данных (SIMD). Большинство современных процессоров являются скалярными. Но многие задачи, которые они решают, требуют большого объема вычислений: обработка видео, звука, работа с графикой, научные расчеты и многое другое. Для ускорения процесса вычислений производители процессоров стали встраивать в свои устройства дополнительные потоковые SIMD-расширения.
Соответственно при определенном подходе программирования стало возможным использование векторной обработки данных в процессоре. Существующие расширения: MMX, SSE и AVX. Они позволяют использовать дополнительные возможности процессора для ускоренной обработки больших массивов данных. При этом векторизация позволяет добиться ускорения без явного параллелизма. Т.е. он есть с точки зрения обработки данных, но с точки зрения программиста это не требует каких-либо затрат на разработку специальных алгоритмов для предотвращения состояния гонки или синхронизации, а стиль разработки не отличается от синхронного. Мы получаем ускорение без особых усилий, почти совершенно бесплатно. И в этом нет никакой магии.

Что такое SSE?


SSE (англ. Streaming SIMD Extensions, потоковое SIMD-расширение процессора) — это SIMD (англ. Single Instruction, Multiple Data, Одна инструкция — множество данных) набор инструкций. SSE включает в архитектуру процессора восемь 128-битных регистров и набор инструкций. Технология SSE была впервые введена в Pentium III в 1999 году. Со временем, этот набор инструкций был улучшен путем добавления более сложных операций. Восемь (в x86-64 — шестнадцать) 128-битовых регистров были добавлены к процессору: от xmm0 до xmm7.
image
Изначально эти регистры могли быть использованы только для одинарной точности вычислений (т.е. для типа float). Но после выхода SSE2, эти регистры могут использоваться для любого примитивного типа данных. Учитывая стандартную 32-разрядную машину таким образом, мы можем хранить и обрабатывать параллельно:
  • 2 double
  • 2 long
  • 4 float
  • 4 int
  • 8 short
  • 16 char

Если же использовать технологию AVX, то вы будете манипулировать уже 256-битными регистрами, соответственно больше чисел за одну инструкцию. Так уже есть и 512-битные регистры.
image
Сначала на примере С++ (кому не интересно, можете пропустить) мы напишем программу, которая будет суммировать два массива из 8 элементов типа float.

Пример векторизации на С++


Технология SSE в С++ реализована низкоуровневыми инструкциями, представленные в виде псевдокода, которые отражают команды на ассемблере. Так, например, команда __m128 _mm_add_ps(__m128 a, __m128 b ); преобразуется в инструкцию ассемблера ADDPS операнд1, операнд2. Соответственно команда __m128 _mm_add_ss(__m128 a, __m128 b ); будет преобразовываться в инструкцию ADDSS операнд1, операнд2. Эти две команды делают почти одинаковые операции: складывают элементы массива, но немного по-разному. _mm_add_ps складывает полностью регистр с регистром, так что:
  • r0 := a0 + b0
  • r1 := a1 + b1
  • r2 := a2 + b2
  • r3 := a3 + b3

При этом весь регистр __m128 и есть набор r0-r3. А вот команда _mm_add_ss складывает только часть регистра, так что:
  • r0 := a0 + b0
  • r1 := a1
  • r2 := a2
  • r3 := a3

По такому же принципу устроены и остальные команды, такие как вычитание, деление, квадратный корень, минимум, максимум и другие операции.
Для написания программы можно манипулировать 128-битовыми регистрами типа __m128 для float, __m128d для double и __m128i для int, short, char. При этом можно не использовать массивы типа __m128, а использовать приведенные указатели массива float к типу __m128*.
При этом следует учитывать несколько условий для работы:
  • Данные float, загруженные и хранящиеся в __m128 объекте должны иметь 16-байтовое выравнивание
  • Некоторые встроенные функции требуют, чтобы их аргумент был типа констант целых чисел, в силу природы инструкции
  • Результат арифметических операций, действующих на два NAN аргументов, не определен

Такой вот маленький экскурс в теорию. Однако, рассмотрим пример программы с использованием SSE:
#include "iostream"
#include "xmmintrin.h"        

int main()
{
        const auto N = 8;

        alignas(16) float a[] = { 41982.0,  81.5091, 3.14, 42.666, 54776.45, 342.4556, 6756.2344, 4563.789 };
        alignas(16) float b[] = { 85989.111,  156.5091, 3.14, 42.666, 1006.45, 9999.4546, 0.2344, 7893.789 };
        
        __m128* a_simd = reinterpret_cast<__m128*>(a);
        __m128* b_simd = reinterpret_cast<__m128*>(b);

        auto size = sizeof(float);
        void *ptr = _aligned_malloc(N * size, 32);
        float* c = reinterpret_cast<float*>(ptr);
        
        for (size_t i = 0; i < N/2; i++, a_simd++, b_simd++, c += 4)
                _mm_store_ps(c, _mm_add_ps(*a_simd, *b_simd));
        c -= N;

        std::cout.precision(10);
        for (size_t i = 0; i < N; i++)
                std::cout << c[i] << std::endl;

        _aligned_free(ptr);

        system("PAUSE");
        return 0;
}


  • alignas(#) — стандартный для С++ переносимый способ задания настраиваемого выравнивания переменных и пользовательских типов. Используется в С++11 и поддерживается Visual Studio 2015. Можно использовать и другой вариант — __declspec( align( #)) declarator. Данные средства для управления выравниванием при статическом выделении памяти. Если необходимо выравнивание с динамическим выделением, необходимо использовать void* _aligned_malloc(size_t size, size_t alignment);
  • Затем преобразуем указатель на массив a и b к типу _m128* при помощи reinterpret_cast, который позволяет преобразовывать любой указатель в указатель любого другого типа.
  • После динамически выделим выравненную память при помощи уже упомянутой выше функции _aligned_malloc(N*sizeof(float), 16);
  • Количество необходимых байт выделяем исходя из количества элементов с учетом размерности типа, а 16 это значение выравнивания, которое должно быть степенью двойки. А затем указатель на этот участок памяти приводим к другому типу указателя, чтобы с ним можно было бы работать с учетом размерности типа float как с массивом.

Таким образом все приготовления для работы SSE выполнены. Дальше в цикле суммируем элементы массивов. Подход основан на арифметике указателей. Так как a_simd, b_simd и с — это указатели, то их увеличение приводит к смещению на sizeof(T) по памяти. Если взять к примеру динамический массив с, то с[0] и покажут одинаковое значение, т.к. с указывает на первый элемент массива. Инкремент с приведет к смещению указателя на 4 байта вперед и теперь указатель будет указывать на 2 элемент массива. Таким образом можно двигаться по массиву вперед и назад увеличивая и уменьшая указатель. Но при этом следует учитывать размерность массива, так как легко выйти за его пределы и обратиться к чужому участку памяти. Работа с указателями a_simd и b_simd аналогична, только инкремент указателя приведет к перемещению на 128-бит вперед и при этом с точки зрения типа float будет пропущено 4 переменных массива a и b. В принципе указатель a_simd и a, как и b_simd и b указывают соответственно на один участок в памяти, за тем исключением, что обрабатываются они по-разному с учетом размерности типа указателя:
image
     for (int i = 0; i < N/2; i++, a_simd++, b_simd++, c += 4)
                _mm_store_ps(c, _mm_add_ps(*a_simd, *b_simd));


Теперь понятно почему в данном цикле такие изменения указателей. На каждой итерации цикла происходит сложение 4-х элементов и сохранение полученного результата по адресам указателя с из регистра xmm0 (для данной программы). Т.е. как мы видим такой подход не изменяет исходных данных, а хранит сумму в регистре и по необходимости передает ее в нужный нам операнд. Это позволяет повысить производительность программы в тех случаях, когда необходимо повторно использовать операнды.
Рассмотрим код, который генерирует ассемблер для метода _mm_add_ps:
mov         eax,dword ptr [b_simd]  ;// поместить адрес b_simd в регистр eax(базовая команда пересылки данных, источник не изменяется)
mov         ecx,dword ptr [a_simd]  ;// поместить адрес a_simd в регистр ecx 
movups      xmm0,xmmword ptr [ecx]  ;// поместить 4 переменные с плавающей точкой по адресу ecx в регистр xmm0;            xmm0 = {a[i], a[i+1], a[i+2], a[i+3]}
addps       xmm0,xmmword ptr [eax]  ;// сложить переменные: xmm0 = xmm0 + b_simd
                                    ;// xmm0[0] = xmm[0] + b_simd[0]
                                    ;// xmm0[1] = xmm[1] + b_simd[1]
                                    ;// xmm0[2] = xmm[2] + b_simd[2]
                                    ;// xmm0[3] = xmm[3] + b_simd[3]

movaps      xmmword ptr [ebp-190h],xmm0 ;// поместить значение регистра по адресу в стеке со смещением
movaps      xmm0,xmmword ptr [ebp-190h] ;// поместить в регистр
mov         edx,dword ptr [c] ;// поместить в регистр ecx адрес переменной с
movaps      xmmword ptr [edx],xmm0 ;// поместить значение регистра в регистр ecx или сохранить сумму по адресу памяти, куда указывает (ecx) или с. При этом xmmword представляет собой один и тот же тип, что и _m128 - 128-битовое слово, в   котором 4 переменные с плавающей точкой 


Как видно из кода, одна инструкция addps обрабатывает сразу 4 переменных, которая реализована и поддерживается аппаратно процессором. Система не принимает никакого участия при обработке этих переменных, что дает хороший прирост производительности без лишних затрат со стороны.
При этом хотел бы отметить одну особенность, что в данном примере и компилятором используется инструкция movups, для которой не требуются операнды, которые должны быть выровнены по 16-байтовой границе. Из чего следует, что можно было бы не выравнивать массив a. Однако, массив b необходимо выровнять, иначе в операции addps возникнет ошибка чтения памяти, ведь регистр складывается со 128-битным расположением в памяти. В другом компиляторе или среде могут быть другие инструкции, поэтому лучше в любом случае для всех операндов, принимающих участие в подобных операциях, делать выравнивание по границе. Во всяком случае во избежание проблем с памятью.
Еще одна причина делать выравнивание, так это когда мы оперируем с элементами массивов (и не только с ними), то на самом деле постоянно работаем с кэш-линиями размером по 64 байта. SSE и AVX векторы всегда попадают в одну кэш линию, если они выравнены по 16 и 32 байта, соответственно. А вот если наши данные не выравнены, то, очень вероятно, нам придётся подгружать ещё одну «дополнительную» кэш-линию. Процесс этот достаточно сильно сказывается на производительности, а если мы при этом и к элементам массива, а значит, и к памяти, обращаемся непоследовательно, то всё может быть ещё хуже.

Поддержка SIMD в .NET


Впервые упоминание o поддержке JIT технологии SIMD было объявлено в блоге .NET в апреле 2014 года. Тогда разработчики анонсировали новую превью-версию RyuJIT, которая обеспечивала SIMD функциональность. Причиной добавления стала довольно высокая популярность запроса на поддержку C# and SIMD. Изначальный набор поддерживаемых типов был не большим и были ограничения по функциональности. Изначально поддерживался набор SSE, а AVX обещали добавить в релизе. Позже были выпущены обновления и добавлены новые типы с поддержкой SIMD и новые методы для работы с ними, что в последних версиях представляет обширную и удобную библиотеку для аппаратной обработки данных.
image
Такой подход облегчает жизнь разработчика, который не должен писать CPU-зависимый код. Вместо этого CLR абстрагирует аппаратное обеспечение, предоставляя виртуальную исполняющую среду, которая переводит свой ​​код в машинные команды либо во время выполнения (JIT), либо во время установки (NGEN). Оставляя генерацию кода CLR, вы можете использовать один и тот же MSIL код на разных компьютерах с разными процессорами, не отказываясь от оптимизаций, специфических для данного конкретного CPU.
На данный момент поддержка этой технологии в .NET представлена в пространстве имен System.Numerics.Vectors и представляет собой библиотеку векторных типов, которые могут использовать преимущества аппаратного ускорения SIMD. Аппаратное ускорение может приводить к значительному повышению производительности при математическом и научном программировании, а также при программировании графики. Она содержит следующие типы:
  • Vector — коллекцию статических удобных методов для работы с универсальными векторами
  • Matrix3x2 — представляет матрицу 3х2
  • Matrix4х4 — представляет матрицу 4х4
  • Plane — представляет трехмерную плоскость
  • Quaternion — представляет вектор, используемый для кодирования трехмерных физических поворотов
  • Vector<(Of <(<'T>)>)> представляет вектор указанного числового типа, который подходит для низкоуровневой оптимизации параллельных алгоритмов
  • Vector2 — представляет вектор с двумя значениями одинарной точности с плавающей запятой
  • Vector3 — представляет вектор с тремя значениями одинарной точности с плавающей запятой
  • Vector4 — представляет вектор с четырьмя значениями одинарной точности с плавающей запятой

Класс Vector предоставляет методы для сложения, сравнения, поиска минимума и максимума и многих других преобразований над векторами. При этом операции работают с использованием технологии SIMD. Остальные типы также поддерживают аппаратное ускорение и содержат специфические для них преобразования. Для матриц это может быть перемножение, для векторов евклидово расстояние между точками и т.д.

Пример программы на C#


Итак, что необходимо для того, чтобы использовать данную технологию? Необходимо в первую очередь иметь RyuJIT компилятор и версию .NET 4.6. System.Numerics.Vectors через NuGet не ставится, если версия ниже. Однако, уже при установленной библиотеке я понижал версию и все работало как надо. Затем необходима сборка под x64, для этого необходимо убрать в свойствах проекта «предпочитать 32-разрядную платформу» и можно собирать под Any CPU.
Листинг:
using System;
using System.Numerics;

    class Program
    {
        static void Main(string[] args)
        {
            const Int32 N = 8;
            Single[] a = { 41982.0F, 81.5091F, 3.14F, 42.666F, 54776.45F, 342.4556F, 6756.2344F, 4563.789F };
            Single[] b = { 85989.111F, 156.5091F, 3.14F, 42.666F, 1006.45F, 9999.4546F, 0.2344F, 7893.789F };
            Single[] c = new Single[N];

            for (int i = 0; i < N; i += Vector<Single>.Count) // Count возвращает 16 для char, 4 для float, 2 для      double и т.п.
            {
                var aSimd = new Vector<Single>(a, i); // создать экземпляр со смещением i
                var bSimd = new Vector<Single>(b, i);
                Vector<Single> cSimd = aSimd + bSimd; // или так Vector<Single> c_simd = Vector.Add(b_simd, a_simd);
                cSimd.CopyTo(c, i); //копировать в массив со смещением
            }

            for (int i = 0; i < a.Length; i++)
            {
                Console.WriteLine(c[i]);
            }
            Console.ReadKey();
        }
    }


С общей точки зрения что С++ подход, что .NET довольно схожи. Необходимо преобразование/копирование исходных данных, выполнить копирование в конечный массив. Однако, подход с C# намного проще, многие вещи сделаны за Вас и Вам только остается пользоваться и наслаждаться. Нет необходимости думать о выравнивании данных, заниматься выделением памяти и делать это статически, либо динамически с определенными операторами. С другой стороны у вас больший контроль над происходящим с использованием указателей, но и больше ответственности за происходящее.
А в цикле происходит все так, как и в цикле в С++. И я не про указатели. Алгоритм расчета такой же. На первой итерации мы заносим первые 4 элемента исходных массивов в структуру aSimd и bSimd, затем суммируем и сохраняем в конечном массиве. Затем на следующей итерации заносим следующие 4 элемента при помощи смещения и суммируем их. Вот так все просто и быстро делается. Рассмотрим код, который генерирует компилятор для этой команды var cSimd = aSimd + bSimd:
addps       xmm0,xmm1  


Отличие от С++ версии только в том, что тут складываются оба регистра, в то время как там было складывание регистра с участком памяти. Помещение в регистры происходит при инициализации aSimd и bSimd. В целом данный подход, если сравнивать код компиляторов С++ и .NET не особо отличается и дает приблизительно равную производительность. Хотя вариант с указателями будет работать все равно быстрее. Хотелось бы отметить, что SIMD-инструкции генерируются при включенной оптимизации кода. Т.е. увидеть их в дизассемблере в Debug не получится: это реализовано в виде вызова функции. Однако в Release, где включена оптимизация, вы получите эти инструкции в явном(встроенном) виде.

Напоследок


Что мы имеем:
  • Во многих случаях векторизация дает 4-8× увеличение производительности
  • Сложные алгоритмы потребуют изобретательность, но без этого никуда
  • System.Numerics.Vectors в настоящее время обхватывает только часть simd-инструкций. Для более серьезного подхода потребуется С++
  • Есть множество других способов помимо векторизации: правильное использование кэша, многопоточность, гетерогенные вычисления, грамотная работа с памятью(чтобы сборщик мусора не потел) и т.д.

В ходе краткой твиттер-переписки с Сашой Голдштейном(одного из авторов книги «Оптимизация приложений на платформе .NET»), который рассматривал аппаратное ускорение в .NET, я поинтересовался, что как на его взгляд реализована поддержка SIMD в .NET и какова она в сравнении с С++. На что он ответил: «Несомненно, вы можете сделать больше на С++, чем на C#. Но вы действительно получаете кросс-процессорную поддержку на С#. Например, автоматический выбор между SSE4 и AVX». В целом, это не может не радовать. Ценой малых усилий мы можем получать от системы как можно большей производительности, задействуя все возможные аппаратные ресурсы.
Для меня это очень хорошая возможность разрабатывать производительные программы. По крайней мере у себя в дипломной работе, по моделированию физических процессов, я в основном добивался эффективности путем создания некоторого количества потоков, а также при помощи гетерогенных вычислений. Использую как CUDA, так и C++ AMP. Разработка ведется на универсальной платформе под Windows 10, где меня очень привлекает WinRT, который позволяет писать программу как на C#, так и на С++/CX. В основном на плюсах пишу ядро для больших расчетов (Boost), а на C# уже манипулирую данными и разрабатываю интерфейс. Естественно, перегон данных через двоичный интерфейс ABI для взаимодействия двух языков имеет свою цену(хотя и не очень большую), что требует более разумной разработки библиотеки на С++. Однако у меня данные пересылаются только в случае необходимости и довольно редко, только для отображения результатов.
В случае надобности манипулирования данными в C#, я их преобразую в типы .NET, чтобы не работать с типами WinRT, тем самым увеличивая производительность обработки уже на C#. Так например, когда нужно обработать несколько тысяч или десятков тысяч элементов или требования к обработке не имеют никаких особых спецификаций, данные можно рассчитать в C# без задействования библиотеки (в ней считаются от 3 до 10 миллионов экземпляров структур, иногда только за одну итерацию). Так что подход с аппаратным ускорением упростит задачу и сделает ее быстрее.
image

Список источников при написании статьи


Ну и отдельное спасибо Саше Голдштейну за предоставленную помощь и информацию.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.