-Поиск по дневнику

Поиск сообщений в rss_rss_hh_new

 -Подписка по e-mail

 

 -Статистика

Статистика LiveInternet.ru: показано количество хитов и посетителей
Создан: 17.03.2011
Записей:
Комментариев:
Написано: 51

Habrahabr/New








Добавить любой RSS - источник (включая журнал LiveJournal) в свою ленту друзей вы можете на странице синдикации.

Исходная информация - http://habrahabr.ru/rss/new/.
Данный дневник сформирован из открытого RSS-источника по адресу http://feeds.feedburner.com/xtmb/hh-new-full, и дополняется в соответствии с дополнением данного источника. Он может не соответствовать содержимому оригинальной страницы. Трансляция создана автоматически по запросу читателей этой RSS ленты.
По всем вопросам о работе данного сервиса обращаться со страницы контактной информации.

[Обновить трансляцию]

[Перевод] AWS DeepLearning AMI — почему (и как) его стоит использовать

Суббота, 15 Июля 2017 г. 04:17 + в цитатник

Иногда хорошие вещи приходят бесплатно ...


Что такое AMI?


Для тех из вас, кто не знает, что такое AMI, позвольте мне процитировать официальную документацию по этому вопросу:


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

Этого должно быть достаточно, чтобы понять остальную часть статьи, однако я бы посоветовал потратить некоторое время на официальную документацию об AMI.


Если вдруг не слышали термин Deep learning ранее...

Глубокое обучение или глубинное (англ. Deep learning) — набор алгоритмов машинного обучения, которые пытаются моделировать высокоуровневые абстракции в данных, используя архитектуры, состоящие из множества нелинейных преобразований
Глубокое обучение является частью более широкого семейства методов машинного обучения, которые подбирают представление данных. Наблюдение (например, изображение) может быть представлено многими способами, такими как вектор интенсивности значений на пиксель, или (в более абстрактной форме) как множество примитивов, областей определенной формы, и т. д.
С другой стороны есть и мнения, что глубокое обучение — не что иное, как модное слово или ребрендинг для нейронных сетей. Wiki.


Что такое AWS DeepLearning AMI (а.к.а. DLAMI) и почему его нужно использовать?


Обучение (тренировку) нейронных сетей можно делать 2-мя путями: с использование CPU или с использованием GPU. Думаю ни для кого не секрет что обучение с помощью GPU показывает лучшие результаты, с точки зрения скорости(а как следствие и затрат), чем обучение с помощью CPU, поэтому все современные системы машинного обучения поддерживают GPU. Однако, чтобы использовать все преимущества GPU мало просто иметь этот самый GPU, вам необходимо еще "по приседать":


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

Так что же нужно сделать что бы решить все эти 4 незадачи? Есть 2 варианта:


  1. Загрузить исходный код сети и библиотеку для работы с GPU, а затем соберите все самостоятельно в нужной конфигурации(в реальности это еще сложнее сделать чем звучит).
  2. Найти сборку фреймворка с поддержкой GPU, затем установить все необходимое и надеется что оно взлетит.

Оба случая имеют разные плюсы и минусы, однако есть один большой минус для этих вариантов — оба требуют от пользователя некоторых технических знаний. Это основная причина, по которой не так много людей, как хотелось бы, тренируют нейронные сети на GPU.


Как DLAMI может решить эту проблему? Да легко, дело в том, что DLAMI, это первое бесплатное решение, включающее все, что необходимо прямо из коробки:


  • Драйвера для новейшего графического процессора от Nvidia;
  • Последние библиотеки CUDA и CuDNN;
  • Предварительно собранные фреймворки с поддержкой GPU (и собранные с теми версиями CUDA и CuDNN которые доступны в AMI).

Аль, к слову, список фреймворков, которые работают из коробки:



DLAMI можно использовать с GPU-совместимым машинами на AWS, например P2 или G2:


image


Можете, кстати, попробовать поиграться со свеже выпущенными G3


Надеюсь, теперь у нас есть ответ на вопрос: почему и кому нужно использовать DLAMI. Теперь давайте обсудим ответ на следующий вопрос ...


Как именно можно создать машину с DLAMI?


Для этого сначала нужно выбрать, какой вариант DLAMI более предпочтителен:


  • на базе Ubuntu (может использоваться с любыми пакетами Ubuntu).
  • на базе Amazon Linux (включает все программы AWS, такие как awscli, из коробки).

Если с типом DLAMI определились то перейдем с способам создания машин на базе DLAMI:


  • С помощью AWS EC2 Marketplace:
  • С помощью консоли EC2.

Консоль EC2 фактически предоставляет два способа ее создания, обычное создание:



И ускоренное создание консоли EC2, применяя конфигурацию по умолчанию:



Проблемы с обновлениями


Существует одна оговорка, которую необходимо обсудить. Поскольку все фреймворки построены с нуля, вы не можете просто так взять и обновить их до последней версии, есть риск получить версию фреймворка, которая не собрана с поддержкой GPU (или не совместима с версией CUDA). Так что обновляйте пакеты на свой страх и риск!


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


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

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/333380/


Метки:  

IBM и ВВС США разрабатывают нейроморфный суперкомпьютер нового поколения

Суббота, 15 Июля 2017 г. 02:19 + в цитатник


Еще в 2014 году компания IBM представила чип нового типа, который предназначен для реализации нейронных сетей. Тогда разработчики рассказывали о том, что чип содержит 1 миллион нейронов и 256 миллионов синапсов, что, с некоторыми оговорками, похоже на архитектуру неокортекса. Такой процессор можно использовать для решения задач, где требуется высокая и сверхвысокая производительность. Например, с его помощью можно классифицировать объекты в видеопотоке (это уже делается) в режиме реального времени.

Несколько позже Ливерморская национальная лаборатория (LLNL) объявила о создании производительного компьютера, принцип работы которого схож с принципом работы мозга человека. Система включает 16 миллионов нейронов и 4 млрд синапсов. В компьютере, который был разработан лабораторией, всего 16 TrueNorth чипов, а сама система получила название IBM Neuromorphic System. Для чего такие системы могут использоваться?

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

Корпорация IBM старается совершенствовать новую технологию. Сейчас она участвует в совместном проекте с ВВС США. Партнеры собираются создать новый суперкомпьютер, который будет включать 64 миллиона нейронов и 16 миллиардов синапсов. При этом потреблять он будет всего 10 Вт энергии, то есть его можно будет подключить к обычной энергосети.




По мнению разработчиков, новый компьютер (его название TrueNorth Neurosynaptic System) позволит работать с несколькими источниками данных одновременно (видео, изображения, аудио и текст). Сейчас один нейроморфный процессор состоит из 5,4 млн транзисторов, составляющих ядра, всего их 4096. Ядра, в свою очередь создают массив из 1 млн цифровых нейронов, которые взаимодействуют друг с другом посредством 256 миллионов электрических синапсов.

Зачем все это военным? Дело в том, что ВВС США, а именно Air Force Research Lab (AFRL), использует возможности процессора для идентификации военных и гражданских транспортных средств при радиолокации с воздуха. Военные утверждают, что чип работает не хуже, чем мощный военный компьютер. Но энергии при этом потребляется в двадцать раз меньше. В дальнейшем, с увеличением производительности чипов, эффективность работы систем будет гораздо выше.



Чип TrueNorth изготовлен по 28 нм техпроцессу. Он содержит 5,4 миллиарда транзисторов и представляет собой нейроморфную систему со следующими характеристиками:

• один миллион эмулируемых «нейронов»
• 256 миллионов эмулируемых связей между нейронами — «синапсов»
• около 400 мегабит SRAM памяти (приблизительно 50 мегабайт)



Но не только военные интересуются такими процессорами. Корпорация Samsung ранее создала на основе TrueNorth систему машинного зрения. Ее принцип работы отличается от принципа работы обычных камер. И эта система обеспечивает обработку видеопотока со скоростью в 2 тысячи кадров в секунду и выше. У самых современных камер этот показатель не превышает 120 fps.

Но все это — лишь начальный этап. Ведь такие процессоры позволяют создавать самообучающиеся системы, которые максимально близко могут приблизиться к архитектуре мозга человека. Сейчас возможности TrueNorth изучают (и расширяют их) более 40 научно-исследовательских организаций, включая самые известные.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/333378/


Метки:  

Security Week 28: а Petya сложно открывался, в Android закрыли баг чипсета Broadcomm, Copycat заразил 14 млн девайсов

Пятница, 14 Июля 2017 г. 22:53 + в цитатник
Прошлогодний троянец-криптолокер Petya, конечно, многое умеет – ломает MBR и шифрует MFT, но сделаться столь же знаменитым как его эпигоны, у него не вышло. Но вся эта история с клонами – уничтожителями данных, видимо, настолько расстроила Януса, автора первенца, что тот взял и выложил закрытый ключ от него.

Однако опубликовал не просто так «нате, владейте!», а решил подойти к процессу творчески и поиграть с «белыми шляпами», запаролив архив и оставив в своем твиттере ссылку на него с подсказкой в виде цитаты из фильма «Золотой глаз». Авось, хотя бы так заметят и запомнят. Автор, видимо, фанат бондианы – отсюда и его никнейм, и название файла с ключом (Natalya), и имена троянцев Petya и Misha. Быстрее всех загадку отгадали в Malwarebytes и выложили содержимое файла:

Congratulations!
Here is our secp192k1 privkey:
38dd46801ce61883433048d6d8c6ab8be18654a2695b4723
We used ECIES (with AES-256-ECB) Scheme to encrypt the decryption password into the "Personal Code" which is BASE58 encoded.


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

Это не первый случай публикации ключа от криптолокера – например, что-то такое произошло год назад с TeslaCrypt. Сейчас Янус просто закрыл свой проект, заодно перекрыв кислород подражателям, зарабатывающим на слегка модифицированном Petya. Увы, жертвам exPetr/non-Petya эта благотворительность никак не поможет.

Google закрыл баг Broadpwn в Android

Новость. Бытует мнение, что подцепить инфекцию на смартфон непросто – надо свалять конкретного дурака: согласиться на загрузку неведомого файла типа adobe_flash_update_mamoi_klyanus_bez_virusov.apk, разрешить установку недоверенных программ и наконец, самому лично все инсталлировать. Однако ж есть и на мобильных осях прямая и явная угроза – RCE-баги, которые регулярно находят и закрывают. На этот раз исследователь из Exodus Intelligence анонсировал доклад на Black HAT USA 2017 об особо неприятном баге CVE-2017-9417, связанном с WiFi-чипами BCM43xx производства Broadcomm. Назвали его Broadpwn, что как бы должно свидетельствовать об уровне опасности. И он позволяет запускать произвольный код в контексте ядра, причем атака проводится удаленно. Кроме того интересно, что продемонстрированный эксплойт успешно обходит DEP и ASLR.

Полного списка уязвимых моделей смартфонов нет, но автор эксплойта заявляет, что баг имеется во всех флагманах Samsung, во многих моделях LG и HTC, а также в нескольких iPhone. Кстати, о возможности эксплуатации Broadpwn под iOS пока вообще ничего неизвестно, и Apple молчит об этом баге, как сами знаете кто. Как Apple.

Помимо Broadpwn в cвежее обновление Google попали патчи еще для 11 критических дыр, в том числе для RCE-бага CVE-2017-0540, который позволяет через специально созданный файл запускать код в контексте привилегированного процесса. Присутствует эта “черная дыра” в Android 5.0.2, 5.1.1, 6.0, 6.0.1, 7.0, 7.1.1 и 7.1.2. Патчи получат владельцы Nexus и Pixel, остальные – как повезет. В общем, то самое чувство, когда Google при очередном апдейте устраняет сразу несколько RCE-уязвимостей, но ты понимаешь, что на твой смартфон, выпущенный год назад, патча не будет никогда.

Троянец Copycat заразил 14 млн Android-устройств

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

Троянец распространяется не только традиционными методами, такими как вредоносные лендинги и спам. Его внедряют в популярные приложения и выкладывают в сторонние магазины приложений, которые так популярны в Азии, – соответственно, большая часть заражений зафиксирована именно в этом регионе.



После установки CopyCat достает пачку отмычек-эксплойтов и пытается получить root-права в системе, после чего радостно внедряет свою библиотеку в процесс демона-лаунчера Zygote. Далее он подменяет параметр реферрера установки (install_referrer) так, чтобы получать деньги, что вкладывает издатель приложения в продвижение. Также он умеет подменяет демонстрируемую пользователю рекламу, и способен устанавливать сторонние приложения, то есть выступает как гарантированный канал распространения чего угодно, что закажут.

Древности


«Attention-629»

Опасный нерезидентный вирус, записывается в .COM-файлы текущего каталога. Крайне примитивен. 800-й «потомок» вируса при старте должен сообщить: «Attention! I’m virus», однако содержит такое количество ошибок, что вряд ли доживет до такого почтенного возраста.

Цитата по книге «Компьютерные вирусы в MS-DOS» Евгения Касперского. 1992 год. Страницa 60.

Disclaimer: Данная колонка отражает лишь частное мнение ее автора. Оно может совпадать с позицией компании «Лаборатория Касперского», а может и не совпадать. Тут уж как повезет.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/333374/


Метки:  

[Перевод] Реверс-инжиниринг одной строчки JavaScript

Пятница, 14 Июля 2017 г. 22:52 + в цитатник
Несколько месяцев назад я получил от друга такое письмо:



Тема: Можешь развернуть и объяснить мне эту одну строчку кода?

Текст:Считай меня тупым, но… я не понимаю её и буду благодарен, если растолкуешь подробно. Это трассировщик лучей в 128 символах. Мне кажется, он восхитительный.




Эта строчка JavaScript отрисует анимацию, которая показана на изображении под катом. В браузере она запускается здесь. Скрипт написан автором www.p01.org, где вы можете найти эту и много других классных демок.



Вызов принят!

Reverse Engineering One Line of JavaScript : https://t.co/QsTzYBvWbu cc @akras14

— Binni Shah (@binitamshah) July 13, 2017

Часть I. Извлекаем читаемый код


Первым делом я оставил HTML в HTML, код JavaScript перенёс в файл code.js, а p закавычил в id="p".

index.html


Я заметил, что там переменная k — просто константа, так что убрал её из строчки и переименовал в delay.

code.js
var delay = 64;
var draw = "for(n+=7,i=delay,P='p.\\n';i-=1/delay;P+=P[i%2?(i%2*j-j+n/delay^j)&1:2])j=delay/i;p.innerHTML=P";
var n = setInterval(draw, delay);

Далее, var draw был просто строкой, которая исполнялась как функция eval с периодичностью setInterval, поскольку setInterval может принимать и функции, и строки. Я перенёс var draw в явную функцию, но сохранил изначальную строку для справки на всякий случай.

Ещё я заметил, что элемент p в действительности ссылался на элемент DOM с идентификатором p, объявленным в HTML, который я недавно закавычил. Оказывается, на элементы в JavaScript можно ссылаться по их идентификатору, если id состоит только из букв и цифр. Я добавил document.getElementById("p"), чтобы сделать код понятнее.

var delay = 64;
var p = document.getElementById("p"); // < --------------
// var draw = "for(n+=7,i=delay,P='p.\\n';i-=1/delay;P+=P[i%2?(i%2*j-j+n/delay^j)&1:2])j=delay/i;p.innerHTML=P";
var draw = function() {
    for (n += 7, i = delay, P = 'p.\n'; i -= 1 / delay; P += P[i % 2 ? (i % 2 * j - j + n / delay ^ j) & 1 : 2]) {
        j = delay / i; p.innerHTML = P;
    }
};
var n = setInterval(draw, delay);

Затем я объявил переменные i, p и j и перенёс их в начало функции.

var delay = 64;
var p = document.getElementById("p");
// var draw = "for(n+=7,i=delay,P='p.\\n';i-=1/delay;P+=P[i%2?(i%2*j-j+n/delay^j)&1:2])j=delay/i;p.innerHTML=P";
var draw = function() {
    var i = delay; // < ---------------
    var P ='p.\n';
    var j;
    for (n += 7; i > 0 ;P += P[i % 2 ? (i % 2 * j - j + n / delay ^ j) & 1 : 2]) {
        j = delay / i; p.innerHTML = P;
        i -= 1 / delay;
    }
};
var n = setInterval(draw, delay);

Я разложил цикл for и преобразовал его в цикл while. Из трёх частей прежнего for осталась только одна часть CHECK_EVERY_LOOP, а всё остальное (RUNS_ONCE_ON_INIT; DO_EVERY_LOOP) перенёс за пределы цикла.

var delay = 64;
var p = document.getElementById("p");
// var draw = "for(n+=7,i=delay,P='p.\\n';i-=1/delay;P+=P[i%2?(i%2*j-j+n/delay^j)&1:2])j=delay/i;p.innerHTML=P";
var draw = function() {
    var i = delay;
    var P ='p.\n';
    var j;
    n += 7;
    while (i > 0) { // <----------------------
        //Update HTML
        p.innerHTML = P;
 
        j = delay / i;
        i -= 1 / delay;
        P += P[i % 2 ? (i % 2 * j - j + n / delay ^ j) & 1 : 2];
    }
};
var n = setInterval(draw, delay);

Здесь я развернул троичный оператор ( condition ? do if true : do if false) in P += P[i % 2 ? (i % 2 * j - j + n / delay ^ j) & 1 : 2];.

i%2 проверяет, является переменная i чётной или нечётной. Если она четная, то просто возвращает 2. Если нечётная, то возвращает «магическое» значение magic (i % 2 * j - j + n / delay ^ j) & 1; (подробнее об этом чуть позже).

Это значение (индекс) используется для сдвига строки P, так что назовём index и превратим строку в P += P[index];.

var delay = 64;
var p = document.getElementById("p");
// var draw = "for(n+=7,i=delay,P='p.\\n';i-=1/delay;P+=P[i%2?(i%2*j-j+n/delay^j)&1:2])j=delay/i;p.innerHTML=P";
var draw = function() {
    var i = delay;
    var P ='p.\n';
    var j;
    n += 7;
    while (i > 0) {
        //Update HTML
        p.innerHTML = P;
 
        j = delay / i;
        i -= 1 / delay;
 
        let index;
        let iIsOdd = (i % 2 != 0); // <---------------
 
        if (iIsOdd) { // <---------------
            index = (i % 2 * j - j + n / delay ^ j) & 1;
        } else {
            index = 2;
        }
 
        P += P[index];
    }
};
var n = setInterval(draw, delay);

Я разложил & 1 из значения index = (i % 2 * j - j + n / delay ^ j) & 1 в ещё один оператор if.

Здесь хитрый способ проверки на чётность результата в круглых скобках, когда для чётного значения возвращается 0, а для нечётного — 1. & — это побитовый оператор AND. Он работает так:

  • 1 & 1 = 1
  • 0 & 1 = 0

Следовательно, something & 1 преобразует "something" в двоичное представление, а также добивает перед единицей необходимое количество нулей, чтобы соответствовать размеру "something", и возвращает просто результат AND последнего бита. Например, 5 в двоичном формате равняется 101, так что если мы применим на ней логическую операцию AND с единицей, то получится следующее:

    101
AND 001
    001

Другими словами, пятёрка — нечётное число, а результатом 5 AND 1 (5 & 1) будет 1. В консоли JavaScript легко проверить соблюдение этой логики.

0 & 1 // 0 - even return 0
1 & 1 // 1 - odd return 1
2 & 1 // 0 - even return 0
3 & 1 // 1 - odd return 1
4 & 1 // 0 - even return 0
5 & 1 // 1 - odd return 1

Обратите внимание, что я также переименовал остальную часть index в magic, так что код с развёрнутым &1 будет выглядеть следующим образом:

var delay = 64;
var p = document.getElementById("p");
// var draw = "for(n+=7,i=delay,P='p.\\n';i-=1/delay;P+=P[i%2?(i%2*j-j+n/delay^j)&1:2])j=delay/i;p.innerHTML=P";
var draw = function() {
    var i = delay;
    var P ='p.\n';
    var j;
    n += 7;
    while (i > 0) {
        //Update HTML
        p.innerHTML = P;
 
        j = delay / i;
        i -= 1 / delay;
 
        let index;
        let iIsOdd = (i % 2 != 0);
 
        if (iIsOdd) {
            let magic = (i % 2 * j - j + n / delay ^ j);
            let magicIsOdd = (magic % 2 != 0); // &1 < --------------------------
            if (magicIsOdd) { // &1 <--------------------------
                index = 1;
            } else {
                index = 0;
            }
        } else {
            index = 2;
        }
 
        P += P[index];
    }
};
var n = setInterval(draw, delay);

Далее я развернул P += P[index]; в оператор switch. К этому моменту стало понятно, что index может принимать только одно из трёх значений — 0, 1 или 2. Также понятно, что переменная P всегда инициализируется со значениями var P ='p.\n';, где 0 указывает на p, 1 указывает на ., а 2 указывает на \n — символ новой строки

var delay = 64;
var p = document.getElementById("p");
// var draw = "for(n+=7,i=delay,P='p.\\n';i-=1/delay;P+=P[i%2?(i%2*j-j+n/delay^j)&1:2])j=delay/i;p.innerHTML=P";
var draw = function() {
    var i = delay;
    var P ='p.\n';
    var j;
    n += 7;
    while (i > 0) {
        //Update HTML
        p.innerHTML = P;
 
        j = delay / i;
        i -= 1 / delay;
 
        let index;
        let iIsOdd = (i % 2 != 0);
 
        if (iIsOdd) {
            let magic = (i % 2 * j - j + n / delay ^ j);
            let magicIsOdd = (magic % 2 != 0); // &1
            if (magicIsOdd) { // &1
                index = 1;
            } else {
                index = 0;
            }
        } else {
            index = 2;
        }
 
        switch (index) { // P += P[index]; <-----------------------
            case 0:
                P += "p"; // aka P[0]
                break;
            case 1:
                P += "."; // aka P[1]
                break;
            case 2:
                P += "\n"; // aka P[2]
        }
    }
};
 
var n = setInterval(draw, delay);

Я разобрался с оператором var n = setInterval(draw, delay);. Метод setInterval возвращает целые числа, начиная с единицы, увеличивая значение при каждом вызове. Это целое число может использоваться для clearInterval (то есть для отмены). В нашем случае setInterval вызывается всего один раз, а переменная n просто установилась в значение 1.

Я также переименовал delay в DELAY для напоминания, что это всего лишь константа.

И последнее, но не менее важное, я поместил круглые скобки в i % 2 * j - j + n / DELAY ^ j для указания, что у ^ (побитового XOR) меньший приоритет, чем у операторов %, *, -, + и /. Другими словами, сначала выполнятся все вышеупомянутые вычисления, а уже потом ^. То есть получается (i % 2 * j - j + n / DELAY) ^ j).

Уточнение: Мне указали, что я ошибочно поместил p.innerHTML = P; //Update HTML в цикл, так что я убрал его оттуда.

const DELAY = 64; // approximately 15 frames per second 15 frames per second * 64 seconds = 960 frames
var n = 1;
var p = document.getElementById("p");
// var draw = "for(n+=7,i=delay,P='p.\\n';i-=1/delay;P+=P[i%2?(i%2*j-j+n/delay^j)&1:2])j=delay/i;p.innerHTML=P";
 
/**
 * Draws a picture
 * 128 chars by 32 chars = total 4096 chars
 */
