Русский
Русский
English
Статистика
Реклама

Блог компании otus. онлайн-образование

Перевод Как я реализовал MVC в JavaScript

08.01.2021 02:22:10 | Автор: admin

для лучшей разделяемости кода

Для будущих студентов курса "Архитектура и шаблоны проектирования" и всех интересующихся подготовили перевод полезного материала.

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


Что из себя представляет архитектурный паттерн Model, View, Controller (MVC)?

Источник: документация Rails

Архитектура MVC разделяет ваш код на три (3) уровня: модели (Models), представления (Views) и контроллеры (Controllers), выполняющие различные задачи внутри программы.

Изображение взято из ВикипедииИзображение взято из Википедии

Уровень модели

В Ruby on Rails этот уровень содержит модель предметной области, которая обычно представляет определенный класс объектов (например, Человек, Животное, Книги). Обычно именно здесь обрабатывается бизнес-логика, поскольку модель связана с базой данных, и данные для нее извлекаются из строк соответствующей таблицы.

Уровень представления

Обрабатывает визуальное представление ответов, предоставляемых контроллерами. Поскольку контроллер может возвращать информацию в формате HTML, XML, JSON и т. д.

Уровень контроллера

В Rails этот уровень отвечает за взаимодействие с моделью, манипулирование ее данными и предоставление соответствующих ответов на различные HTTP-запросы.

Как бы паттерн MVC выглядел в JavaScript?

Источник: документация MDN

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

Изображение взято с MDNИзображение взято с MDN

Уровень модели

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

Возьмем, к примеру, приложение Classroom, которое отслеживает, какие классы посещает человек. В этом случае уровень модели можно разделить на классы, такие как Classroom, Person и модель на основе массива под названием Subjects.

Базовые классы модели

class Classroom {  constructor(id, subject = 'Homeroom') {    this.id = id;    this.persons = [];    this.subject = subject;  }}

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

class Person {  constructor(id, firstN = 'John', lastN = 'Doe') {    this.id = id;    this.firstName = firstN;    this.lastName = lastN;    this.subjects = [];    this.classrooms = [];  }}

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

const subjects = [  "English",  "Math",  "Computer Science",  "Business",  "Finance",  "Home Economics"];

Модель Subjects будет просто массивом, поскольку для этого примера я не собираюсь разрешать манипулировать моделью дисциплин.

Уровень контроллера

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

Например, в приложении Classroom контроллер получает данные, вводимые пользователем, от элементов представления, таких как ввод текста (text input) или выбор из списка опций (select options), а также нажатия кнопок, которые используются для изменения модели.

import classroomModel from "../models/classroom";class ClassroomController {  constructor() {    this.lastID = 0;    this.classrooms = [];    this.selectedClass = null;  }  selectClassroom(classroomID) {    this.selectedClass = this.classrooms    .filter(c => c.id === parseInt(classroomID, 10))[0];  }  addClassroom(subject) {    this.classrooms.push(      new classroomModel(this.lastID, subject)      );    this.lastID += 1;  }  removeClassroom(classroomID) {    this.classrooms = this.classrooms      .filter(c => c.id !== parseInt(classroomID, 10));  }  setSubject(subject, classroomID) {    const classroom = this.classrooms      .filter(c => c.id === parseInt(classroomID, 10))[0];    classroom.subject = subject;  }  addPerson(person, classroom) {    // const classroom = this.classrooms    // .filter(c => c.id === parseInt(classroomID, 10))[0];    if (!person) return;    classroom.addPerson(person);  }  removePerson(person, classroomID) {    const classroom = this.classrooms    .filter(c => c.id === parseInt(classroomID, 10))[0];    classroom.removePerson(person);  }}

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

Этот контроллер имеет три собственные переменные: lastID (каждый раз, когда объект класса создается и добавляется к массиву классов, значение этой переменной инкрементируется), classrooms (массив всех созданных объектов класса) и selectedClass.

Уровень представления

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

Например, в приложении Classroom представление будет предоставлять элементы DOM (объектной модели документа), такие как кнопки, инпуты и контейнеры (<div/>, <span/ >, <p/> и т. д.) для отображения различных людей и классов, и связанных с ними данных.

import classroomController from "../controllers/classroom";import subjects from "../models/subjects";class ClassroomView {  constructor(appDiv) {    this.classroomController = new classroomController();    this.classroomSectionDiv = document.createElement('div');    this.classroomsDiv = document.createElement('div');    this.addclassBtn = document.createElement('button');    this.selectSubjectInput = document.createElement('select');    this.classroomSectionDiv.classList.add('classroom-section');    this.classroomsDiv.classList.add('classroom-container');    this.selectSubjectInput.innerHTML = subjects.map((option, index) => (      `<option key=${index} value=${option}>${option.toUpperCase()}</option>`    ));    this.addclassBtn.textContent = 'New Class';    this.addclassBtn.addEventListener('click', () => this.addClassroom());    this.classroomSectionDiv.append(      this.classroomsDiv, this.selectSubjectInput,      this.addclassBtn,      );    appDiv.appendChild(this.classroomSectionDiv);  }  updateView() {    const { classroomController, classroomsDiv } = this;    const allClassrooms = classroomController.classrooms.map(      c => {        const removeBtn = document.createElement('button');        const classDiv = document.createElement('div');        classDiv.classList.add('classroom');        if (classroomController.selectedClass === c) {          classDiv.classList.add('selected');        }        classDiv.addEventListener('click', () => this.selectClassroom(classDiv.getAttribute('data-classroom-id')));        classDiv.setAttribute('data-classroom-id', c.id);        removeBtn.addEventListener('click', () => this.removeClassroom(removeBtn.getAttribute('data-classroom-id')));        removeBtn.setAttribute('data-classroom-id', c.id);        removeBtn.classList.add('remove-btn');        removeBtn.textContent= 'remove';        const allPersons = c.persons.map(p => (          `<div class="person-inline">            <span class="fname">${p.firstName}</span>            <span class="lname">${p.lastName}</span>            <span class="${p.occupation}">${p.occupation}</span>          </div>`        ));        classDiv.innerHTML = `<div class="m-b">            <span class="id">${c.id}</span>            <span class="subject">${c.subject}</span></div>            <div class="all-persons">${allPersons.join('')}</div>`;        classDiv.appendChild(removeBtn);        return classDiv;      }    );    classroomsDiv.innerHTML='';    allClassrooms.map(div => classroomsDiv.append(div));  }    selectClassroom(classroomID) {    const { classroomController } = this;    classroomController.selectClassroom(classroomID);     this.updateView();  }  addClassroom() {    const {      classroomController,      selectSubjectInput,    } = this;    const subjectChosen = selectSubjectInput.value;    classroomController.addClassroom(subjectChosen);    this.updateView();  }  removeClassroom(classroomID) {    const { classroomController } = this;    classroomController.removeClassroom(classroomID);    this.updateView();  }  addPerson(person, classroomID) {    const { classroomController } = this;    classroomController.addPerson(person, classroomID);    this.updateView();  }}

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

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

Все функции в представлении просто захватывают значения из UI элементов DOM и передают их как переменные функциям контроллера. Функции selectClassroom(), addClassroom() и removeClassroom() добавляются к элементам DOM через функцию updateView() как события через функцию addEventListener().

Доступ ко всем контроллерам и представлениям с помощью одного представления

Теперь, поскольку для этого примера у нас есть два контроллера, ClassroomController и PersonController (можно найти в полном проекте), у нас также было бы два представления, и если бы мы хотели, чтобы эти два представления могли взаимодействовать друг с другом, нам пришлось бы создать единое всеобъемлющее представление. Мы могли бы назвать это представление AppView.

import classroomView from './classroom';import personView from './person';class AppView {  constructor(appDiv) {    this.classroomView = new classroomView(appDiv);    this.personView = new personView(appDiv);    this.addPersonToClassBtn = document.createElement('button');    this.addPersonToClassBtn.textContent = 'Add selected Person to Selected Class';    this.addPersonToClassBtn.addEventListener('click', () => this.addPersonToClass());    appDiv.appendChild(this.addPersonToClassBtn);  }  addPersonToClass() {    const { classroomView, personView } = this;    const { classroomController } = classroomView;    const { personController } = personView;    const selectedClassroom = classroomController.selectedClass;    const selectedPerson = personController.selectedPerson;    classroomView.addPerson(selectedPerson, selectedClassroom);    personView.updateView();  }}

Класс AppView будет иметь собственные переменные, которые будут связываться как с ClassroomView, так и с PersonView. Поскольку он имеет доступ к этим двум представлениям, он также имеет доступ и к их контроллерам.

Кнопка выше создается AppView. Оно получает значения selectedClassroom и selectedPerson из соответствующих контроллеров и при взаимодействии запускает функцию addPerson() в ClassroomView.

Чтобы полностью посмотреть приложение Classroom, переходите в CodeSandBox по этой ссылке.

Некоторые преимущества использования структуры MVC

Источники: Brainvire, c-sharpcorner, StackOverflow, Wikipedia

1. Разделение обязанностей

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

2. Одновременная разработка

Поскольку модель MVC четко разделяет проект на три (3) уровня, становится намного проще поделить и распределить задачи между несколькими разработчиками.

3. Простота модификации

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

4. Разработка через тестирование (TDD)

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


Узнать подробнее о курсе "Архитектура и шаблоны проектирования".

Зарегистрироваться на открытый вебинар на тему "Интерпретатор".

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

Кстати, о "красивой упаковке" онлайн-сертификатов мырассказываем в этой статье.

ЗАБРАТЬ СКИДКУ

Подробнее..

Перевод Как на обратной стороне луны появились радиоактивные пятна

28.12.2020 08:09:56 | Автор: admin

Ближняя и дальняя стороны Луны очень разные. Причиной этому может быть химический элемент торий тяжелый слаборадиоактивный металл

Кратер Бассейн Южный полюс Эйткен (South PoleAitken basin). Источник: JAXAКратер Бассейн Южный полюс Эйткен (South PoleAitken basin). Источник: JAXA

На протяжении десятилетий ученых озадачивала загадка: аномальные горячие пятна с радиоактивными следами на поверхности Луны. Теперь у ученых есть ответ, и он может повлиять на наше понимание формирования планет и даже происхождения жизни.

У Луны было тяжелое детство. Во-первых, она появилась вследствие столкновения Земли с космическим объектом размером с Марс. Это произошло около 4,5 миллиарда лет назад. Согласно теории, Луна провела свои ранние годы в виде расплавленного шара магмы. Примерно полмиллиарда лет спустя Луна столкнулась с огромным космическим каменистым объектом, который оставил гигантский ударный кратер под названием Бассейн Южный полюс-Эйткена (South PoleAitken basin, SPA). Он находится на обратной стороне Луны и простирается примерно на 1600 миль. Далее в статье, упоминая этот кратер, будем кратко говорить SPA.

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

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

Результаты имеют важное значение для понимания, как формировалась и эволюционировала Луна, особенно того, почему ее ближняя и дальняя стороны так сильно различаются. Предполагается, что образцы из SPA должны рассматриваться в числе наиболее приоритетных целей для развития планетарных исследований говорится в исследовании, опубликованном в JGR Planets (Journal of Geophysical Research: Planets, рус. Журнал геофизических исследований: планеты).

Это первый случай прямых доказательств такого рода расслоения верхней мантии, сказал руководивший исследованием Дэниел Мориарти, ученый из Центра космических полетов имени Годдарда (NASA Goddard Space Flight Center).

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

В течение многих лет ученые были озадачены асимметричным распределением на Луне так называемых KREEP краткое обозначение-акроним лунных пород, состоящих из: калия K, редкоземельных элементов REE и фосфора P. Наличие этих элементов связано с вулканической деятельностью. Это может частично объяснить причину их концентрации на ближней стороне Луны, поскольку в прошлом эта сторона была гораздо более вулканически активной. Но возникает вопрос: как аномальные горячие пятна KREEP оказались в SPA, на дальней стороне Луны, где вулканизм был редкостью?

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

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

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

Мориарти и его коллеги смогли обнаружить нетронутые остатки этого древнего радиоактивного выброса, который, по их мнению, является характерным для дальней стороны Луны. В этом им помогли данные двух проектов: NASAs Lunar Prospector (американская автоматическая межпланетная станция для исследования Луны, созданная в рамках программы НАСА Discovery) и Moon Mineralogy Mapper (M3) (Картограф лунной минералогии) инструмента, которым НАСА посодействовала развитию проекта Индийский лунный зонд Чандраяан-1 (Chandrayaan-1).

Lunar Prospector, который конец 1990-х провел на полярной орбите вокруг Луны, был оснащен гамма-спектрометрами и нейтронными спектрометрами, которые позволяли ему регистрировать радиоактивные сигналы с поверхности. Приборы обнаружили обильные отложения тория слаборадиоактивного элемента, который является и ключевым индикатором KREEP в SPA.

Изображение: moriarty et alИзображение: moriarty et al

Комментарий к иллюстрации: Удар, образовавший SPA самый большой известный кратер Луны (англ. South PoleAitken basin, Бассейн Южный полюс Эйткен) на дальней стороне луны, вызвал извержение торий-содержащих материалов из глубин лунной мантии. Карта показывает концентрацию тория в кратере, измеренную автоматической межпланетной станцией Lunar Prospector и иллюстрирует, как распределены по лунной поверхности следы выброса лунной мантии.

Инструмент M3, который был выведен на лунную орбиту с помощью лунного зонда Чандраяан-1 в 2008 году, представлял собой спектрометр ближнего инфракрасного диапазона, предназначенный для составления карты более широких минералогических свойств лунной поверхности. Объединив эти два набора данных из проектов, которые прекратились более десяти лет назад, Мориарти и его коллеги смогли раскрыть новые идеи о загадочных горячих пятнах на SPA.

Это дар, который продолжает одаривать нас, сказал Мориарти о прошлых исследованиях Луны. Мы до сих пор узнаем случайные вещи из 90-х и 2000-х годов. Широта этого исследования и подхода объединяют различные вопросы, добавил он. Это не получилось бы сделать только с одним набором данных. Необходимо объединить факт содержание тория из Lunar Prospector с распределением минералогического состава из Moon Mineralogy Mapper, потому что в противном случае мы получим только неполную часть картины.

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

Некоторые думают, что это асимметричное распределение радиоактивных элементов было заложено в самом начале, сказал Мориарти. Наша газета показывает, что это не так. Эти элементы находятся на всей территории, поэтому нужны аргументы для объяснения, почему ближняя и дальняя стороны Луны так отличаются.

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

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

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

С научной точки зрения, некоторые из этих материалов доступны на SPA для миссий пилотируемой космической программы НАСА Артемида (Artemis), сказал он, ссылаясь на план НАСА по возвращению людей на Луну в этом десятилетии. Это важный момент, поскольку благодаря результатам исследования этих образцов мы можем многое узнать о том, как именно формировалась и развивалась мантия Луны.

Реклама которая может быть полезна

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

Кстати, о "красивой упаковке" онлайн-сертификатов мырассказываем в этой статье.

ЗАБРАТЬ СКИДКУ

Подробнее..

Перевод Контроль версий в базах данных Сравнение Liquibase и Flyway

21.12.2020 14:15:23 | Автор: admin

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

Тем не менее, в связи с ростом популярности аджайл методологии в последние годы и востребованностью непрерывной интеграции и развертывания, мы больше не можем ограничивать применение CI/CD только к коду приложения, оставив SQL позади.

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

Ниже будут рассмотрены сходства и различия ныне хорошо известных продуктов Flyway и Liquibase.

Как работают инструменты Flyway и Liquibase

Оба инструмента реализуют концепцию эволюционных баз данных, объясняет Мартин Фаулер.

Вы начинаете с пустой модели базы данных, для которой таблица контроля версий (специфичная для каждого инструмента) также пуста, и еще не были применены никакие скрипты. Затем вы применяете несколько скриптов (Script1 и Script2) и в итоге получаете первую версию базы данных. Скрипты 3 и 4 регистрируются в таблице контроля версий, и мы получаем вторую версию базы данных. Вы всегда знаете имя автора, имя скрипта и его контрольную сумму.

Пример кода

1.Создайте пустой проект maven

mvn archetype:generate -B     -DarchetypeGroupId=org.apache.maven.archetypes     -DarchetypeArtifactId=maven-archetype-quickstart     -DarchetypeVersion=1.1     -DgroupId=robloxro     -DartifactId=flywaysample     -Dversion=1.0-SNAPSHOT     -Dpackage=flywaysample

2. Установите плагин Flyway в pom.xml (и базу данных H2, чтобы мы запускали на ней пример).

<project>.....<groupId>robloxro</groupId>  <artifactId>flywaysample</artifactId>  <version>1.0-SNAPSHOT</version>  <packaging>jar</packaging><name>flywaysample</name>  <url>http://maven.apache.org</url><build>        <plugins>            <plugin>                <groupId>org.flywaydb</groupId>                <artifactId>flyway-maven-plugin</artifactId>                <version>5.2.4</version>                               <dependencies>                    <dependency>                        <groupId>com.h2database</groupId>                        <artifactId>h2</artifactId>                        <version>1.4.191</version>                    </dependency>                </dependencies>            </plugin>        </plugins>    </build></project>

3. Определите файл конфигурации flyway

flyway.user=saflyway.password=flyway.schemas=INFORMATION_SCHEMAflyway.url=jdbc:h2:~/testflyway.locations=filesystem:.\src\main\resources\db\migration

4. Создайте первый скрипт миграции

create table PERSON (    ID int not null,    NAME varchar(100) not null);

5. Выполните первую миграцию

mvn clean flyway:migrate -Dflyway.configFile=myFlywayConfig.properties

Вывод должен быть следующий

Вы также можете проверить базу данных

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

Просто запустите его снова

mvn clean flyway:migrate -Dflyway.configFile=myFlywayConfig.properties

Вывод отобразит

7. Примените вторую миграцию

insert into PERSON (ID, NAME) values (1, 'Axel');insert into PERSON (ID, NAME) values (2, 'Mr. Foo');insert into PERSON (ID, NAME) values (3, 'Ms. Bar');

8. Что произойдет, если я изменю уже примененный скрипт?

Измените в уже существующем примененном файле V2_Addpeople.sql.sql

это

insert into PERSON (ID, NAME) values (3, 'Ms. Bar');

на это

insert into PERSON (ID, NAME) values (3, 'Ms. Barrrr');

Повторный запуск миграции покажет, что Flyway заметил изменение контрольной суммы и не пропустил новую миграцию.

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

Возможности, предлагаемые Flyway и Liquibase

Как Flyway, так и Liquibase являются инструментами, написанными на Java. Они легко интегрируются с maven, gradle и другими инструментами сборки, что способствует большей кастомизации. Они предлагают Java API и могут быть расширены. Их можно использовать из командной строки или используя их jar параметры.

Оба продукта предлагают

  • контроль версий

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

  • предотвращение ситуаций, когда скрипт, примененный к базе данных, был изменен в системе контроля версий вместо инкрементного добавления

  • решения для коррекции развертывания или исправления ситуации, когда скрипт был неправильно изменен

  • параметры контекста - $var - чтобы вы могли настроить модель базы данных, чтобы она подходила для нескольких развертываний на схемах в конвейере CI/CD

  • оба полагаются на таблицы контроля версий с контрольной суммой (Flyway: SCHEMA_VERSION, Liquibase: DATABASECHANGELOG и DATABASECHANGELOGLOCK)

  • baselining - если ваша схема базы данных уже была создана без использования какого-либо из этих инструментов, но вы захотите начать деплоить с их помощью спустя некоторое время после ее создания, вы можете использовать baseline-команды для создания бейслайна схемы поверх которого можно быдет инкрементно добавлять новые скрипты. Бейслайн будет включать DDL, но не данные. Иногда разработчики, запускающие инструмент в первый раз, оказываются сбиты с толку, думая, что бейслайн также мигрирует данные.

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

+------------------------------------+------------------+--------+--+|              Feature               |    Liquibase     | Flyway |  |+------------------------------------+------------------+--------+--+| Database Source Control/Versioning | yes              | yes    |  || Source Code Control Integrations   | yes              | yes    |  || Baseline generation                | yes              | yes    |  || Language used for scripts          | XML,JSON,YML,SQL | SQL    |  || Diff Support                       | yes              | no     |  || Rollback Support                   | yes              | no     |  || Dynamic Parameter Support          | yes              | yes    |  || CLI                                | yes              | yes    |  || Java API for integration           | yes              | yes    |  || Maven,gradle build tools support   | yes              | yes    |  ||                                    |                  |        |  |+------------------------------------+------------------+--------+--+

Общие сценарии использования для FlyWay и Liquibase

Оба инструмента предлагают все мощные функции, необходимые для полной автоматизации рефакторинга и эволюционной модели базы данных. Они легко интегрируется в экосистему Spring, хорошо работают с maven, gradle, java.

Вам следует использовать эти инструменты для обеспечения

  • контроля версий для всех артефактов баз данных

  • аудита изменений модели базы данных

  • чтобы каждый в команде имел собственное развертывание базы данных

  • предотвращать развертывания, когда база данных не синхронизирована с приложением

  • создавать новые среды

  • заставлять разработчиков непрерывно интегрировать свой код базы данных

Когда следует использовать Flyway, а когда Liquibase

Flyway использует SQL, что упрощает жизнь разработчикам. Я обнаружил, что разработчики баз данных не рады работать с xml-тегами Liquibase, предпочитая <sql>. Тем не менее, сила Liquibase заключается в том, что, учитывая, что он работает с XML или JSON, вы можете использовать его в средах, в которых база данных использует разные языки на разных машинах (из личного опыта: H2 на dev, Oracle на test).

В Liquibase есть автоматически генерируемые скрипты отката, если вы используете xml-теги, которые позволяют автоматически генерировать откат. Сюда входят простые теги, такие как создать таблицу, добавить столбец и т. д., для которых движок может легко определить откат. Для более сложных скриптов со сложной бизнес-логикой, с использованием <sql> тегов Liquibase внутри, вам нужно писать откаты самостоятельно.

Также я использовал DIFF в Liquibase, которого нет в Flyway, он позволяет сравнивать две схемы базы данных и определять их различия.

Также вы можете сделать одну вещь с помощью обоих инструментов встроить создание обязательных скриптов отката в процесс CI/CD. В одном из моих проектов при каждом pull-реквесте инкрементальных скриптов базы данных сервер сборки выполнял сборку pull-реквеста, в рамках которой мы выполняли и скрипты отката для выделенной схемы CI. Если для одного скрипта не предусмотрен откат, или если выполнение отката завершилось неудачно, сборка завершалась ошибкой.

Ограничения

У каждого инструмента есть свои ограничения. Некоторые из них не актуальны в коммерческих версиях (например, атомарный откат в Flyway доступен только в коммерческой версии).

Тем не менее, вкратце, это единственные ограничения, с которыми я столкнулся в своем опыте работы с обоими инструментам:

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

  2. Baselining с данными невозможен. Это связано с тем, что средства этих инструментов не связаны с миграцией данных, поэтому разумно не определять базовый уровень данных, а использовать соответствующие специальные задачи DBA для выравнивания данных.


На правах рекламы

А прямо сейчас в OTUS действует новогодняя скидка на все курсы. Рекомендуем обратить внимание:

ЗАБРАТЬ СКИДКУ

Подробнее..

Перевод Splay-дерево. Поиск

28.12.2020 14:06:07 | Автор: admin

Привет, Хабр! Будущих студентов курса "Алгоритмы и структуры данных" приглашаем на открытый вебинар по теме "Заповедники двоичных деревьев поиска."

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


Наихудшая временная сложность таких операций, как поиск, удаление и вставка, для двоичного дерева поиска (Binary Search Tree) составляет O(n). Наихудший случай случай возникает, когда дерево несбалансировано. Мы можем улучшить наихудший результат временной сложности до O(log n) с помощью красно-черных и АВЛ-деревьев.

Можем ли мы добиться на практике лучшего результата, чем тот, что нам дают красно-черные или АВЛ-деревья?

Подобно красно-черным и АВЛ-деревьям, Splay-дерево (или косое дерево) также является самобалансирующимся бинарным деревом поиска. Основная идея splay-дерева состоит в том, чтобы помещать элемент, к которому недавно осуществлялся доступ, в корень дерева, что делает этот элемент, доступным за время порядка O(1) при повторном доступе. Вся суть заключается в том, чтобы использовать концепцию локальности ссылок (в среднестатистическом приложении 80% обращений приходятся на 20% элементов). Представьте себе ситуацию, когда у нас есть миллионы или даже миллиарды ключей, и лишь к некоторым из них обращаются регулярно, что весьма вероятно для многих типичных приложениях.

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

Операция поиска

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

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

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

2. Zig: узел является дочерним по отношению к корню (у узла нет прародителя). Узел является либо левым потомком корня (мы делаем правый разворот), либо правым потомком своего родителя (мы делаем левый разворот).

T1, T2 и T3 поддеревья дерева с корнем y (слева) или x (справа)

3. У узла есть и родитель, и прародитель. Возможны следующие варианты:

а) Zig-Zig и Zag-Zag. Узел является левым потомком родительского элемента, и родитель также является левым потомком прародителя (два разворота вправо) ИЛИ узел является правым потомком своего родительского элемента, и родитель также является правым потомком своего прародитель (два разворота влево).

б) Zig-Zag и Zag-Zig. Узел является левым потомком по отношению к родительскому элементу, а родитель является правым потомком прародителя (разворот влево с последующим разворотом вправо) ИЛИ узел является правым потомком своего родительского элемента, а родитель является левым потомком прародителя (разворот вправо с последующим разворотом влево).