var draw = function() {
    var i = DELAY; // 64
    var P ='p.\n'; // First line, reference for chars to use
    var j;
 
    n += 7;
 
    while (i > 0) {
 
        j = DELAY / i;
        i -= 1 / DELAY;
 
        let index;
        let iIsOdd = (i % 2 != 0);
 
        if (iIsOdd) {
            let magic = ((i % 2 * j - j + n / DELAY) ^ j); // < ------------------
            let magicIsOdd = (magic % 2 != 0); // &1
            if (magicIsOdd) { // &1
                index = 1;
            } else {
                index = 0;
            }
        } else {
            index = 2;
        }
 
        switch (index) { // P += P[index];
            case 0:
                P += "p"; // aka P[0]
                break;
            case 1:
                P += "."; // aka P[1]
                break;
            case 2:
                P += "\n"; // aka P[2]
        }
    }
    //Update HTML
    p.innerHTML = P;
};
 
setInterval(draw, 64);

Окончательный результат выполнения можно увидеть здесь.

Часть 2. Понимание кода


Так что здесь происходит? Давайте разберёмся.

Изначально значение i установлено на 64 посредством var i = DELAY;, а затем каждый цикл оно уменьшается на 1/64 (0,015625) через i -= 1 / DELAY;. Цикл продолжается, пока i больше нуля (код while (i > 0) {). Поскольку за каждый проход i уменьшается на 1/64, то требуется 64 цикла, прежде чем оно уменьшится на единицу (64/64 = 1). В целом уменьшение i произойдёт 64x64 = 4096 раз, чтобы уменьшиться до нуля.

Изображение состоит из 32 строк, со 128 символами в каждой. Очень удобно, что 64 x 64 = 32 x128 = 4096. Значение i может быть чётным (не нечётным let iIsOdd = (i % 2 != 0);), если i является строго чётным числом. Такое произойдёт 32 раза, когда оно равняется 64, 62, 60 и т. д. Эти 32 раза index примет значение 2 index = 2;, а к строке добавится символ новой строки: P += "\n"; // aka P[2]. Остальные 127 символов в строке примут значения p или ..

Но когда устанавливать p, а когда .?

Ну, для начала нам точно известно, что следует установить . при нечётном значении let magic = ((i % 2 * j - j + n / DELAY) ^ j);, или установить p, если «магия» чётная.

var P ='p.\n';
 
...
 
if (magicIsOdd) { // &1
    index = 1; // second char in P - .
} else {
    index = 0; // first char in P - p
}

Но когда magic чётное, а когда нечётное? Это вопрос на миллион долларов. Перед тем как перейти к нему, давайте определим ещё кое-что.

Если убрать + n/DELAY из let magic = ((i % 2 * j - j + n / DELAY) ^ j);, то получится статическая картинка, на которой вообще ничего не двигается:



Теперь посмотрим на magic без + n/DELAY. Как получилась эта красивая картинка?

(i % 2 * j - j) ^ j

Обратите внимание, что получается в каждом цикле:

j = DELAY / i;
i -= 1 / DELAY;

Другими словами, мы может выразить j через конечное i как j = DELAY/ (i + 1/DELAY). Но поскольку 1/DELAY слишком малое число, то для этого примера можно отбросить + 1/DELAY и упростить выражение до j = DELAY/i = 64/i.

В таком случае мы можем переписать (i % 2 * j - j) ^ j как i % 2 * 64/i - 64/i) ^ 64/i.

Используем онлайновый графический калькулятор для отрисовки графиков некоторых из этих функций.

Прежде всего, отрисуем i%2.

Выходит симпатичный график со значениями y от 0 до 2.



Если отрисовать 64/i, то получим такой график:



Если отрисовать всю левую сторону выражения, то получится график, который выглядит как сочетание двух предыдущих.



В конце концов, если мы отрисуем две функции рядом друг с другом, то увидим следующее.



О чём говорят эти графики?


Давайте припомним вопрос, на который мы пытаемся ответить, то есть каким образом получилась такая красивая статическая картинка:



Мы знаем, что если «магия» (i % 2 * j - j) ^ j принимает чётное значение, то нужно добавить p, а для нечётного числа нужно добавить ..

Увеличим первые 16 строк нашего графика, где i имеет значения от 64 до 32.



Побитовый XOR в JavaScript отбросит все значения справа от запятой, так что это равнозначно применению метода Math.floor, который округляет число в меньшую сторону.

Он вернёт 0, если оба бита равны 1 или оба равны 0.

Наша j начинается с единицы и медленно продвигается к двойке, останавливаясь прямо около неё, так что можем считать её всегда единицей при округлении в меньшую сторону (Math.floor(1.9999) === 1), и нам нужна ещё одна единица с левой стороны, чтобы получить в результате ноль и дать нам p.

Другими словами, каждая зелёная диагональ представляет собой один ряд в нашем графике. Поскольку для первых 16 рядов значение j всегда больше 1, но меньше 2, то мы можем получить нечётное значение только в том случае, если левая сторона выражения (i % 2 * j - j) ^ j, она же i % 2 * i/64 — i/64, то есть зелёная диагональ, тоже будет выше 1 или ниже -1.

Вот некоторые результаты из консоли JavaScript, чтобы посмотреть результаты вычислений: 0 или -2 означают, что результат чётный, а 1 соответствует нечётному числу.

1 ^ 1 // 0 - even p
1.1 ^ 1.1 // 0 - even p
0.9 ^ 1 // 1 - odd .
0 ^ 1 // 1 - odd .
-1 ^ 1 // -2 - even p
-1.1 ^ 1.1 // -2 - even p

Если посмотреть на наш график, то там самая правая диагональная линия едва выходит выше 1 и ниже -1 (мало чётных чисел — мало символов p). Следующая выходит чуть дальше за эти границы, третья — ещё чуть дальше и т. д. Линия номер 16 едва удерживается в границах между 2 и -2. После линии 16 мы видим, что наш статический график меняет свой характер.



После 16-й строки значение j пересекает лимит 2, так что меняется ожидаемый результат. Теперь мы получим чётное число, если зелёная диагональная линия выше 2 или ниже -2, или внутри рамок 1 и -1, но не соприкасается с ними. Вот почему мы видим на картинке две или больше групп символов p начиная с 17-й строки.

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

Теперь вернёмся к + n/DELAY. В коде мы видим, что значение n начинается с 8 (1 от setInteval и плюс 7 на каждый вызов метода). Затем оно увеличивается на 7 при каждом срабатывании setInteval.

После достижения значения 64 график изменяется следующим образом.



Обратите внимание, что j по-прежнему находится около единицы, но теперь левая половина красной диагонали в пределах примерно 62-63 находится примерно около нуля, а правая половина в пределах примерно 63-64 — около единицы. Поскольку наши символы появляются в убывающем порядке от 64 к 62, то можно ожидать, что правая половина диагонали в районе 63-64 (1 ^ 1 = 0 // even) добавит кучку символов p, а левая половина диагонали в районе 62-63 (1 ^ 0 = 1 // odd) добавит кучку точек. Всё это будет нарастать слева направо, как обычный текст.

Рендеринг HTML для такого условия выглядит следующим образом (вы можете жёстко вбить значение n в редакторе CodePen и посмотреть). Это совпадает с нашими ожиданиями.



К этому моменту количество символов p выросло до постоянной величины. Например, в первом ряду половина всех значений всегда будут чётными. Теперь символы p и . будут только меняться местами.

Для примера, когда n увеличивается на 7 на следующем вызове setInterval, график немного изменится.



Обратите внимание, что диагональ для первого ряда (около отметки 64) сдвинулась примерно на один маленький квадратик вверх. Поскольку четыре больших квадратов представляют собой 128 символов, в одном большом квадрате будет 32 символа, а в одном маленьком квадрате 32/5 = 6,4 сивола (примерно). Если посмотрим на рендеринг HTML, то там первый ряд действительно сдвинулся вправо на 7 символов.



И один последний пример. Вот что происходит, если вызвать setInterval ещё семь раз, а n будет равняться 64+9x7.



Для первого ряда j по-прежнему равняется 1. Теперь верхняя половина красной диагонали около отметки 64 примерно упирается в два, а нижний конец около единицы. Это переворачивает картинку в другую сторону, поскольку теперь 1^2 = 3 // odd - . и 1 ^ 1 = 0 //even - p. Так что можно ожидать кучу точек, за которыми пойдут символы p.

Выглядеть это будет так.



График бесконечно зациклен.

Надеюсь, наша работа имеет какой-то смысл. Вряд ли я когда-нибудь смог бы самостоятельно придумать нечто подобное, но было интересно разобраться в этом коде.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/333372/


Метки:  

Цикл стартапа: как (в общем) работает венчурное инвестирование

Пятница, 14 Июля 2017 г. 20:56 + в цитатник

Метки:  

Чемпионы мира — о спортивном программировании

Пятница, 14 Июля 2017 г. 19:50 + в цитатник


DataArt давно дружит с командой ИТМО по спортивному программированию и помогает ей. Этим летом в гости в наш петербургский центр разработки пришли Илья Збань, Иван Белоногов и Владимир Смыкалов. Чемпионы мира 2017 года рассказали о том, как именно программисты соревнуются между собой, о тренировочных сборах, любимых задачах и сильнейших соперниках.

Олимпиада по программированию


Главное соревнование программистов — международная студенческая олимпиада под эгидой ACM (ACM-ICPC, или просто ICPC) — проходит с 1970-х, а в виде, близком к сегодняшнему, оформилась в 1989 году. Олимпиада предназначена для студентов и аспирантов, за редким исключением к соревнованиям не допускают программистов старше 24-х лет. К тому же, испытывать силы в финале можно только дважды, а в региональных отборах разрешается участвовать всего пять раз. На ранних этапах, проходящих по всему миру, соревнуются тысячи команд. Около сотни лучших доходят до финала.

Основные правила


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

Правила ICPC очень доходчиво изложены в ролике, выпущенном к чемпионату Урала по программированию — одному из первичных этапов отбора на Олимпиаду. Они едины для всех регионов и с 2013 года остались без изменений.

Языки и среда


В финале 2017 года можно было использовать языки Java, C++ и Python. Впрочем, понятно, что Python в принципе не очень быстрый — жюри не гарантировало, что на нем можно будет сдать задачу. Однако оно давало гарантии, что у них есть решения, написанные на этих языках, которые проходят все тесты.

На разных соревнованиях набор языков может быть различным. Например, на онлайн-платформе Codeforces допускается около 20 языков: от C++ и Java до Haskell и Perl.

Большинство команд в финалах пишет на C++, поскольку на первый план выходит скорость. В качестве среды разработки многие команды используют VIM (в нем, например, работали Иван и Илья) или Gina (в ней работал Владимир). Те, кто все же пишет на Java, как правило пользуются средой вроде Eclipse, поскольку писать на Java без автокомплита гораздо сложнее.

В ближайшее время можно ждать изменений, поскольку финалы теперь будет спонсировать JetBrains (20 лет до конца мая 2017 года спонсором ICPC был IBM). Это значит, что на них появится и продукция спонсора: IDEA для Java и CLion для С++. Возможно, после этого команды начнут широко пользоваться отладчиками, хотя пока чаще справляются без них.

Эволюция задач


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

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

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

Примеры задач


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



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

Процесс решения


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

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

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


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

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

Алгоритмы


Мы знаем довольно много алгоритмов, используем разные структуры данных: дерево отрезков, дерево Фенвика, декартово дерево и т. д. Иногда сбалансированное дерево поиска приходится писать самостоятельно, параллельно модифицировав его так, чтобы оно считало информацию, определенную условием задачи. Например, в языке С++ есть структура множества, которая умеет поддерживать множество чисел и, например, находить следующее. Задача же может требовать найти не следующее число, а сумму всех чисел, меньших или равных заданному. Стандартными структурами реализовать это не получится.

Приносить какие-то фрагменты коды с собой нельзя, но на чемпионате мира разрешается пользоваться так называемым team reference — распечатанным на бумаге набором алгоритмов. Хотя многое мы умеем писать с ходу, в этом году немало времени потратили на его подготовку — тестировали более сложные алгоритмы. Но в итоге записями не воспользовались вообще.

Объем кода напрямую не влияет на итоговую оценку, другое дело, что 1000 строк написать за отведенное время сложно. А придумав красивое лаконичное решение, можно уложиться всего в 10–15 минут. Именно под поиск таких изящных путей и заточены большинство условий: средний объем решения — 100-200 строк кода, хотя в некоторых случаях он может доходить до 300. В обычной жизни 300 строк не так уж много, но здесь у тебя есть всего пять часов на решение всех задач. Писать нужно быстро, а если в трех сотнях строк будет допущена ошибка — задача не пройдет, значит, все время на ее решение будет попросту потеряно. К тому же, чем длиннее код, тем труднее найти ошибку в распечатанной версии.

Другие турниры и тренировки



Денежные призы далеко не основная мотивация участников турниров. На фото: Иван Белоногов и Илья Збань — призеры VK Cup 2015 (источник — страница Ивана Белоногова). В 2017-м призером VK Cup стал третий участник чемпионской команды ИТМО Владимир Смыкалов.

Мы постоянно участвуем в индивидуальных турнирах — их проводится очень много. Например, соревнования на российском сайте Codeforces регулярно собирают по несколько тысяч человек, из которых россиян обычно около 20 %. Стандартный тур здесь состоит из пяти алгоритмических задач, которые нужно решить за два часа. Самое главное в сложившемся вокруг этого ресурса сообществе — личный рейтинг, рассчитанный по системе Эло, как в шахматах. Успешно выступая на турнирах, программисты получают очки — их определенное количество автоматически меняет цвет ника. Те, у кого ники красные, получают не только просьбы о помощи, но и предложения от работодателей. А главное, как любые спортсмены-чемпионы, пользуются всеобщим уважением — для многих участников «красный ник» сам по себе служит достаточным стимулом для борьбы.


Круче красных ников, только красные ники с первой черной буквой. 13 июля в двадцатке лучших на Codeforces было восемь россиян, по двое украинцев, поляков и китайцев и по одному представителю Швейцарии, Австралии, Кореи, США, Тайваня и Беларуси. При этом белорусский программист сейчас возглавляет рейтинг, хотя в принципе перестановки в таблице происходят постоянно.

Крупные соревнования проводят Mail.ru, Яндекс, Facebook, Google и другие компании. Например, в первом раунде текущего турнира Google Code Jam участвовало 20 тысяч человек. Тысяча лучших получили фирменные футболки, 25 — поедут на финал, который в этом году пройдет в Дублине.

Помимо Google Code Jam, Google проводил еще один турнир — Hash Code, финал которого в проходил в головном офисе компании. Участникам, в частности, выдавались планы зданий, которые нужно было максимально покрыть сетью Wi-Fi-точек, используя как можно меньше роутеров и проводов. Оптимального решения у такой задачи не существует, но решить ее лучше других, конечно, возможно.


Одним из зданий, в котором организаторы Google Hash Code предлагали расставить роутеры, была парижская Гранд-Опера.

Отдельный вид соревнований представляет AI Cup, где нужно написать программу искусственного интеллекта, способную играть против оригинальной программы, предоставленной организаторами в виде библиотеки. Игры создают специально для турниров, т. е. поиграть в них руками в принципе нельзя. Но сценарии подбирают так, чтобы писать для них стратегии было интересно.




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

Подобные соревнования постоянно проходят на французском сайте CodinGame. И приятно, что в турнирах AI мы добиваемся неплохих результатов, занимая места в первых двух двадцатках при полном отсутствии тренировок. Все-таки в спортивном программировании главный навык — сесть, подумать и написать код.

Платформа Topcoder иногда проводит марафонские соревнования, которые могут продолжаться несколько недель. Есть несколько турниров, например, Deadline24 или Challenge24, финальная часть которых длится сутки. Отбираются на них, решая обычные алгоритмические задачи, а в финале здесь нужно создавать стратегии для управления игрой. На этом турнире самым эффективным нам показалось писать код первые 12 часов, а исправлением ошибок заниматься после перерыва на сон.

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

Тренировки


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

Несколько раз в год проходят сборы, на которые собираются команды из разных стран. В частности, в Петрозаводск в этом году приезжали сильные поляки, а в МФТИ в Долгопрудном — программисты из Китая и Австралии. На сборах мы полторы недели разбираем новые, специально подготовленные задачи, в том числе, привезенные другими командами.

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

Соперники


Самые сильные команды стабильно собирают вузы из России, Польши, Китая, Кореи и Японии. Временами удачные составы подбираются у одного из западно-европейских или американских университетов, но в целом там спортивным программированием явно интересуются меньше. Восточная Европа и Азия доминируют и в индивидуальных турнирах, например, на Codeforces. По количеству участников на многих соревнованиях первой постоянно оказывается Индия, однако чаще всего результаты их представители показывают не самые высокие. Хотя в решении проблем дизайна индийцы как раз сильны — это проявляется на cпециальных турнирах на Topcoder.


Школьная сборная США сезона 2017/18. Успеха в спортивном программировании в Америке в основном достигают молодые люди с азиатскими корнями.

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


В этом году в десятку лучших на международной Олимпиаде вошла всего одна команда из Западной Европы — студенты Королевского технологического института из Стокгольма.

Успехи России выглядят вполне объяснимыми, поскольку здесь — и очень сильная математическая школа, и сложившаяся тусовка олимпиадников, готовая помочь начинающим. В России, помимо ИТМО, очень сильные команды представляют СПбГУ (чемпионы прошлого года), МГУ, московский Физтех, вузы Екатеринбурга и Саратова, хотя время от времени хорошие составы удается собрать и другим университетам.


На фото еще одна команда ИТМО: Артем Васильев и Бориса Минаев и Геннадий Короткевич — чемпионы мира 2015 года. Кубки Международной олимпиады по программированию не переходящие — теперь в ИТМО хранится уже семь.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/333354/


Метки:  

[Перевод] Scala коллекции: секреты и трюки

Пятница, 14 Июля 2017 г. 19:28 + в цитатник

Представляю вашему вниманию перевод статьи Павла Фатина Scala Collections Tips and Tricks. Павел работает в JetBrains и занимается разработкой Scala плагина для IntelliJ IDEA. Далее, повествование идет от лица автора.


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


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


Этот список вдохновлен моими попытками разработать практичные инспекции для Scala коллекций, для Scala плагина IntelliJ. Сейчас мы внедряем эти инспекции, так что, используя Scala плагин в IDEA, вы автоматически выигрываете от статического анализа кода.


Тем не менее, эти рецепты ценны сами по себе. Они могут помочь вам углубить понимание стандартной библиотеки коллекций Scala и сделать ваш код быстрее и выразительнее.


Обновление:
Если вы испытываете тягу к приключениям,
вы можете узнать, как помочь в развитии IntelliJ плагина для Scala и попробовать свои силы в реализации, подобрав подходящую инспекцию.


Содержание:


  1. Легенда
  2. Композиция
  3. Побочные эффекты
  4. Последовательности (Sequences)
    4.1. Создание
    4.2. Длина
    4.3. Равенство
    4.4. Индексация
    4.5. Существование
    4.6. Фильтрация
    4.7. Сортировка
    4.8. Свертка
    4.9. Сопоставление
    4.10. Перерабатываем
  5. Множества (Sets)
  6. Option-ы
    6.1. Значение
    6.2. Null
    6.3. Обработка
    6.4. Перерабатываем
  7. Таблицы
  8. Дополнение

Все примеры кода доступны в репозитории на GitHub.


1. Легенда


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


  • seq — экземпляр основанной на Seq коллекции, вроде Seq(1, 2, 3)
  • set — экземпляр Set, например Set(1, 2, 3)
  • array — массив, такой как Array(1, 2, 3)
  • option — экземпляр Option, например, Some(1)
  • map — экземпляр Map, подобный Map(1 -> "foo", 2 -> "bar")
  • ??? — произвольное выражение
  • p — предикат функции типа T => Boolean, например _ > 2
  • n — целочисленное значение
  • i — целочисленный индекс
  • f, g — простые функции, A => B
  • x, y — некоторые произвольные значения
  • z — начальное или значение по умолчанию
  • P — паттерн

2. Композиция


Помните, вопреки тому, что рецепты изолированы и самодостаточны, их можно скомпоновать для последующего постепенного превращения в более продвинутые выражения:


seq.filter(_ == x).headOption != None

// от seq.filter(p).headOption к seq.find(p)
seq.find(_ == x) != None

// от option != None к option.isDefined
seq.find(_ == x).isDefined

// от seq.find(p).isDefined к seq.exists(p)
seq.exists(_ == x)

// от seq.exists(_ == x) к seq.contains(x)
seq.contains(x)

Так, мы можем полагаться на "заменяющую модель применения рецептов" (аналогично SICP), и использовать ее для упрощения сложных выражений.


3. Побочные эффекты


"Побочный эффект" (Side effect) это основное понятие, которое стоит рассмотреть перед тем, как мы перечислим основные преобразования.


По сути, побочный эффект — любое действие, которое наблюдается за пределами функции или выражения помимо возврата значения, например:


  • операция ввода-вывода,
  • модификация переменной (доступной за пределами области видимости),
  • изменение состояния объекта (наблюдаемое вне области видимости),
  • возбуждение исключения (которое также не обрабатывается внутри области видимости).

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


Почему побочные эффекты так важны? Потому что с ними порядок исполнения имеет значение. Например, два «чистых» выражения, (связанных с соответствующими значениями):


val x = 1 + 2
val y = 2 + 3

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


val x = { print("foo"); 1 + 2 }
val y = { print("bar"); 2 + 3 }

А это уже другая история — мы не можем изменить порядок выполнения, потому что в нашем терминале будет напечатано "barfoo" вместо "foobar" (и это явно не то, чего хотелось).


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


Схожие рассуждения применимы и к родственным коллекциям, выражениям. Представим, что где-то за пределами области видимости у нас есть некий builder:


seq.filter(x => { builder.append(x); x > 3 }).headOption

В принципе, вызов seq.filter(p).headOption упрощается до seq.find(p), впрочем, наличие побочного эффекта не дает нам это сделать:


seq.find( x => {builder.append(x); x > 3 })

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


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


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

Поэтому нам нужно либо избавиться от builderа (вместе с его API, в котором есть побочные эффекты), либо отделить вызов builderа от чистого выражения. Предположим, что этот builder является неким сторонним объектом, изжить который мы не можем, так что нам остается лишь изолировать вызов:


seq.foreach(builder.append)
seq.filter(_ > 3).headOption

Теперь мы можем благополучно выполнить преобразование:


seq.foreach(builder.append)
seq.find(x > 3)

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


Наименее очевидным и при этом наиболее важным преимуществом изоляции побочных эффектов будет улучшение надежности нашего кода вне зависимости от других возможных оптимизаций. Касательно примера: первоначальное выражение может порождать различные побочные эффекты, зависящие от текущей реализации Seq. Для Vector, например, оно добавит все элементы, для Stream оно пропустит все элементы после первого удачного сопоставления с предикатом (потому что стримы «ленивы» — элементы вычисляются только тогда, когда это необходимо). Отделение побочных эффектов позволяет нам избежать этих неопределенностей.


4. Последовательности (Sequences)


Хотя советы в этом разделе и относятся к наследникам Seq, некоторые преобразования допустимы и для других типов коллекций (и не коллекций), например: Set, Option, Map и даже Iterator (потому что все они предоставляют похожие интерфейсы с монадическими методами).


4.1 Создание


Явно создавайте пустые коллекции


// До
Seq[T]()

// После
Seq.empty[T]

Некоторые неизменяемые (immutable) классы коллекций имеют синглтон-реализацию метода empty. Однако, далеко не все фабричные методы проверяют длину созданных коллекций. Так что, обозначив пустоту на этапе компиляции, вы можете сохранить либо место в куче (путем переиспользования экземпляра), либо такты процессора (которые могли бы быть потрачены на проверки размерности во время выполнения).
Также применимо к: Set, Option, Map, Iterator.


4.2 Длины


Для массивов используйте length вместо size


// До
array.size

// После
array.length

Хотя size и length по существу синонимы, в Scala 2.11 вызовы Array.size по-прежнему выполняются через неявное преобразование (implicit conversion), таким образом создавая промежуточные объекты-обертки для каждого вызова метода. Если вы, конечно, не включите эскейп анализ для JVM, временные объекты станут обузой для сборщика мусора и ухудшат производительность кода (особенно внутри циклов).


Не отрицайте isEmpty


// До
!seq.isEmpty
!seq.nonEmpty

// После
seq.nonEmpty
seq.isEmpty

Простые свойства содержат меньше визуального шума, чем составные выражения.
Также применимо к: Set, Option, Map, Iterator.


Не вычисляйте длину при проверке на пустоту.


// До
seq.length > 0
seq.length != 0
seq.length == 0

// После
seq.nonEmpty
seq.nonEmpty
seq.isEmpty

С одной стороны, простое свойство воспринимается гораздо легче, чем составное выражение. С другой стороны, наследникам LinearSeq (таким как List) может потребоваться O(n) времени на вычисление длины списка (вместо O(1) для IndexedSeq), таким образом мы можем ускорить наш код, избегая вычисления длины, когда нам, вобщем-то, это значение не очень-то и нужно.
Имейте также в виду, что вызов .length для бесконечных стримов может никогда не закончиться, поэтому всегда проверяйте стрим на пустоту явно.
Также применимо к: Set, Map.


Во время сравнения не вычисляйте полный размер коллекции


// До
seq.length > n
seq.length < n
seq.length == n
seq.length != n

// После
seq.lengthCompare(n) > 0
seq.lengthCompare(n) < 0
seq.lengthCompare(n) == 0
seq.lengthCompare(n) != 0

Поскольку расчет размера коллекции может быть достаточно «дорогим» вычислением для некоторых типов коллекций, мы можем сократить время сравнения с O(length) до O(length min n) для наследников LinearSeq (которые могут быть спрятаны под Seq-подобными значениями). Кроме того, такой подход незаменим, когда имеем дело с бесконечными стримами.


Не используйте exists для проверки на пустоту


// До
seq.exists(_ => true)
seq.exists(const(true))

// После
seq.nonEmpty

Разумеется, такой трюк будет совсем излишним.
Также применимо к: Set, Option, Map, Iterator.


4.3 Равенство


Не полагайтесь на == для сравнения содержимого массивов


// До
array1 == array2

// После
array1.sameElements(array2)

Проверка на равенство всегда будет выдавать false для различных экземпляров массивов.
Также применимо к: Iterator.


Не проверяйте на равенство коллекции различных категорий


// До
seq == set

// После
seq.toSet == set

Проверки на равенство могут быть использованы для сравнения коллекций и различных категорий (например List и Set).
Прошу вас дважды подумать о смысле данной проверки (касательно примера выше — как рассматривать дубликаты в последовательности).


Не используйте sameElements для сравнения обыкновенных коллекций


// До
seq1.sameElements(seq2)

// После
seq1 == seq2

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


Не используйте corresponds явно


// До
seq1.corresponds(seq2)(_ == _)

// После
seq1 == seq2

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


4.4 Индексация


Не получайте первый элемент по индексу


// До
seq(0)

// После
seq.head

Для некоторых классов коллекций обновленный подход может быть немного быстрее (ознакомьтесь с кодом List.apply, например). К тому же, доступ к свойству намного проще (как синтаксически, так и семантически), чем вызов метода с аргументом.


Не получайте последний элемент по индексу


// До
seq(seq.length - 1)

// После
seq.last

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


Не проверяйте нахождение индекса в границах коллекции явно


// До
if (i < seq.length) Some(seq(i)) else None

// После
seq.lift(i)

Семантически второе выражение эквивалентно, однако более выразительно


Не эмулируйте headOption


// До
if (seq.nonEmpty) Some(seq.head) else None
seq.lift(0)

// После
seq.headOption

Упрощенное выражение более лаконично.


Не эмулируйте lastOption


// До
if (seq.nonEmpty) Some(seq.last) else None
seq.lift(seq.length - 1)

// После
seq.lastOption

Последнее выражение короче (и потенциально быстрее).


Будьте осторожны с типами аргументов для indexOf и lastIndexOf


// До
Seq(1, 2, 3).indexOf("1") // скомпилируется
Seq(1, 2, 3).lastIndexOf("2") // скомпилируется

// После
Seq(1, 2, 3).indexOf(1)
Seq(1, 2, 3).lastIndexOf(2)

Из-за особенностей работы вариантности, методы indexOf и lastIndexOf принимают аргументы типа Any. На практике это может приводить к труднонаходимым багам, которые невозможно обнаружить на этапе компиляции. Вот где будут к месту вспомогательные инспекции вашей IDE.


Не создавайте диапазон индексов последовательности вручную


// До
Range(0, seq.length)

// После
seq.indices

У нас есть встроенный метод, который возвращает диапазон из всех индексов последовательности.


Не используйте zip для связывания коллекции с индексами вручную


// До
seq.zip(seq.indices)

// После
seq.zipWithIndex

Во-первых, последнее выражение короче. Кроме того, мы можем ожидать некоторый прирост производительности, из-за того, что мы избегаем скрытого вычисления размера коллекции (что в случае линейных последовательностей может обойтись недешево).
Дополнительное преимущество последнего выражения в том, что оно хорошо работает с потенциально бесконечными коллекциями (например Stream).


Используйте экземпляр IndexedSeq как объект-функцию:


// До (seq: IndexedSeq[T])
Seq(1, 2, 3).map(seq(_))

// После
Seq(1, 2, 3).map(seq)

Поскольку экземпляр IndexedSeq[T] также является Function1[Int, T], вы можете использовать его как таковой.


4.5 Существование


Не используйте предикат сравнения для проверки наличия элемента


// До
seq.exists(_ == x)

// После
seq.contains(x)

Второе выражение семантически эквивалентно, при этом более выразительно. Когда эти выражения применяются к Set, производительность может разительно отличаться, потому что поиск у множеств стремится к O(1) (из-за внутреннего индексирования, не использующегося при вызове exists).
Также применимо к: Set, Option, Iterator.


Будьте осторожны с типом аргумента contains


// До
Seq(1, 2, 3).contains("1") // компилируется

// После
Seq(1, 2, 3).contains(1)

Так же как методы indexOf и lastIndexOf, contains принимает аргументы типа Any, что может привести к труднонаходимым багам, которые не обнаруживаются на этапе компиляции. Будьте с ними осторожны.


Не используйте предикат неравенства для проверки отсутствия элемента


// До
seq.forall(_ != x)

// После
!seq.contains(x)

И снова последнее выражение чище и, вероятно, быстрее (особенно для множеств).
Также применимо к: Set, Option, Iterator.


Не считайте вхождения для проверки существования


// До
seq.count(p) > 0
seq.count(p) != 0
seq.count(p) == 0

// После
seq.exists(p)
seq.exists(p)
!seq.exists(p)

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


  • Предикат p должен быть чистой функцией.
  • Также применимо к: Set, Map, Iterator.

Не прибегайте к фильтрации для проверки существования


// До
seq.filter(p).nonEmpty
seq.filter(p).isEmpty

// После
seq.exists(p)
!seq.exists(p)

Вызов filter создает промежуточную коллекцию, которая занимает место в куче и нагружает GC. К тому же, предшествующие выражения находят все вхождения, в то время как требуется найти только первое (что может замедлить код в зависимости от возможного содержимого коллекции). Потенциальный выигрыш в производительности менее значим для ленивых коллекций (таких как Stream и, в особенности, Iterator).


  • Предикат p должен быть чистой функцией.
  • Также применимо к: Set, Option, Map, Iterator.

Не прибегайте к поиску, чтобы проверить существование


// До
seq.find(p).isDefined
seq.find(p).isEmpty

// После
seq.exists(p)
!seq.exists(p)

Поиск определенно лучше фильтрации, однако и это далеко не предел (по крайней мере, с точки зрения ясности).
Также применимо к: Set, Option, Map, Iterator.


4.6 Фильтрация


Не отрицайте предикат filter


// До
seq.filter(!p)

// После
seq.filterNot(p)

Последнее выражение синтактически проще (при том, что семантически они эквивалентны).
Также применимо к: Set, Option, Map, Iterator.


Не фильтруйте, чтобы посчитать


// До
seq.filter(p).length

// После
seq.count(p)

Вызов filter создает промежуточную (и не очень-то нужную) коллекцию, которая будет занимать место в куче и нагружать GC.
Также применимо к: Set, Option, Map, Iterator.


Не используйте фильтрацию для того, чтобы найти первое вхождение


// До
seq.filter(p).headOption

// После
seq.find(p)

Конечно, если seq не является ленивой коллекцией (как, например, Stream), фильтрация найдет все вхождения (и создаст временную коллекцию) при том, что требовался только первый элемент.
Также применимо к: Set, Option, Map, Iterator.


4.7 Сортировка


Не сортируйте по свойству вручную


// До
seq.sortWith(_.property <  _.property)

// После
seq.sortBy(_.property)

Для этого у нас есть свой метод, более ясный и выразительный.


Не сортируйте по тождеству вручную


// До
seq.sortBy(identity)
seq.sortWith(_ < _)

// После
seq.sorted

И для этого тоже есть метод.


Выполняйте обратную сортировку в один шаг


// До
seq.sorted.reverse
seq.sortBy(_.property).reverse
seq.sortWith(f(_, _)).reverse

// После
seq.sorted(Ordering[T].reverse)
seq.sortBy(_.property)(Ordering[T].reverse)
seq.sortWith(!f(_, _))

Таким образом, мы можем избежать создания промежуточной коллекции и исключить дополнительные преобразования (чтобы сберечь место в куче и циклы процессора).


Не используйте сортировку для нахождения минимального элемента


// До
seq.sorted.head
seq.sortBy(_.property).head

// После
seq.min
seq.minBy(_.property)

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


Не используйте сортировку для нахождения максимального элемента


// До
seq.sorted.last
seq.sortBy(_.property).last

// После
seq.max
seq.maxBy(_.property)

Объяснение совпадает с предыдущим советом.


4.8 Свертка


Не вычисляйте сумму вручную


// До
seq.reduce(_ + _)
seq.fold(z)(_ + _)

// После
seq.sum
seq.sum + z

Преимущества этого подхода — ясность и выразительность.


  • Другие возможные методы: reduceLeft, reduceRight, foldLeft, foldRight.
  • Второе преобразование может быть заменено первым, если z равняется 0.
  • Также применимо к: Set, Iterator.

Не вычисляйте произведение вручную


// До
seq.reduce(_ * _)
seq.fold(z)(_ * _)

// После
seq.product
seq.product * z

Причины те же, что и в предыдущем случае.


  • Второе преобразование может быть заменено первым, если z равняется 1.
  • Также применимо к: Set, Iterator.

Не ищите минимальный элемент вручную


// До
seq.reduce(_ min _)
seq.fold(z)(_ min _)

// После
seq.min
z min seq.min

Обоснование такое же, как и в предыдущем случае.
Также применимо к: Set, Iterator.


Не выполняйте поиск максимального элемента вручную


// До
seq.reduce(_ max _)
seq.fold(z)(_ max _)

// После
seq.max
z max seq.max

Все как и в предыдущем случае.
Также применимо к: Set, Iterator.


Не эмулируйте forall


// До
seq.foldLeft(true)((x, y) => x && p(y))
!seq.map(p).contains(false)

// После
seq.forall(p)

Цель упрощения — ясность и выразительность.


  • Предикат p должен быть чистой функцией.
  • Также применимо к: Set, Option (для второй строки), Iterator.

Не эмулируйте exists


// До
seq.foldLeft(false)((x, y) => x || p(y))
seq.map(p).contains(true)

// После
seq.exists(p)

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


  • Предикат p должен быть чистой функцией.
  • Также применимо к: Set, Option (для второй строки), Iterator.

Не эмулируйте map


// До
seq.foldLeft(Seq.empty)((acc, x) => acc :+ f(x))
seq.foldRight(Seq.empty)((x, acc) => f(x) +: acc)

// После
seq.map(f)

Это «классическая» в функциональном программировании реализация отображения (map) через свертку. Бесспорно, она поучительна, но нужды ее использовать нет. Для этого у нас есть встроенный и выразительный метод (который еще и быстрее, так как в своей реализации использует простой цикл while).
Также применимо к: Set, Option, Iterator.


Не эмулируйте filter


// До
seq.foldLeft(Seq.empty)((acc, x) => if (p(x)) acc :+ x else acc)
seq.foldRight(Seq.empty)((x, acc) => if (p(x)) x +: acc else acc)

// После
seq.filter(p)

Причины те же, что и в предыдущем случае.
Также применимо к: Set, Option, Iterator.


Не эмулируйте reverse


// До
seq.foldLeft(Seq.empty)((acc, x) => x +: acc)
seq.foldRight(Seq.empty)((x, acc) => acc :+ x)

// После
seq.reverse

И опять-таки встроенный метод быстрее и чище.
Также применимо к: Set, Option, Iterator.


4.9 Сопоставление


Вот несколько обособленных советов, посвященных сопоставлению с образцом в Scala и частичным функциям.


Используйте частичные функции вместо функций с паттерн-матчингом


// До
seq.map {
  _ match {
    case P => ??? // x N
  }
}

// После
seq.map {
  case P => ??? // x N
}

Обновленное выражение дает сходный результат и выглядит при этом проще.


Описанные выше преобразования можно применить к любым функциям, а не только к аргументам функции map. Этот совет относится не только к коллекциям. Однако, в виду вездесущести функций высшего порядка в API стандартной библиотеки коллекций Scala, он будет весьма кстати.


Конвертируйте flatMap с частичной функцией collect


// До
seq.flatMap {
  case P => Seq(???) // x N
  case _ => Seq.empty
}

// После
seq.collect {
  case P => ??? // x N
}

Обновленное выражение дает аналогичный результат и выглядит намного проще.
Также применимо к: Set, Option, Map, Iterator.


Преобразовать match к collect, когда результатом является коллекция


// До
v match {
  case P => Seq(???) // x N
  case _ => Seq.empty
}

// После
Seq(v) collect {
  case P => ??? // x N
}

Учитывая, что все case-операторы создают коллекции, можно упростить выражение, заменив match на вызов collect. Так мы создаем коллекцию всего один раз, опустив при этом явные ветки case для дефолтных случаев.
Лично я обычно использую этот трюк с Option, а не с последовательностями как таковыми.
Также применимо к: Set, Option, Iterator.


Не эмулируйте collectFirst


// До
seq.collect{case P => ???}.headOption

// После
seq.collectFirst{case P => ???}

Для такого случая у нас есть особый метод, который работает быстрее для неленивых коллекций.


  • Частичная функция должна быть чистой.
  • Также применимо к: Set, Map, Iterator.

4.10 Перерабатываем


Соединяем последовательные вызовы filter


// До
seq.filter(p1).filter(p2)

// После
seq.filter(x => p1(x) && p2(x))

Так мы можем избежать создания промежуточной коллекции (после первого вызова filter), чем облегчим участь сборщика мусора.
Мы так же можем использовать обобщенный подход, который полагается на представления (смотрите ниже), получив: seq.view.filter(p1).filter(p2).force.


  • Предикаты p1 и p2 должны быть чистыми функциями.
  • Также применимо к: Set, Option, Map, Iterator.

Соединяем последовательные вызовы map


// До
seq.map(f).map(g)

// После
seq.map(f.andThen(g))

Как и в предыдущем случае, мы сразу создаем конечную коллекцию без создания промежуточной.


Мы так же можем применить обобщенный подход, который полагается на view (смотрите ниже), получив: seq.view.map(f).map(g).force.


  • Функции f и g должны быть чистыми.
  • Также применимо к: Set, Option, Map, Iterator.

Сортируйте после фильтрации


// До
seq.sorted.filter(p)

// После
seq.filter(p).sorted

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


  • Подобное применимо ко всем возможным методам сортировки, таким как sortWith и sortBy.
  • Предикат p должен быть чистой функцией.

Не переворачивайте коллекцию явно перед вызовом map


// До
seq.reverse.map(f)

// После
seq.reverseMap(f)

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


Не переворачивайте коллекцию явно для получения обратного итератора


// До
seq.reverse.iterator

// После
seq.reverseIterator

К тому же последнее выражение проще и может быть более эффективным.


Не конвертируйте коллекцию Set для нахождения отдельных элементов


// До
seq.toSet.toSeq

// После
seq.distinct

Нет нужды создавать временное множество (во всяком случае явно), чтобы найти отдельные элементы.


Не эмулируйте slice


// До
seq.drop(x).take(y)

// После
seq.slice(x, x + y)

Для линейных последовательностей, ничего кроме ясно выраженных мыслей и намерений мы не получим. Однако, в случае с индексированными последовательностями мы можем ожидать потенциальный прирост производительности.
Также применимо к: Set, Map, Iterator.


Не эмулируйте splitAt


// До
val seq1 = seq.take(n)
val seq2 = seq.drop(n)

// После
val (seq1, seq2) = seq.splitAt(n)

Для линейных последовательностей (как для List, так и для Stream), упрощенные выражения будут выполняться быстрее из-за того, что результаты вычисляются за один проход.
Также применимо к: Set, Map.


Не эмулируйте span


// До
val seq1 = seq.takeWhile(p)
val seq2 = seq.dropWhile(p)

// После
val (seq1, seq2) = seq.span(p)

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


  • Предикат p не должен иметь побочных эффектов.
  • Также применимо к: Set, Map, Iterator.

Не эмулируйте partition


// До
val seq1 = seq.filter(p)
val seq2 = seq.filterNot(p)

// После
val (seq1, seq2) = seq.partition(p)

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


  • Предикат p не должен иметь побочных эффектов.
  • Также применимо к: Set, Map, Iterator.

Не эмулируйте takeRight


// До
seq.reverse.take(n).reverse

// После
seq.takeRight(n)

Последнее выражение более выразительно и потенциально более эффективно (как для индексированных, так и для линейных последовательностей).


Не эмулируйте flatten


// До (seq: Seq[Seq[T]])
seq.reduce(_ ++ _)
seq.fold(Seq.empty)(_ ++ _)
seq.flatMap(identity)

// После
seq.flatten

Нет необходимости делать это вручную: у нас уже есть встроенный метод.
Также применимо к: Set, Option, Iterator.


Не эмулируйте flatMap


// До (f: A => Seq[B])
seq.map(f).flatten

// После
seq.flatMap(f)

Опять-таки незачем писать велосипед. Улучшится не только выразительность, дополнительная коллекция создаваться тоже не будет.
Также применимо к: Set, Option, Iterator.


Не используйте map если результат игнорируется


// До
seq.map(???) // результат игнорируется

// После
seq.foreach(???)

Когда вам нужны именно побочные эффекты, оправданий вызову map нет. Такой вызов вводит в заблуждение, при том еще и менее эффективен.
Также применимо к: Set, Option, Map, Iterator.


Не используйте unzip для извлечения единственного элемента


// До (seq: Seq[(A, B]])
seq.unzip._1

// После
seq.map(_._1)

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


  • Другой возможный метод: unzip3.
  • Также применимо к: Set, Option, Map, Iterator.

Не создавайте временные коллекции


Этот рецепт разбит на три части (в зависимости от конечного результата преобразования).


1) Преобразование сокращает коллекцию до единственного значения.


// До
seq.map(f).flatMap(g).filter(p).reduce(???)

// После
seq.view.map(f).flatMap(g).filter(p).reduce(???)

Вместо reduce может быть любой метод, который сокращает коллекцию до единственного значения, например: reduceLeft, reduceRight, fold, foldLeft, foldRight, sum, product, min, max, head, headOption, last, lastOption, indexOf, lastIndexOf, find, contains, exists, count, length, mkString, и т.д.


Точный порядок преобразований не столь важен — важно то, что мы создаем одну, а то и несколько промежуточных коллекций не очень-то и нужных, при этом они будут занимать место в куче и нагружать GC. Это происходит потому, что по умолчанию все преобразователи коллекций (map, flatMap, filter, ++, и т.д.) являются «строгими» (за исключениемStream) и, как результат, порождают новую коллекцию со всеми ее элементами.


Здесь на помощь приходят представления (view) — о которых вы можете думать, как о своего рода итераторах, позволяющих повторную итерацию:


  • Представления "ленивы" — элементы создаются только когда необходимы.
  • Представления не содержат созданных элементов в памяти (чем грешат даже Stream).

Чтобы перейти от коллекции к ее представлению, используйте метод view.


2) Преобразование, порождающее коллекцию того же типа.


Представления можно использовать и тогда, когда конечный результат преобразования по-прежнему остается коллекцией — метод force построит коллекцию первоначального типа (при этом все промежуточные коллекции созданы не будут):


// До
seq.map(f).flatMap(g).filter(p)

// После
seq.view.map(f).flatMap(g).filter(p).force

Если фильтрация — единственное промежуточное преобразование, то, как вариант, вы можете рассмотреть метод withFilter:


seq.withFilter(p).map(f)

Первоначально этот метод предназначался для использования внутри "for comprehensions". Он работает так же, как и представление — создает временный объект, который ограничивает область последующих преобразований коллекции (так, что он реорганизует возможные побочные эффекты). Однако, нет нужды явно преобразовывать коллекцию к (или наоборот) от временного представления (вызвав veiw и force)


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


3) Преобразование порождает коллекцию другого типа.


// До
seq.map(f).flatMap(g).filter(p).toList

// После
seq.view.map(f).flatMap(g).filter(p).toList

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


Также существует альтернативный способ совладать с «преобразованием + конверсией». И случай этот полагается на breakOut:


seq.map(f)(collection.breakOut): List[T]

Функционально выражение эквивалентно использованию представления, однако:


  • требует явного указания ожидаемого типа (что, зачастую, требует дополнительного указания типа),
  • ограничивается единичным преобразованием (как, например, map, flatMap, filter, fold, и т.д.),
  • выглядит весьма заумно (в виду того, что неявные билдеры обычно опускают из документации стандартной библиотеки коллекций Scala).

Так что, скорее всего, лучше заменить breakOut на более гибкое и выразительнее представление.


Представления наиболее целесообразны при относительно большом размере коллекций.


  • Все перечисленные функции (как f и g) и предикаты (p) должны быть чистыми функциями (так как представление может задерживать, пропускать, а то и вовсе переупорядочивать вычисления).
  • Также применимо к: Set, Map.

Используйте операторы для переприсванивания последовательностей


// До
seq = seq :+ x
seq = x +: seq
seq1 = seq1 ++ seq2
seq1 = seq2 ++ seq1