Пример:

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

Реализации:

C++

#include <bits/stdc++.h> using namespace std; // An AVL tree node class node { public: int key; node *left, *right; }; /* Helper function that allocates a new node with the given key and NULL left and right pointers. */node* newNode(int key) { node* Node = new node(); Node->key = key; Node->left = Node->right = NULL; return (Node); } // A utility function to right // rotate subtree rooted with y // See the diagram given above. node *rightRotate(node *x) { node *y = x->left; x->left = y->right; y->right = x; return y; } // A utility function to left // rotate subtree rooted with x // See the diagram given above. node *leftRotate(node *x) { node *y = x->right; x->right = y->left; y->left = x; return y; } // This function brings the key at // root if key is present in tree. // If key is not present, then it // brings the last accessed item at // root. This function modifies the // tree and returns the new root node *splay(node *root, int key) { // Base cases: root is NULL or // key is present at root if (root == NULL || root->key == key) return root; // Key lies in left subtree if (root->key > key) { // Key is not in tree, we are done if (root->left == NULL) return root; // Zig-Zig (Left Left) if (root->left->key > key) { // First recursively bring the // key as root of left-left root->left->left = splay(root->left->left, key); // Do first rotation for root, // second rotation is done after else root = rightRotate(root); } else if (root->left->key < key) // Zig-Zag (Left Right) { // First recursively bring // the key as root of left-right root->left->right = splay(root->left->right, key); // Do first rotation for root->left if (root->left->right != NULL) root->left = leftRotate(root->left); } // Do second rotation for root return (root->left == NULL)? root: rightRotate(root); } else // Key lies in right subtree { // Key is not in tree, we are done if (root->right == NULL) return root; // Zag-Zig (Right Left) if (root->right->key > key) { // Bring the key as root of right-left root->right->left = splay(root->right->left, key); // Do first rotation for root->right if (root->right->left != NULL) root->right = rightRotate(root->right); } else if (root->right->key < key)// Zag-Zag (Right Right) { // Bring the key as root of // right-right and do first rotation root->right->right = splay(root->right->right, key); root = leftRotate(root); } // Do second rotation for root return (root->right == NULL)? root: leftRotate(root); } } // The search function for Splay tree. // Note that this function returns the // new root of Splay Tree. If key is // present in tree then, it is moved to root. node *search(node *root, int key) { return splay(root, key); } // A utility function to print // preorder traversal of the tree. // The function also prints height of every node void preOrder(node *root) { if (root != NULL) { cout<<root->key<<" "; preOrder(root->left); preOrder(root->right); } } /* Driver code*/int main() { node *root = newNode(100); root->left = newNode(50); root->right = newNode(200); root->left->left = newNode(40); root->left->left->left = newNode(30); root->left->left->left->left = newNode(20); root = search(root, 20); cout << "Preorder traversal of the modified Splay tree is \n"; preOrder(root); return 0; } // This code is contributed by rathbhupendra 

C

// The code is adopted from http://goo.gl/SDH9hH #include<stdio.h> #include<stdlib.h> // An AVL tree node struct node { int key; struct node *left, *right; }; /* Helper function that allocates a new node with the given key and NULL left and right pointers. */struct node* newNode(int key) { struct node* node = (struct node*)malloc(sizeof(struct node)); node->key = key; node->left = node->right = NULL; return (node); } // A utility function to right rotate subtree rooted with y // See the diagram given above. struct node *rightRotate(struct node *x) { struct node *y = x->left; x->left = y->right; y->right = x; return y; } // A utility function to left rotate subtree rooted with x // See the diagram given above. struct node *leftRotate(struct node *x) { struct node *y = x->right; x->right = y->left; y->left = x; return y; } // This function brings the key at root if key is present in tree. // If key is not present, then it brings the last accessed item at // root. This function modifies the tree and returns the new root struct node *splay(struct node *root, int key) { // Base cases: root is NULL or key is present at root if (root == NULL || root->key == key) return root; // Key lies in left subtree if (root->key > key) { // Key is not in tree, we are done if (root->left == NULL) return root; // Zig-Zig (Left Left) if (root->left->key > key) { // First recursively bring the key as root of left-left root->left->left = splay(root->left->left, key); // Do first rotation for root, second rotation is done after else root = rightRotate(root); } else if (root->left->key < key) // Zig-Zag (Left Right) { // First recursively bring the key as root of left-right root->left->right = splay(root->left->right, key); // Do first rotation for root->left if (root->left->right != NULL) root->left = leftRotate(root->left); } // Do second rotation for root return (root->left == NULL)? root: rightRotate(root); } else // Key lies in right subtree { // Key is not in tree, we are done if (root->right == NULL) return root; // Zag-Zig (Right Left) if (root->right->key > key) { // Bring the key as root of right-left root->right->left = splay(root->right->left, key); // Do first rotation for root->right if (root->right->left != NULL) root->right = rightRotate(root->right); } else if (root->right->key < key)// Zag-Zag (Right Right) { // Bring the key as root of right-right and do first rotation root->right->right = splay(root->right->right, key); root = leftRotate(root); } // Do second rotation for root return (root->right == NULL)? root: leftRotate(root); } } // The search function for Splay tree. Note that this function // returns the new root of Splay Tree. If key is present in tree // then, it is moved to root. struct node *search(struct node *root, int key) { return splay(root, key); } // A utility function to print preorder traversal of the tree. // The function also prints height of every node void preOrder(struct node *root) { if (root != NULL) { printf("%d ", root->key); preOrder(root->left); preOrder(root->right); } } /* Driver program to test above function*/int main() { struct node *root = newNode(100); root->left = newNode(50); root->right = newNode(200); root->left->left = newNode(40); root->left->left->left = newNode(30); root->left->left->left->left = newNode(20); root = search(root, 20); printf("Preorder traversal of the modified Splay tree is \n"); preOrder(root); return 0; } 

Java