// После
seq :+= x
seq +:= x
seq1 ++= seq2
seq1 ++:= seq2

Scala предлагает «синтаксический сахар», известный как «операторы присваивания» (“assignment operators”) — он автоматически приводит операторы типа x = y к виду x = x y, где: некий символьный оператор (например: +, -, и т.д). Обратите внимание, что если заканчивается на :, то он считается право-ассоциативным (т.е. вызывается для правого выражения, вместо левого). Для списков и стримов также существует особый синтаксис:


// До
list = x :: list
list1 = list2 ::: list

stream = x #:: list
stream1 = stream2 #::: stream

// После
list ::= x
list1 :::= list2

stream #::= x
stream1 #:::= stream2

Упрощенные выражения лаконичны.
Также применимо к Set, Map, Iterator (учитывая специфику операторов).


Не приводите коллекции к заданному типу вручную


// До
seq.foldLeft(Set.empty)(_ + _)
seq.foldRight(List.empty)(_ :: _)

// После
seq.toSet
seq.toList

Для этого существуют встроенные методы, которые и чище, и быстрее. А если вам нужно преобразовать или отфильтровать значения во время преобразования, рассмотрите использование представлений или схожих техник, описанных выше.
Также применимо к: Set, Option, Iterator.


Берегитесь toSeq для нестрогих коллекций.


// До (seq: TraversableOnce[T])
seq.toSeq

// После
seq.toStream
seq.toVector

Из-за того, что Seq(...) создает строгую коллекцию (а именно, Vector), мы можем захотеть использовать toSeq для преобразования нестрогой сущности (как Stream, Iterator или view) к строгой коллекции. Однако TraversableOnce.toSeq на самом деле возвращает Stream, являющийся ленивой коллекцией, что может привести к труднонаходимым багам и проблемам с производительностью. Даже если вы изначально ожидали стрим, подобное выражение может ввести в заблуждение тех, кто читает ваш код.


А вот и типичный пример ловушки:


val source = Source.fromFile("lines.txt")
val lines = source.getLines.toSeq
source.close()
lines.foreach(println)

Такой код выбросит IOException, сетующий на то, что стрим уже закрыт.


Чтобы ясно обозначить наши намерения, лучше добавить toStream явно или, если нам после всего потребуется строгая коллекция, использовать toVector вместо toSeq.


Не приводите к строковому типу вручную


// До (seq: Seq[String])
seq.reduce(_ + _)
seq.reduce(_ + separator + _)
seq.fold(prefix)(_ + _)
seq.map(_.toString).reduce(_ + _) // seq: Seq[T]
seq.foldLeft(new StringBuilder())(_ append _)

// После
seq.mkString
seq.mkString(prefix, separator, "")

Последний подход чище и потенциально быстрее, так как внутри он использует единственный StringBuilder.


  • Другие возможные методы: reduceLeft, reduceRight, foldLeft, foldRight.
  • Также применимо к: Set, Option, Iterator.

5. Множесва (Sets)


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


Не используйте sameElements для сравнения неупорядоченных коллекций


// До
set1.sameElements(set2)

// После
set1 == set2

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


Метод sameElements может возвращать недетерминированные результаты для неупорядоченных коллекций, потому что этот метод принимает во внимание порядок элементов, на который мы не можем полагаться в случае с множествами.
Исключениями из правила будут классы, которые явно гарантируют предсказуемый порядок итерации: например, LinkedHashSet.
Также применимо к: Map.


Используйте экземпляр Set как объект-функцию


// До (set: Set[Int])
Seq(1, 2, 3).filter(set(_))
Seq(1, 2, 3).filter(set.contains)

// После
Seq(1, 2, 3).filter(set)

Так как Set[T] также явялется экземпляром Function1[T, Boolean], вы можете использовать его в этом качестве.


Не вычисляйте пересечения множеств вручную


// До
set1.filter(set2.contains)
set1.filter(set2)

// После
set1.intersect(set2) // или set1 & set2

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


Не вычисляйте разницу множеств вручную


// До
set1.filterNot(set2.contains)
set1.filterNot(set2)

// После
set1.diff(set2) // или set1 &~ set2

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


6. Options


Технически Option не является частью Scala коллекций, однако предоставляет похожий интерфейс (с монадическими методами и т.д.) и даже ведет себя как специальный тип коллекций, который может иметь, а может и не иметь какое-то значение.


Многие из приведенных советов для последовательностей применимы и к Option. Кроме того, здесь представлены советы, характерные для Option API.


6.1 Значение


Не сравнивайте значения Option с None


// До
option == None
option != None

// После
option.isEmpty
option.isDefined

При том, что сравнение является вполне законным, есть более простой способ, который позволяет проверить объявлен ли Option.
Еще одно преимущество данного упрощения в том, что если вы решите изменить тип от Option[T] к T, scalac скомпилирует предшествующее выражение (выдав только одно предупреждение), тогда как компиляция последнего справедливо закончится ошибкой.


Не сравнивайте значения Option с Some


// До
option == Some(v)
option != Some(v)

// После
option.contains(v)
!option.contains(v)

Этот совет дополняет предыдущий.


Не полагайтесь isInstanceOf для проверки наличия элемента


// До
option.isInstanceOf[Some[_]]

// После
option.isDefined

В подобном трюкачестве нет нужды.


Не прибегайте к сопоставлению с образцом для проверки существования


// До
option match {
  case Some(_) => true
  case None => false
}

option match {
  case Some(_) => false
  case None => true
}

// После
option.isDefined
option.isEmpty

Опять же, первое выражение и является корректным — оправдывать подобную экстравагантность не стоит. Более того, упрощенное выражение будет работать быстрее.
Также применимо к: Seq, Set.


Не отрицайте значения свойств, связанных с существованием


// До
!option.isEmpty
!option.isDefined
!option.nonEmpty

// После
seq.isDefined
seq.isEmpty
seq.isEmpty

Причина та же, что и для последовательностей — простое свойство добавит меньше визуального шума, нежели составное выражение.
Заметьте, что у нас есть синонимы: isDefined (специфичный для option) и nonEmpty (специфичный для последовательностей). Возможно, было бы разумно отдать предпочтение первому для явного отделения Option и последовательностей.


6.2 Null


Не выполняйте явное сравнение значений с null, чтобы создать Option


// До
if (v != null) Some(v) else None

// После
Option(v)

Для этого у нас есть более подходящий синтаксис.


Не предоставляйте null как явную альтернативу


// До
option.getOrElse(null)

// После
option.orNull

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


6.3 Обработка


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


В документации, посвященной интерфейсу Option, говорится, что «самый идиоматичный способ использования экземпляра Option — это рассмотрение его в качестве коллекции или монады на ряду с использованием map, flatMap, filter или foreach». Основной принцип здесь заключается в том, чтобы избегать "check & get" (проверь и возьми) цепочек, которые обычно реализуются через оператор if или сопоставлением с образцом.
Цель — надежность, выразительность и «монадический» код:


  • более выразительный и понятный,
  • защищенный от NoSuchElementException и MatchError ислючений во время выполнения

Это объяснение объединяет все последующие случаи.


Не эмулируйте getOrElse


// До
if (option.isDefined) option.get else z

option match {
  case Some(it) => it
  case None => z
}

// После
option.getOrElse(z)

Не эмулируйте orElse


// До
if (option1.isDefined) option1 else option2

option1 match {
  case Some(it) => Some(it)
  case None => option2
}

// После
option1.orElse(option2)

Не эмулируйте exists


// До
option.isDefined && p(option.get)

if (option.isDefined) p(option.get) else false

option match {
  case Some(it) => p(it)
  case None => false
}

// После
option.exists(p)

Не эмулируйте forall


// До
option.isEmpty || (option.isDefined && p(option.get))

if (option.isDefined) p(option.get) else true

option match {
  case Some(it) => p(it)
  case None => true
}

// После
option.forall(p)

Не эмулируйте contains


// До
option.isDefined && option.get == x

if (option.isDefined) option.get == x else false

option match {
  case Some(it) => it == x
  case None => false
}

// После
option.contains(x)

Не эмулируйте foreach


// До
if (option.isDefined) f(option.get)

option match {
  case Some(it) => f(it)
  case None =>
}

// После
option.foreach(f)

Не эмулируйте filter


// До
if (option.isDefined && p(option.get)) option else None

option match {
  case Some(it) && p(it) => Some(it)
  case _ => None
}

// После
option.filter(p)

Не эмулируйте map


// До
if (option.isDefined) Some(f(option.get)) else None

option match {
  case Some(it) => Some(f(it))
  case None => None
}

// После
option.map(f)

Не эмулируйте flatMap


// До (f: A => Option[B])
if (option.isDefined) f(option.get) else None

option match {
  case Some(it) => f(it)
  case None => None
}

// После
option.flatMap(f)

6.4 Перерабатываем


Приводим цепочку из map и getOrElse в fold


// До
option.map(f).getOrElse(z)

// После
option.fold(z)(f)

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


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


Не эмулируйте exists


// До
option.map(p).getOrElse(false)

// После
option.exists(p)

Мы представили довольно похожее правило для последовательностей (которое применимо и к Option). Нетипичное преобразование для вызова getOrElse.


Не эмулируйте flatten


// До (option: Option[Option[T]])
option.map(_.get)
option.getOrElse(None)

// После
option.flatten

Последнее выражение смотрится чище.


Не конвертируйте Option в Seq вручную


// До
option.map(Seq(_)).getOrElse(Seq.empty)
option.getOrElse(Seq.empty) // option: Option[Seq[T]]

// После
option.toSeq

Для этого есть специальный метод, который делает это кратко и наименее затратно.


7. Таблицы


Как и с другими типами коллекций, многие советы для последовательностей применимы и к таблицам, поэтому перечислим только характерные для таблиц.


Не выполняйте поиск значений вручную


// До
map.find(_._1 == k).map(_._2)

// После
map.get(k)

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


Не используйте get, когда необходимо сырое значение


// Before
map.get(k).get

// After
map(k)

Нет необходимости плодить промежуточный Option, когда необходимо сырое (raw) значение.


Не используйте lift вместо get


// Before
map.lift(k)

// After
map.get(k)

Незачем рассматривать значение таблицы, как частичную функцию для получения опционального результата (что полезно для последовательностей), потому что у нас есть встроенный метод с такой же функциональностью. Хотя lift отлично работает, он выполняет дополнительное преобразование (от Map доPartialFunction) и может выглядеть весьма запутанным.


Не вызывайте get и getOrElse раздельно


// До
map.get(k).getOrElse(z)

// После
map.getOrElse(k, z)

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


Используйте экземпляр Map в качестве объекта-функции


// До (map: Map[Int, T])
Seq(1, 2, 3).map(map(_))

// После
Seq(1, 2, 3).map(map)

Так как экземпляр Map[K, V] также является Function1[K, V], вы можете использовать его как функцию.


Не извлекайте ключи вручную


// До
map.map(_._1)
map.map(_._1).toSet
map.map(_._1).toIterator

// После
map.keys
map.keySet
map.keysIterator

Оптимизированные выражения являются более понятными (и потенциально более быстрыми).


Не извлекайте значения вручную


// До
map.map(_._2)
map.map(_._2).toIterator

// После
map.values
map.valuesIterator

Упрощенные выражения понятней (и потенциально быстрее).


Будьте осторожны с filterKeys


// До
map.filterKeys(p)

// После
map.filter(p(_._1))

Метод filterKeys обертывает исходную таблицу без копирования каких-либо элементов. В этом нет ничего плохого, однако вы вряд ли ожидаете от filterKeys подобного поведения. Поскольку оно неожиданно ведет так же, как представление, производительность кода может быть существенно снижена для некоторых случаев, например, для filterKeys(p).groupBy(???).


Другой вероятной неприятностью является неожиданная «ленивость» (по умолчанию, фильтры коллекций должны быть строгими) – при вызове самого метода предикат вообще не вычисляется, из-за чего возможные побочные эффекты могут быть переупорядочены.


Метод filterKeys, скорее всего, следовало бы объявить устаревшим, из-за невозможности сделать его строгим, не сломав обратную совместимость. Более подходящим именем для текущей реализации будет withKeyFilter (по аналогии с withFilter).


В общем, наиболее разумно будет следовать Правилу наименьшего удивления и фильтровать ключи вручную.


Тем не менее, поскольку схожая с представлением функциональность filterKeys потенциально полезна (когда доступ будет только к небольшому числу записей,


Метки:  

Баланс между устройствами безопасности в режиме прокси и влиянием на производительность сети

Пятница, 14 Июля 2017 г. 19:09 + в цитатник
Почти экспоненциальный рост за последнее десятилетие кибератак на различные типы приложений укрепил потребность в улучшенной инфраструктуре безопасности периметра сети, которая может проверять и блокировать любые виды трафика. Производители устройств безопасности следующего поколения (NGFW) понимают необходимость глубоких проверок (deep inspection) и перешли за пределы файерволинга транспортного уровня на уровень приложений для web, электронной почты, передачи файлов и т. д.





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

Эффективность безопасности проверки зашифрованного трафика неоспорима. Тем не менее, история показала, что любое in-line устройство безопасности, которое вносит значительные задержки, либо резервируется, либо перемещается out-of-band через определенное время. Здесь мы обсудим реализацию прокси, накладные расходы, которые они добавляют в сети, сценарии тестирования, которые могут помочь обнаружить такие влияния на производительность, а также советы и рекомендации для лучшей реализации прокси.

Что такое прокси?

Проще говоря, прокси – это компьютер или устройство, которое является посредником между двумя системами, такими как:

— хосты в защищенной сети и Интернет;
— Интернет-клиенты и серверы в приватной сети.

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



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

Работа прокси для безопасности

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

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

Влияние включения прокси на производительность

Функционал глубокой проверки (deep inspection) делает устройства безопасности в режиме прокси основным «узким местом» (так называемый bottleneck) и может привести снижению производительности всей сети.

Из-за стойких шифров SSL и больших размеров ключей, прокси может влиять на производительность, даже если сеть работает на 10% от максимальной емкости.

Снижение производительности в большинстве случаев сопровождается ошибками, вызванными перезапросом пакетов, задержкой сеанса (session-delay), сбоем сеанса (TCP Retries и Timeouts) и ошибками транзакций (Packet Drop).

Тесты, демонстрирующие влияние включения прокси на производительность

Чтобы сделать прокси-файрвол (FW) более надежным и эффективным при обработке этих «узких мест» (bottleneck), необходимо их протестировать и проверить перед внедрением. Ниже демонстрируются серьезные задержки, возникающие при включении режима прокси на устройствах безопасности.

Сценарий 1: Прокси без SSL. HTTP GET с ответом 200OK с размером страницы 44 КБ. Для теста используется IXIA BreakingPoint для имитации HTTP-клиентов и серверов с устройством безопасности в середине. Цель теста — достичь максимального количества уникальных TCP/HTTP сессий в секунду. Чтобы понять влияние производительности прокси, были включены режим прокси и инспекция на время тестирования.

Наблюдение 1: Среднее время TCP ответа (response), когда устройство работает без и с режимом прокси, отличается более чем в 22 раза.



Наблюдение 2: Средняя продолжительность TCP сессии увеличивается в 225 раз, если сравнить режим работы устройства без и с прокси.



Сценарий 2: Подобно описанному выше сценарию, за исключением того, что теперь HTTP-GET 44KB страница шифруется сессией TLS1.1.

Наблюдение 1: с зашифрованным трафиком, в режиме прокси, наблюдается увеличение времени TCP ответа в 20 раз. [Примечание. В общем, время TCP ответа выше для зашифрованного трафика из-за задержки, которую вносит прокси, которая тратит больше ресурсов для обработки этого трафика].



Наблюдение 2: Средняя продолжительность TCP сессии увеличивается в ошеломляющих 400 раз.



Советы по внедрению эффективного прокси

1. Выбор правильного производителя

Аппаратное и программное обеспечение постоянно оптимизируется для лучшей обработки в режиме прокси. Так называемая «разгрузка» (offloading) и методы распределения выделенных ресурсов повышают эффективность режима прокси в устройствах безопасности. Клиенты должны знать об этом и сравнивать характеристики устройств безопасности в режиме прокси, как один из критериев выбора производителя.
2. Выбор правильных шифров и методов шифрования, когда это возможно

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

3. Используйте разные уровни шифрования на защищенных и незащищенных сторонах

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



Выводы

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

За основу взято исследование Amritam Putatunda и Rakesh Kumar.

Дополнительные ресурсы:

https://www.ixiacom.com/
https://www.ixiacom.com/products/breakingpoint
https://www.ixiacom.com/products/breakingpoint-ve
https://www.ixiacom.com/products/breakingpoint-aws
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/333360/


Метки:  

[Из песочницы] Решето Эратосфена, попытка минимизировать память

Пятница, 14 Июля 2017 г. 17:08 + в цитатник

Введение


Одним из алгоритмов для поиска простых чисел является Решето Эратосфена предложенное еще древнегреческим математиком.

Картинка из википедии:

image

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

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

Для решения используется сегментация (когда память выделяется по кускам) и другие ухищрения (см. тут).

Реализация алгоритма


Алгоритм внизу (написан на java) предполагает минимальный объем памяти — по сути для каждого найденного простого числа мы храним еще одно число — последнее зачеркнутое (наибольшее). Если я правильно оцениваю объем памяти ln(n) — число найденных простых.

Суть алгоритма:

Допустим мы имеем несколько уже найденных простых чисел отсортированных по возрастанию. (Алгоритм стартует с [2,3]). Для каждого из них храним последнее (наибольшее) зачеркнутое число. Инициализируем самими простыми числами.

Выбираем кандидата в простые например наибольшее найденное простое число +1 (в алгоритме внизу перескакиваем четные как заведомо не простые).

Кандидат сравнивается с последним зачеркнутым текущего простого. Пока текущее зачеркнутое меньше кандидата увеличиваем его на текущее простое. Если текущее зачеркнутое равно кандидату, то кандидат не простое число. Выбираем следующего кандидата.

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

Если добрались до конца списка простых чисел (то есть все зачеркнутые больше кандидата) мы нашли очередное простое число.

Добавляем его в список и инициализируем последнее зачеркнутое найденным простым.

Код на java


import java.util.ArrayList;
import java.util.List;

public class SieveEratosthenes {
    static class PrimePair {
        Integer prime;
        Integer lastCrossed;

        PrimePair(Integer prime, Integer lastCrossed) {
            this.prime = prime;
            this.lastCrossed = lastCrossed;
        }
    }

    private List primes;

    private SieveEratosthenes() {
        primes = new ArrayList<>();
        primes.add(new PrimePair(2, 2));
        primes.add(new PrimePair(3, 3));
    }

    private void fillNPrimes(int n) {
        while (primes.size()/restart
                candidate+=2;
                i=-1;
            }
        }
        System.out.println(candidate);
        primes.add(new PrimePair(candidate, candidate));
    }

    public static void main(String[] args) {
        SieveEratosthenes test = new SieveEratosthenes();
        test.fillNPrimes(1000);
    }
}

Тот же код на питоне:

primes = [2, 3]
last_crossed = [2, 3]


def add_next_prime():
    candidate = primes[-1] + 2
    i = 0
    while i < len(primes):
        while last_crossed[i] < candidate:
            last_crossed[i] += primes[i]
        if last_crossed[i] == candidate:
            candidate += 2
            i = 0
        i += 1

    primes.append(candidate)
    last_crossed.append(candidate)


def fill_primes(n):
    while len(primes) < n:
        add_next_prime()


fill_primes(1000)
print(primes)

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

-> GitHub

Возможно это очередное изобретение велосипеда. Готов выслушать замечания.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/333350/


Метки:  

Что такое Display Rate и как он влияет на доход вашего приложения?

Пятница, 14 Июля 2017 г. 16:38 + в цитатник


Rewarded Video или просмотр видеорекламы за вознаграждение стали полноценной частью монетизационной стратегии для мобильных приложений. В эффективности этого формата рекламы уже никто не сомневается — компания Soomla сообщает, что видео с вознаграждением повышают выручку на 20-40%, а Facebook анонсировал подключение Rewarded Video в Facebook Audience Network. Однако, работать с Rewarded Video не всегда так просто, как кажется.


При использовании Rewarded Video в приложении нужно обращать внимание на Display Rate — это метрика, которая отображает соотношение показанной рекламы к загруженной. Значения Display Rate отличаются для каждого из форматов рекламы: около 90% для небольших баннеров и 60-70% для Static Interstitial (“промежуточные” полноэкранные баннеры), по данным Appodeal.

В случае с Rewarded Video и Video Interstitial (полноэкранные видео между экранами приложения) в статистике появляются печальные цифры — 10-20%, а то и 1-5%. Это значит, что из 1000 загруженных видео были показаны только 100. Такая ситуация никому не идет на пользу: ни рекламной сети, предоставившей рекламу, ни разработчику, так как реклама остается неотработанной и неоплаченной. Иными словами, дохода от нее не дождешься.

Низкий Display Rate и, как следствие, потеря выручки — результат непродуманной загрузки видео-рекламы. Золотое правило работы с Rewarded Video: не загружайте видео, если знаете, что пока не сможете его показать, — а узнать это во многих случаях можно заранее.

Многие разработчики загружают видео сразу при запуске новой сессии приложения, вместе с другими форматами.Например, если включена опция auto-cache (автоматическое кэширование) — видео подгружается при запуске, вместе с баннерами и полноэкранной рекламой, и дожидается своего часа. Этот процесс — дополнительная нагрузка на сервера рекламных сетей. Сети получают неотработанную рекламу и из-за этого не могут оценить реальный потенциал приложения. Приложение загружает рекламы значительно больше, чем может показать — разумеется, это приводит к плохому Display Rate и снижает доход разработчика.

Рассмотрим первый пример, при котором видео загружается слишком рано. Предположим, после пятого уровня в вашей игре пользователю предлагается открыть сундучок с вознаграждением. Для того, чтобы получить это вознаграждение сразу после пятого уровня, игрок может посмотреть Rewarded Video. Если же пользователь не хочет смотреть видео, то открыть этот самый сундучок он сможет, скажем, только через два часа. Вероятнее всего, пользователь захочет посмотреть видео — тогда загруженное (подготовленное заранее) видео действительно пригодится.
Тем не менее, чтобы дойти до пятого уровня, пользователю может понадобиться несколько игровых сессий — например, десять. Все десять раз, что пользователь заходит в приложение, но не доходит до пятого уровня, видеореклама грузится заново из-за автоматического кэширования и уходит в небытие непоказанной.



Теперь рассмотрим более продуманную схему работы с Rewarded Video в том же самом приложении. Допустим, что мы разделяем пользователей на две группы: на тех, кто, скорее всего, посмотрит видео, и тех, кто еще не дошел до момента показа.
В игре, рассмотренной выше, не стоит загружать заранее видео для пользователя, который пока играет на третьем уровне. Этот игрок может запустить приложение еще несколько раз прежде, чем дойдет до пятого уровня с Rewarded Video и предложенной наградой. Как только пользователь приближается к событию вручения сундучка — например, к четвертому уровню, — можно приготовить видео для показа. Вероятность, что оно будет просмотрено, гораздо выше. В этом случае мы отключаем автокэш для Rewarded Video, ориентируемся на ход игры и переходим на ручное кэширование для Rewarded Video. Мы откладываем момент загрузки (подготовки) видео на событие четвертого уровня. Таким образом, видео загружается заранее, но не слишком рано.
Получается, что лучший, пускай и самый очевидный способ избежать неприятностей — работать с аудиторией и анализировать ее поведение в приложении.