// Java implementation for above approach class GFG { // An AVL tree node static class node { int key; node left, right; }; /* Helper function that allocates a new node with the given key and null left and right pointers. */static node newNode(int key) { node Node = new node(); Node.key = key; Node.left = Node.right = null; return (Node); } // A utility function to right // rotate subtree rooted with y // See the diagram given above. static node rightRotate(node x) { node y = x.left; x.left = y.right; y.right = x; return y; } // A utility function to left // rotate subtree rooted with x // See the diagram given above. static node leftRotate(node x) { node y = x.right; x.right = y.left; y.left = x; return y; } // This function brings the key at // root if key is present in tree. // If key is not present, then it // brings the last accessed item at // root. This function modifies the // tree and returns the new root static node splay(node root, int key) { // Base cases: root is null or // key is present at root if (root == null || root.key == key) return root; // Key lies in left subtree if (root.key > key) { // Key is not in tree, we are done if (root.left == null) return root; // Zig-Zig (Left Left) if (root.left.key > key) { // First recursively bring the // key as root of left-left root.left.left = splay(root.left.left, key); // Do first rotation for root, // second rotation is done after else root = rightRotate(root); } else if (root.left.key < key) // Zig-Zag (Left Right) { // First recursively bring // the key as root of left-right root.left.right = splay(root.left.right, key); // Do first rotation for root.left if (root.left.right != null) root.left = leftRotate(root.left); } // Do second rotation for root return (root.left == null) ? root : rightRotate(root); } else // Key lies in right subtree { // Key is not in tree, we are done if (root.right == null) return root; // Zag-Zig (Right Left) if (root.right.key > key) { // Bring the key as root of right-left root.right.left = splay(root.right.left, key); // Do first rotation for root.right if (root.right.left != null) root.right = rightRotate(root.right); } else if (root.right.key < key)// Zag-Zag (Right Right) { // Bring the key as root of // right-right and do first rotation root.right.right = splay(root.right.right, key); root = leftRotate(root); } // Do second rotation for root return (root.right == null) ? root : leftRotate(root); } } // The search function for Splay tree. // Note that this function returns the // new root of Splay Tree. If key is // present in tree then, it is moved to root. static node search(node root, int key) { return splay(root, key); } // A utility function to print // preorder traversal of the tree. // The function also prints height of every node static void preOrder(node root) { if (root != null) { System.out.print(root.key + " "); preOrder(root.left); preOrder(root.right); } } // Driver code public static void main(String[] args) { node root = newNode(100); root.left = newNode(50); root.right = newNode(200); root.left.left = newNode(40); root.left.left.left = newNode(30); root.left.left.left.left = newNode(20); root = search(root, 20); System.out.print("Preorder traversal of the" + " modified Splay tree is \n"); preOrder(root); } } // This code is contributed by 29AjayKumar 

C#

// C# implementation for above approach using System; class GFG { // An AVL tree node public class node { public int key; public node left, right; }; /* Helper function that allocates a new node with the given key and null left and right pointers. */static node newNode(int key) { node Node = new node(); Node.key = key; Node.left = Node.right = null; return (Node); } // A utility function to right // rotate subtree rooted with y // See the diagram given above. static node rightRotate(node x) { node y = x.left; x.left = y.right; y.right = x; return y; } // A utility function to left // rotate subtree rooted with x // See the diagram given above. static node leftRotate(node x) { node y = x.right; x.right = y.left; y.left = x; return y; } // This function brings the key at // root if key is present in tree. // If key is not present, then it // brings the last accessed item at // root. This function modifies the // tree and returns the new root static node splay(node root, int key) { // Base cases: root is null or // key is present at root if (root == null || root.key == key) return root; // Key lies in left subtree if (root.key > key) { // Key is not in tree, we are done if (root.left == null) return root; // Zig-Zig (Left Left) if (root.left.key > key) { // First recursively bring the // key as root of left-left root.left.left = splay(root.left.left, key); // Do first rotation for root, // second rotation is done after else root = rightRotate(root); } else if (root.left.key < key) // Zig-Zag (Left Right) { // First recursively bring // the key as root of left-right root.left.right = splay(root.left.right, key); // Do first rotation for root.left if (root.left.right != null) root.left = leftRotate(root.left); } // Do second rotation for root return (root.left == null) ? root : rightRotate(root); } else // Key lies in right subtree { // Key is not in tree, we are done if (root.right == null) return root; // Zag-Zig (Right Left) if (root.right.key > key) { // Bring the key as root of right-left root.right.left = splay(root.right.left, key); // Do first rotation for root.right if (root.right.left != null) root.right = rightRotate(root.right); } else if (root.right.key < key)// Zag-Zag (Right Right) { // Bring the key as root of // right-right and do first rotation root.right.right = splay(root.right.right, key); root = leftRotate(root); } // Do second rotation for root return (root.right == null) ? root : leftRotate(root); } } // The search function for Splay tree. // Note that this function returns the // new root of Splay Tree. If key is // present in tree then, it is moved to root. static node search(node root, int key) { return splay(root, key); } // A utility function to print // preorder traversal of the tree. // The function also prints height of every node static void preOrder(node root) { if (root != null) { Console.Write(root.key + " "); preOrder(root.left); preOrder(root.right); } } // Driver code public static void Main(String[] args) { node root = newNode(100); root.left = newNode(50); root.right = newNode(200); root.left.left = newNode(40); root.left.left.left = newNode(30); root.left.left.left.left = newNode(20); root = search(root, 20); Console.Write("Preorder traversal of the" + " modified Splay tree is \n"); preOrder(root); } } // This code is contributed by 29AjayKumar 

Выходные данные:

Preorder traversal of the modified Splay tree is 20 50 30 40 100 200

Резюме

1) Splay-деревья обладают отличным свойством локальности. Часто используемые элементы легко найти. Редкие элементы не мешаются при поиске.

2) Все операции со splay-деревом в среднем занимают время порядка O(log n). Можно строго доказать, что Splay-деревья работают в среднем за время порядка O(log n) на операцию при любой последовательности операций (при условии, что мы начинаем с пустого дерева)

3) Splay-деревья проще по сравнению с красно-черными и АВЛ-деревьями, так как узлы splay-дерева не требуют дополнительных полей.

4) В отличие от АВЛ-дерева, splay-дерево может изменяться даже при выполнении операций чтения, таких как поиск.

Применение Splay-деревьев

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

Splay-деревья используются в Windows NT (в виртуальной памяти, сети и коде файловой системы), компиляторе gcc и библиотеке GNU C++, редакторе строк sed, сетевых маршрутизаторах Fore Systems, наиболее популярной реализации Unix malloc, загружаемых модулях ядра Linux и во многих других программах (Источник: http://www.cs.berkeley.edu/~jrs/61b/lec/36)

Смотрите также Splay Tree | Set 2 (Insert).

Ссылки:

http://www.cs.berkeley.edu/~jrs/61b/lec/36

http://www.cs.cornell.edu/courses/cs3110/2009fa/recitations/rec-splay.html

http://courses.cs.washington.edu/courses/cse326/01au/lectures/SplayTrees.ppt


Узнать подробнее о курсе "Алгоритмы и структуры данных".

Записаться на открытый вебинар по теме "Заповедники двоичных деревьев поиска."

Реклама которая может быть полезна

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

Кстати, о "красивой упаковке" онлайн-сертификатов мырассказываем в этой статье.

ЗАБРАТЬ СКИДКУ

Подробнее..

Перевод 6 малоизвестных фич C.NET

28.12.2020 18:17:25 | Автор: admin

Эксперт OTUS - Алексей Ягур приглашает всех желающих на Demo Day курса "Разработчик C#".


В преддверии старта курса делимся с вами традиционным переводом.


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

1. Stopwatch

Начать я собираюсь с того, чем мы будем пользоваться в остальных частях этой статьи, - со Stopwatch. Вполне вероятно, что в какой-то момент у вас будет причина захотеть профилировать части вашего кода, чтобы найти узкие места в производительности. Хотя существует множество пакетов для тестирования, которые вы можете использовать в своем коде (Benchmark.NET является одним из самых популярных), иногда вам просто нужно быстро что-то протестировать без лишних заморочек. Я полагаю, что большинство людей сделают что-то наподобие этого:

var start = DateTime.Now;Thread.Sleep(2000); //Code you want to profile herevar end = DateTime.Now;var duration = (int)(end - start).TotalMilliseconds;Console.WriteLine($"The operation took {duration} milliseconds");

Это сработает - вам будет объявлен результат в ~2000 миллисекунд. Однако это не золотой стандарт тестирования производительности, поскольку DateTime.Now может не дать вам необходимого уровня точности - DateTime.Now обычно имеет примерную точность до 15 миллисекунд. Чтобы продемонстрировать это, рассмотрим очень надуманный пример ниже:

var sleeps = new List<int>() { 5, 10, 15, 20 };foreach (var sleep in sleeps){var start = DateTime.Now;Thread.Sleep(sleep);var end  = DateTime.Now;var duration = (int)(end - start).TotalMilliseconds;Console.WriteLine(duration);}

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

15151531 

Итак, мы установили, что это не очень точный метод, но что, если вам все равно? Вы, конечно, можете продолжать использовать метод бенчмаркинга через DateTime.Now, но есть гораздо более приятная альтернатива - Stopwatch. который находится в пространстве имен System.Diagnostics. Это намного удобнее, чем использовать DateTime.Now, и выражает ваши намерения гораздо лаконичнее. Он также намного точнее! Давайте изменим наш последний фрагмент кода с использованием класса Stopwatch:

var sleeps = new List<int>() { 5, 10, 15, 20 };foreach (var sleep in sleeps){    var sw = Stopwatch.StartNew();    Thread.Sleep(sleep);    Debug.WriteLine(sw.ElapsedMilliseconds);}

Теперь наш результат таков (конечно, он все еще вариабельный)

6101520

Намного лучше! Учитывая простоту использования класса Stopwatch, в действительности нет никаких причин использовать старомодный способ.

2. Библиотека параллельных задач (TPL - Task Parallel Library)

var items = Enumerable.Range(0,100).ToList();var sw = Stopwatch.StartNew();foreach (var item in items){Thread.Sleep(50);}Console.WriteLine($"Took {sw.ElapsedMilliseconds} milliseconds..."); 

Как и следовало ожидать, этот фрагмент занимает примерно 5000 миллисекунд/5 секунд (100 * 50 = 5000). Теперь давайте посмотрим на нашу альтернативную версию с использованием TPL

var items = Enumerable.Range(0,100).ToList();var sw = Stopwatch.StartNew();Parallel.ForEach(items, (item) => {Thread.Sleep(50); });Console.WriteLine($"Took {sw.ElapsedMilliseconds} milliseconds..."); 

Теперь он занимает в среднем всего 1000 миллисекунд, что на 500% меньше! Результаты будут отличаться в зависимости от вашего сетапа, но вполне вероятно, что вы увидите улучшение, аналогичное тому, что я продемонстрировал здесь. И обратите внимание, насколько прост цикл - он едва ли сложнее обычного цикла foreach.

Но если вы работаете с не потокобезопасным объектом внутри цикла, тогда вам придется заморочиться. Так что увы, вы не можете просто взять и заменить любой foreach, который считаете нужным! Опять же, некоторые советы по этому поводу вы можете найти в моей статье о TPL.

3. Деревья выражений

Деревья выражений - чрезвычайно мощная фича .NET Framework, но они также являются одной из самых плохо понимаемых (неопытными программистами). Мне потребовалось много времени, чтобы полностью понять их концепцию, и я все еще далек от экспертных познаний в этом вопросе, но по сути они позволяют вам обернуть лямбда-выражения, такие как Func<T> или Action<T>, а также проанализировать само лямбда-выражение. Вероятно, лучше всего проиллюстрировать это можно с помощью примера - а в .NET Framework их предостаточно, особенно в LINQ to SQL и Entity Framework.

Метод расширения 'Where' в LINQ to Objects принимает в качестве основного параметра Func<T, int, bool> - смотрите приведенный ниже код, который я позаимствовал из Reference Source (который содержит исходный код .NET)

static IEnumerable<TSource> WhereIterator<TSource>(IEnumerable<TSource> source, Func<TSource, int, bool> predicate) {     int index = -1;     foreach (TSource element in source)      {          checked { index++; }          if (predicate(element, index)) yield return element;     }}

Как и следовало ожидать - оно выполняет итерацию по IEnumerable и возвращает (yields) то, что соответствует предикату. Однако очевидно, что это не будет работать в LINQ to SQL/Entity Framework - ему необходимо преобразовать ваш предикат в SQL! Так что сигнатура для версии IQueryable немного отличается

static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, int, bool>> predicate) {      return source.Provider.CreateQuery<TSource>(                 Expression.Call(                    null,                    GetMethodInfo(Queryable.Where, source, predicate),                    new Expression[] { source.Expression, Expression.Quote(predicate) }                    ));}

Если вы посмотрите на пугающие внутренности метода, то заметите, что функция теперь принимает Func<T, int, bool>, обернутую в Expression - это, по сути, позволяет провайдеру LINQ читать через Func, чтобы увидеть какой именно предикат был пропущен, и преобразовать его в SQL. По сути, Expressions позволяют вам проверять ваш код во время выполнения.

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

var settings = new List<Setting>();settings.Add(new Setting("EnableBugs",Settings.EnableBugs));settings.Add(new Setting("EnableFluxCapacitor",Settings.EnableFluxCapacitor));

Надеюсь, здесь несложно заметить опасность/повторение - имя параметра в словаре представляет собой строку, и опечатка здесь может вызвать проблемы. К тому же, это утомительно! Если мы создадим новый метод, который принимает Expression<Func<T>> (по сути, принимает лямбда-выражение, которое возвращает что-то), мы получим фактическое имя переданной переменной!