Конечно, геймплей может не подразумевать наличие четкого плана по раздаче сундучков, да и самих сундучков в игре может не быть. Например, за Rewarded Video можно получить внутриигровую валюту. В этом случае стоит ориентироваться на статистику приложения и отслеживать, когда пользователи идут в магазин игры. Возможно, по статистике будет видно, что в первые 3-5 минут игроки пробуют разных героев или разгадывают простые паззлы. В эти первые минуты игры внутриигровой магазин их может не интересовать. Также по статистике можно обнаружить, что никто не открывает Rewarded Video раньше, чем через 4 минуты — тогда нет смысла готовить ролик с самого начала, а стоит начать грузить его через 3 минуты после старта игры.

Мы рассмотрели несколько общих примеров борьбы с низким Display Rate за счет продуманной загрузки Rewarded Video. Конечно, здесь возможно множество стратегий в зависимости от жанра и аудитории приложения. Изучайте своих пользователей, смотрите на приложение их глазами, и всё будет хорошо.





Кроме того, есть немало способов повысить Display Rate, побуждая игроков смотреть видео за вознаграждение. В этом помогут всплывающие уведомления: они сообщат о заканчивающейся валюте, о конце уровня, о необходимости обновления героя и так далее. Уведомления должны быть максимально прозрачными — пользователь хочет четко понимать, какое вознаграждение он получит за просмотр, и почему оно ему пригодится.

Несмотря на эти нюансы, Rewarded Video — явный тренд в рекламной монетизации, и он только набирает обороты. Пользователи лояльны к просмотру видео за вознаграждение — такая реклама раздражает их гораздо меньше, поэтому экспериментируйте и анализируйте результаты
работы с этим форматом.

О том, как сделать так, чтобы пользователи смотрели вашу рекламу, читайте в нашем предыдущем посте.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/333336/


Куда пойти, что читать, с кем общаться на профессиональные темы: дорожная карта для iOS-разработчика

Пятница, 14 Июля 2017 г. 16:03 + в цитатник
В предыдущей статье я вскользь затрагивал тему развития мобильного разработчика. Когда вся твоя команда — это ты, это действительно острая тема. Некому помочь советом, поделиться интересной статьей или посоветовать годный видеокурс. Около года назад я решил бороться с этой проблемой и начал вести два Telegram-канала, в которых ежедневно публикую подборку самых актуальных материалов по iOS- и Android-разработке. За это время у меня накопилось множество отличных ресурсов, чатов, рассылок и событий, которыми стоит поделиться с сообществом. Начнем с iOS-разработки.


В целом: если вы хотите получать самые оперативные новости об iOS, подпишитесь на iOS Good Reads. Начинка телеграм-канала: архитектура, процессы, инструменты, новости, события, машинное обучение, холивары, безопасность. И все это — про нашу любимую ось. Не больше трех материалов в день, всегда в кармане. А теперь — об источниках, которые я изучаю, чтобы его наполнять.

Email-рассылки


MBLTdev Digest
Единственная живая русскоязычная рассылка для iOS-разработчиков. Захватывают основные новости, статьи по разработке, иногда мелькает дизайн и бизнес. Уже 124 выпуска сделали.
Авторы: Саша Черный, Руслан Гуменный, Иван Козлов.
Периодичность: еженедельно, по пятницам.

iOS Dev Weekly
Самая известная iOS-рассылка в мире. Обязательна для подписки любому уважающему себя разработчику.
Авторы: Dave Verwer и компания.
Периодичность: еженедельно, по пятницам.

This Week in Swift
Все, что как-то связано со Swift — и мобильная разработка, и back-end, и вакансии.
Автор: Natasha Murashev.
Периодичность: еженедельно, по понедельникам.

mokacoding
Про различные виды тестирования в iOS и автоматизацию процессов. Много авторских материалов.
Автор: Giovanni Lodi.
Периодичность: еженедельно, по вторникам.

Swift Weekly Brief
Для тех, кому лень самостоятельно следить за развитием Swift на GitHub и в mailing list’е. Каждую неделю составляется подборка самых интересных предложений по изменениям, коммитов и pull request’ов в репозиторий языка.
Автор: Jesse Squires.
Периодичность: еженедельно, по четвергам.

iOS Dev Tools Weekly
Обзоры различных инструментов для разработки, дизайна, маркетинга, аналитики.
Автор: Adam Swinden.
Периодичность: нерегулярно.

Чаты


iOS Good Talks
Обсуждение последних интересных статей и новостей, холивары, code review. Запрещены стикеры, мат и stackoverflow-style вопросы по разработке.
Количество участников: 700+.

Cocoa Developers Club
Русскоязычное slack-коммьюнити iOS-разработчиков.
Количество участников: 3.000.

iOS Developers HQ
Огромный slack англоговорящих разработчиков. Можно встретить много известных членов iOS-сообщества.
Количество участников: 15.000+.

Темы для холиваров


Что сделать, чтобы влиться в любой из чатов? Нужно понимать актуальные темы для холиваров. Вот несколько. Они помогут вам быстро стать своим.


Подкасты


Podlodka Podcast
Обсуждаются различные темы, так или иначе связанные с мобильной разработкой — архитектура, паттерны, библиотеки, процессы разработки в различных компаниях. Практически каждый выпуск — новый гость, известный в сообществе. Уже приходили Алексей Скутаренко, Алекс Денисов, Роман Бусыгин, Ксения Покровская.
Авторы: Егор Толстой, Стас Цыганов, Глеб Новик.
Периодичность: еженедельно, по понедельникам.

Fatal Error
Изменения в Swift, конференции, инструменты, библиотеки, архитектура. Хороший подкаст с хорошими ведущими.
Авторы: Soroush Khanlou и Chris Dzombak.
Периодичность: еженедельно, по понедельникам.

Swift Unwrapped
Подкаст, целиком и полностью посвященный Swift. Каждый выпуск подробно разбирается какая-то одна тема — обработка ошибок, тестирование, SourceKit.
Авторы: JP Simmard, Jesse Squires.
Периодичность: еженедельно, по понедельникам.

События


CocoaHeads Russia
Ежемесячные митапы по iOS-разработке. В основном проходят в Москве, иногда захватывают Питер, Новосибирск, Екатеринбург и другие города. Темы абсолютно разные — начиная от неочевидных возможностей Swift, заканчивая менеджерскими рассказами. Уровень докладов тоже очень разный, но в целом качество доставляет.
Организаторы: Александр Зимин, Станислав Жуковский.
Периодичность: в Москве ежемесячно, в других городах нерегулярно.

Avito.iOS
Тематические митапы от компании Avito. Последний прошел в июне, были доклады про архитектуру, тестирование, рабочие процессы.
Организаторы: Avito.
Периодичность: 2-3 раза в год.

Rambler.iOS
Традиционно одни из лучших митапов Москвы, где подавляющая часть докладов готовится сотрудниками Rambler&Co. Много практики, мало воды.
Организаторы: Rambler&Co.
Периодичность: 2-3 раза в год.

Mobius
Серия питерских конференций по мобильной разработке. Осенью этого года решили перебраться в Москву. Если вы уже откладываете деньги с обедов, чтобы куда-нибудь поехать, Mobius в ноябре — отличный выбор. Качество докладов обычно на высоте, уровень сложности — выше среднего.
Организаторы: JUG.ru.
Периодичность: 2 раза в год.

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

AppsConf
Это, по сути, не отдельная конференция, а одна из секций РИТ. В этом году я на ней не был, но отзывы посетителей достаточно неплохие — было много крутых докладов, до этого не засветившихся нигде.
Организатор: Олег Бунин.
Периодичность: раз в год.

Twitter-аккаунты


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

Это все источники, о которых я хотел рассказать сегодня. Надеюсь, вы узнали что-то полезное для себя в эту пятницу. Если у читателей возникнет интерес — сделаю такую же дорожную карту по источникам об Android-разработке. Напоминаю, что анонсы тематических выступлений об iOS сотрудников Avito, мероприятий и конкурсов публикуются также в телеграм-канале AvitoTech, в одноименном твиттере и в группе на Facebook.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/333348/


Метки:  

[Перевод] На пути к Go 2

Пятница, 14 Июля 2017 г. 15:49 + в цитатник

Перевод блог поста и доклада Russ Cox с GopherCon 2017, с обращением ко всему Go сообществу помочь в обсуждении и планировании Go 2. Видео доклада будет добавлено сразу после опубликования.

25 сентября 2007 года, после того как Роб Пайк, Роберт Грисмайер и Кен Томпсон несколько дней обсуждали идею создания нового языка, Роб предложил имя "Go".



В следующем году, Ян Лэнс Тейлор и я присоединились к команде и мы впятером создали два компилятора и стандартную библиотеку, которые были публично открыты 10 ноября 2009.



В следующие два года, с помощью нового open-source сообщества гоферов, мы экспериментировали и пробовали различные идеи, улучшая Go и ведя его к запланированному релизу Go 1, предложенному 5 октября 2011.



С ещё более активной помощью Go сообщества, мы пересмотрели и реализовали этот план, в итоге выпустив Go 1 28 марта 2012.



Релиз Go 1 ознаменовал кульминацию почти пяти полных лет креативных и неистовых усилий, которые привели нас от выбора имени и обсуждения идей до стабильного готового языка. Он также знаменовал явный переход от изменений и непостоянства к стабильности.


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


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


На сегодня у нас есть 5 лет реального опыта использования Go для создания огромных, качественных продакшн-систем. Это дало нам чувство того, что работает, а что нет. И сейчас самое время начать новый этап в эволюции и развитии Go. Сегодня я прошу вас всех, сообщество Go разработчиков, будь вы сейчас тут в зале GopherCon или смотрите видео или читаете это в Go блоге, работать вместе с нами по мере того, как мы будем планировать и реализовывать Go 2.


Далее я расскажу и объясню задачи, которые стоят перед Go 2; наши ограничения и препятствия; сам процесс в целом; важность описания вашего опыта с Go, особенно если он относится к проблемами которые мы можем пытаться решить; возможные варианты решений; как мы будем внедрять Go 2 и как вы все можете в этом помочь.


Задачи


Задачи перед Go сегодня стоят точно такие же, какими были в 2007 году. Мы хотим сделать программистов более эффективными в управлении двумя видами масштабируемости: масштабируемости систем, особенно многопоточных(concurrent) систем, взаимодействующих со многими другими серверами — широко представленными в виде серверов для облака, и масштабируемость разработки, особенно большие кодовые базы, над которыми работают множество программистов, часто удалённо — как, например, современная open-source модель разработки.


Эти виды масштабируемости сегодня присутствуют в компаниях всех размеров. Даже стартап из 5 человек может использовать большие облачные API сервисы, предоставленные другими компаниями и использовать больше open-source софта, чем софта, который они пишут сами. Масштабируемость систем и масштабируемость разработки также актуальны для стартапа, как и для Google.


Наша цель для Go 2 — исправить основные недочёты в Go, мешающие масштабируемости.


(Если вы хотите больше узнать про эти задачи, посмотрите статью Роба Пайка 2012 года “Go at Google: Language Design in the Service of Software Engineering” и мой доклад с GopherCon 2015 “Go, Open Source, Community”.)


Препятствия


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


Go 2 должен способствовать всем этим разработчикам. Мы должны просить их разучить старые привычки и выучить новые только если выгода от этого действительно того стоит. Например, перед Go 1, метод интерфейсного типа error назывался String. В Go 1 мы переименовали его в Error, чтобы отличить типы для ошибок от других типов, который просто могут иметь отформатированное строчное представление. Однажды я реализовывал тип, удовлетворяющий error интерфейсу, и, не думая, называл метод String, вместо Error, что, конечно же, не скомпилировалось. Даже через 5 лет я всё ещё не до конца разучил старый способ. Этот пример проясняющего переименования было важным и полезным изменением для Go 1, но был бы слишком разрушительным для Go 2 без действительно очень весомой причины.


Go 2 должен также хорошо дружить с существующим Go 1 кодом. Мы не должны расколоть Go экосистему. Смешанные программы, в которых пакеты написаны на Go 2 и импортируют пакеты на Go 1 или наоборот, должны безпрепятственно работать в течении переходного периода в несколько лет. Нам ещё предстоит придумать, как именно этого достичь; инструментарий для автоматического исправления и статического анализа вроде go fix определённо сыграют тут свою роль.


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


При этом я не считаю мелкие вспомогательные изменения, как, возможно, разрешение идентификаторов на большем количестве натуральных языков или добавления литералов для чисел в двоичной форме. Подобные мелкие изменения также важны, но их гораздо проще сделать правильно. Сегодня я буду концентрироваться на возможных крупных изменениях, таких как дополнительная поддержка обработки ошибок или добавление неизменяемых (immutable) или read-only значений, или добавления какой-нибудь формы generics, или ещё какой-нибудь важной пока не озвученной темы. Мы сможем сделать только несколько таких крупных изменений. И мы должны будем выбрать их очень внимательно.


Процесс


Это поднимает важный вопрос. Какой процесс разработки Go в целом?


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


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



Первый шаг — использовать Go, чтобы наработать опыт работы с ним.


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


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


Четвертый шаг — реализовать решение, проверить его и улучшить, основываясь на результатах проверки.


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


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


И хотя я не думаю, что мы когда-либо рассказывали про этот процесс целиком, но мы объясняли его по частям. В 2012, когда мы выпустили Go 1 и сказали, что настало время начать использовать Go и перестать изменять, мы объясняли первый шаг. В 2015, когда мы представили изменения в процесс предложений (proposals) для Go, мы объясняли шаги 3, 4 и 5. Но мы никогда не объясняли второй шаг подробно, и я бы хотел сделать это сейчас.


(Более подробно про разработку Go 1 и про прекращение изменений в языке, посмотрите доклад Роба Пайка и Эндрю Герранда на OSCON в 2012 году “The Path to Go 1.”. Более детально про процесс предложений можно посмотреть в докладе Эндрю Герранда на GopherCon в 2015 “How Go was Made” и в документации к самому процессу)


Объяснение проблем



Объяснение проблемы состоит из двух частей. Первая часть — лёгкая — это просто озвучить, в чём, собственно, проблема заключается. Мы, разработчики, в целом достаточно хорошо это умеем. В конце концов, каждый тест, который мы пишем это формулировка проблемы, которая должна быть решена, причём написанная на таком точном языке, который поймёт даже компьютер. Вторая часть — сложная — заключается в том, чтобы описать важность проблемы достаточно хорошо, чтобы все остальные поняли, почему мы должны тратить время на её решение и его поддержку. В отличие от точной формулировки проблемы, мы не так часто описываем их важность и мы не слишком это хорошо умеем. Компьютер никогда нас не спросит “Почему этот случай для теста важен? А ты уверен, что это именно та проблема, которую ты должен решать? Точно ли решение этой проблемы это самая важная задача, которой ты должен заниматься?”. Возможно, однажды так и будет, но точно не сегодня.


Давайте взглянем на старый пример из 2011. Вот, что я написал про переименование os.Error в error.Value, когда мы планировали Go 1.


error.Value
(rsc) Проблема, которую мы имеем в низкоуровневых библиотеках заключается в том, что всё зависит от “os” из-за os.Error, поэтому сложно делать вещи, которые пакет os сам мог бы использовать (как пример с time.Nano ниже). Если бы не os.Error, не было было бы столько других пакетов, которые зависят от пакета os. Сугубо вычислительные пакеты вроде hash/* или strconv или strings или bytes могли бы обойтись без него, к примеру. Я планирую исследовать (пока что ничего не предлагая) определить пакет error примерно с таким API:

package error
type Value interface { String() string }
func New(s string) Value

Он начинается с краткой однострочной формулировки проблемы: в низкоуровневых библиотеках всё импортирует “os” ради os.Error. Далее идут 5 строк, которые я подчеркнул, описывающие значимость проблемы: пакеты, которые “os” использует не могут использовать тип error в своих API, и другие пакеты зависят от os по причинам никак не связанным с работой операционной системы.


Убедят ли вас эти 5 строк, что проблема стоит внимания? Это зависит от того, насколько хорошо вы можете заполнить контекст, который я оставил за рамками: чтобы быть понятым, нужно уметь предугадать, что другие люди знают. Для моей аудитории в то время — десять других людей в команде Google работающей над Go, которые читали этот документ — этих 50 слов было достаточно. Чтобы представить ту же самую проблему аудитории на конференции GothamGo прошлой осенью — аудитории с гораздо более разнообразным опытом — я должен быть предоставить больше контекста, и я использовал уже 200 слов, плюс примеры реального кода и диаграмму. И это факт, что современное Go сообщество, которое пытается объяснить важность какой-либо проблемы, должно добавлять контекст, причём проиллюстрированный конкретными примерами, который можно было бы исключить в беседе с вашими коллегами, например.


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


Пример: високосные секунды


Мой первый пример связан с временем.


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


    start := time.Now()       // 3:04:05.000
    event()
    end := time.Now()         // 3:04:05.010

    elapsed := end.Sub(start) // 10 ms

Эта очевидная процедура может не сработать во время “високосной секунды” (leap second). Когда наши часы не совсем точно синхронизированы с дневным вращением Земли, специальная високосная секунда — официально это секунды 23:59 и 60 — вставляется прямо перед полуночью. В отличие от високосного года, у високосных секунд нет легко предсказуемого паттерна, что затрудняет автоматизацию их учета в программах и API. Вместо того, чтобы ввести специальную, 61-ю, секунду, операционные системы обычно реализуют високосную секунду переводя часы на секунду назад аккурат перед полуночью, так что при этом 23:59 происходит дважды. Такой сдвиг часов выглядит, как поворот времени вспять, и наш замер 10-миллисекундного события теперь может оказаться отрицательным значением в 990 миллисекунд.


    start := time.Now()       // 11:59:59.995
    event()
    end := time.Now()         // 11:59:59.005 (really 11:59:60.005)

    elapsed := end.Sub(start) // –990 ms

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


Только при этом нестандартном сдвиге часов, монотонные часы не особо лучше обычных часов, которые, в отличие от монотонных, умеют показывать текущее время. Поэтому, ради простоты API пакета time в Go 1 доступ есть только к обычным часам компьютера.


В октябре 2015 появился баг-репорт о том, что Go программы некорректно возвращают длительность событий во время подобных сдвигов часов, особенно в случае с високосной секундой. Предложенное решение было также и заголовком репорта: “Добавить новый API для доступа к монотонным часам”. Тогда я утверждал, что проблема не была достаточно значима, чтобы ради неё создавать новый API. Несколько месяцев перед этим, для високосной секунды в середине 2015 года, Akamai, Amazon и Google научились замедлять свои часы таким образом, что эта дополнительная секунда “размазывалась” по всему дню и не нужно было переводить часы назад. Всё шло к тому, что повсеместное использовать этого подхода “размазывания секунды” позволило бы избавиться от перевода часов вообще и проблема исчезнет сама собой. Для контраста, добавление нового API в Go добавило бы две новые проблемы: мы должны были бы объяснять про эти два типа часов, обучать пользователей когда использовать какой из них и конвертировать массу существующего кода, и всё лишь для ситуации, когда очень редка и, скорее всего, вообще исчезнет сама.


Мы поступили так, как делаем всегда, когда решение проблемы не очевидно — мы стали ждать. Ожидание даёт нам больше времени, чтобы накопить больше опыта и углубить понимание проблемы, плюс больше времени на поиски хорошего решения. В этом случае, ожидание добавило понимание серьёзности проблемы, в виде сбоя в работе Cloudflare, к счастью незначительного. Их Go код замеряющий длительность DNS запросов во время високосной секунды в конце 2016 года возвращал негативное значение, подобное примеру с -990 миллисекундами выше, и это приводило к панике на их серверах, поломав около 0.2% всех запросов в самом пике проблемы.


Cloudflare это именно тот тип облачных систем, для которых Go и создавался, и у них случился сбой в продакшене из-за того, что Go не мог замерять время правильно. Дальше, и это ключевой момент тут, Cloudflare написали про свой опыт — Джон Грэхем-Камминг опубликовал блог-пост “Как и почему високосная секунда повлияла на DNS Cloudflare”. Рассказав конкретные детали и подробности инцидента и их опыт работы с Go, Джон и Cloudflare помогли нам понять, что проблема неточного замера во время високосной секунды была слишком важной, чтобы оставлять её не решенной. Через два месяца после публикации статьи, мы разработали и реализовали решение, которое появится в Go 1.9 (и, кстати, мы сделали это без добавления нового API).


Пример: алиасы


Мой второй пример о поддержке алиасов в Go.


За последние несколько лет, Google собрал команду, сфокусированную на крупномасштабных изменениях в коде, вроде миграций API и исправлений багов по всей кодовой в базе, состоящей из миллионов файлов исходных кодов и миллиардов строк кода, написанных на C++, Go, Java, Python и других языках. Одна из вещей, которую я усвоил из их трудов, было то, что при замене в API старого имени на новое, важно иметь возможность делать изменения шаг за шагом, а не всё за один раз. Чтобы это сделать, должна быть возможность задекларировать, что под старым именем, подразумевается новое. В C++ есть #define, typedef и использование деклараций позволяют это сделать, но в Go такого механизма не было. И поскольку одной из главных задача перед Go стоит умение масштабироваться в больших кодовых базах, было очевидно, что нам нужен какой-то механизм перехода от старых имён к новым во время рефакторинга, и что другие компании также упрутся в эту проблему по мере роста их кодовых баз на Go.


В марте 2016 я начал обсуждать с Робертом Грисмайером и Робом Пайком то, как Go мог бы справляться с многошаговым рефакторингом кодовых баз, и мы пришли к идее алиасов (alias declarations), которые были именно тем механизмом, что нужно. В тот момент я был очень доволен тем, как Go развивался. Мы обсуждали идею алиасов ещё с ранних дней Go — на самом деле первый черновик спецификации Go содержит пример, использующий алиасы — но, каждый раз при обсуждении алиасов, и, чуть позже, алиасов типов, мы не сильно понимали для чего они могут быть важны, поэтому мы отложили идею. Теперь же мы предлагали добавить алиасы в язык не потому что они были прямо элегентным концептом, а потому что они решали очень серьезную практическую проблему, к тому же помогающая Go лучше решать поставленную перед ним задачу масштабируемости разработки. Я искренне надеюсь это послужит хорошей моделью для будущих изменений в Go.


Чуть позднее той же весной Роберт и Роб написали предложение, и Роберт предоставил его на коротком докладе (lightning talk) на GopherCon 2016. Следующие несколько месяцев были достаточно смутными, и точно не могут быть примером того, как делать изменения в Go. Один из многих уроков, который мы тогда вынесли была важность описания значимости проблемы.


Минуту назад я объяснил вам суть проблемы, дав некоторую минимальную информацию о том, как и почему эта проблема может возникнуть, но не дав конкретных примеров о том, как вам вообще решить, коснётся ли эта проблема вас когда-нибудь или нет. То предложение и доклад оперировали абстрактными примерами, включающими пакеты C, L, L1 и C1..Cn, но ничего конкретного, с чем программисты могли ассоциировать проблему. В результате, большая часть ответной реакции от сообщества была основана на идее того, что алиасы решают проблему Google, и которая не актуальна для остальных.


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


Осенью мы начали заново. Я выступил с докладом и написал статью, подробно объясняющую проблему, используя множество конкретных примеров из реальных open-source проектов, показывающих, что эта проблема актуальна для всех, а не только для Google. Теперь, после того как больше людей поняли проблему и могли оценить её важность, мы смогли начать продуктивное обсуждение о том, какое решение подойдёт лучше всего. Результатом этого стало то, что алиасы типов будут включены в Go 1.9 и помогут Go лучше масштабироваться во всё более крупных кодовых базах.


Рассказы об опыте использования


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


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


К примеру, недавно я изучал проблему дженериков (generics), и пока что я не вижу в голове чёткой картины подробного и детального примера проблемы, для решения которой пользователям Go нужны дженерики. Как результат, я не могу чётко ответить на вопрос о возможном дизайне дженериков — например, стоит ли поддерживать generic-методы, тоесть методы, которые параметризованы отдельно от получателя (receiver). Если бы у нас был большой набор реальных практических проблем, мы бы смогли отвечать на подобные вопросы, отталкиваясь от них.


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


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


Эти статьи и рассказы будут служить сырым материалом для процесса подачи предложений для Go 2, и мы нуждаемся в вас, чтобы помочь понять нам ваш опыт с Go. Вас около полумиллиона, работающих в разных окружениях, и совсем немного нас. Напишите блог пост в своем блоге или на Medium, или Github Gist (добавив расширение .md для Markdown), или в Google doc, или любым другим удобным вам способом. Написав пост, пожалуйста, добавьте его в эту новую страницу Wiki: https://golang.org/wiki/ExperienceReports


Решения



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


Одна из проблем, которую мы, возможно, будем решать, это то, что компьютеры часто при базовых арифметических вычислениях выдают дополнительные результаты, но в Go нет прямого доступа к этим результатам. В 2013 Роберт предложил, что мы можем расширить идею двойных выражений (“comma-ok”) на арифметические операции. Например, если x и y, скажем, uint32 значения, lo, hi = x * y вернет не только обычные нижние 32 бита, но и верхние 32 бита умножения. Эта проблема не выглядела особо важной, поэтому мы записали потенциальное решение, но не реализовывали его. Мы ждали.


Совсем недавно, мы разработали для Go 1.9 новый пакет math/bits, в котором находятся различные функции для манипулирования битами:


    package bits // import "math/bits"

    func LeadingZeros32(x uint32) int
    func Len32(x uint32) int
    func OnesCount32(x uint32) int
    func Reverse32(x uint32) uint32
    func ReverseBytes32(x uint32) uint32
    func RotateLeft32(x uint32, k int) uint32
    func TrailingZeros32(x uint32) int
    ...

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


Другая проблема, которую мы могли бы хотеть решить после выхода Go 1 был факт того, что горутины и разделённая (shared) память позволяли слишком легко создать ситуацию гонки (races) в Go программах, приводящих к падениям и прочим проблемам в работе. Решение, основанное на изменении языка, могло бы заключаться в том, чтобы найти какой-то способ гарантировать отсутствие ситуаций гонки, сделать так, чтобы такие программы не компилировались, например. Как это сделать для такого языка, как Go пока что остается открытым вопросом в мире языков программирования. Вместо этого мы добавили базовый инструмент, который очень просто использовать — этот инструмент, детектор гонок (race detector) стал неотъемлемой частью опыта работы с Go. В этом случае наилучшим решением оказалось изменение в runtime и в инструментарии, а не изменение языка.


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


Выпуск Go 2



В заключение, как же мы будем выпускать Go 2?


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


Нам потребуется время и планирование перед тем, как какие-либо изменения вообще начнут попадать в релизы Go 1, но, вполне вероятно, что мы можем увидеть мелкие изменения уже где-то через год, в Go 1.12 или около того. Это также даст нам время завершить сначала проект с менеджментом зависимостей.


Когда все обратно-совместимые изменения будут внедрены, допустим в Go 1.20, тогда мы сможем приступить к обратно-несовместимым изменениям в Go 2. Если окажется так, что не будет обратно-несовместимых изменений, то мы просто объявим, что Go 1.20 это и есть Go 2. В любом случае, на том этапе мы плавно перейдем от работы над Go 1.X релизами к Go 2.X, возможно с более продлённым окном поддержки для финальных Go 1.X релизов.


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


Вы можете нам помочь


Нам нужна ваша помощь


Обсуждение Go 2 начинается сегодня, и оно будет вестись публично, на открытых площадках вроде почтовой рассылки или трекера проблем. Пожалуйста, помогайте нам на каждом шагу на этом пути.


Сегодня мы больше всего нуждаемся в вашем опыте использования. Пожалуйста, расскажите и напишите о том, как Go работает для вас, и, что более важно, где и как он не работает. Напишите блог пост, покажите реальные примеры, конкретные детали и поделитесь своим опытом. И не забудьте добавить в вики-страничку. Это то, как мы начнём говорить о том, что мы, Go сообщество, можем захотеть изменить в Go.


Спасибо.


Russ Cox, 13 июля 2017

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/333346/


Метки:  

[Из песочницы] Измеряя Telegram

Пятница, 14 Июля 2017 г. 15:41 + в цитатник
«Пока что возможности по полноценной аналитике каналов
ограничены, в первую очередь, возможностями BotAPI Telegram»

канал «Телеграм-маркетинг», 28 июня 2016

Всё хорошо c каналами в Телеграме, кроме одного — их слишком сложно искать. Ссылки есть практически везде,…

Например:
В интернете:

1. С помощью роботов, индексирующих одни каналы в поисках других каналов (1.1, 1.2)
2. В каталогах каналов, пополняемых владельцами каналов (2.1, 2.2)
3. На биржах каналов (3.1, 3.2)
4. В тематических подборках каналов (сюда тоже залетало: 4.1, 4.2)
5. В списках каналов (5.1)
6. В Гугл Доке с каналами о каналах (взял из @raskruti: 6.1)

В мобильном приложении:

7. Скачав приложение с каталогом каналов (под iOS: TeleBots)

В самом Телеграме:

8. На каналах о каналах (8.1)
9. Используя ботов для каналов (9.1)
10. На каналах о каналах о каналах (10.1)

… но процесс поиска остаётся далек от совершенства. Без единого источника данных и нормального аналитического инструментария сложно не просто найти, но даже понять:
1. Насколько каналы распространены в России?
2. Насколько каналы пользуются популярностью и что такое «популярность» в числах?

На такие вопросы нужно отвечать цифрами. В интернете удаётся разыскать только разрозненные данные. Что-то есть на Rusbase (тут), в Ведомостях (тут), в Твиттере (тут), но вся эта информация получена не систематически и сложно верифицируема.

Данная статья посвящена анализу рынка русскоязычных Телеграм-каналов. Проделана работа от сбора обобщенного (не обязательно полного) списка русскоязычных каналов до кроулинга их содержимого и построения метрик. Анализировались только те каналы, которые продвигаются в большом интернете, указывая ссылки на себя. Такое поведение – индикатор каналов, создаваемых для большой аудитории.


Шаг 1. Сбор списка каналов


Для начала создадим единую базу данных методами партизанской аналитики. В качестве источников данных были выбраны два онлайн-каталога, в которые пользователи заносят свои каналы вручную: tlgrm.ru и tchannels.me, и два самопополняющихся каталога: tsear.ch и inten.to (оба, кстати, созданные нашими соотечественниками (одни и вторые -> раздел «О нас» внизу страницы), один переиндексируется вручную, другой – в реальном времени. На всех сайтах будем искать каналы, явно заявленные как «на русском языке». Короткая таблица для сравнения источников внизу.

image

Что позволит нам быть уверенными, что найденные каналы именно на русском?

1. tlgrm.ru по умолчанию создан для каналов на русском языке
2. tchannels.me состоит из каналов, добавленных вручную и явно указанных как русские (два языка для канала там указать нельзя by design)
3. tsear.ch определяет язык канала на основе анализа его содержимого с помощью API Яндекс.Переводчика
4. inten.to – это изначально unified API для переводческих сервисов, который как раз используется для автоматического определения языка каналов, скорее всего ребята шарят

Нам понадобятся: Chrome Developer Tools, cURL to Python конвертер, сам Python, общая эрудиция.

Найдены более 10,000 каналов, результаты — одной картинкой.

image

Немного технических деталей о том, как достать данные.

Каталоги, пополняемые вручную

1. tlgrm.ru Целевой раздел: /channels/. Для получения полного списка нужно обойти все релевантные категории и «списать» IDшники каналов, они будут прямо в теле страницы.

2. tchannels.me Можно выбрать русский в настройках каталога и проскроллить 27 категорий одну за другой. А можно использовать API сервиса в своих интересах, немного изменив параметры: tchannels.me/api/channels?list=top&categoryId=&languages=russian&offset=0&count=1000000

Автоматически пополняемые каталоги

3. tsear.ch Целевой раздел: /list/ru/. Раз за разом кликая на кнопку Next можно записать каналы с каждой страницы и составить общий список.

4. inten.to Целевой раздел: /telegram/channels/russian/. Эти ребята очень беспокоятся за сохранность своей базы: канал можно найти либо по полному совпадению запроса c ID канала, либо по частичному совпадению с текстом его описания, при этом поиск выдаёт не более 100 первых совпадений, и то не сразу всю сотню, а по 10 штук. Испробованы метод «в лоб», и поиск перебором сочетаний из трёх букв, но таким образом было найдено всего 1722 канала. Это точно не все, так как вручную собранный по этим данным рейтинг с оригинальным рейтингом inten.to не совпал.
Можно было бы плюнуть, но перед уходом решил кое-что проверить: собрал список уникальных каналов из остальных трёх источников (8283 канала) и постарался найти их в inten.to (по точному совпадению ведь работает, не так ли?). Результат: найдены 3325 из 8283 (~40%).

Шаг 2. Разработка инструмента сбора статистики


Никто в здравом уме не будет руками обходить все 10,000 каналов – нет времени. К тому же, каталоги постоянно обновляются – тут нужен инструмент для регулярного использования. Инженер всегда выбирает то, с чем привык работать, и так как у меня уже были какие-то наработки в «прокачке» веб-браузера, я решил пойти мне известным, но не самым тривиальным путём – автоматизировать веб-клиент Телеграма с помощью … расширения к браузеру. Да, да, расширения к браузеру.

Мои аргументы в защиту этого способа:

1. Достаточность для решения задачи: расширение позволяют делать javascript инъекции и даёт удобный доступ к коду страницы с помощью JQuery
2. Наглядность работы: скрипт, который за тебя кликает и вбивает текст на твоих глазах понятно как допиливать
3. Принципиальная масштабируемость: установив кроулер-агент на пять виртуальных машин и развернув где-то сервер, раздающий агентам «задания» на кроулинг, можно получить ботнет (в расширениях есть возможность отправлять реквесты наружу, раздачу можно сделать через webhook)
4. Простая и быстрая установка: кроулер устанавливается на компьютер любой домохозяйки, код можно оставить в наследство внукам
5. Кросс-платформенность: Гугл Хром есть под все распространенные операционные системы
6. VPN за три копейки: благодаря распространенности соответствующих расширений к браузеру (Hola, frigate, и т.д.)

Такой подход по сути превращает кроулинг в извращенное (без Selenium’а) написание GUI автотестов. Своими глазами посмотреть процесс кроулинга можно на видео:





Забегая вперёд скажу, что подход оказался не универсальный, так как у веб-клиента Телеграма течёт память. Когда я пытался загрузить всю историю канала с флудом (скроллил вверх), все посты кешировались, и браузер начинал ужасно тормозить. Вместо того, чтобы фиксить клиент (код до webpack’а), поднимать свой инстанс и там кроулить, я решил не идти в гору и просто исключил каналы с флудом из рассмотрения.

Шаг 3. Масштабирование процесса


Чтобы попасть в Телеграм нужно пройти авторизацию по СМС. В наше время это, казалось бы, уже не требует человека: можно купить номеров на twilio и использовать SMS API, но у меня не сработало. Поэтому я сделал в лоб: отправился на Белорусский вокзал и с рук купил 15 сим-карт. Зарядив все ненужные телефоны, что были в квартире, и подняв пару виртуалок на домашней тачке, я приступил к сбору данных.

Шаг 4. Сбор статистики изнутри Телеграма, обсуждение результатов


Основные закономерности рассмотрим на выборке каналов с сайта tlgrm.ru. Мои аргументы в пользу этого источника:

1. Всего 13% уникальных каналов, то есть большинство из них содержатся ещё где-то – владельцы каналов озабочены раскруткой и заняты делом
2. Наличие категорий в каталоге – будет более интересная и точная аналитика
3. Русский домен + популярность в рунете = каналы будут русские в 99% случаев, можно не перепроверять.

Для исследования выберем почти все категории каналов, кроме каналов с контентом 18+ и каналов на узбекском – первые ни о чём и один флуд, а вторые в среднем более раскрученные, поэтому их присутствие «сдвинет» нам статистику вверх.

Далее, разбивая список всех каналов на кванты по ~500 штук (после 500 Телеграм забанит за слишком частый вызов метода search) и запуская в разных виртуалках кроулеры (обожаю high performance computing), соберем статистику за период от последнего поста на канале вглубь как минимум на неделю. Если на канале не было постов уже семь дней – считаем канал «мёртвым». Если посты идут чаще, чем раз в три часа – это флудильня. Далее следуют слайды с результатами и обсуждение в формате F.A.Q.

image

Каков размер моей выборки?
Я делал три замера: 3 мая, 4 июня и 23-го июня. Каналов в интересующих нас категориях, присутствовавших всё это время в базе tlgrm.ru, насчитал почти 1,889 штук.

Создаются ли новые каналы?
Да, постоянно. Если судить по датам создания каналов, каждый день появляется как минимум 3-4 канала.

Читают ли эти каналы и как активно?
Да, читают, достаточно активно. Более 70% каналов «выросли» за последние 2 месяца, суммарный прирост в подписках – почти 900 тысяч (я отфильтровал от этой цифры подозрительно быстро растущие каналы, о них далее), при этом всего 60 тысяч «отписались» от каналов, на которых однажды побывали.

Сколько каналов являются «нормальными»?
65% каналов регулярно обновляются и не похоже, чтобы они сильно флудили. С другой стороны, каждый третий произвольно взятый канал либо давно не обновлялся, либо с флудом.

image

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

Резюме


Цифры указаны на графиках, качественные выводы привожу списком ниже.

По этапу сбора списка каналов

1. Каналов на русском языке более десяти тысяч – это очень много, за жизнь не перечитать
2. Каждый из каталогов ведёт свой маленький список и не обменивается им с «соседями», что для пользователя означает необходимость искать сразу во многих местах – это очень неудобно

По этапу анализа каналов

1. Каналы как явление продолжают развиваться – их становится всё больше и больше каждый день
2. Число подписок на каналы растёт чудовищными темпами, почти на полмиллиона каждый месяц
3. Введение более сложных метрик позволяет генерировать автоматические подборки каналов, которые тем не менее нуждаются в человеческом review

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

Учитывая вышесказанное, пора жечь разработческие ресурсы. Для того, чтобы повысить юзабилити, как менеджер каталога каналов я бы сделал следующее:

1. Обновил бы свой классификатор каналов – web.archive подсказывает, что он почти ни у кого не изменялся с момента создания. Сейчас лето, нанял бы студентов.
2. Изменил бы интерфейс сайта, сделал бы «листалку» каналов в духе Яндекс.Музыки: для выбора нового канала понадобился бы максимум один клик.
3. Начал бы следить за тем, как люди ищут каналы и мерить, сколько они могут просмотреть за сессию, девелоперам давал бы премии за превышение показателей.
4. Связался бы с ребятами из inten.to и предложил бы им интеграцию – они на каналах всё равно не зарабатывают в прямую, а мне движок писать не хочется. Платил бы PRом и уважухой.

Материалы, использованные в статье
Все результаты (презентации, таблицы, код) выкладываю в открытый доступ.

1. Презентация, спредшиты, списки каналов – архив на Гугл.Драйве
2. Код расширения – репозиторий на Гитхабе
3. Контактные данные в Телеграме: devrazdev

Спасибо.

P.S. Напоследок короткая смешная история про русских и Telegraph

1. Идёте на telegra.ph
2. Осваиваете механику: вбиваете во все поля test, нажимаете publish. У меня в своё время получилось так. Это значит, что мой пост был шестнадцатым постом с заголовком test в тот день (14-го июня).
3. У вас случается прозрение
4. Вы начинаете путешествовать во времени и подглядывать за другими такими же, как вы, кто тоже написал test в заголовке, просто меняя цифры. Можете, к примеру, перенестись в 20 января и случайно встретить родную душу.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/333344/


Делаем сервис по распознаванию изображений с помощью TensorFlow Serving

Пятница, 14 Июля 2017 г. 15:00 + в цитатник

image
Всегда наступает то самое время, когда обученную модель нужно выпускать в production. Для этого часто приходится писать велосипеды в виде оберток библиотек машинного обучения. Но если Ваша модель реализована на Tensorflow, то у меня для Вас хорошая новость — велосипед писать не придется, т.к. можно использовать Tensorflow Serving.


В данной статье мы рассмотрим как использовать Tensorflow Serving для быстрого создания производительного сервиса по распознаванию изображений.


Tensorflow Serving — система для развертывания Tensorflow-моделей с такими возможностями как:


  • автоматический батчинг;
  • горячая замена моделей и версионирование;
  • возможность обработки параллельных запросов.

Дополнительным плюсом является возможность перегнать модель из Keras в Tensorflow-модель и задеплоить через Serving (если конечно в Keras используется Tensorflow бэкенд).


Как работает Tensorflow Serving



Основной частью Tensorflow Serving является сервер моделей (Model Server).


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


Если сервер моделей получает запрос, то он может выполнить следующие действия:


  • Запустить выполнение модели для этого запроса.
  • Объединить несколько запросов в батч и провести вычисление для всего батча, если соответствующая опция (флаг --enable_batching) активирована при запуске. Обработка батчами является более эффективной (особенно на GPU), поэтому эта функция позволяет увеличить количество обрабатываемых запросов на единицу времени.
  • Поставить запрос в очередь, если на текущий момент вычислительные ресурсы заняты.

Как уже было упомянуто ранее Tensorflow Serving поддерживает горячую замену моделей. Сервер моделей постоянно сканирует указанный при запуске путь на наличии новых моделей и при нахождение новой версии автоматически загружает эту версию. Это позволяет выкладывать новые версии моделей без необходимости остановки сервера моделей.


Таким образом, Tensorflow Serving имеет достаточный функционал для полноценной работы в production. Поэтому использование таких подходов, как создание собственной обертки над моделью, выглядит неоправданно, т.к. Tensorflow Serving предлагает те же возможности и даже больше без необходимости писать и поддерживать самописные решения.


Установка


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


Для сборки используется система сборки bazel.


Установка Tensorflow Serving описана на официальном сайте https://tensorflow.github.io/serving/setup. Я не буду расписывать подробно каждый шаг, а расскажу о проблемах, которые могут возникнуть при выполнении установки.


Со всеми шагами до конфигурации Tensorflow (./configure) не должно возникнуть проблем.


При конфигурации Tensorflow почти для всех параметров можно оставлять дефолтные значения. Но если вы выберете установку с CUDA, то конфигуратор спросит версию cuDNN. Надо вводить полную версию cuDNN (в моем случае 5.1.5).


Доходим до сборки (bazel build tensorflow_serving/...).


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


bazel build -c opt --copt=-mavx --copt=-mavx2 --copt=-mfma --copt=-mfpmath=both --copt=-msse4.2 tensorflow_serving/...


Проверьте, что все эти оптимизации доступны вашему процессору. У меня процессор не поддерживает AVX2 и FMA поэтому я собирал следующей командой:


bazel build -c opt --copt=-mavx --copt=-mfpmath=both --copt=-msse4.2 tensorflow_serving/...


По дефолту сборка Tensorflow потребляет много памяти, поэтому если у Вас ее не слишком много, то надо ограничить потребление ресурсов. Сделать это можно следующим флагом --local_resources availableRAM,availableCPU,availableIO (RAM in MB, CPU in cores, available I/O (1.0 being average workstation), например, --local_resources 2048,.5,1.0).


Если вы хотите собрать Tensorflow Serving с поддержкой GPU, то надо добавить флаг --config=cuda. Получится примерно такая команда.


bazel build -c opt --copt=-mavx --copt=-mfpmath=both --copt=-msse4.2 --config=cuda tensorflow_serving/...


При сборке может возникнуть следующая ошибка.


Текст ошибки

ERROR: no such target '@org_tensorflow//third_party/gpus/crosstool:crosstool': target 'crosstool' not declared in package 'third_party/gpus/crosstool' defined by /home/movchan/.cache/bazel/_bazel_movchan/835a50f8a234772a7d7dac38871b88e9/external/org_tensorflow/third_party/gpus/crosstool/BUILD.


Чтобы исправить эту ошибку, надо в файле tools/bazel.rc заменить @org_tensorflow//third_party/gpus/crosstool на @local_config_cuda//crosstool:toolchain


Еще может появиться следующая ошибка.


Текст ошибки

ERROR: /home/movchan/.cache/bazel/_bazel_movchan/835a50f8a234772a7d7dac38871b88e9/external/org_tensorflow/tensorflow/contrib/nccl/BUILD:23:1: C++ compilation of rule '@org_tensorflow//tensorflow/contrib/nccl:python/ops/_nccl_ops.so' failed: crosstool_wrapper_driver_is_not_gcc failed: error executing command external/local_config_cuda/crosstool/clang/bin/crosstool_wrapper_driver_is_not_gcc -U_FORTIFY_SOURCE '-D_FORTIFY_SOURCE=1' -fstack-protector -fPIE -Wall -Wunused-but-set-parameter ... (remaining 80 argument(s) skipped): com.google.devtools.build.lib.shell.BadExitStatusException: Process exited with status 1. In file included from external/org_tensorflow/tensorflow/conERROR: /home/movchan/.cache/bazel/_bazel_movchan/835a50f8a234772a7d7dac38871b88e9/external/org_tensorflow/tensorflow/contrib/nccl/BUILD:23:1: C++ compilation of rule '@org_tensorflow//tensorflow/contrib/nccl:python/ops/_nccl_ops.so' failed: crosstool_wrapper_driver_is_not_gcc failed: error executing command external/local_config_cuda/crosstool/clang/bin/crosstool_wrapper_driver_is_not_gcc -U_FORTIFY_SOURCE '-D_FORTIFY_SOURCE=1' -fstack-protector -fPIE -Wall -Wunused-but-set-parameter ... (remaining 80 argument(s) skipped): com.google.devtools.build.lib.shell.BadExitStatusException: Process exited with status 1. In file included from external/org_tensorflow/tensorflow/contrib/nccl/kernels/nccl_manager.cc:15:0: external/org_tensorflow/tensorflow/contrib/nccl/kernels/nccl_manager.h:23:44: fatal error: external/nccl_archive/src/nccl.h: No such file or directory compilation terminated.


Чтобы ее исправить надо удалить префикс /external/nccl_archive в строчке #include "external/nccl_archive/src/nccl.h" в следующих файлах:
tensorflow/tensorflow/contrib/nccl/kernels/nccl_ops.cc tensorflow/tensorflow/contrib/nccl/kernels/nccl_manager.h


Ура! Собрали наконец!


Экспорт модели


Экспорт модели из Tensorflow подробно описан на https://tensorflow.github.io/serving/serving_basic в разделе "Train And Export TensorFlow Model".


Для экспорта используется класс SavedModelBuilder. Я же использую Keras для тренировки Tensorflow-моделей, т.ч. я опишу процесс экспорта модели из Keras в Serving с помощью этого модуля.


Код экспорта ResNet-50, обученного на ImageNet.


import os
import tensorflow as tf
from keras.applications.resnet50 import ResNet50
from keras.preprocessing import image
from keras.applications.resnet50 import preprocess_input, decode_predictions
from tensorflow.contrib.session_bundle import exporter
import keras.backend as K

# устанавливаем режим в test time.
K.set_learning_phase(0)

# создаем модель и загружаем веса
model = ResNet50(weights='imagenet')

sess = K.get_session()

# задаем путь сохранения модели и версию модели
export_path_base = './model'
export_version = 1

export_path = os.path.join(
  tf.compat.as_bytes(export_path_base),
  tf.compat.as_bytes(str(export_version)))
print('Exporting trained model to', export_path)
builder = tf.saved_model.builder.SavedModelBuilder(export_path)

# создаем входы и выходы из тензоров
model_input = tf.saved_model.utils.build_tensor_info(model.input)
model_output = tf.saved_model.utils.build_tensor_info(model.output)

# создаем сигнатуру для предсказания, в которой устанавливаем входы и выходы модели
prediction_signature = (
  tf.saved_model.signature_def_utils.build_signature_def(
      inputs={'images': model_input},
      outputs={'scores': model_output},
      method_name=tf.saved_model.signature_constants.PREDICT_METHOD_NAME))

# добавляем сигнатуры к SavedModelBuilder
legacy_init_op = tf.group(tf.tables_initializer(), name='legacy_init_op')
builder.add_meta_graph_and_variables(
  sess, [tf.saved_model.tag_constants.SERVING],
  signature_def_map={
      'predict':
          prediction_signature,
  },
  legacy_init_op=legacy_init_op)

builder.save()

Вместо 'images' и 'scores' при установке входов и выходов можно указать любые названия. Эти названия будут использоваться далее.
Если модель имеет несколько входов и/или выходов, то нужно указать это в tf.saved_model.signature_def_utils.build_signature_def. Для этого нужно использовать model.inputs и model.outputs. Тогда код установки входов и выходов будет выглядеть следующим образом:


# создаем входы и выходы из тензоров
model_input = tf.saved_model.utils.build_tensor_info(model.inputs[0])
model_output = tf.saved_model.utils.build_tensor_info(model.outputs[0])
model_aux_input = tf.saved_model.utils.build_tensor_info(model.inputs[1])
model_aux_output = tf.saved_model.utils.build_tensor_info(model.outputs[1])

# создаем сигнатуру для предсказания
prediction_signature = (
  tf.saved_model.signature_def_utils.build_signature_def(
      inputs={'images': model_input, 'aux_input': model_aux_input},
      outputs={'scores': model_output, 'aux_output': model_aux_output},
      method_name=tf.saved_model.signature_constants.PREDICT_METHOD_NAME))

Еще стоит заметить, что в signature_def_map указываются все доступные методы (сигнатуры), которых может быть больше чем 1. В примере выше добавлен только один метод — predict. Название метода будет использоваться позже.


Запуск сервера моделей


Запуск сервера моделей осуществляется следующей командой:


./bazel-bin/tensorflow_serving/model_servers/tensorflow_model_server --enable_batching --port=9001 --model_name=resnet50 --model_base_path=/home/movchan/ml/serving_post/model


Рассмотрим, что означают флаги в данной команде.


  • enable_batching — флаг активации автоматического батчинга, позволяет Tensorflow Serving объединять запросы в батчи для более эффективной обработки.
  • port — порт, который модель будет прослушивать.
  • model_name — имя модели (будет использоваться далее).
  • model_base_path — путь до модели (туда, куда вы ее сохранили на предыдущем шаге).

Использование Tensorflow Serving из python


Для начала поставим пакет grpcio через pip.


sudo pip3 install grpcio


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


Для использования python API можно скопировать (сделать софтлинк) директорию bazel-bin/tensorflow_serving/example/inception_client.runfiles/tf_serving/tensorflow_serving. Там содержится все необходимое для работы python API. Я обычно просто копирую в директорию, в которой лежит скрипт, использующий это API.


Рассмотрим пример использования python API.


import numpy as np
from grpc.beta import implementations
from tensorflow_serving.apis import predict_pb2
from tensorflow_serving.apis import prediction_service_pb2

# Создаем канал и заглушку для запроса к Serving

host = '127.0.0.1'
port = 9001

channel = implementations.insecure_channel(host, port)
stub = prediction_service_pb2.beta_create_PredictionService_stub(channel)

# Создаем запрос

request = predict_pb2.PredictRequest()

# Указываем имя модели, которое было указано при запуске сервера (флаг model_name)

request.model_spec.name = 'resnet50'

# Указываем имя метода, которое было указано при экспорте модели (см. signature_def_map). 

request.model_spec.signature_name = 'predict'

# Копируем входные данные. Названия входов такие же как при экспорте модели.

request.inputs['images'].CopyFrom(
    tf.contrib.util.make_tensor_proto(image, shape=image.shape))

# Выполняем запрос. Второй параметр - timeout.

result = stub.Predict(request, 10.0)

# Извлекаем результаты. Названия выходов такие же как при экспорте модели.

prediction = np.array(result.outputs['scores'].float_val)

Полный код примера использования python API
import time
import sys
import tensorflow as tf
import numpy as np
from grpc.beta import implementations
from tensorflow_serving.apis import predict_pb2
from tensorflow_serving.apis import prediction_service_pb2
from keras.preprocessing import image
from keras.applications.resnet50 import preprocess_input, decode_predictions

def preprocess_image(img_path):
    img = image.load_img(img_path, target_size=(224, 224))
    x = image.img_to_array(img)
    x = np.expand_dims(x, axis=0)
    x = preprocess_input(x)
    return x

def get_prediction(host, port, img_path):
    image = preprocess_image(img_path)

    start_time = time.time()

    channel = implementations.insecure_channel(host, port)
    stub = prediction_service_pb2.beta_create_PredictionService_stub(channel)
    request = predict_pb2.PredictRequest()
    request.model_spec.name = 'resnet50'
    request.model_spec.signature_name = 'predict'

    request.inputs['images'].CopyFrom(
        tf.contrib.util.make_tensor_proto(image, shape=image.shape))

    result = stub.Predict(request, 10.0)
    prediction = np.array(result.outputs['scores'].float_val)

    return prediction, (time.time()-start_time)*1000.

if __name__ == "__main__":
    if len(sys.argv) != 4:
        print ('usage: serving_test.py   ')
        print ('example: serving_test.py 127.0.0.1 9001 ~/elephant.jpg')
        exit()

    host = sys.argv[1]
    port = int(sys.argv[2])
    img_path = sys.argv[3]

    for i in range(10):
        prediction, elapsed_time = get_prediction(host, port, img_path)
        if i == 0:
            print('Predicted:', decode_predictions(np.atleast_2d(prediction), top=3)[0])
        print('Elapsed time:', elapsed_time, 'ms')

Сравним скорость работы Tensorflow Serving c Keras-версией.


Код на Keras
import sys
import time
from keras.applications.resnet50 import ResNet50
from keras.preprocessing import image
from keras.applications.resnet50 import preprocess_input, decode_predictions
import numpy as np

def preprocess_image(img_path):
    img = image.load_img(img_path, target_size=(224, 224))
    x = image.img_to_array(img)
    x = np.expand_dims(x, axis=0)
    x = preprocess_input(x)
    return x

def get_prediction(model, img_path):
    image = preprocess_image(img_path)

    start_time = time.time()
    prediction = model.predict(image)

    return prediction, (time.time()-start_time)*1000.

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print ('usage: keras_test.py ')
        print ('example: keras_test.py ~/elephant.jpg')
        exit()

    img_path = sys.argv[1]

    model = ResNet50(weights='imagenet')

    for i in range(10):
        prediction, elapsed_time = get_prediction(model, img_path)
        if i == 0:
            print('Predicted:', decode_predictions(np.atleast_2d(prediction), top=3)[0])
        print('Elapsed time:', elapsed_time, 'ms')

Все замеры производились на CPU.


Для тестирования возьмем эту фотографию кота с Pexels.com, которую я нашел через https://everypixel.com.



Keras


Predicted: [('n02127052', 'lynx', 0.59509182), ('n02128385', 'leopard', 0.050437182), ('n02123159', 'tiger_cat', 0.049577814)]
Elapsed time: 419.47126388549805 ms
Elapsed time: 125.33354759216309 ms
Elapsed time: 122.70569801330566 ms
Elapsed time: 122.8172779083252 ms
Elapsed time: 122.3604679107666 ms
Elapsed time: 116.24360084533691 ms
Elapsed time: 116.51420593261719 ms
Elapsed time: 113.5416030883789 ms
Elapsed time: 112.34736442565918 ms
Elapsed time: 110.09907722473145 ms

Serving


Predicted: [('n02127052', 'lynx', 0.59509176015853882), ('n02128385', 'leopard', 0.050437178462743759), ('n02123159', 'tiger_cat', 0.049577809870243073)]
Elapsed time: 117.71702766418457 ms
Elapsed time: 75.67715644836426 ms
Elapsed time: 72.94225692749023 ms
Elapsed time: 71.62714004516602 ms
Elapsed time: 71.4271068572998 ms
Elapsed time: 74.54872131347656 ms
Elapsed time: 70.8014965057373 ms
Elapsed time: 70.94025611877441 ms
Elapsed time: 70.58024406433105 ms
Elapsed time: 68.82333755493164 ms

Как видно, Serving работает даже быстрее, чем версия на Keras. Это будет еще заметнее при большом количестве запросов.


Реализация REST API к Tensorflow Serving через Flask


Сначала установим Flask.


sudo pip3 install flask


Полный код REST-сервиса
from flask import Flask
from flask import request
from flask import jsonify
import tensorflow as tf
from grpc.beta import implementations
from tensorflow_serving.apis import predict_pb2
from tensorflow_serving.apis import prediction_service_pb2
from keras.preprocessing import image
from keras.applications.resnet50 import preprocess_input, decode_predictions
import numpy as np

application = Flask(__name__)

host = '127.0.0.1'
port = 9001

def preprocess_image(img):
    img = image.load_img(img, target_size=(224, 224))
    x = image.img_to_array(img)
    x = np.expand_dims(x, axis=0)
    x = preprocess_input(x)
    return x

def get_prediction(img):
    image = preprocess_image(img)

    channel = implementations.insecure_channel(host, port)
    stub = prediction_service_pb2.beta_create_PredictionService_stub(channel)
    request = predict_pb2.PredictRequest()
    request.model_spec.name = 'resnet50'
    request.model_spec.signature_name = 'predict'

    request.inputs['images'].CopyFrom(
        tf.contrib.util.make_tensor_proto(image, shape=image.shape))

    result = stub.Predict(request, 10.0)
    prediction = np.array(result.outputs['scores'].float_val)

    return decode_predictions(np.atleast_2d(prediction), top=3)[0]

@application.route('/predict', methods=['POST'])
def predict():
    if request.files.get('data'):
        img = request.files['data']
        resp = get_prediction(img)
        response = jsonify(resp)
        return response
    else:
        return jsonify({'status': 'error'})

if __name__ == "__main__":
    application.run()

Запустим сервис.


python3 serving_service.py


Протестируем сервис. Отправим запрос через curl.


curl '127.0.0.1:5000/predict' -X POST -F "data=@./cat.jpeg"


Получаем ответ следующего вида.


[ [ "n02127052", "lynx", 0.5950918197631836 ], [ "n02128385", "leopard", 0.05043718218803406 ], [ "n02123159", "tiger_cat", 0.04957781359553337 ] ]


Замечательно! Оно работает!


Заключение


В данной статье мы рассмотрели как можно использовать Tensorflow Serving для деплоймента моделей в production. Также рассмотрели как можно реализовать простой REST-сервис на Flask, обращающийся к серверу моделей.


Ссылки


Официальный сайт Tensorflow Serving
Код всех скриптов статьи

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332584/


Какой firewall лучше всех? Лидеры среди UTM и Enterprise Firewalls (Gartner 2017)

Пятница, 14 Июля 2017 г. 14:48 + в цитатник

Метки:  

Что такое ERP система

Пятница, 14 Июля 2017 г. 14:20 + в цитатник
Логотип ERP к статьеМногие компании по мере роста бизнеса приходят к понимаю, что им требуется какая-то ERP-система. Если в малом бизнесе удается обойтись без этого инструмента, то средний бизнес с каждым днем активнее пользуется подобными средствами. Но чтобы выбрать ERP-систему, и даже для того, чтобы понимать, требуется ли в бизнесе этот продукт и какие преимущества принесет его использование, важно правильно понимать, что это такое.

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

Описание ERP системы


ERP – термин маркетинговый. Чтобы понять, о чем идет речь, а также изучить историю происхождения этого сокращения, можно обратиться к Википедии:

ERP (англ. Enterprise Resource Planning, планирование ресурсов предприятия) — организационная стратегия интеграции производства и операций, управления трудовыми ресурсами, финансового менеджмента и управления активами, ориентированная на непрерывную балансировку и оптимизацию ресурсов предприятия посредством специализированного интегрированного пакета прикладного программного обеспечения, обеспечивающего общую модель данных и процессов для всех сфер деятельности[1][2]. ERP-система — конкретный программный пакет, реализующий стратегию ERP. Википедия

Это определение абсолютно верное, но относительно сложное для восприятия, особенно, если человек незнаком с такими понятиями, как «балансировка ресурсов» или «модель данных». При этом важно понимать, что термин ERP является, прежде всего, маркетинговым, и не имеет под собой какой-то глубокой методической базы. А потому я предлагаю такое определение:

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

Что такое критически важные данные? И почему я говорю «большинство»? Дело в том, что некоторые ERP-системы имеют модуль «Производство», при этом он является отдельным программным продуктом, который не подключен по умолчанию к другим системам и блокам. Другие пытаются совмещать в себе все возможные процессы, необходимые для работы компании. Можно ли считать разные виды ERP полезными? Естественно, ниже я остановлюсь на этом вопросе подробнее. А сейчас вернемся к терминам.

  • Критически важные данные – это перечень данных, без которых работа компании невозможна. Это и данные работы отдела продаж, и производство (если компания является производителем). Некоторые компании применяют ERP преимущественно для управления производством, так как для производства лучших решений не существует. Другие компании не являются производителями, например, дистрибьюторы, но также успешно применяют ERP. Для них критически важными становятся – дистрибьюция, управление персоналом, реализация товаров и услуг.
  • Большинство данных: речь идет о перечне процессов и сведений, оптимальном для каждой конкретной компании. Конечно, идеально было бы собирать все данные и сведения обо всех процессах. Но это ведет к удорожанию внедрения. В результате руководство бизнеса вместе со специалистами по внедрению выбирают некое компромиссное решение, при котором в ERP системе собираются действительно необходимые для оперативного контроля и принятия управленческих решений сведения и процессы, а часть данных и процессов собираются в специализированных системах, к которым руководитель обращается по мере необходимости.

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

Из чего состоит ERP система


Все ERM-системы, независимо от того, кто их разработчик, объединяет общая архитектура, которую можно описать следующим образом:
  • Платформа. Базовые возможности и среда для работы модулей и компонентов. В код платформы изменения может вносить только разработчик. Пользователи и специалисты по внедрению не имеют доступа к этому программному коду. В состав платформы входят:
  • Управление данными. База данных, в том числе, хранение и методы обработки (интерпретации) данных. В эту категорию входят хранилище данных на сервере, программное обеспечение для работы с базами данных (SQL или любая альтернатива), инструменты для интерпретации и обработки данных и отправки их в программные модули.
  • Модули. Компоненты, которые подключаются к платформе по мере необходимости. Все они работают с единой базой данных и применяют базовый функционал (по мере необходимости). В остальном модули работают независимо друг от друга, могут «бесшовно» подключаться и без проблем отключаться, если потребность в них исчезла. Такая модульная структура – важная отличительная черта ERP-систем. Модули делятся, в свою очередь, на несколько типов:

Структура ERP системы
Описанная выше структура характерна для ERP с логической точки зрения. У некоторых систем нет ярко выраженной модульности, они все уже встроены в программу, но использовать их можно отдельно друг от друга по мере необходимости. Другие называют отключаемые модули подсистемами. А часть ERP-систем выделяют все модули действительно в отдельные продукты. И предлагают купить ядро, а к нему – перечень модулей на выбор. С возможностью в будущем покупать и добавлять возможности по мере необходимости.

Преимущества модульной структуры ERP


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

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

Нередко я слышу от клиентов фразу «мы разработали свою ERP-систему». На самом деле в этом случае применяется не ERP, а некое подобие, разросшееся из какой-то специализированной системы. Например, начали работать в CRM, потом дописали к ней модуль для работы склада, настроили обмен данными с бухгалтерскими программами и т.д. Или начали с учетной системы и сайта, и также – проводили интеграцию, дописывали модули, все это каким-то образом «стыковали» друг с другом.

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

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

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

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

Для чего используется ERP система?


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

Принцип единой базы данных: контроль, управление, точность и оперативность


Для понимания этого принципа давайте представим компанию до и после внедрения ERP. Допустим, организация имеет собственное производство. Вероятнее всего, учет на производстве ведется в таблицах Excel либо в специализированной программе. Складской учет работает в собственной учетной системе, бухгалтерия – в бухгалтерском программном обеспечении. Передача данных от подразделения к подразделению производится в виде бумажных документов, а иногда даже в устной форме, после чего вручную вносится в нужную систему учета.

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

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

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

А потому ERP система необходима компаниям, для которых скорость и точность передачи данных между подразделениями является критическим фактором.

Гибкость в работе компании с учетом изменений рынка


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

Небольшая компания в случае поступления нетипичных заказов или изменения объемов закупки тех или иных товаров, конечно, может обойтись и без ERP-системы. Когда все сотрудники работают в одном небольшом офисе, всегда можно скоординировать действия между собой, а заказ сформировать на основе данных из заказов даже в Excel-таблицах.

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

Сложные бизнес-процессы: интеграция уже не помогает


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

Что получает бизнес от внедрения ERP


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

  • Доступность данных. Один раз внесенные данные становятся доступны в рамках целой системы, при этом не требуются сверки, дополнительные согласования и проверки.
  • Согласованность данных. Применение общей базы данных позволяет избежать этапов сверки и согласования данных. Например, если отдел проектирования внес и утвердил какой-то проект, данные из этого проекта отдел закупок может использовать для работы сразу, без дополнительного этапа подтверждения.
  • Контроль работы сотрудников. В случае, когда одно из подразделений вносит данные, например, о расходе (перемещении) товара, то другое подразделение сразу же получает об этом сведения, и после реального получения перечисленных позиций ставит его себе на приход. Расхождение в цифрах в этом случае практически невозможно, общая база данных исключает вероятность многих злоупотреблений, а руководитель в режиме реального времени может выявить любые расхождения и их причины.
  • Значительное снижение числа ошибок, связанных с человеческим фактором. Единая система не позволит списать товар не с того склада, так как общая база данных укажет, что товара там нет. Информация об оплатах будет передаваться в бухгалтерию и отдел продаж автоматически на основе данных из банка или кассового аппарата, что также исключает ошибки. Технические параметры и данные проекта также будут переданы автоматически, без искажений и т.д.
  • Готовый набор объединенных между собой инструментов. Например, если отдел продаж создает счет-фактуру, то она является основанием для автоматического создания бухгалтерских документов, а после оплаты – расходных документов со склада.

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

Что важно знать при выборе ERP системы


Один из первых вопросов, которые возникают при выборе любого программного обеспечения – это выбор между Saas или Stand-Alone, т.е. оплачивать доступ к системе, расположенной в «облаках» или покупать «коробочное решение». Подробно об этом выборе я рассказывал в статье Что такое CRM-системы и как их правильно выбирать?

В случае ERP-систем существует точно такой же выбор, как и при внедрении CRM-систем. Вы также можете обратить внимание на SAAS-решения или купить и внедрить “коробку”. Но есть один нюанс, который очень важно учитывать. Дело в том, что ERP — система сама по себе крупная, включающая большое число возможностей. Фактически, в ней объединяются все данные о работе компании. И в случае применения “облака” будет крайне сложно сменить сервис, если в этом возникнет необходимость. В отличие от CRM-систем, которые очень популярны как раз в варианте SAAS, массив данных в ERP объемен и громоздок, и в случае перехода с одного программного продукта на другой, возникает вопрос, что с ними делать.

А потому имеет смысл ориентироваться на покупку программного продукта «в коробке» и последующую его установку на собственных или арендованных серверах. Итак, на что нужно в первую очередь обращать внимание:

  • Выбор команды или специалиста по внедрению. Если некоторые программные продукты, например, CRM-системы для небольшой компании, при большом желании реально внедрить даже без специалиста или с его минимальным участием, то ERP-система без опытного специалиста останется очередной «невостребованной коробкой». Сложность программных продуктов этого семейства обусловлена сложностью задач, которые перед ними ставятся. Единая база данных, интеграция с другими программами, многие другие нюансы работы невозможно настроить без участия профессионала.
  • ERP система это не ключ от всех дверей и не швейцарский нож. Так же как и остальные системы они могут иметь специфику, и отраслевую принадлежность. То есть если у вы занимаетесь продажей, а не производством, то ERP вам следует брать прежде всего для продаж. Тоже самое касается модулей.


    Понимание бизнес-процессов. Очень важно, чтобы специалист по внедрению глубоко понимал не только особенности программного продукта, но также имел достаточно широкий перечень знаний в сфере бизнеса, так как для настройки работы бухгалтерии необходимо понимание того, как и с чем работает бухгалтер. Для внедрения ERP на складе нужны знания складского учета. А потому здесь особенно важен уровень знаний и понимание бизнес-процессов компании руководителем проекта, который будет ставить перед техническими специалистами задачи и контролировать их исполнение.
    Бюджет. ERP-система как программный продукт стоит сравнительно дорого, независимо от разработчика. А ведь для успешного внедрения потребуется сотрудничество с опытными специалистами. И если бюджета хватает только на оплату программы, то в результате «коробка» оказывается невостребованной, т.е. компания впустую тратит значительную сумму. Рассчитывайте свои возможности заранее.

  • В этой статье я постарался максимально просто и без лишних технических подробностей рассказать о том, что такое ERP-системы и какую пользу они могут принести бизнесу. Я надеюсь, что сумел дать базовые знания о том, зачем может быть нужна такая система, в каких случаях она нужна, а где затраты на ее внедрение, скорей всего, не окупятся. А понимание того, что могут ERP системы, а также, что вы хотите получить от их внедрения, поможет сделать правильный выбор среди программных продуктов этого класса.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/333018/


Метки:  

С новым (айтишным) «годом» Вас, други

Пятница, 14 Июля 2017 г. 14:00 + в цитатник

Как то прошло незамеченным, что где-то 8 часов назад началась новая "эра", как минимум на наших железках всех мастей.


Я не перепил, если что...
PoC (несколько часов назад):


$ date +%s
1500000000
$ date +%s
1500000001

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


Посему, с Праздником всех! Холодного и вкусного пива! Безбажного кода! Тихо шуршащего железа! И да обойдут вас вируса стороной!


Следующая новая эпоха (1600000000) состоится через три человеческих года, а именно "Sun Sep 13 12:26:40 GMT 2020".
Следующая же юбилейная секунда отсчитает только в "Wed May 18 03:33:20 GMT 2033" и разменяет уже два миллиарда.


Как оно все сложится...

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/333274/


Метки:  

[Перевод] Семантика exactly-once в Apache Kafka

Пятница, 14 Июля 2017 г. 13:25 + в цитатник


Всем привет! Меня зовут Юрий Лилеков, я работаю в Server Team Badoo. На днях мне попалась довольно интересная статья о новой семантике exactly-once в Apache Kafka, которую я с радостью для вас перевёл.


Наконец, свершилось то, что сообщество Kafka так долго ждало: в Apache Kafka версии 0.11 появилась семантика exactly-once («строго однократная доставка»). В этом посте я расскажу вам о следующих моментах:


– что представляет собой семантика exactly-once в Apache Kafka;
– почему эта проблема сложна;
– как новые свойства идемпотентности и транзакций позволяют корректно выполнять потоковую exactly-once-обработку с помощью Kafka Streams API.


Exactly-once – действительно сложная проблема


Я догадываюсь, о чём каждый из вас сейчас подумал: доставка exactly-once невозможна. Для практического применения это слишком дорого (или поправьте меня, если я ошибаюсь). Вы не одиноки в таких мыслях. Некоторые из моих коллег признают, что доставка exactly-once – одна из самых сложных проблем в сфере распределённых приложений.



Итак, кое-кто недвусмысленно дал понять, что считает доставку exactly-once с большой вероятностью невозможной!



Я не отрицаю, что семантика доставки exactly-once (и поддержка потоковой обработки в этом же режиме) – действительно трудноразрешимая задача. Но также я больше года была свидетелем того, как талантливые инженеры Confluent совместно с open-source-сообществом усердно работали над решением этой проблемы в Apache Kafka. Так что давайте перейдём к обзору семантики передачи сообщений.


Обзор семантики передачи сообщений


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


Семантика at least once («хотя бы один раз»).


Если продюсер получает подтверждение от брокера Kafka, и при этом acks=all, это означает, что сообщение было записано в топик Kafka строго однократно. Но если продюсер не получает подтверждение по истечении тайм-аута или получает ошибку, то он может попробовать снова отправить сообщение, считая, что оно не было записано в топик Kafka. Если брокер дал сбой непосредственно перед отправкой подтверждения, но после того, как сообщение было успешно записано в топик Kafka, эта повторная попытка отправки приведёт к тому, что сообщение будет записано и отправлено конечному потребителю дважды. Все будут довольны неутомимостью раздающего, но такой подход приводит к дублированию работы и некорректным результатам.


Семантика at most once («не более одного раза»).


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


Семантика exactly once («строго однократная доставка»).


Даже при повторной попытке продюсера отправить сообщение, сообщение доставляется строго один раз. Семантика exactly once – наиболее желаемая гарантия, но при этом наименее понимаемая. Причина в том, что она требует взаимодействия между самой системой передачи сообщений и приложением, генерирующим и получающим сообщения. Например, если после удачного получения сообщения вы перемотаете Kafka-потребитель на предыдущее положение, то снова получите оттуда все до последнего сообщения. Это наглядно показывает, почему система обмена сообщениями и клиентское приложение должны взаимодействовать друг с другом, чтобы работала семантика exactly-once.


Сбои, которые нужно обрабатывать


Описание трудностей поддержки семантики exactly-once начнём с простого примера.


Допустим, однопоточное приложение-продюсер отправляет сообщение “Hello, Kafka” в состоящий из одного раздела топик Kafka с именем “EoS” Далее, предположим, что единственный экземпляр приложения-потребителя на другом конце берёт данные из топика и выводит сообщение. Если повезёт и сбоев не будет, то всё сработает отлично, и сообщение “Hello, Kafka” будет однократно записано в раздел топика “EoS”. Потребитель получает сообщение, обрабатывает его и фиксирует его положение, тем самым сообщая о завершении обработки – и приложение-потребитель уже не получит это сообщение повторно даже в случае своего сбоя и перезагрузки.


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


  1. Сбой брокера. Kafka – это высокодоступная, устойчивая и надёжная система, в которой каждое сообщение, записанное в раздел, сохраняется и реплицируется n-ное кличество раз. Поэтому Kafka может выдержать n-1 сбоев брокера, а значит, раздел доступен до тех пор, пока доступен хотя бы один брокер. Протокол репликации Kafka гарантирует, что, если сообщение однажды было успешно записано в главную реплику, оно будет растиражировано по всем доступным репликам.


  2. Сбой RPC «продюсер-брокер». Устойчивость Kafka зависит от продюсера, получающего от брокера подтверждение. Сбой при получении подтверждения не обязательно означает сбой самого запроса. Брокер может «упасть» уже после записи сообщения, но ещё до отправки подтверждения продюсеру. Также он может «упасть» ещё до записи сообщения в топик. Поскольку продюсеру неоткуда узнать причину сбоя, он вынужден предположить, что сообщение не было успешно записано, и сделает ещё одну попытку. В некоторых случаях это приведёт к дублированию сообщения в логе раздела Kafka, а конечный потребитель получит это сообщение более одного раза.


  3. Сбой клиента. При доставке exactly-once нужно учитывать и вероятность сбоя клиента. Но откуда мы можем узнать, что это сбой клиента, а не просто временное отделение от брокера или пауза приложения? Необходимо достоверно различать постоянный сбой от временного. По идее, брокер должен отбрасывать сообщения от зомби-продюсера. То же касается и потребителя: как только появляется новый экземпляр клиента, он должен иметь возможность восстановиться из любого состояния, в котором был сбойный экземпляр, и начать обработку с нужного места. Это означает, что получаемые положения всегда должны быть синхронизированы с генерируемыми данными.

Объяснение семантики exactly-once в Apache Kafka


До версии 0.11.x Apache Kafka поддерживал для каждого раздела семантику доставки at least once и доставку с сохранением порядка. Как видно из вышеприведённого примера, это означает, что повторные попытки отправки сообщения продюсером могли привести к его дублированию. В новой семантике exactly-once мы усилили семантику обработки данных Kafka тремя различными, но взаимосвязанными способами.


Идемпотентность: семантика exactly-once с сохранением порядка доставки для каждого раздела


Идемпотентная операция – это операция, которая при многократном выполнении даёт тот же результат, что и при однократном. Операция отправки продюсером теперь является идемпотентной. В случае ошибки, заставляющей продюсера повторить попытку, сообщение, которое многократно отправлялось продюсером, будет однократно записано в логе брокера Kafka. Применительно к одиночному разделу идемпотентные операции отправки продюсером избавляют нас от вероятности дублирования сообщений вследствие ошибок продюсера или брокера.


Для включения этой функции и получения семантики exactly-once для каждого раздела (то есть никакого дублирования, никакой потери данных и сохранение порядка доставки) просто укажите в настройках продюсера enable.idempotence=true.


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


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


Транзакции: атомарные записи в нескольких разделах


Теперь Kafka поддерживает атомарную запись в нескольких разделах при помощи новых транзакционных API. Это позволяет продюсеру отправлять пакеты сообщений в несколько разделов так, что либо все сообщения из пакета будут видны любому потребителю, либо ни одно из них не будет видно никому. Данная функция также позволяет осуществлять смещение потребителя в одной транзакции с данными, которые вы обработали, а, следовательно, делает возможной сквозную семантику exactly-once. Вот сниппет с примером кода, который демонстрирует использование транзакционного API:


producer.initTransactions();
try {
 producer.beginTransaction();
 producer.send(record1);
 producer.send(record2);
 producer.commitTransaction();
} catch(ProducerFencedException e) {
 producer.close();
} catch(KafkaException e) {
 producer.abortTransaction();
}

Этот пример показывает, как можно использовать новый Producer API для атомарной отправки сообщений в набор разделов топика. Стоит отметить, что раздел топика Kafka может одновременно содержать сообщения, как являющиеся частью транзакции, так и не являющиеся.


Поэтому с точки зрения потребителя есть два способа чтения транзакционных сообщений, выраженных через isolation level — настройку потребителя:


  1. read_committed: вдобавок к чтению сообщений, не являющихся частью транзакции, есть возможность считывать и те, что являются частью транзакции, после коммита транзакции.


  2. read_uncommitted: считываются все сообщения в порядке смещения без ожидания коммита транзакции; эта опция аналогична существующей семантике Kafka-потребителя.

Чтобы использовать транзакцию, необходимо сконфигурировать потребителя для задания нужного isolation level, использовать новые Producer API и задать в конфигурации продюсера параметр Transactional ID как некий уникальный ID (он нужен для обеспечения непрерывности транзакционного состояния при перезапусках приложения).


То, что нужно: потоковая Exactly-once-обработка в Apache Kafka


Благодаря Streams API в Apache Kafka теперь доступна потоковая exactly-once- обработка на основании идемпотентности и атомарности. Чтобы ваше потоковое приложение могло использовать эту семантику, достаточно указать в конфигурации processing.guarantee=exactly_once. В результате вся обработка будет выполняться строго однократно. Это относится как к обработке, так и к воссозданному состоянию, созданному заданием на обработку данных и записанному обратно в Kafka.


«Вот почему гарантии exactly-once-обработки, предоставляемые Streams API в Kafka, являются сильнейшими среди всех гарантий, предлагаемых сегодня системами потоковой обработки. Он обеспечивает сквозные exactly-once-гарантии для приложения потоковой обработки, начиная с чтения данных из Kafka, с любого состояния, воссозданного в Kafka потоковым приложением, до записи в Kafka финального результата. Системы потоковой обработки, в которых поддержка воссозданных состояний опирается лишь на внешние системы данных, ослабляют гарантии потоковой exactly-once-обработки. Даже когда они используют Kafka в качестве источника для обработки и должны восстановиться после сбоя, то могут лишь переключить свои Kafka-положения для перепотребления и переобработки сообщений. Но они не могут откатить ассоциированное состояние во внешней системе, что приводит к некорректным результатам в случае неидемпотентности обновления состояния».


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


Итак, потоковая обработка – это простая операция «чтение – обработка – запись» в топике Kafka. Потребитель считывает из топика сообщения, обрабатывающая логика преобразует их или меняет состояние, поддерживаемое обработчиком, а продюсер записывает результат в другой топик Kafka. Потоковая exactly-once-обработка – это просто возможность строго однократно выполнять операцию «чтение – обработка – запись». При таком раскладе получить правильный ответ означает лишь не пропустить какое-нибудь входящее сообщение или не продублировать выходные данные. И это как раз тот режим работы, который потребители ждут от потокового exactly-once-обработчика.


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


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


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


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


Это относится и к потреблению из топика, и к воссозданию промежуточного состояния в топике Kafka и отправке в него. Не все возможные вычисления в сообщении выполняются с помощью Streams API, некоторые из них (например, зависящие от внешнего сервиса или потребления из нескольких топиков-источников) в основе своей являются недетерминированными.


«Применительно к гарантиям потоковой exactly-once-обработки для детерминированных операций нужно убедиться, что выходные данные операции «чтение – обработка – запись» будут такими же, как если бы потоковый обработчик видел каждое сообщение строго однократно – как будто при условии бесперебойной работы».


Стоп, так чем же всё-таки является потоковая exactly-once-обработка для недетерминированных операций?


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


«Применительно к гарантиям потоковой exactly-once-обработки для недетерминированных операций нужно убедиться, что выходные данные операции «чтение – обработка – запись» принадлежат к подмножеству допустимых выходных данных, генерируемых комбинацией допустимых значений недетерминированных входных данных».


Итак, в нашем примере потокового обработчика при текущем значении счётчика 31 и значении входящего события 2 верными выходными данными в случае сбоя могут быть только 31 или 33: 31 – при условии, что входное событие отклонено, как указано внешними условиями, а 33 – если оно не отклонено.


Это статья лишь поверхностно касается вопросов потоковой exactly-once-обработки в Streams API. В следующем посте на эту тему будет подробнее рассказано о гарантиях, а также проведено сравнение гарантий exactly-once в других системах потоковой обработки.


Гарантии exactly-once в Kafka: действительно ли они работают?


Любая крупная работа вроде этой всегда вызывает вопрос «А работает ли эта фича так, как обещано?» Чтобы ответить на него, давайте рассмотрим её правильность (как мы спроектировали, построили и протестировали эту фичу) и производительность.


Скрупулезный дизайн и процесс проверки


Корректность и производительность начинаются с надёжной архитектуры. Работу над ней и прототипами мы начали около трёх лет назад в LinkedIn. Также мы занимались этим более года в Confluent, пытаясь найти элегантный способ свести идемпотентность и транзакционные требования в целостный пакет. Мы составили 60-страничное описание, охватывающее все аспекты архитектуры, начиная с высокоуровневой передачи сообщений до рутинных подробностей реализации каждой структуры данных и RPC. Этот процесс шёл целых девять месяцев под пристальным вниманием общественности, за это время архитектура значительно улучшилась благодаря обратной связи сообщества.


Например, благодаря open-source-дискуссии мы заменили буферизацию транзакционных чтений со стороны потребителя на более умную фильтрацию в серверной части, избежав тем самым возможных больших проблем с производительностью. Также мы улучшили взаимодействие транзакций со сжатыми топиками и расширили возможности по обеспечению безопасности.


В результате мы пришли к простой архитектуре, которая во многом опирается на надёжные примитивы Kafka.


  1. Наш транзакционный лог – это топик Kafka с соответствующими гарантиями устойчивости.
  2. Наш новый координатор транзакций (который управляет состоянием транзакций для продюсера) работает в брокере и, естественно, использует алгоритм выбора лидера для обработки сбоев.
  3. Для приложений потоковой обработки, созданных с помощью Kafka Streams API, мы воспользовались тем, что топики Kafka являются «источником истины» для хранения состояний и входящих положений. Поэтому мы смогли «прозрачно» положить эти данные в транзакции, которые атомарно пишутся в несколько разделов, а значит, в рамках операций «чтение – обработка – запись» мы обеспечили гарантии exactly-once для потоков.

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


Итеративный процесс разработки


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


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


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


Хорошая новость: Kafka остался таким же быстрым!


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


3% по сравнению с его работой, когда он сконфигурирован на контролируемую (in-order) доставку at least once (acks=all, max.in.flight.requests.per.connection=1),
на 20% по сравнению с его работой, когда он сконфигурирован на at most once без соблюдения порядка сообщений (acks=1, max.in.flight.requests.per.connection=5), используется по умолчанию.


С момента выхода первого релиза семантики exactly-once появились планы по дальнейшему улучшению производительности. Например, как только мы решим задачу KAFKA-5494, улучшающую конвейеризацию в продюсере, то надеемся значительно снизить накладные расходы в производительности транзакционного продюсера даже по сравнению с продюсером, поддерживающим доставку at most once без соблюдения порядка сообщений. Мы также обнаружили, что идемпотентность оказывает ничтожно малое влияние на производительность продюсера. Если вам интересно, вот результаты наших бенчмарков, тестовая конфигурация и методика тестирования.


Помимо обеспечения низких накладных расходов при работе новых фич, нам хотелось не допустить регрессию производительности приложений, не использующих фичи exactly-once. Мы не только добавили поля в заголовки сообщений Kafka для внедрения exactly-once, но и доработали формат сообщений Kafka, чтобы ещё эффективнее сжимать их при передаче по сети или на диске. В частности, мы перенесли часть метаданных в заголовки пакетов и внедрили кодирование длин переменных для каждой записи в пакете. Это позволило значительно уменьшить размер сообщения. Например, пакет с семью записями по 10 байтов каждая в новом формате будет весить на 35% меньше. Это привело к улучшению чистой производительности Kafka в приложениях, использующих ввод/ вывод: при обработке маленьких сообщений до 20% ускоряется работа продюсера и до 50% – потребителя. Такой рост производительности доступен любому пользователю Kafka 0.11, даже если он не пользуется фичами exactly-once.


Мы также пересмотрели накладные расходы на потоковую exactly-once обработку с помощью Streams API. При коротком интервале коммита в 100 мс (что необходимо для поддержания низкой сквозной задержки) порисходит падение производительности от 15% (однокилобайтные сообщения) до 30% (стобайтные сообщения). Однако при большом интервале коммита в 30 с вообще не наблюдается повышения накладных расходов для сообщений размером 1 Кб и больше. В следующем релизе мы планируем внедрить спекулятивное исполнение, что позволит поддерживать низкую сквозную задержку даже при большом интервале коммита. То есть мы хотим добиться нулевых накладных расходов на транзакцию.


Наконец, кардинально переработав некоторые ключевые структуры данных, мы освободили пространство для построения идемпотентности и транзакционных фич с минимальным снижением производительности, чтобы Kafka стал ещё быстрее. Годы напряжённой работы – и мы невероятно рады выпустить фичи exactly-once для всего сообщества Apache Kafka. Благодаря усилиям, вложенным в создание семантики exactly-once, по мере её широкого распространения в сообществе будут внедряться и новые улучшения. С нетерпением ждём обратной связи и работаем над преобразованиями для предстоящих релизов Apache Kafka.


Это что, волшебная пыль, которую я могу распылить на своё приложение?


Нет, не совсем. Обработка данных exactly-once – это сквозная гарантия, и приложение должно быть спроектировано так, чтобы не нарушать это свойство. Если вы пользуетесь потребительским API, это значит, что вы фиксируете изменения состояния вашего приложения в соответствии со своими смещениями, как описано в этой статье.


Ситуация с потоковой обработкой немного лучше. Поскольку потоковая обработка – закрытая система, где входные и выходные данные, а также изменения состояний смоделированы в одной операции, то это действительно во многом похоже на волшебную пыль. Единственное изменение в конфиге обеспечит вам сквозные гарантии. Хотя вам всё ещё нужно получить данные из Kafka. Вы получите возможность потоковой обработки в сочетании с exactly-once коннектором.


Дополнительные ресурсы


Эта статья в основном посвящена описанию характера ориентированных на пользователей гарантий, обеспечиваемых грядущей exactly-once-фичей в Apache Kafka 0.11, а также тому, как вы можете её использовать.


Если вы хотите подробнее изучить гарантии exactly-once, рекомендую пройтись по KIP-98, чтобы узнать о свойствах транзакции, и KIP-129, чтобы узнать о потоковой exactly-once обработке. Если хотите ещё глубже узнать об архитектуре этих фич, вам в помощь этот дизайн-документ.

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/333046/


Метки:  

Information Security Europe: тренды мирового рынка ИБ, о которых вы не прочитаете у Gartner

Пятница, 14 Июля 2017 г. 13:25 + в цитатник
В начале июня мы с моим другом и коллегой Андреем Данкевичем съездили на несколько дней в Лондон на Information Security Europe. Это крупнейшая выставка в Европе и «одна из» в мире. В этом году ее посетило более 15 000 человек, и проходила она уже в 22 раз.

Хотя на Information Security Europe и представлена довольно обширная программа докладов, но ехать стоит не за ними, а для того, чтобы набрать классной сувенирки пообщаться с производителями СЗИ, посмотреть их решения на стендах и самостоятельно (а не просто доверившись Gartner) сделать выводы о том, какие тренды сейчас на пике. Под катом — рассказ о том, какие технологии внедряют вендоры DLP, UEBA, GRC, IGA и MDR.

Сначала немного о самой выставке: несмотря на то, что длится она 3 дня, внимательно изучить все стенды сложно, их ОЧЕНЬ много (в этом году их было 399 на 2 этажа общего пространства). Посетителей очень много, но очередей и давки нет даже на входе. Все организовано очень хорошо, есть выделенные зоны для общения, различные временные кафешки, стенды с информацией.



Как я уже упомянул, на Information Security Europe больше всего представлены именно производители средств защиты информации (СЗИ), ради общения с ними и стоит посетить эту выставку:



Были и крупные игроки, например, Cisco, IBM, FireEye, и небольшие нишевые. Из российских (ой, простите, global) вендоров встретили «ИнфоТеКС», Positive Technologies и DeviceLock.



Мы приехали изучить рынок и, в первую очередь, посмотреть решения DLP, UEBA, GRC, IGA, MDR и другие СЗИ. И вот лишь некоторые заметные тренды европейского рынка ИБ:

  1. Очень активно развивается сегмент Managed Detection and Response — MDR (это как раз центры мониторинга и реагированию на инциденты ИБ типа нашего Solar JSOC). Причем если компания предоставляет такие услуги, часто это ее единственное направление деятельности.



  2. При этом уже довольно заметно стремление хотя бы частично автоматизировать деятельность SOC'ов, чтобы сократить затраты. На выставке было представлено много решений в этой области — это и различные аналитические инструменты, и средства агрегации и визуализации данных, и модное нынче машинное обучение.

  3. Очень модная тема — Threat Intelligence. Производители предлагают свои платформы для сбора и управления информацией об угрозах и/или готовы предоставлять ее по подписке.

  4. Вообще большинство производителей ИБ-решений активно осваивают формат Security-as-a-Service. Они предлагают свои решения «по подписке» и/или в формате сервисов MSSP (Managed Security Service Provider).

  5. Предсказуемо, но все же: практически на каждом стенде представлены кейсы и решения по соответствию требованиям The General Data Protection Regulation (GDPR) — европейского закона по защите персональных данных, который вступает в полную силу в 2018 году. Конечно же, самые «громкие голоса» у DLP-шников и производителей средств разграничения доступа.

  6. Очень популярны технологии контроля и анализа поведения пользователей (UEBA/UBA). На выставке было представлено большое количество таких решений — как в качестве самостоятельных продуктов, так и в качестве модулей (надстроек) к SIEM или DLP, например. Во многих продуктах используются технологии машинного обучения для составления типовых профилей работы пользователей, отклонение от которых и рассматривается в качестве инцидента. Посмотрите на риторику рекламных слоганов — чувствуется, что проблема внутренних угроз стоит довольно остро:



  7. Активно развиваются решения, предназначенные для менеджеров по ИБ. Это и всевозможные средства по визуализации данных, и продукты класса GRC, и сервисы по повышению и контролю осведомленности пользователей.

В целом мероприятие крайне интересное. За 3 дня можно получить огромное количество полезной информации. Пока трудно сказать, что из этого придет на российский рынок и приживется у нас, но все представленные решения, на мой взгляд, объединяет один очень заметный вектор — вендоры уходят от попыток создать решения, работающие «из коробки», и предлагают сложные системы, требующие от безопасника определенных компетенций и знаний. Так что автоматизация и машинное обучение, конечно, вещи хорошие, но над ними все равно должен будет стоять человек. По крайней мере, в ближайшее время.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/333132/


Метки:  

Как построить маленькую, но хорошую сеть?

Пятница, 14 Июля 2017 г. 13:17 + в цитатник

Метки:  

Поиск сообщений в rss_rss_hh_new
Страницы: 1437 ... 1051 1050 [1049] 1048 1047 ..
.. 1 Календарь