private Setting GetSetting<T>(Expression<Func<T>> expr){var me = expr.Body as MemberExpression;if (me == null)             throw new ArgumentException("Invalid expression. It should be MemberExpression");        var func = expr.Compile(); //This converts our expression back to a Funcvar value = func(); //Run the func to get the setting valuereturn new Setting(me.Member.Name,value);}

Мы можем назвать это следующим образом

var settings = new List<Setting>();settings.Add(GetSetting(() => Settings.EnableBugs));settings.Add(GetSetting(() => Settings.EnableFluxCapacitor));

Намного приятнее! Вы заметите, что в нашем методе GetSetting мне нужно проверить, передано ли выражение как 'MemberExpression' - это потому, что ничто не мешает вызывающему коду передать что-то вроде вызова метода или константы, что в этом случае не является "именем члена".

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

4. Атрибуты сведений о вызывающем объекте

Атрибуты сведений о вызывающем объекте были введены в .NET Framework 4.0, и, хотя они в большинстве случаев не используются, они действительно показывают свою ценность при написании кода для регистрации дебажной информации. Представим, что у вас есть довольно грубая функция логирования, как показано ниже:

public static void Log(string text){   using (var writer = File.AppendText("log.txt"))   {       writer.WriteLine(text);   }}

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

public static void Log(string text){   using (var writer = File.AppendText("log.txt"))   {       writer.WriteLine($"{text} - {new StackTrace().GetFrame(1).GetMethod().Name});   }}

Это работает - он напечатает Main, если я вызову его из своего метода Main. Однако это медленно и неэффективно, поскольку вы по сути захватываете стектрейс так, как если бы возникло исключение. .NET Framework 4.0 представляет вышеупомянутые атрибуты сведений о вызывающем объекте, которые позволяют Framework автоматически сообщать вашему методу информацию о том, что его вызывает, в частности, путь к файлу, имя метода/свойства и номер строки. По сути, вы используете их, позволяя вашему методу принимать необязательные строковые параметры, которые вы помечаете атрибутом. Ниже я использую 3 доступных атрибута.

public static void Log(string text,[CallerMemberName] string memberName = "",[CallerFilePath] string sourceFilePath = "",[CallerLineNumber] int sourceLineNumber = 0){Console.WriteLine($"{text} - {sourceFilePath}/{memberName} (line {sourceLineNumber})");}

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

5. Класс Path

Этот может быть немного более известной фичей, но я все еще наблюдаю вживую, как разработчики делают такие вещи, как получение расширения файла по имени файла вручную, при живом встроенном классе Path и наличии проверенных и надежных методов, которые сделают это за вас. Этот класс находится в пространстве имен System.IO и содержит множество полезных методов, которые сокращают объем стандартного кода, который вам необходимо написать. Многие из вас знакомы с такими методами, как Path.GetFileName и Path.GetExtension (которые работают именно так, как и следовало ожидать из названия), но я упомяну несколько более неизвестных ниже

Path.Combine

Этот метод берет 2,3 или 4 пути и объединяем их в один. Обычно люди делают это, чтобы добавить имя файла к пути к каталогу, например, directoryPath + \ + filename . Проблема в том, что вы делаете предположение, что именно символ '\' является разделителем каталогов в системе, в которой работает ваше приложение, - что, если приложение работает в Unix, который использует косую черту ('/') в качестве разделителя каталогов? Это становится все более серьезной проблемой, поскольку .NET Core позволяет запускать приложения .NET на достаточно большем количестве платформ. Path.Combine будет использовать разделитель каталогов, применимый к целевой операционной системе, а также обработает ситуацию с избыточными разделителями, т.е. если вы добавите каталог с '\' в конце к имени файла с '\' в начале, Path.Combine вырежет один '\'. Вы можете найти больше причин, по которым вам следует использовать Path.Combine здесь

Path.GetTempFileName

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

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

Path.GetInvalidPathChars / Path.GetInvalidFileNameChars

Path.GetInvalidPathChars и его брат Path.GetInvalidFileNameChars, возвращает массив всех символов, которые недопустимы в качестве текущих путей/имен файлов в текущих путях/именах файлов. Я видел так много кода, который вручную удаляет некоторые из наиболее распространенных недопустимых символов, таких как кавычки, но не удаляет любые другие недопустимые символы, что является катастрофой, ожидающей своего часа. И в духе кроссплатформенной совместимости неверно предполагать, что то, что недопустимо в одной системе, будет недопустимо в другой. Моя единственная критика этих методов заключается в том, что они не обеспечивают способа проверки, содержит ли путь какой-либо из этих символов, что обычно требует написания нижеприведенных шаблонных методов.

public static bool HasInvalidPathChars(string path){if (path  == null)throw new ArgumentNullException(nameof(path));return path.IndexOfAny(Path.GetInvalidPathChars()) >= 0;}public static bool HasInvalidFileNameChars(string fileName){if (fileName == null)throw new ArgumentNullException(nameof(fileName));return fileName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0;}

6. StringBuilder

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

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

var sw = Stopwatch.StartNew();string test = "";for (int i = 0; i < 10000; i++){test += $"test{i}{Environment.NewLine}";}Console.WriteLine($"Took {sw.ElapsedMilliseconds} milliseconds to concatenate strings using string concatenation");sw = Stopwatch.StartNew();var sb = new StringBuilder();for (int i = 0; i < 10000; i++){sb.Append($"test{i}");sb.AppendLine();}Console.WriteLine($"Took {sw.ElapsedMilliseconds} milliseconds to concatenate strings using StringBuilder");

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

Took 785 milliseconds to concatenate strings using the string concatenationTook 3 milliseconds to concatenate strings using StringBuilder 

Ого - это более чем в 250 раз быстрее! И это только для 10000 конкатенаций - если вы создаете большой файл CSV вручную (что в любом случае является плохой практикой, но давайте не будем пока об этом беспокоиться), ваши значения могут быть больше. Если вы объединяете только пару строк, вероятно, будет нормально использовать concatenate без использования StringBuilder, но, честно говоря, мне нравится иметь привычку всегда использовать его - стоимость обновления StringBuilder, условно говоря, не такая уж и большая.

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

Заключение

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

Я хотел бы услышать мнение всех, кто использует другие недооцененные фичи в .NET/C #, поэтому, пожалуйста, прокомментируйте эту статью, если вам есть, что рассказать!

Записаться на Demo Day курса "Разработчик C#"

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

Кстати, о "красивой упаковке" онлайн-сертификатов мырассказываем в этой статье.

ЗАБРАТЬ СКИДКУ

Подробнее..

Перевод Язык программирования PHP 8 новый JIT-компилятор нацелен на лучшую производительность

08.01.2021 02:22:10 | Автор: admin

Привет, Хабр. Будущих студентов курса "PHP-разработчик" и всех интересующихся приглашаем принять участие в открытом вебинаре на тему "PHP 8 Что нового?".

А сейчас делимся традиционным переводом интересного материала.


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

Команда, создавшая скриптовый язык PHP, анонсировала версию PHP 8.0 крупный релиз, который может потребовать от разработчиков проверку кода на предмет критических изменений.

Эта версия 25-летнего PHP представляет улучшенную систему типов, новый JIT-компилятор в движке PHP и некоторые функции, заимствованные из Python и JavaScript, такие как именованные аргументы и null safe операторы.

PHP как язык не обрел всеобщей любви, но он широко используется веб-разработчиками. Разработчики оценивают PHP как шестой самый страшный язык в опросе разработчиков Stack Overflow 2020 года, но он также оказался восьмым по популярности языком.

Аналитик-разработчик RedMonk в настоящее время позиционирует PHP как четвертый по популярности язык, уступающий только Java, Python и JavaScript. Tiobe Software ставит PHP на восьмое место. И, по данным поисковой системы Indeed, количество объявлений о вакансиях PHP-разработчиков начального уровня за последний год увеличилось более чем на 800%.

PHP поддерживается основной группой разработчиков PHP и Zend, американской консалтинговой компанией по разработке PHP, основанной Энди Гутмансом, генеральным менеджером и вице-президентом по разработке баз данных в Google. Гутманс сказал, что он "в восторге" от JIT-компилятора.

JIT-компилятор предназначен для улучшения производительности веб-приложений. Однако Брент Руз, бельгийский разработчик stitcher.io, сказал, что у него есть нарекания, когда дело касается запросов.

Как у интерпретируемого языка, такого как JavaScript и Python, код PHP транслируется во время выполнения. Это не компилируемый язык, такой как C, Java или Rust, и его нужно транслировать, чтобы ЦП понимал код PHP.

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

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

Поскольку сегодня лишь несколько человек могут поддерживать кодовую базу [PHP], вопрос о том, возможно ли поддерживать JIT-компилятор как подобает, кажется оправданным. Конечно, люди могут разобраться в работе компилятора. Но, как ни крути, это сложный материал, сказал Руз.

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

Кроме того, выпуск PHP 8 в качестве мажорного релиза может означать, что старый код PHP может стать нерабочим после обновления. Однако Руз отметил, что большинство критических изменений объявлены устаревшими до версий 7.x.

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

Но пользователи PHP всегда могут заплатить консалтинговой фирме, такой как Zend, за долгосрочную поддержку, и они получат патчи после 30 ноября.

Что до пользователей операционных систем с предложениями долгосрочной поддержки, то их бинарники PHP зачастую продолжают получать патчи от поставщика ОС, даже когда заканчивается период поддержки сообщества", сказал Мэтью Вейер О'Финни, инженер Zend.

В случае с Ubuntu 18.04 и RHEL/CentOS 8, каждая из которых поставляется с PHP 7.2, это означает, что вы можете продолжать получать патчи. Однако если операционная система, в которой вы работаете, не находится под политикой LTS, тогда ваша версия со временем станет уязвимым для новых эксплойтов".

Марк Стори, главный разработчик Sentry, который поддерживает проекты PHP CakePHP и XHGui, сказал, что именованные параметры и типы объединения PHP 8 обеспечивают улучшения эргономичности и корректности.

Именованные параметры позволяют вызывать методы с параметрами на основе их имени, а не только их порядка, объяснил Стори.

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

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

Между тем, типы объединений расширяют систему типов PHP.


Узнать подробнее о курсе "PHP-разработчик".

Зарегистрироваться на открытый вебинар на тему "PHP 8 Что нового?".

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

Кстати, о "красивой упаковке" онлайн-сертификатов мырассказываем в этой статье.

ЗАБРАТЬ СКИДКУ

Подробнее..

Перевод Поиск, устранение и предупреждение утечек памяти в C .NET 8 лучших практик

12.01.2021 16:20:24 | Автор: admin

Для будущих студентов курса Разработчик C# и всех интересующихся подготовили перевод полезного материала.

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


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

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

Что из себя представляют утечки памяти в .NET

В среде со сборкой мусора термин утечка памяти представляется немного контринтуитивным. Как может произойти утечка памяти, когда есть сборщик мусора (GC garbage collector), который берет на себя задачу высвобождения памяти?

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

Вторая причина заключается в том, что вы каким-то образом выделяете неуправляемую память (без сборки мусора) и не освобождаете ее. Сделать это не так уж и сложно. В самой .NET есть множество классов, которые выделяют неуправляемую память. Практически все, что связано с потоками, графикой, файловой системой или сетевыми вызовами, делает это под капотом. Обычно эти классы реализуют метод Dispose, который освобождает память (мы поговорим об этом позже). Вы можете легко выделить неуправляемую память самостоятельно с помощью специальных классов .NET .Например, Marshal или PInvoke (пример этого будет ниже).

Давайте же перейдем к моему списку лучших практик:

1. Обнаружение утечек памяти с помощью окна средств диагностики

Если вы перейдете в Debug | Windows | Show Diagnostic Tools, вы увидите это окно. Как и я когда-то, вы, вероятно, уже видели это окно после установки Visual Studio, сразу же закрыли его и никогда больше о нем не вспоминали. Окно средств диагностики может быть весьма полезным. Оно может помочь вам легко обнаружить 2 проблемы: утечки памяти и GC Pressure (давление на сборщик мусора).

Когда у вас есть утечки памяти, график использования памяти процессом (Process Memory) выглядит следующим образом:

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

В случае GC Pressure, график использования памяти процессом выглядит следующим образом:

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

С помощью этого метода вы не сможете найти определенные утечки памяти, но вы навскидку можете обнаружить, что у вас есть проблема с утечкой памяти, что само по себе уже несет пользу. В Visual Studio Enterprise окно средств диагностики также включает встроенный профилировщик памяти, который позволяет обнаружить конкретную утечку. Мы поговорим о профилировании памяти в третьем пункте.

2. Обнаружение утечек памяти с помощью диспетчера задач, Process Explorer или PerfMon

Второй самый простой способ обнаружить серьезные проблемы с утечками памяти с помощью диспетчера задач (Task Manager) или Process Explorer (от SysInternals). Эти инструменты могут показать объем памяти, который использует ваш процесс. Если она постоянно увеличивается со временем, возможно, у вас утечка памяти.

PerfMon немного сложнее в использовании, но у него есть хороший график потребления памяти с течением времени. Вот график моего приложения, которое бесконечно выделяет память, не освобождая ее. Я использую счетчик Process | Private Bytes.

Обратите внимание, что этот метод заведомо ненадежен. Вы можете наблюдать увеличение потребления памяти только потому, что еще не отработал сборщик мусора. Также стоит вопрос об общей и приватной памяти, поэтому вы можете упустить утечки памяти и/или диагностировать утечки, которые не являются вашими собственными (объяснение). Наконец, вы можете принять утечку памяти за GC Pressure. В этом случае у вас нет утечек памяти, но вы создаете и удаляете объекты так быстро, что сборщик мусора не поспевает за вами.

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

3. Использование профилировщика памяти для обнаружения утечек

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

Вот несколько довольно известных профилировщиков для .NET: dotMemory, SciTech Memory Profiler и ANTS Memory Profiler. Также есть бесплатный профилировщик, если у вас стоит Visual Studio Enterprise.

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

Вы можете увидеть, сколько аллоцировано экземпляров каждого типа, сколько памяти они занимают и путь ссылки на GC Root.

GC Root это объект, который сборщик мусора не может освободить, поэтому все, на что ссылается GC Root, также не может быть освобождено. Статические и локальные объекты, текущие активные потоки, являются GC Roots. Подробнее об этом читайте в статье Сборка мусора в .NET.

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

  1. Начните с какого-либо состояния бездействия (Idle state) в вашем приложении. Это может быть Главное меню или что-то в этом роде.

  2. Сделайте снапшот с помощью профилировщика памяти, присоединившись к процессу или сохранив дамп.

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

  4. Сделайте второй снапшот.

  5. Сравните оба снапшота с помощью своего профилировщика.

  6. Изучите New-Created-Instances, возможно, это утечки памяти. Изучите path to GC Root и попытайтесь понять, почему эти объекты не были освобождены.

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

4. Используйте Make Object ID для поиска утечек памяти

В моей последней статье 5 методов, позволяющих избежать утечек памяти из-за событий в C# .NET, которые вы должны знать, я показал способ найти утечку памяти, поместив точку останова в класс Finalizer. Я покажу вам похожий метод, который еще проще в использовании и не требует изменения кода. Здесь используется функция отладчика Make Object ID и окно непосредственной отладки (Immediate Window).

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

  1. Поместите точку останова туда, где создается экземпляр класса.

  2. Наведите курсор на переменную, чтобы открыть всплывающую подсказку отладчика, затем щелкните правой кнопкой мыши и используйте Make Object ID. Вы можете ввести в окне Immediate $1, чтобы убедиться, что Object ID был создан правильно.

  3. Завершите сценарий, который должен был освободить ваш экземпляр от ссылок.

  4. Спровоцируйте сборку мусора с помощью известных волшебных строчек кода.

GC.Collect();GC.WaitForPendingFinalizers();GC.Collect();

5. В появившемся окне непосредственной отладки введите $1. Если оно возвращает null, значит, сборщик мусора собрал ваш объект. Если нет, у вас утечка памяти.

Здесь я отлаживаю сценарий с утечкой памяти:

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

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

Важно: этот метод не работает в отладчике .NET Core 2.X (проблема). Принудительная сборка мусора в той же области, что и выделение объекта, не освобождает этот объект. Вы можете сделать это, приложив немного больше усилий, спровоцировав сборку мусора в другом методе вне области видимости.

5. Избегайте известных способов заиметь утечки памяти

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

Вот некоторые из наиболее распространенных подозреваемых:

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

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

  • Привязки WPF могут быть опасными. Практическое правило всегда выполнять привязку к DependencyObject или к INotifyPropertyChanged. Если вы этого не сделаете, WPF создаст сильную ссылку на ваш источник привязки (то есть ViewModel) из статической переменной, что приведет к утечке памяти. Дополнительную информацию о WPF утечках можно найти в этом полезном треде StackOverflow.

  • Захваченные члены. Может быть достаточно очевидно, что метод обработчика событий подразумевает, что на объект ссылаются, но когда переменная захвачена анонимным методом на нее также ссылаются. Вот пример такой утечки памяти:

public class MyClass{    private int _wiFiChangesCounter = 0;     public MyClass(WiFiManager wiFiManager)    {        wiFiManager.WiFiSignalChanged += (s, e) => _wiFiChangesCounter++;    }
  • Потоки, которые никогда не завершаются. Live Stack каждого из ваших потоков считается GC Root. Это означает, что до тех пор, пока поток не завершится, любые ссылки из его переменных в стеке не будут собираться сборщиком мусора. Это также включает таймеры. Если обработчик тиков вашего таймера является методом, то объект метода считается ссылочным и не собирается. Вот пример такой утечки памяти:

public class MyClass{    public MyClass(WiFiManager wiFiManager)    {        Timer timer = new Timer(HandleTick);        timer.Change(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5));    }     private void HandleTick(object state)    {        // do something    }

Подробнее об этом читайте в моей статье 8 способов вызвать утечки памяти в .NET.

6. Используйте шаблон Dispose для предотвращения утечек неуправляемой памяти

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

Классы .NET Framework, использующие неуправляемый код, обычно реализуют IDisposable. Это связано с тем, что неуправляемые ресурсы должны быть явно освобождены, а это происходит в методе Dispose. Ваша единственная задача не забыть вызвать метод Dispose. Если возможно, используйте для этого оператор using.

public void Foo(){    using (var stream = new FileStream(@"C:\Temp\SomeFile.txt",                                       FileMode.OpenOrCreate))    {        // do stuff     }// stream.Dispose() will be called even if an exception occurs

Оператор using за кулисами преобразует код в оператор try / finally, где метод Dispose вызывается в finally.

Но даже если вы не вызовете метод Dispose, эти ресурсы будут освобождены, поскольку классы .NET используют шаблон Dispose. Это означает, что если Dispose не был вызван раньше, он вызывается из Finalizer, когда объект собирается сборщиком мусора. То есть, если у вас нет утечки памяти и действительно вызывается Finalizer.

Когда вы сами выделяете неуправляемые ресурсы, вам определенно следует использовать шаблон Dispose. Вот пример:

public class MyClass : IDisposable{    private IntPtr _bufferPtr;    public int BUFFER_SIZE = 1024 * 1024; // 1 MB    private bool _disposed = false;     public MyClass()    {        _bufferPtr =  Marshal.AllocHGlobal(BUFFER_SIZE);    }     protected virtual void Dispose(bool disposing)    {        if (_disposed)            return;         if (disposing)        {            // Free any other managed objects here.        }         // Free any unmanaged objects here.        Marshal.FreeHGlobal(_bufferPtr);        _disposed = true;    }     public void Dispose()    {        Dispose(true);        GC.SuppressFinalize(this);    }     ~MyClass()    {        Dispose(false);    }}

Смысл этого шаблона разрешить явное удаление ресурсов. А также чтобы добавить гарантии того, что ваши ресурсы будут удалены во время сборки мусора (в Finalizer), если Dispose() не был вызван.

GC.SuppressFinalize(this) также имеет важное значение. Она гарантирует, что Finalizer не будет вызван при сборке мусора, если объект уже был удален. Объекты с Finalizer-ами освобождаются иначе и намного дороже. Finalizer добавляется к F-Reachable-Queue, которая позволяет объекту пережить дополнительную генерацию сборщика мусора. Есть и другие сложности.

7. Добавление телеметрии памяти из кода

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

Из самого приложения мы можем получить много информации. Получить текущую используемую память очень просто:

Process currentProc = Process.GetCurrentProcess();var bytesInUse = currentProc.PrivateMemorySize64;

Для получения дополнительной информации вы можете использовать PerformanceCounter класс, который используется для PerfMon:

PerformanceCounter ctr1 = new PerformanceCounter("Process", "Private Bytes", Process.GetCurrentProcess().ProcessName);PerformanceCounter ctr2 = new PerformanceCounter(".NET CLR Memory", "# Gen 0 Collections", Process.GetCurrentProcess().ProcessName);PerformanceCounter ctr3 = new PerformanceCounter(".NET CLR Memory", "# Gen 1 Collections", Process.GetCurrentProcess().ProcessName);PerformanceCounter ctr4 = new PerformanceCounter(".NET CLR Memory", "# Gen 2 Collections", Process.GetCurrentProcess().ProcessName);PerformanceCounter ctr5 = new PerformanceCounter(".NET CLR Memory", "Gen 0 heap size", Process.GetCurrentProcess().ProcessName);//...Debug.WriteLine("ctr1 = " + ctr1 .NextValue());Debug.WriteLine("ctr2 = " + ctr2 .NextValue());Debug.WriteLine("ctr3 = " + ctr3 .NextValue());Debug.WriteLine("ctr4 = " + ctr4 .NextValue());Debug.WriteLine("ctr5 = " + ctr5 .NextValue());

Доступна информация с любого счетчика perfMon, чего нам хватит с головой.

Однако вы можете пойти еще дальше. CLR MD (Microsoft.Diagnostics.Runtime) позволяет проверить текущую кучу и получить любую возможную информацию. Например, вы можете вывести все выделенные типы в памяти, включая количество экземпляров, пути к корням и так далее. Вы в значительной степени реализовали профилировщик памяти из кода.

Чтобы получить представление о том, чего можно достичь с помощью CLR MD, ознакомьтесь с DumpMiner Дуди Келети.

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

8. Тестирование на утечки памяти

Профилактическое тестирование на утечки памяти незаменимая практика. И это не так уж и сложно. Вот небольшой шаблон, который вы можете использовать:

[Test]void MemoryLeakTest(){  var weakRef = new WeakReference(leakyObject)  // Ryn an operation with leakyObject  GC.Collect();  GC.WaitForPendingFinalizers();  GC.Collect();  Assert.IsFalse(weakRef.IsAlive);}

Для более глубокого тестирования профилировщики памяти, такие как .NET Memory Profiler от SciTech и dotMemory, предоставляют тестовый API:

MemAssertion.NoInstances(typeof(MyLeakyClass));MemAssertion.NoNewInstances(typeof(MyLeakyClass), lastSnapshot);MemAssertion.MaxNewInstances(typeof(Bitmap), 10);

Заключение

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

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


- Узнать подробнее о курсе Разработчик C#.

- Зарегистрироваться на открытый вебинар Методы LINQ, которые сделают всё за вас.

Подробнее..

Перевод Трюки с виртуальной памятью

15.01.2021 18:06:16 | Автор: admin

Я уже довольно давно хотел написать пост о работе с виртуальной памятью. И когда @jimsagevid в ответ на мой твит написал о ней, я понял, что время пришло.

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

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

Неприлично большой массив

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

Создать массив фиксированного размера очень просто:

objecto *objects[MAXOBJECTS]

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

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

Итак, в данной ситуации мы можем просто выбрать достаточно большое число для размера массива и виртуально выделить под него память. Например, 1 миллиард объектов:

#define MAXOBJECTS 1000000000ULLobjecto **objects = virtualalloc(MAXOBJECTS * sizeof(objecto ));

Мы используем 8 ГБ адресного пространства и виртуальной памяти, но физической только столько, сколько нам действительно нужно для наших объектов. Очень простое решение, для которого потребовалась всего одна строчка кода.

Примечание Я использую здесь условный virtualalloc() в качестве системного вызова для выделения виртуальной памяти, не зависящего от ОС. На самом деле в Windows вы бы вызвали VirtualAlloc(), а в Linux mmap().

Еще одно примечание Windows разделяет выделение виртуальной памяти на отдельные вызовы MEMRESERVE и MEMCOMMIT. MEMRESERVE резервирует адресное пространство, а MEMCOMMIT выделяет его в физической памяти. Но это не значит, что физическая память реально выделяется при вызове MEMCOMMIT, физическая память не выделяется, пока вы не обратитесь к страницам. MEMCOMMIT резервирует память в файле подкачки, а если вы попытаетесь выделить больше памяти, чем доступно в файле подкачки, то MEMCOMMIT завершится ошибкой. Поэтому в Windows вы, скорее всего, не будете использовать MEMCOMMIT для всей таблицы из моего примера (потому что у файла подкачки размер ограничен). Вместо этого лучше сначала использовать MEMRESERVE для всей таблицы, а затем MEMCOMMIT только для фактически используемого диапазона.

В отличие от этого, Linux допускает overcommit (чрезмерное выделение памяти). То есть вы можете выделить больше памяти, чем доступно в файле подкачки. По этой причине Linux не нуждается в отдельных операциях резервирования (reserve) и подтверждения (commit), что упрощает использование виртуальной памяти.

Есть ли проблема в резервировании виртуальной памяти для массива в 8 ГБ? Здесь два ограничения. Первое это адресное пространство. В 64-битном приложении адресное пространство составляет 264. Это очень большое число, в котором можно разместить миллиарды массивов гигабайтного размера. Второе ограничение касается виртуальной памяти. Операционная система обычно не позволяет выделять все возможное адресное пространство. Например, в 64-битной Windows мы можем выделить только 256 ТБ виртуальной памяти. Тем не менее в этом объеме можно разместить 32 000 массивов по 8 ГБ каждый, так что пока мы не совсем сходим с ума, все будет в порядке.

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

Вспомните об олдскульном способе писать игры на Си с использованием статических массивов:

uint32t numtanks;tankt tanks[MAXTANKS];uint32C11Cbullets;bulletC12CBULLETS];

Если вы пишете подобный код, то будьте уверены, что найдутся те, кто его будет критиковать, так как здесь есть ограничения на количество объектов. Выглядит забавно, но можно вместо использования std::vector просто избавиться от MAXC13C и выделить 1 ГБ виртуальной памяти для каждого из массивов:

#define GB 1000000000uint32_t num_tanks;tank_t *tanks = virtual_alloc(GB);uint32_t num_bullets;bullet_t *bullets = virtual_alloc(GB);

Уникальные ID в рамках всего приложения

Многим игровым движкам требуются уникальные идентификаторы (ID) для идентификации объектов. Часто код выглядит примерно так:

uint64_t allocate_id(system_t *sys){    return sys->next_free_id++;} 

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

Это может выглядеть примерно так:

system_id_t *allocate_id(system_t *sys){    if (!sys->id_block || sys->id_block_used == PAGE_SIZE) {        sys->id_block = virtual_alloc(PAGE_SIZE);        sys->id_block_used = 0;    }    return (system_id_t *)(sys->id_block + sys->id_block_used++);}

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

Обнаружение перезаписи памяти

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

Чтобы понять как, давайте сначала обратим внимание на то, что термин "случайная перезапись памяти" на самом деле неправильный. Адресное пространство в основном пустое. При 64-битном адресном пространстве и размере приложения, скажем, 2 ГБ, адресное пространство пусто на 99,999999988%. Это означает, что если перезапись памяти действительно случайная, то, скорее всего, она попала бы в это пустое пространство, что привело бы к ошибке/нарушению доступа к странице. А это бы привело к падению приложения в момент некорректной записи, а не при невинном чтении, что бы значительно упростило поиск и исправление ошибки.

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

  • Запись в память, которая была освобождена.

  • Запись за пределами выделенной памяти для объекта.

В обоих случаях весьма вероятно, что запись действительно попадет в какой-то другой объект, а не в пустое место. В первом случае память, скорее всего, предназначалась для чего-то другого. А во втором запись, вероятно, попадет в соседний объект или заголовок блока распределения (allocation block header).

Мы можем сделать это более случайным, заменив стандартный системный аллокатор на end-of-page аллокатор (аллокатор в конце страницы). Такой аллокатор размещает каждый объект в виртуальной памяти в собственном множестве страниц и выравнивает объект так, чтобы он располагался в конце блока памяти.

Размещение блока в конце блока страниц.Размещение блока в конце блока страниц.

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

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

Так как такой аллокатор округляет выделение памяти по размеру страницы, то для небольших данных будет тратиться лишняя память. Поэтому обычно я работаю со стандартным аллокатором, но если подозреваю, что происходит перезапись памяти, то переключаюсь на end-of-page аллокатор. После устранения проблемы я переключаюсь обратно на стандартный аллокатор. Конечно, возможность переключения аллокатора должна быть заложена в архитектуру вашего проекта.

Написание end-of-page аллокатора совсем несложно. Вот как может выглядеть malloc:

void *eop_malloc(uint64_t size){    uint64_t pages = (size + PAGE_SIZE - 1) / PAGE_SIZE;    char *base = virtual_alloc(pages * PAGE_SIZE);    uint64_t offset = pages * PAGE_SIZE - size;    return base + offset;}

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

Обе эти проблемы можно исправить. Для решения первой проблемы мы можем оставить страницы зарезервированными (reserved), но не подтвержденными (commited). Таким образом, физическая память освобождается и мы получим ошибки страниц, но адреса остаются зарезервированными и не смогут использоваться другими объектами. Для второй проблемы можно зарезервировать дополнительную страницу после наших страниц, но не подтверждать ее. Тогда никакой другой объект не сможет претендовать на эти адреса и запись в них все равно приведет к ошибке доступа (access violation). (Примечание: это работает только в Windows, где reserve и commit являются отдельными операциями.)

Однако на практике мне никогда не приходилось принимать эти дополнительные меры предосторожности. Для меня всегда было достаточно обычного end-of-page аллокатора.

Непрерывное выделение памяти

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

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

Фрагментация памяти.Фрагментация памяти.

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

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

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

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

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

Обратите внимание, что в адресном пространстве у нас все еще остается фрагментация. Т.е. мы не можем выделить большой непрерывный блок памяти в адресном пространстве, где находятся красные страницы. Фрагментация адресного пространства, на самом деле, нас особо не беспокоит, потому что, как было сказано выше, обычно адресное пространство на 99.999999988% пустое. Так что у нас нет проблем с поиском смежных страниц в адресном пространстве. (Но в 32-битных системах это совсем другая история.)

Недостаток такого подхода по сравнению с обычным аллокатором заключается в том, что нам приходится округлять выделяемую память по размеру страницы, которая в современных системах обычно составляет 4 КБ. Это означает, что мы не используем всю доступную память проблема, которая называется внутренней фрагментацией.

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

Размер динамически увеличивающегося буфера можно сделать соответствующим размеру страницы. Это простой, но интересный метод, о котором я очень редко слышу. Допустим, у вас есть массив объектов размером 300 байт. Обычно при необходимости размещения большего количества записей вы увеличиваете размер массива геометрически, например, удваивая. Таким образом, получается увеличение количества элементов с 16 до 32 до 64 и до 128 элементов. Геометрический рост важен, чтобы было меньше затрат на частое увеличение массива.

Однако 16 * 300 = 4800. При выделении виртуальной памяти вам придется округлить это до 8 КБ, тратя впустую почти целую страницу. Но это можно легко исправить. Вместо того чтобы концентрироваться на количестве элементов, мы просто увеличиваем размер буфера кратно размеру страницы: 4 КБ, 8 КБ, 16 КБ, 32 КБ, , а затем помещаем туда столько элементов, сколько поместится в него (13, 27, 54, 109,). Это по-прежнему геометрический рост, но теперь внутренняя фрагментация составляет в среднем всего 150 байт вместо 2 КБ.

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

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

Возможно, появятся некоторые дополнительные затраты памяти ОС для отслеживания большого количества отдельных выделений памяти. Также время тратится на системные вызовы выделения и освобождения страниц. Может быть, в этом и есть причина. Или просто дело в том, что аллокаторы написаны для работы в различных средах и в 32-битных системах и в системах с большими страницами поэтому они не могут использовать преимуществ 64-битных систем и 4KБ страниц.

Кольцевой буфер

Об этом трюке я узнал из блога Фабиана Гизена (Fabian Giesen). Но, кажется, что это довольно давняя идея.

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

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

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

enum {BUFFER_SIZE = 8*1024};struct ring_buffer_t {    uint8_t data[BUFFER_SIZE];    uint64_t read;    uint64_t written;};

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

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

void write(ring_buffer_t *rb, uint8_t *p, uint64_t n){    uint64_t offset = rb->written % BUFFER_SIZE;    uint64_t space = BUFFER_SIZE - offset;    uint64_t first_write = n < space ? n : space;    memcpy(rb->data + offset, p, first_write);    memcpy(rb->data, p + first_write, n - first_write);    rb->written += n;}

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

Как здесь может помочь виртуальная память? Мы можем использовать технику "огромного массива" и просто зарезервировать большой массив вместо кольцевого буфера и фиксировать (commit) страницы по мере продвижения указателя на запись, а по мере продвижения читателя отменять фиксацию (decommit). При этом нам даже не нужно будет задавать фиксированный размер массива он просто может использовать столько памяти, сколько потребуется. Довольно красивое решение. Но учтите, что вам может понадобиться очень большой массив. Для буферизации сетевого потока 1 Гбит/с с аптаймом в течение года вам потребуется зарезервировать 4 ПБ (петабайта) памяти. К сожалению, как мы видели выше, 64-разрядная Windows ограничивает объем виртуальной памяти 256 ТБ. Кроме того, вызовы commit и decommit не бесплатны.

Но мы можем поступить и другим способом, воспользовавшись тем, что в одну физическую страницу может отображаться несколько виртуальных. Обычно это используется для совместного использования памяти разными процессами. Но можно использовать и в рамках одного процесса. Чтобы использовать этот подход для кольцевого буфера, создадим несколько страниц сразу после кольцевого буфера (ring buffer), указывающих на одну и ту же физическую память.

Кольцевой буфер (ring buffer) с маппингом страниц.Кольцевой буфер (ring buffer) с маппингом страниц.

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

void write(ring_buffer_t *rb, uint8_t *p, uint64_t n){    memcpy(rb->data + (rb->written % BUFFER_SIZE), p, n);    rb->written += n;}uint8_t *read(ring_buffer_t *rb, uint64_t n){    uint8_t *p = rb->data + (rb->read % BUFFER_SIZE);    rb->read += n;    return p;}

Это намного лучше, но мы по-прежнему используем тот же объем физической памяти.

Обратите внимание, что настройка такой схемы размещения в памяти может быть немного запутанной. В Windows нужно создать отображение файлов в виртуальную память с помощью CreateFileMapping(). Да, даже если никакие файлы на диске не задействованы, все равно нужно использовать "отображение файла", потому что совместно виртуальная память используется именно так. Но поскольку файл на диске нам не нужен, то для дескриптора файла используется INVALID_HANDLE_VALUE, создающий отображение в файл подкачки. Затем мы используем MapViewOfFileEx(), чтобы настроить отображение на две области памяти. К сожалению, нет никакого способа гарантировать, что переданные области памяти будут доступны. Мы можем зарезервировать их, а затем освободить непосредственно перед вызовом MapViewOfFileEx(), но все равно остается промежуток времени, когда, если нам очень не повезет, кто-то другой может прийти и выделить что-то в этом пространстве адресов. Возможно, нам придется повторить попытку отображения несколько раз, прежде чем оно будет успешным. Но после этого мы можем использовать буфер, не беспокоясь ни о чем.

Если вы знаете какие-нибудь изящные трюки с виртуальной памятью, не упомянутые здесь, пишите мне в твиттере в @niklasfrykholm.

Дополнения про Linux

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

По умолчанию Linux допускает "некоторый" избыточный commit (определяется эвристически). Потому что если вы отключите overcommit, то будете тратить много памяти зря, поскольку процессы выделяют фактически не используемую память. С другой стороны, если вы разрешите слишком большой overcommit, вы можете столкнуться с ситуацией, когда процесс успешно выделил память, но при попытке получить к ней доступ, он не сможет этого сделать, потому что у системы не будет физической памяти. Для предотвращения таких ситуаций приходит OOM killer и убивает некоторые процессы.

Вы можете настроить систему, чтобы разрешить неограниченный overcommit (vm.overcommit_memory = 1) или указать ограничение (vm.overcommit_memory = 2). См. https://www.kernel.org/doc/Documentation/vm/overcommit-accounting

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

Это можно реализовать так же, как в Windows: разделить операции резервирования (reserve) и подтверждения (commit). Резервирование памяти не зависит от параметра overcommit_memory.

По документации mmap() это не совсем очевидно, но виртуальную память в Linux можно зарезервировать через вызов mmap() с PROT_NONE. После этого commit зарезервированной памяти можно сделать, используя системный вызов mprotect().

Примечание Использование MAP_NORESERVE вместо PROT_NONE не работает, когда overcommit_memory = 2, поскольку в этом случае флаг MAP_NORESERVE игнорируется. См. https://lwn.net/Articles/627557/


Перевод статьи подготовлен специально для будущих студентов курса "Программист С".

Также приглашаем всех желающих зарегистрироваться на открытый онлайн-вебинар: "ООП на C: пишем видеоплеер".

Подробнее..

Идеальное хэширование

11.01.2021 16:10:01 | Автор: admin

Какова сложность поиска элемента по ключу?

Это зависит от того, какую структуру данных использовать.

В односвязном списке - линейная сложность.

В отсортированном массиве или в двоичном дереве поиска - логарифмическая сложность.

В хэш-таблице - сложность константная. Но это в лучшем случае. А в худшем стремится к линейной

А можно ли создать идеальную хэш-таблицу, чтобы сложность поиска элемента даже в худшем случае оставалась константной?

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

Идеально! Поговорим о том, как это сделать.
Кстати, на курсе "Алгоритмы и структуры данных" на платформе OTUS у нас есть отдельный модуль, посвящённый вопросам создания хэш-таблиц.


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

Взял, такой, хэш-код слова dog, вычислил хэш-функцию, поколдовал ещё немножко, и, вуаля, в руках уже собака на поводке указателе!

Как такое возможно?

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

Если же ограничиваться простейшей хэш-функцией вида (A * k + B) % N, с возможностью подбора лишь двух коэффициентов A и В, то создать волшебную биекцию вряд ли удастся - коллизий не избежать. В этой формуле k - уникальный хэшкод (ulong) уникального ключа, N - количество ключей.

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

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

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

Первый этап

Минимизация количества комнат в квартирах.

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

Например, за 100 итераций подбора коэффициентов для словаря из 10.000 ключей максимальное количество коллизий для одного элемента не превышает 10.

Итак, на первом этапе мы подбираем глобальные коэффициенты А и В для хэш-функции вида (A * k + B) % N. Эта функция вычисляет номер квартиры, в которой находится значение для заданного ключа. При этом минимизируется максимальное количество комнат (коллизий) в каждой квартире.

Второй этап

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

Вот и всё!

Для поиска любого элемента в идеальной хэш-таблице необходимо выполнить следующие действия:

  1. Найти номер квартиры i по хэш-коду ключа k: (A * k + B) % N.

  2. Взять коэффициенты для вычисления номера комнаты: Ai, Bi, K.

  3. Найти номер комнаты по формуле: (Ai * k + Bi) % K

  4. Вернуть значение из найденной комнаты.

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

Расход памяти

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

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

Итого: для хранения квартир нужно N ячеек памяти, для хранения комнат - примерно 1.9 * N. Суммарный расход памяти: 2.9 * N = О(N) - линейный.

Заключение

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


А теперь небольшой бонус. 15 января я проведу Demo Day курса "Алгоритмы и структуры данных" на платформе OTUS, приглашаю всех желающих узнать подробно о курсе и задать вопросы лично.

Также хочу посоветовать еще несколько статей из своего блога, которые уже успели получить хороший отклик у читателей:

Подробнее..

Kubernetes или с чего начать чтобы понять что это и зачем нужно

12.01.2021 18:23:11 | Автор: admin

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


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

Давайте вспомним как работают контейнеры на примере Docker. Основные понятия это namespaces, cgroups, UnionFS, RunC.

Namespaces это про изолирование окружения и про то что каждый контейнер работает со своими namespaceами и namespace закрывается, если PID 1 умер.

  • pid : Изоляция процессов (PID: Process ID)

  • net : Изоляция сетей (NET: Networking)

  • ipc : Изоляция IPC (IPC: InterProcess Communication)

  • mnt : Изоляция файловой системы (MNT: Mount)

  • uts : Изоляция UTS (UTS: Unix Timesharing System)

  • user: Изоляция пользователей

Control groups позволяет контейнерам использовать общие ресурсы и ограничить набор доступных ресурсов.

Union File Systems дает разделение по слоям и возможность их переиспользования.

А что же такое слои?

Управление образами, например в Docker, происходит с помощью внешнего драйвера хранилища. Есть несколько поддерживаемых драйверов - AUFS, BTRFS и наложения.

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

Сразу заметим что порядок важен. При добавлении файла на один уровень и удаления его на другом уровне стоит делать это в правильном порядке. Docker например отслеживает каждый уровень. У образа могут быть десятки слоев, но предел есть и он равен 127.


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

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

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

Оркестрация это:

  • управление кластером хостов;

  • планирование и распределение задач;

  • автоматизация.

А теперь от оркестрации плавно перейдем к тому по какой же концепции DevOps будет происходить модель обслуживания.


Одна из основных концепций DevOps - это <Cattle, NOT pets>.

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

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

Потребность в автоматизации и координации настройки и развертывания привела к появлению таких инструментов, как Salt Stack, Ansible и Terraform. Эти инструменты дали разработчикам и DevOps инженерам возможность программно развертывать инфраструктуру, необходимую для поддержки ваших приложений. Этот процесс теперь известен как Инфраструктура как код.

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

Управление конфигурацией vs оркестрация контейнеров

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

Ansible и Terraform также являются обязательными навыками.

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

Все это и более детальное рассмотрение архитектуры и основных концепций Kubernetes, на равне с такими интересными темами как кластеризация, highload web, администрирование СУБД, виртуализация и контейнеризация, оркестрация вы сможете изучить на курсе Administrator Linux. Advanced.

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

Ну и как же без такого важного вопроса как хранение данных, мониторинг и Kubernetes secrets Hashicorp Vault.

А прямо сейчас мы приглашаем всех желающих на бесплатный демо-урок по теме "Кластерная файловая система Lustre". В рамках урока рассмотрим архитектуру и компоненты файловой системы Lustre. Разберем области применения файловой системы и ее особенности. Ответим на вопросы как используется file striping и что такое сетевой транспортный уровень LNET. На практической части установим и сконфигурируем файловую систему вручную. Посмотрим пример работы графической пользовательского интерфейса Integrated Manager for Lustre (IML)

Подробнее..

Kubernetes или с чего начать, чтобы понять что это и зачем он нужен

13.01.2021 14:06:06 | Автор: admin

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





Kubernetes
был создан Google на основе собственного опыта работы с контейнерами в производственной среде, и своим успехом он во многом обязан именно Google.

Так что же такое Kubernetes и для чего мы в принципе хотим использовать именно его, а не обычные контейнеры, например Docker.

Давайте вспомним что такое контейнеры.

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

Все было бы отлично, но есть одно но - container runtime API (API среды запуска контейнера) хорошо подходит для управления отдельными контейнерами, но совершенно не подходит для управления приложениями на сотне контейнеров и на большом количестве хостов.

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

Вот для такого и нужен Kubernetes.

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

Kubernetes по сути является не просто системой оркестрации. Технически оркестрация это про выполнение определенного рабочего процесса: сначала сделай A, затем B, затем C.

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

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

Сделать это только с помощью контейнеров не получится. А вот в Kubernetes это можно достичь с помощью Pods (подов).

Pod (под) - это группа из одного или более контейнера с общим хранилищем/сетевыми ресурсами и спецификацией как запускать контейнеры. Так же это отдельный инстанс приложения. Размещая контейнеры таким образом, Kubernetes устраняет соблазн втиснуть слишком много функций в один образ контейнера.

Концепция Service (Сервисы) в Kubernetes используется для группирования нескольких подов, которые выполняют те же функции. Сервисы легко настраиваются для таких целей как обнаружение, горизонтальное масштабирование и балансировка нагрузки.

Kubernetes, согласно официальной документации, так же сможет предоставить вам:

  • Используя имя DNS или собственный IP-адрес мониторинг сервисов и распределение нагрузки Kubernetes может обнаружить контейнер. При высоком трафике в нем Kubernetes сбалансирует нагрузку и распределить сетевой трафик так, что развертывание будет стабильным.

  • Система хранения по вашему выбору (например, локальное хранилище, провайдеры общедоступного облака и многое другое) может быть автоматически смонтирована с помощью оркестрации хранилища Kubernetes.

  • Автоматическое развертывание и откаты.

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

  • Автоматическое распределение нагрузки.

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

  • Самоконтроль.

    Если в работе контейнеров что-то пошло не так, то Kubernetes сам перезапускает, заменяет и завершает работу контейнеров, которые не проходят проверку работоспособности.

  • Управление конфиденциальной информацией и конфигурацией.
    Пароли, OAuth-токены и ключи SSH могут храниться и управляться Kubernetes без изменений образов контейнеров и не раскрывая конфиденциальную информацию в конфигурации стека.

Как видим на рисунке, это наглядная демонстрация того что есть внутри Kubernetes на примере одной мастер ноды (Master node) и одной воркер ноды (Worker node).

На Master node находится Kubernetes Control Plane (kube-scheduler, kube-controller-manager, kube-apiserver, etcd), с помощью которой происходит управление всем кластером Kubernetes.

На Worker node находятся container runtime (среда запуска контейнера), kubelet и kube-proxy.

Сontainer runtime это то на чем будет запущен ваш Под (например Docker, Container D, Rocket и т.д.).

Kubelet это основной агент узла, который работает на каждой ноде. Гарантирует, что контейнеры в Pod(поде)работают и исправны. Не управляет контейнерами, которые не были созданы Kubernetes.

Kube-proxy это демон на каждой ноде, управляет правилами iptable на хосте для достижения балансировки нагрузки службы (одна из реализаций) и следит за изменениями Service и Endpoint.

Более детальное рассмотрение архитектуры, основных концепций Kubernetes в теории и главное на практике, наравне с такими интересными темами как кластеризация, highload web, администрирование СУБД, виртуализация и контейнеризация, оркестрация вы сможете изучить на курсе Administrator Linux. Advanced.

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

Ну и как же без такого важного вопроса как хранение данных, мониторинг и Kubernetes secrets Hashicorp Vault.

А прямо сейчас мы приглашаем всех желающих на бесплатный демо-урок по теме "Кластерная файловая система Lustre". В рамках урока рассмотрим архитектуру и компоненты файловой системы Lustre. Разберем области применения файловой системы и ее особенности. Ответим на вопросы как используется file striping и что такое сетевой транспортный уровень LNET. На практической части установим и сконфигурируем файловую систему вручную. Посмотрим пример работы графической пользовательского интерфейса Integrated Manager for Lustre (IML)

Подробнее..

Linux Experiments LAB

22.12.2020 18:11:09 | Автор: admin

Будущих студентов курса "Administrator Linux.Basic" и всех интересующихся приглашаем на открытый урок по теме "Bash. Написание простых скриптов".

Автор статьи: эксперт OTUS - Александр Колесников.



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

Описание стенда

Стенд для тестирования подсистем ОС будет состоять из:

  • Средства виртуализации VirtualBox

  • Операционной системы Windows 8

  • Операционной системы Kali Linux (Debian)

  • Виртуальной машины GNS3, которая будет использоваться для эмуляции оборудования для настройки VLAN.

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

Настройка стенда

Для нормальной работы GNS3 необходимо использовать VM, которую можно скачать на официальном сайте. Именно на ней будут эмулироваться устройства, которые могут быть развернуты из официальных образов операционных систем устройств, например CISCO. Основные проблемы с конфигурации стенда проводятся в системе GNS3. Следующие этапы стоит произвести перед началом работы:

  1. Открыть Настройки и перейти в раздел Server:

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

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

3. Необходимо добавить прошивку устройства, найти тестовые прошивки можно в сети. Добавить в интерфейс их можно через раздел IOS Routers.

4. Отключить все виртуальные машины и выбрать опцию Not Attached для сетевого интерфейса.

5. Создаем схему сети, как указано в разделе Описание стенда.

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

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

Для настройки устройств необходимо ввести следующие команды:

Операционная система Windows:

1.Запустить операционную систему

2.Установить через любой удобный интерфейс ip адрес 10.0.20.3

Операционная система Kali Linux:

1.Запустить операционную систему, установить ip адрес 10.0.3.5

EtherSwitch1:

- vlan database

- vlan 20

- exit

- conf t

-(int fa0/0) порт Kali

- no shut

- exit

- int fa0/1

- switchport mode trunk

- no shut

- exit

- exit

-write

EtherSwitch2:

- vlan database

- vlan 20

- exit

- conf t

- int fa0/0 (порт между свичами)

- switchport mode trunk

- no shut

- exit

- int fa0/1(порт для Windows 8)

- switchport mode access

- switchport access vlan 20

- no shut

- exit

- exit

- write

  1. Запустить команду Ping 10.0.20.3 из операционной системы Kali Linux. Необходимый результат показан на снимке ниже:

Почему нет ответа от целевого хоста? Причины как минимум две:

1.Нет маршрута до сети, где находится хост.

2.Сеть использует VLAN, что накладывает специальные условия на разметку трафика. По умолчанию, они не работают для отправляемых данных.

Можно ли отправлять пакеты в соседние VLAN с использованием подсистем, которые есть по умолчанию в операционной системе Linux, в данном случае Debian? Спойлер можно.

Эксперимент с сетевыми устройствами

Для дальнейшего проведения тестирования воспользуемся инструментом, который называется yersinia. Базово, этот инструмент используется для работы с протоколами сети. Его используют для тестирования неверной конфигурации устройств. Мы воспользуемся его функционалом по отправке пакетов с дополнительными тегами для VLAN сетей. Для его запуска придется установить его в операционную систему Kali Linux. Для установки достаточно набрать в терминале:

apt-get update && apt-get install yersinia

Наша импровизированная сеть использует в качестве протокола для VLAN - 802.1q. Этот протокол предполагает, что в заголовке пакетов будет указан специальный Тег, который будет использоваться для маршрутизации. Попробуем имитировать поведение протокола, если бы мы были клиентом целевой виртуальной сети. Для этого запустим команду:

yersinia dot1q -attack 1 -source 08:00:27:1f:30:76 -dest FF:FF:FF:FF:FF:FF -vlan1 0001 -priority1 07 -cfi1 0 -l2proto1 800 -vlan2 0020 -priority2 07 -cfi2 0 -l2proto2 800 -ipsource 10.0.20.6 -ipdest 10.0.20.3 -ipproto 1 -payload LINUXOTUS -interface eth0

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

В результате выбора откроется WireShark:

И можно будет увидеть весь трафик на любом отрезке сети. Кстати, вот и наш payload:

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

modprobe 8021q Загрузить модуль ядра для работы с инкапсуляцией

vconfig add eth0 1 добавить виртуальный интерфейс

ifconfig eth0.1 up

vconfig add eth0.1 20 добавить еще один виртуальный интерфейс

ifconfig eth0.1.20 10.0.20.6 netmask 255.255.255.0 up

ip route add 10.0.20.0/24 via 10.0.20.6 dev eth0.1.20 сконфигурировать маршрут до целевой сети

arp -s 10.0.20.3 FF:FF:FF:FF:FF:FF -i eth0.1.20 небольшой лайфхак, чтобы ОС посчитала, что у нее есть информация об удаленной машине.

Стоит упомянуть, что подобное взаимодействие в реальных условиях возможно только в UDP формате. Для TCP нужно будет угадывать идентификаторы, которые используются для проведения handshake процедуры. Попробуем провести отправку чего-то осмысленного на целевой хост. Для этого на хосте развернем netcat:

А с машины Kali Linux посредством инструмента для работы с сетевыми пакетами Scapy. Отправим UDP сообщение: LINUXOTUSNC:

Просмотрим данные, которые передавались в трафике:

Что в итоге отобразилось на целевой машине:

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


Узнать подробнее о курсе "Administrator Linux.Basic"

Записаться на открытый урок по теме
"Bash. Написание простых скриптов".

ЗАБРАТЬ СКИДКУ

Подробнее..

Обход ограничений терминала

11.01.2021 16:10:01 | Автор: admin

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

Как уменьшить количество команд?

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

  1. Софт, который позволяет задать стандартный захардкоженный набор ограничений;

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

К первой категории можно отнести следующие шеллы:

rbash

rzsh

rksh

Ко второй категории относятся:

lshell

rssh

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

Попробуем протестировать приложения для ограничения доступных команд. В качестве базовой системы возьмем Kali Linux на базе ОС Debian 5.9. Первым инструментом для исследования возьмем rbash, найти базовую информацию касательно его работы можно вот здесь. Запускается rbash достаточно просто:

  1. Создадим директорию для тестирования и перейдем в нее: mkdir test; cd test

  2. Запустим команду rbash в директории test. Для нее будут применены все стандартные ограничения, описанные в документации, в том числе и на выполнение команды cd. Проверим:

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

Попробуем использовать другое приложение rzsh. Его настройка такая же, как и у предыдущего подопытного. Любопытный факт запускать rzsh или устанавливать его дополнительно не нужно, если есть zsh: достаточно указать при запуске флаг -r. Список ограничений можно найти здесь.

Запустим ограничение на той же директории, которую создали в прошлом эксперименте и так же постараемся выйти в другую директорию:

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

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

Какие существуют методы обхода ограничений?

При использовании описанных выше приложений или их аналогов можно задаться резонным вопросом: насколько безопасно так ограничивать терминал? Может ли злоумышленник обойти выставленные ограничения? Для ответа на эти вопросы можно попробовать применить стандартные этапы исследования систем или приложений на безопасность, то есть провести самостоятельный пентест (мысли, как преступник):

  1. Сбор информации (Enumiration)

  2. Создание сценариев атаки

  3. Реализация атаки

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

  1. Какой именно шелл используется для настройки ограничений?

  2. Какие команды поддерживает шелл?

  3. Какое наименование имеет операционная система?

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

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

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

В нашем случае сам терминал при попытке ввода команды рассказал, что для ограничений запущен rbash:

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

2. Сценарий атаки. Как следует поступить для выполнения команды перехода cd? Любая система или механизм сдается, если попытаться протестировать функции, объединяющие несколько подсистем или отдельных блоков ОС, поскольку не всегда возможно программно обеспечить корректное взаимодействие между ними. В нашем случае выполнение команды cd это запуск процесса в операционной системе. Попробуем это сделать за счет дополнительных утилит. Даже при наличии ограничений от rbash у юзера есть возможность пользоваться безобидными приложениями, например vim:

3. Реализация атаки. Для проведения атаки можно попытаться использовать функционал vim, который может передавать команды в ОС: !.

В открытом файле vim вводим последовательность команд: :!bash.

В итоге получаем доступ к терминалу без ограничений:

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

Вывод

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


Статья подготовлена экспертом OTUS - Александром Колесниковым в преддверии старта курса Administrator Linux. Professional.

Приглашаем всех желающих записаться на бесплатный demo-урок Методы и возможности отладки скриптов оболочки Bash.

Подробнее..

Перевод Когда QA-специалист становится профессионалом в игровой индустрии?

21.12.2020 16:08:05 | Автор: admin

Крис Бьюик ветеран индустрии QA и член экспертного совета ассоциации Game Global. Он расскажет о работе в отделах обучения и повышения квалификации, а также об их роли в управлении QA-командами на проектах аутсорсинга.

5 причин, почему отдел обучения и повышения квалификации необходим для современного QA на аутсорсинге

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

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

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

Вот пять причин, почему, как я считаю, современный аутсорсинг QA не может существовать без отдела обучения и повышения квалификации (англ. Learning and Development, L&D).

1.Мышление консультанта

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

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

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

Без отдела L&D компании может быть сложно адаптироваться к изменениям на рынке QA, усваивать все его тонкости и постоянно предоставлять клиентам гибких специалистов по контролю качества.

2.Развитие партнерских отношений

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

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

Если клиент хочет, чтобы QA-команда участвовала в ежедневных SCRUM-собраниях, отдел L&D должен убедиться, что все сотрудники знают этот метод. Кроме того, L&D несет ответственность за налаживание связей с потенциальными клиентами. Сотрудники этого отдела изучают особенности культуры клиента и его подходы к работе, а затем обучают им других сотрудников и включают в будущие программы повышения квалификации.

3.Деловая зрелость

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

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

Необходимо создавать инструменты по индивидуальным требованиям и курировать работу с ними.Без поддержки со стороны отдела L&D эти процессы остаются в ведении руководителей QA-команд, а у них не всегда есть достаточно времени и опыта, чтобы помогать коллегам развивать деловую зрелость.

4. Взаимоуважение

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

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

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

5.Здоровая иерархия

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

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

Без отдела L&D сила иерархии и способность компании удерживать ценные кадры могут очень быстро ослабнуть, а путь к восстановлению после такого упадка может занять месяцы. Когда поддержке и обслуживанию иерархии уделяется особое внимание, она остается зрелой, и компания может справляться с самыми трудными ситуациями.

Об авторе

Крис Бьюик (Chris Bewick),

Региональный директор QA в Евразии, компания Keywords Studios

Крис Бьюик работает в индустрии игр и интерактивных развлечений более 19 лет. Сейчас Крис региональный руководитель подразделения FQA, он работает над формированием и совершенствованием отделений Keywords Studios в Европе и Азии. Ранее Крис Бьюик занимался развитием услуг по контролю качества в варшавском и лондонском офисах компании Testronics, разработал индивидуальное решение для сертификации консоли Microsoft Xbox One, был менеджером по соблюдению требований в Electronic Arts и зарекомендовал себя в отрасли, проработав 12 лет в Babel Media, где высшей точкой в его карьере стала позиция руководителя подразделения FQA-тестирования в Нью-Дели (Индия).


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

-Python QA Engineer

-Java QA Engineer

-Game QA Engineer

-QA Engineer (Базовый курс)

Подробнее..

Перевод Новые задачи из мира непрерывной доставки

30.12.2020 18:06:06 | Автор: admin

Для будущих студентов курса "QA Lead" и всех интересующихся подготовили перевод интересного материала.

Также приглашаем принять участие в открытом уроке по теме:
"Организация тестирования при различных методологиях разработки".


Непрерывная доставка и развертывание (с общей аббревиатурой CD) далеко не новые концепции. Десять лет назад Джез Хамбл и Дэвид Фарли опубликовали книгу Continuous Delivery. Патрик Дебуа в 2009 году организовал конференцию DevOpsDays и создал хэштег #DevOps (также пишется как devops, devOps или Devops).

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

Тестирование

Сессии DeliveryConf, на которых я была (или позже смотрела на YouTube-канале), помогли мне увидеть больше примеров практических кейсов, в которых технология непрерывной доставки меняет представление о тестировании.

Автоматизация сложных работ или тяжелого труда не новая идея, однако выступление Джессики Керр открыло мне новые перспективы. Ниже я приведу несколько примеров того, как можно использовать автоматизацию, чтобы освободить больше вашего времени для важных дел:

  • Автоматизация для сокращения времени на подтверждение или проверки доступности машины.

  • Автоматизация таких вещей, как процедуры, инструменты, получение учетных данных, процесс проверки PR.

  • Развертывание сред тестирования в облаке для каждого нового изменения.

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

Преобразование пайплайнов

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

Стив Перейра в своем выступлении рекомендовал составить карту потока создания ценности вашего пайплайна, чтобы выявить узкие места и сократить петли обратной связи. Его команда создала карту в Google Sheets, чтобы каждый член команды мог посмотреть на нее. Они оценили те шаги, которые выполняются вручную, автоматические шаги, время на передачу и время ожидания. Они выполняли один шаг в единицу времени. В конце концов, им удалось сократить время, необходимое для запуска пайплайна с четырех недель до четырех дней!

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

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

Команды, которые уже какое-то время занимаются CI/CD скорее всего имеют legacy пайпланы те, которые уже существуют какое-то время. Обычно они плохо документированы, и никто не понимает их от начала до конца, они даже могут оказаться крайне нестабильными. На DeliveryConf Лаура Сантамария поделилась своими переживаниями об устаревании пайплайнов. Она также рекомендовала создать карту потока создания ценности для пайплайна с помощью любой документации, которую вы сможете найти, включая все, что записано в системе отслеживания инцидентов. Так можно лучше понять пайплайн и найти способы его стабилизации, улучшения и документирования.

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

Нетрадиционные программные приложения

Эмили Горченски рассказывала о непрерывной доставке в контексте машинного обучения (ML), о шаблонах и распространенных проблемах. Мои знания ML весьма поверхностны, однако мне было очень интересно. Я и не подозревала, что у моделей машинного обучения есть срок годности.

Эмили объясняла, что системы, управляемые данными, недетерминированы, что затрудняет их тестирование и совершенствование. У них сложный критерий приемки и они нелинейны, их можно расценивать как черный ящик. Даже небольшие изменения могут иметь непредсказуемый эффект в неожиданном месте. Границы определяются крайне тяжело. Она говорила следующее: Разработка, управляемая данными, это действительно сложно, и мы все сейчас разбираемся в этом недостаточно хорошо.. Если коротко, то ML и CD в связке все еще работают тяжко. Звучит немного обескураживающе, однако для работы над этим требуется больше экспериментов.

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

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

Меньше слов, больше дела

Самый сильный посыл DeliveryConf заключается в том, что нужно на самом деле двигаться к CD хотя бы маленькими шажками. Сосредоточьтесь на самом важном для ускорения работы. Экспериментируйте, визуализируйте эти эксперименты, измеряйте свой прогресс. CD можно внедрить даже в legacy-системы мы слышали историю о том, как кто-то внедрил CD в приложение по сборке VAX, которому уже 45 лет!

Меня поразил акцент на маленьких шагах и экспериментах. Те же мысли мы слышим на конференциях, посвящённых тестированию и Agile-разработке. DevOps это устранение силосов в организации. Что случится, если мы будем усердно работать над устранением силосов в сообществах? Мы сможем многому научиться друг у друга. Есть множество виртуальных конференций, посвящённых DevOps, например, DivOps, Failover Conf, и All Day DevOps. Используйте эти возможности для обучения!

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

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


Узнать подробнее о курсе "QA Lead".

Записаться на вебинар по теме: "Организация тестирования при различных методологиях разработки".

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

Кстати, о "красивой упаковке" онлайн-сертификатов мырассказываем в этой статье.

ЗАБРАТЬ СКИДКУ

Подробнее..

Перевод Багрепорты могут быть простыми

21.12.2020 14:15:23 | Автор: admin

Привет, Хабр. В преддверии старта курса "QA Engineer" приглашаем записаться на открытый урок на тему "Как правильно составлять баг репорт".

Также предлагаем вашему вниманию перевод короткой полезной статьи.


Меня недавно спросили, могу ли я поговорить с некоторыми из моих коллег о работе с багами. В частности, меня спросили, могу ли я объяснить, как выглядит хороший багрепорт и какую информацию он может содержать. Под прицелом оказалась команда Halaxy Service. Я восхищаюсь людьми, которые работают в службе поддержки, это работа может быть тяжелой, и когда у вас возникают трудные клиенты или ситуации, вы находитесь под изрядным давлением. Я работал на этой должности (в другой компании), временами это может быть очень сложно. При этом сотрудники Halaxy Service нечто особенное. Их взаимопонимание с клиентами Halaxy превосходит все, что я когда-либо видел или слышал.

У меня было 10-15 минут на выступление. Не так много времени, чтобы по-настоящему вникнуть, тем более, что люди, с которыми я разговаривал, не были специалистами по тестированию. Моя цель состояла в том, чтобы передать некоторые ключевые моменты в понятных терминах, которые можно было бы извлечь и использовать. Я решил, что обсуждение разницы между сбоем (fault), ошибкой (error) и отказом (failure) не влезает в это обсуждение. Точно так же не было времени раскрывать эвристику как концепцию, но я кратко рассказал о важности согласованности. Чего мы хотели достигнуть так это того, чтобы люди из команды поддержки могли писать багрепорты достаточно детализированные, чтобы четко определить контекст и облегчить дальнейшее исследование и решение проблемы другим участникам разработки (включая всех членов команды, а не только разработчиков).

Я сформировал структуру на 3-х ключевых моментах при регистрации бага на основе взаимодействия с клиентами. Три простых вопроса, на которые следует полагаться при создании багрепортов. Что произошло? Как это произошло? Что должно было произойти? Давайте рассмотрим каждый из них по очереди.

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

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

Что должно было произойти полезно знать не только, в чем проблема, но и почему наш клиент считает, что это проблема. Это преследует несколько целей. Во-первых, это дает нам представление о том, чего хотят наши клиенты. Это может быть просто: Я бы хотел, чтобы он работал так, как когда я делал то же самое два дня назад. Это также может быть обсуждение из разряда Я хочу Х, а получаю Y. В обоих примерах независимо от того, основана ли обратная связь клиентов на непреднамеренном ли изменении результата или на предполагаемом (наш клиент ошибается или документация клиента допускает двоякое толкование), мы понимаем, как наш клиент видит эту часть нашей системы. Это важно как для исследования и решения задач, так и для помощи в управлении ожиданиями клиентов, если позже нам понадобится объяснить, почему различия между желаемым и фактическим отражают то, как функциональность должна выполняться на бизнес-уровне.

Немного поразмыслив после моей короткой презентации, мне пришло в голову, что я мог бы включить четвертый пункт Каковы последствия? Это полезная информация, которая помогает нам определить, насколько быстро мы хотим решить эту проблему и как нам с ней справляться. Я знаю, что когда что-то серьезное проходит через нашу команду поддержки, об этом очень быстро сообщается, и команда разработчиков (опять же, включая тестировщиков) концентрируется вокруг проблемы и потенциальных решений. Тем не менее, полезно фиксировать отражение на бизнесе как часть подробных сведений об ошибке, независимо от того, является ли влияние большим, незначительным или где-то посередине.

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


Узнать подробнее о курсе "QA Engineer"

Записаться на открытый урок на тему "Как правильно составлять баг репорт".

ЗАБРАТЬ СКИДКУ

Подробнее..

Перевод Как малоизвестный британский производитель ПК изобрел ARM и изменил мир

23.12.2020 16:22:50 | Автор: admin

Acorn Archimedes 1987 года стала первой серией персональных компьютеров на базе RISC.

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

Телефоны и большая часть нашего цифрового окружения работают на семействе процессоров ARM. Apple выпустил совершенно новую линейку компьютеров Mac на базе своего нового процессора M1 процессора на базе ARM и эти машины получают фантастические отзывы. Поэтому самое время напомнить о странных корнях этих микросхем, распространившихся по всему миру.

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

Но что, если вместо этого вы решили бы сделать так, чтобы все эти процессоры были созданы малоизвестной компанией из страны, которая, как правило, не первая, которая приходит на ум в качестве мирового лидера в области высокотехнологичных инноваций (по крайней мере не с 1800-х годов)? А что, если бы этот ЦПУ был обязан своим существованием, по крайней мере косвенно, образовательному телешоу? Скорее всего, продюсеры посоветуют вам немного отмотать сценарий Давай же, отнесись к этому серьезно. И все же почему-то на самом деле это именно так.

Вначале было телевидение

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

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

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

Итак, BBC обратилась с призывом к молодой компьютерной индустрии Великобритании, в которой тогда доминировала компания Sinclair, которая сделала свое состояние на калькуляторах и крошечных телевизорах. В конечном итоге прибыльный контракт получила гораздо меньшая молодая компания Acorn Computers.

Расцвет Acorn

Acorn, компания родом из Кембриджа, начала свою деятельность в 1979 году после разработки компьютерных систем, изначально предназначенных для работы игровых автоматов, которые затем превратила в небольшие любительские компьютерные системы на базе процессоров 6502. Это было то же семейство процессоров, которое среди многих других использовалось в компьютерах Apple II, Atari 2600 и Commodore 64. Дизайн этого процессора станет важным позже.

Acorn разработала домашний компьютер под названием Atom, и когда появилась эта возможность от BBC, они начали планы по развитию преемника Atom в BBC Micro.

Список необходимых фичей от BBC гарантировал, что получившаяся машина будет достаточно мощной для той эпохи, хотя и не такой мощной, как оригинальная разработка Acorn преемника Atom. Этот преемник Atom имел бы два процессора, проверенный временем 6502 и еще не определившийся 16-разрядный процессор.

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

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

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

Послушайте, что рассказывает один из ключевых инженеров:

BBC Micro оказался большим успехом для Acorn, став доминирующим компьютером для образовательных целей в Великобритании в 1980-х годах. Каждый читатель этой статьи наверняка знает, что 80-е годы были очень важным временем в истории компьютеров. Персональный компьютер от IBM был выпущен в 1981 году, установив стандарт для ПК на десятилетия вперед. ПК Apple Lisa в 1983 году предвосхитила Mac и революцию графического пользовательского интерфейса окна-значки-мышь, который будет доминировать в будущем.

Acorn видел, как происходят эти разработки, и понял, что им понадобится что-то более мощное, чем стареющий, но надежный 6502, для питания своих будущих машин, если они хотят конкурировать. Acorn экспериментировал с множеством 16-битных процессоров: 65816, 16-битным вариантом 6502, Motorola 68000, на котором установлен Apple Macintosh, и сравнительно редким National Semiconductor 32016.

Однако ни один из них на самом деле не выполнял свою работу, и Acorn обратилась к Intel с просьбой внедрить процессоры Intel 80286 в их новую архитектуру. Intel их проигнорировала.

RISCованный бизнес

Спойлер: это окажется очень плохим решением для Intel.

Затем компания Acorn приняла судьбоносное решение разработать собственный процессор. Вдохновленный бережливым производством Western Design Center (компания, которая разрабатывала новые версии 6502) и исследованиями нового типа концепции проектирования процессоров, называемых компьютер с сокращённым набором команд (англ. Restricted (reduced) Instruction Set Computer, сокращ. RISC), Acorn решила двигаться вперед. Инженеры Стив Фербер и Софи Уилсон оказались ключевыми участниками проекта.

Теперь процессоры RISC называются так, как они называются по сравнению с процессорами CISC (англ. complex instruction set computing или complex instruction set computer, сокращ.). Попытаюсь дать очень упрощенное объяснение того, что это на самом деле означает.

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

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

Компания Acorn хорошо подходила для разработки процессора RISC, поскольку чип 6502, с которым они были хорошо знакомы, часто называют своего рода прототипом дизайна RISC. Интернет полон всевозможных мнений по этому поводу, и я не собираюсь порождать болезненный и утомительный спор, но хотя бы в благодарность этой статье просто поверьте мне, что 6502 обладает по крайней мере некоторыми чертами, очень похожими на RISC.

Новый чип Acorn был настолько RISC-подобен, что Софи Уилсон, разрабатывая набор инструкций для нового процессора Acorn, кажется, явно вдохновлялась рядом концепций дизайна 6502.

Используя интерфейс BBC Micro Tube в качестве испытательного стенда, новый процессор на базе RISC, разработанный Acorn, был назван Acorn RISC Machine, или ARM. Поставщик микросхем Acorn, компания VLSI, начала производить процессоры ARM, в первую очередь для внутреннего R&D Acorn. Вскоре после этого была готова серийная версия ARM2.

В 1987 году был представлен первый серийный ПК на базе RISC, Acorn Archimedes, работающий на процессоре ARM2. ARM показал лучшую производительность, чем Intel 286, несмотря на то, что в нем на 245 000 транзисторов меньше, чем у большого чипа Intel.

Archimedes с его ОС Arthur в ПЗУ (постоянное запоминающее устройство) оказался гибкой, быстрой и мощной машиной. У него была хорошая графика для того времени, графический пользовательский интерфейс, а также несколько крутых и быстрых низкополигональных демонстраций и игр, которые демонстрировали скорость машины благодаря ее процессору.

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

Меньше значит больше

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

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

Низкое энергопотребление и низкое тепловыделение сделали ARM подходящим для мобильных устройств, поэтому в конце 1980-х Apple начала искать процессор, достаточно мощный, чтобы (часто смешно) переводить рукописный текст в текст и запускать графический интерфейс, при этом питаясь от батареек типа АА. Карманное устройство, которое они хотели использовать, было печально известным Newton, и только быстрое и компактное ядро ARM могло питать его.

Apple и партнер Acorn по микросхемам VLSI заключили партнерство с Acorn, чтобы выделить подразделение ARM в свою собственную новую компанию под названием Advanced RISC Machines, что позволило сохранить название ARM. В рамках этого альянса, при добавлении значительных ресурсов Apple, ARM разработает ядро ARM6, при этом процессор ARM610 станет первым производственным чипом, основанным на этом ядре, а в версии с частотой 20 МГц будет использоваться для Apple Newton в 1993 году.

Хотя, конечно, Newton был своего рода впечатляющим провалом, в ретроспективе он станет чем-то гораздо большим: портативным устройством с сенсорным экраном на батарейках и процессором ARM. Сегодня то же самое описание можно использовать, чтобы описать миллиарды смартфонов, которые постоянно используются по всему миру. Впервые оно было испытано в полевых условиях с устройством, которое большинство людей помнит из того эпизода Симпсоны, где оно преобразовало рукописную фразу Ударь Мартина ("Beat up Martin") в Съешь Марту ("Eat up Martha")

ARM610 станет питанием нового поколения компьютеров Acorn Archimedes и странного ноутбука на базе Ньютона под названием eMate. В 2001 году 7-ядерный процессор ARM будет работать на iPod от Apple и игровой консоли Game Boy Advance от Nintendo. В 2004 году пара ARM будет управлять двумя экранами Nintendo DS.

Затем, в 2007 году, Apple выпустит первый iPhone с 11-ядерным процессором ARM. С этого момента все помешаются на ARM.

Процессоры ARM стали выбором по умолчанию для смартфонов, будь то Apple или что-либо другое. Процессоры ARM были в каждой думающей машине, кроме настольных компьютеров, ноутбуков или серверов на базе Intel. Теперь, с Chromebook и новыми настольными компьютерами и ноутбуками Apple MacOS на базе ARM, похоже, что ARM, наконец, вернется туда, откуда все начиналось к настольному компьютеру.

Так много лет спустя история происхождения ARM остается достойной рассказа, потому что она настолько невероятна; это такая странная, незапланированная последовательность событий из неожиданных источников. Несмотря на то, что сейчас этот процессор абсолютно доминирует в мире, скромные начинания делают его менее бесчувственным гигантом индустрии, чем, скажем, почти биополия (от монополия) Intel / AMD.

Можно воспользоваться моментом и задуматься: поскольку британцы чувствовали свое отставание от компьютерной революции, они решили снимать телешоу о компьютерах. Для этого им был нужен компьютер, и одна не самая прорывная компания создала его. И когда этой маленькой компании потребовалось создать более быстрый процессор, потому что Intel не потрудилась ответить на их звонки, они сделали свой собственный. Просто так случилось, что этот процессор не потребляет много энергии и не выделяет много тепла, что и привлекло внимание Apple, которая стала его использовать. После чего эта компания, конечно же, захватила мир.

Если бы я это выдумал, вы бы сказали, что я слишком извернулся или что посмотрел слишком много фильмов Уэса Андерсона. Но это реальность.

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


Реклама которая может быть полезна

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

Кстати, о "красивой упаковке" онлайн-сертификатов мырассказываем в этой статье.

ЗАБРАТЬ СКИДКУ

Подробнее..

(Не)обычный CustDev. Лайфхаки по тестированию продуктов

28.12.2020 20:06:16 | Автор: admin

Привет, Хабр. Для будущих студентов курса "Product Manager IT-проектов" и всех интересующихся подготовили авторскую статью. Также приглашаем записаться на открытый вебинар по теме "CustDev для самой быстрой проверки идеи".


Что такое проблемные интервью?

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

Чтобы провести беседу, нужен сценарий

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

Для выяснения отношения собеседника к проблеме можно использовать скрипт под ключ или вопросы типа:

  • Случалось ли вам ?

  • Попадали ли вы в ситуацию ?

  • Как часто с вами происходит ?

  • Когда вы последний раз оказывались в ситуации ?

  • Вас беспокоит ?

  • Как на вашу жизнь влияет ?

Примеры развернутых вопросов:

  • Можете вспомнить случай, когда вы оказывались в ситуации ?

  • Расскажите подробнее, как было в последний раз?

  • Можете рассказать, как вы тогда поступили?

  • Расскажите подробнее, как вы решали эту проблему?

  • Вы всегда так поступаете?

  • В других похожих случаях как вы поступали?

  • Как вы решаете эту проблему сейчас?

  • Какие трудности у вас вызывает это решение?

  • Что вас не устраивает в нынешнем решении?

  • Почему вы поступили именно так?

  • Какие еще варианты вы рассматривали?

  • Каких результатов вы ранее добивались, решая подобные проблемы?

  • Что происходило, когда вы не решали эту проблему?

  • К каким трудностям это приводило?

  • К каким расходам это приводило?

  • Что вы теряли в той ситуации?

  • Сколько времени или денег вы тратили на решение этой проблемы?

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

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

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

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

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

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

Основы основ

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

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

  • Это не количественный тест, а качественный. Не нужно нанимать много людей и создавать репрезентативную картину. Главное понять могут ли люди справиться с заданием и устраивает ли продуктовую команду, как это происходит (мучительно или не очень). Достаточно заказать 3-4 теста, чтобы гарантированно получить один содержательный или обратить внимание на несколько эпизодов из разных тестов.

  • Не обязательно искать тестера именно из своей ЦА. Подходит почти любой уверенный пользователь ПК, по крайней мере для нашего продукта. Если продукт изначально очень сложный, то кейс должен быть простым. Например, для сложной мед программы: найти куда загрузить данные эксперимента, запустить обсчет и получить результат, далее сохранить в файл.

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

Про поиск дополнительных источников выручки и точек роста

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

Создание экосистемы для удержания клиента

Все мировые лидеры, будь то Facebook или Apple, уже давно идут в сторону экосистем, замыкая клиентов внутри себя. У нас это направление успешно развивают Яндекс и Сбер. Когда в рамках одной системы и одного id можно и заказать еду, и вызвать такси, и кино посмотреть, клиенту намного сложнее соскочить с продуктов компании. За счет таких расширенных предложений наращивается дополнительное конкурентное преимущество. Делать ставку только на одну услугу сегодня слишком рискованно.

Повышение маржинальности бизнеса

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

Как работать с командами и искать гипотезы роста

Работая с внутренними продуктами в компаниях, мы обычно полагаемся на возможный потенциал продукта в течение определенного периода времени. Например, можно ли за 3-5 лет получить доход в миллиард рублей? Чтобы найти и развить гипотезы роста с потенциалом дохода в сотни миллионов рублей, нужно пройти несколько важных этапов.Собирайте отзывы от потенциальных клиентов. Чтобы отойти от рыночных требований, а не от возможностей компании, необходимо выявить наличие ценности в продукте. Можно взять интервью у клиентов, проанализировать отзывы, рассчитать коэффициент НПС (отношение положительных оценок к негативам). Проанализируйте продажи. Если процесс продаж уже запущен, вам необходимо собрать данные, проанализировать воронку продаж и ключевые показатели продукта. Затем ответить на вопрос-что может быть рычагом в процессе продажи, как их увеличить? Множественный рост происходит в моменты продвижения с партнерами, он собрал воронку между каналами и запустил нового пользователя на борту.декомпозиции в интернете. Разработка стратегии на рынок. Это включает в себя позиционирование, ценообразование, каналы продаж, определение соответствия продукта ожиданиям целевой аудитории, продвижение по службе, корректировку от конкурентов. Внутри компаний, безусловно, есть свои особенности. Во многих случаях серьезным конкурентным преимуществом внутренних продуктов было бы наличие материнской компании, которая уже имеет знания о клиентах, облегчая аудиторию, сегментацию и таргетинг.

Увеличьте компетенции, мотивируйте команду. Любая работа выполняется людьми. Внутри компании обычно не более 5% сотрудников с предпринимательским мышлением, которые могут производить и продавать новые продукты. Но им нужно постоянно изучать новые навыки в управлении продуктами, им нужно быть мотивированными и давать достаточную свободу для развития.

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

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


Узнать подробнее о курсе "Product Manager IT-проектов".

Записаться на открытый вебинар по теме "CustDev для самой быстрой проверки идеи".


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

Кстати, о "красивой упаковке" онлайн-сертификатов мырассказываем в этой статье.

ЗАБРАТЬ СКИДКУ

Подробнее..

Смелее в Новый год

31.12.2020 12:04:32 | Автор: admin

Хабровчане, принимайте наши поздравления с наступающим 2021! Берегите себя, чтобы не перегореть, пытаясь и дедлайны закрыть, и к новогодней ночи все приготовить. Может быть, вам важно это услышать, но в этот раз точно можно встретить его по-простому. Да вот просто кинуть комочком гирлянду, пусть горит.

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

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

Можно без упаковки, в обычном пакете-майке.

Можно просто по чашкам, без всяких бокалов. Зато самое вкусное из запасов. Чтобы чокаться смачно с размаху, заливая салаты.

Можно без каблуков. Без парада, в любимой толстовке, халате, той самой футболке, которая стала за год почти второй кожей.

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

Можно не слушать этот год был трудным для всех нас. Не считать бой курантов.

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

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

Можно в смех и слезы от облегчения, и недоверчивое Он точно кончился?.

Мы желаем каждому найти то, что сейчас вам нужнее всего. Свою цель. Новые возможности. Работу мечты. Любовь. Смелость и силы действовать. Друзей. Пропавший любимый носок. Решение проблемы. Надежду.

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

Ждем вас отдохнувшими после праздников осваивать новые вершины мастерства.

С Новым годом и Рождеством!


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

Кстати, о "красивой упаковке" онлайн-сертификатов мырассказываем в этой статье.

ЗАБРАТЬ СКИДКУ

Подробнее..

IT-гороскоп 2021

03.01.2021 12:17:37 | Автор: admin

Хабровчане, с наступившим! 2020 наконец позади, можно выдохнуть. Наступление нового года дает повод вспомнить о своих устремлениях и пожеланиях, разложить всё по полочкам. Время ставить себе амбициозные цели и воплощать смелые мечты, или решить просто спокойно пожить без лишнего напряжения и преодоления вызовов. Даже если вы не верите в астрологию, звезды помогут вам в делах, и новый год будет лучше предыдущего, вот увидите. Отыщите свой знак зодиака и получайте персональное предсказание, а возможно, и подарок от OTUS.


Энергичный Овен

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


Благородный Телец

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


Яркие Близнецы

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


Многогранный Рак

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


Деятельный Лев

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


Дева-интеллектуал

Практичные и проницательные девы точно знаю, чего хотят и как этого добиться. Они хорошенько поработали над своими планами на ближайшие пять лет даже точно смогут ответит на это вопрос рекрутерам. Жизнь, конечно, штука непредсказуемая (почти как поведение фронтенда сайта в IE) и вносит свои коррективы но девам точно понравится тот неожиданный поворот событий, который может произойти. Дева, словно вода, удивительно гармонично подстраивает свои мечты и цели под реальные обстоятельства. Можно с уверенностью сказать, что 2021 год дева проведет в полной внутренней гармонии.


Талантливые Весы

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


Целеустремлённый Скорпион

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


Стремительный Стрелец

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


Жизнелюбивый Козерог

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


Любознательный Водолей

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


Творческие Рыбы

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

Подарок от OTUS

Друзья, мы продлеваем новогодние скидки на все курсы до конца каникул. Ознакомиться с полным списком курсов вы можете по ссылке ниже.

ЗАБРАТЬ СКИДКУ

Подробнее..

Категории

Последние комментарии

© 2006-2021, personeltest.ru