Ad-social Bot

Smmok Bot

Vkserfing Bot

Vkstorm Bot

Vktarget Bot

Все программы

Запись опубликована: 23.02.2018

Привет, меня зовут Pavel Germanika

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


 

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

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

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

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

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

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

Любой из вас может связаться со мной. Я рад каждому! Пишите на мой Telegram.

Метки: ,





Запись опубликована: 14.11.2018

TelegramBot в облаке Wolfram

Введение

Прошел тот период, когда каждая вторая статья на Habrahabr была посвящена написанию своего телеграмм-бота. Также прошел период времени, когда бота без трудностей можно было разместить на своем компьютере или хостинге в России. Еще полгода назад мой бот запускался просто на ноутбуке и не испытывал никаких проблем с подключением к API. Но сейчас, когда я задумался над тем, чтобы вернуть его в работу, я понял, что это будет не так легко. Не хотелось искать и настраивать прокси-сервер и тем более за рубежом. Также до этого я писал бота на Wolfram Language и не имел представления о том, как язык работает с прокси-серверами, так как до сих пор их не использовал. И тут появилась замечательная идея! Использовать Wolfram Cloud. В этой статье я хочу показать, как очень просто с регистрацией, но без смс можно запустить своего простого телеграм-бота, написанного на Wolfram Language. Из инструментов понадобится для этого только браузер.

Немного про облако Wolfram

Чтобы получить доступ к облаку необходимо создать аккаунт Wolfram. Для этого нужно перейти по адресу https://account.wolfram.com и следовать инструкциям после нажатия на кнопку Create One.

После всех проделанных манипуляций на главной странице облака по адресу https://www.wolframcloud.com будут отображаться все продукты и их планы использования. Необходимо выбрать Development Platform и создать новый блокнот.

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

Совсем немного о телеграмм-ботах

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

/newbot

Дальше просто необходимо следовать инструкциям и ввести имя и логин. Пусть его имя будет Wolfram Cloud Bot и логин @WolframCloud5973827Bot.

Реализация API

Воспользуемся рекомендациями @BotFather и бегло осмотрим HTTP API телеграм-ботов. Задачи по реализации всего API целиком пока не стоит. Для написания бота достаточно только небольшой части. Проверим, что API доступно и бот с указанным выше токеном существует. Для этого достаточно выполнить всего одну строчку:

URLExecute["https://api.telegram.org/bot753681357:AAFqdRFN_QoODJxsBy3VN2sVwWTPKJEqteY/getMe"]

Out[..] := …

{"ok" -> True,
 "result" -> {"id" -> 753681357, "is_bot" -> True,
   "first_name" -> "Wolfram Cloud Bot",
   "username" -> "WolframCloud5973827Bot"}}

Команда выше — самый простой способ выполнить HTTP запрос из Wolfram Language. Но немного усложним его, чтобы было легко реализовать все остальные методы API. Создадим общий метод выполнения запроса к API:

TelegramBot::usage = "TelegramBot[token]";
$telegramAPI = "https://api.telegram.org";
telegramExecute[
    TelegramBot[token_String], method_String,
    parameters: {(_String -> _)...}: {}
] := Module[{
    request, requestURL, requestRules, requestBody,
    response, responseBody
},
    requestURL = URLBuild[{$telegramAPI, "bot" <> token, method}];
    requestRules = DeleteCases[parameters, _[_String, Automatic | Null | None]];
    requestBody = ImportString[ExportString[requestRules, "JSON"], "Text"];
    request = HTTPRequest[requestURL, <|
        Method -> "POST",
        "ContentType" -> "application/json; charset=utf-8",
        "Body" -> requestBody
    |>];
    response = URLRead[request];
    responseBody = response["Body"];
    Return[ImportString[responseBody, "RawJSON"]]
]

Проверим работает ли это на уже протестированном выше методе:

token = "753681357:AAFqdRFN_QoODJxsBy3VN2sVwWTPKJEqteY";
bot = TelegramBot[token];
telegramExecute[bot, "getMe"]

Out[..] := …

<|"ok" -> True,
 "result" -> <|"id" -> 753681357, "is_bot" -> True,
   "first_name" -> "Wolfram Cloud Bot",
   "username" -> "WolframCloud5973827Bot"|>|>

Отлично. Создадим отдельно функцию для выполнения проверки бота:

  • getMe — информация о боте

getMe::usage="getMe[bot]";
TelegramBot /:
getMe[bot_TelegramBot] :=
telegramExecute[bot, "getMe"]
getMe[bot]

Out[..] := …

<|"ok" -> True,
 "result" -> <|"id" -> 753681357, "is_bot" -> True,
   "first_name" -> "Wolfram Cloud Bot",
   "username" -> "WolframCloud5973827Bot"|>|>

Теперь подобным образом осталось добавить основные методы, которые необходимы для создания бота в облаке:

  • getUpdates — получает все последние сообщения написанные боту

getUpdates::usage = "getUpdates[bot, opts]";
Options[getUpdates] = {
    "offset" -> Automatic,
    "limit" -> Automatic,
    "timeout" -> Automatic,
    "allowed_updates" -> Automatic
};
TelegramBot /:
getUpdates[bot_TelegramBot, opts: OptionsPattern[getUpdates]] :=
telegramExecute[bot, "getUpdates", Flatten[{opts}]]

  • setWebhook — устанавлиевает адрес серевера для обработки обновлений

setWebhook::usage = "setWebhook[bot, url, opts]";
Options[setWebhook] = {
    "certificate" -> Automatic,
    "max_connections" -> Automatic,
    "allowed_updates" -> Automatic
};
TelegramBot /:
setWebhook[bot_TelegramBot, url_String, opts: OptionsPattern[setWebhook]] :=
telegramExecute[bot, "setWebhook", Join[{"url" -> url}, Flatten[{opts}]]]

deleteWebhook::usage = "deleteWebhook[bot]";
TelegramBot /:
deleteWebhook[bot_TelegramBot] :=
telegramExecute[bot, "deleteWebhook"]

getWebhookInfo::usage = "getWebhookInfo[bot]";
TelegramBot /:
getWebhookInfo[bot_TelegramBot] :=
telegramExecute[bot, "getWebhookInfo"]

  • sendMessage — отправка сообщения в чат

sendMessage::usage = "sendMessage[bot, chat, text]";
Options[sendMessage] = {
    "parse_mode" -> Automatic,
    "disable_web_page_preview" -> Automatic,
    "disable_notification" -> Automatic,
    "reply_to_message_id" -> Automatic,
    "reply_markup" -> Automatic
};
TelegramBot /:
sendMessage[bot_TelegramBot, chat_Integer, text_String,
    opts: OptionsPattern[sendMessage]] :=
telegramExecute[
    bot, "sendMessage",
    Join[{"chat_id" -> chat, "text" -> text}, Flatten[{opts}]]
]

Минимальная версия API готова. Проверим как работает отправка сообщение и получение обновлений. Для этого создадим чат с нашим ботом. При создании боту отправится первое сообщение с тектом /start. Посморим — попало ли оно в список обновлений:

updates = getUpdates[bot]

Out[..] := …

<|"ok" -> True,
 "result" -> {<|"update_id" -> 570790461,
    "message" -> <|"message_id" -> 1,
      "from" -> <|"id" -> 490138492, "is_bot" -> False,
        "first_name" -> "Kirill", "last_name" -> "Belov",
        "username" -> "KirillBelovTest"|>,
      "chat" -> <|"id" -> 490138492, "first_name" -> "Kirill",
        "last_name" -> "Belov", "username" -> "KirillBelovTest",
        "type" -> "private"|>, "date" -> 1542182547,
      "text" -> "/start",
      "entities" -> {<|"offset" -> 0, "length" -> 6,
         "type" -> "bot_command"|>}|>|>}|>

Получить из списка обновлений данные последнего обновления можно так:

lastUpdate = updates["result"][[-1]]

Out[..] := …

<|"update_id" -> 570790461,
 "message" -> <|"message_id" -> 1,
   "from" -> <|"id" -> 490138492, "is_bot" -> False,
     "first_name" -> "Kirill", "last_name" -> "Belov",
     "username" -> "KirillBelovTest"|>,
   "chat" -> <|"id" -> 490138492, "first_name" -> "Kirill",
     "last_name" -> "Belov", "username" -> "KirillBelovTest",
     "type" -> "private"|>, "date" -> 1542182547, "text" -> "/start",
   "entities" -> {<|"offset" -> 0, "length" -> 6,
      "type" -> "bot_command"|>}|>|>

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

chat = lastUpdate["message", "chat", "id"]
text = lastUpdate["message", "text"]

Out[..] := …

490138492
/start

Как видно из результат выполнения — все на месте. Теперь отправим сообщение от имени бота используя sendMessage.

sendMessage[bot, chat, "hello"]

Out[..] := …

<|"ok" -> True,
 "result" -> <|"message_id" -> 2,
   "from" -> <|"id" -> 753681357, "is_bot" -> True,
     "first_name" -> "Wolfram Cloud Bot",
     "username" -> "WolframCloud5973827Bot"|>,
   "chat" -> <|"id" -> 490138492, "first_name" -> "Kirill",
     "last_name" -> "Belov", "username" -> "KirillBelovTest",
     "type" -> "private"|>, "date" -> 1542182601, "text" -> "hello"|>|
 >

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

Создание webhook

В Wolram Langauge есть специальный вид функций, которые создаются с помощью APIFunction. Вот пример одной из таких:

apiFunc = APIFunction[{"n" -> "Integer"}, Plot[Sin[#n * x], {x, -2Pi, 2Pi}]&, "PNG"];
apiFunc[{"n"->3}]

Out[..] := …

Такие функции предназначены специально для развертывания в облаке. Данная функция будет принимать на вход один параметр запроса. Чтобы развернуть ее в облаке достаточно передать саму функцию в CloudDeploy.

apiObject = CloudDeploy[apiFunc, "Deploy/apiObject"]

Out[..] := …

CloudObject[https://www.wolframcloud.com/objects/kirillbelovtest/apiObject]

Затем можно перейти по полученной ссылке в браузере и добавить параметр запроса:

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

deployWebhook[bot_TelegramBot, handler_] :=
    CloudDeploy[APIFunction[{}, handler[HTTPRequestData["Body"]] &],
        "Deploy/Webhooks/" <> Hash[bot, "SHA", "HexString"],
        Permissions -> "Public"
    ]

handler — другая функция обработчик. Пусть обработчик превращает строку тела запроса в ассоциацию, получает оттуда идентификатор чата и высылает обратно слово «hello».

handlerHello[bot_TelegramBot][body_String] :=
    Block[{json = ImportString[body, "RawJSON"], chat},
        chat = json["message", "chat", "id"];
        sendMessage[bot, chat, "hello"];
    ]

Теперь развернем фунцкию в облаке.

webhookObject = deployWebhook[bot, handlerHello[bot]]

Out[..] := …

CloudObject[https://www.wolframcloud.com/objects/kirillbelovtest/Deploy/Webhooks/b9bd74f89348faecd6b683ba02637dd4d4028a28]

И последний шаг — передадим адрес этого объекта телеграм-боту.

setWebhook[bot, webhookObject[[1]]]

Out[..] := …

<"ok" -> True, "result" -> True, "description" -> "Webhook was set">

Теперь напишем что-нибуд боту и посмотрим что он ответит:

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

Логика ответов

Это будет последняя часть в процессе создания бота в облаке Wolfram. Дальше таким же образом можно усложнять логику и добавлять новые методы API. Теперь о самом диалоге. Пусть, после отправки команды /start бот возвращает ответ «Привет» и меняет клавиатуру пользователя. В клавиатуре остается всего две кнопки: «Привет» и «Кто ты?». Реализуем диалог в виде ассоциации. Ключами будут команды, которые высылает пользователь боту. Значения ключей — сам ответ бота и новая клавиатура. При этом множество ключей и кнопок должны полностью совпадать. Иначе может появиться ситуациция, когда бот не знает что ответить. В таких случаях, конечно, можно добавить ответ по умолчанию.

keyboard[buttons : {__String}] :=
 {"keyboard" -> {Table[{"text" -> button}, {button, buttons}]},
  "resize_keyboard" -> True}
$answers = <
    (*user_text-><"answer"->bot_text,"keyboard"->next_text>*)
    "/start"-><"answer"->"Привет","keyboard"->
        keyboard[{"Привет","Кто ты?"}]>,
    "Привет"-><"answer"->"Как дела?",
        "keyboard" -> keyboard[{"А твои?"}]> ,
    "А твои?"-><"answer"->"Нормально",
        "keyboard" -> keyboard[{"Назад"}]> ,
    "Кто ты?"-><"answer"->"Бот написанный на Wolfram Language специально для статьи",
        "keyboard"->keyboard[{"Какая статья?","Кто автор?"}]> ,
    "Какая статья?"-><"answer"->"вот ссылка на нее:nhttps://habr.com/post/422517/",
        "keyboard"->keyboard[{"Назад","Кто автор?"}]> ,
    "Кто автор?"-><"answer"->"Вот этот пользователь:n@KirillBelovTest",
         "keyboard"->keyboard[{"Какая статья?","Назад"}]> ,
    "Назад"-><"answer"->"Привет",
        "keyboard"->keyboard[{"Привет","Кто ты?"}]>
>;
answer[text_String] /; KeyExistsQ[$answers, text] := $answers

Теперь создадим обработчик:

handlerAbout[bot_TelegramBot][body_String] :=
    Block[{json = ImportString[body, "RawJSON"], chat, text},
        chat = json["message", "chat", "id"];
        text = json["message", "text"];
        sendMessage[bot, chat, answer["answer"],
            "_markup" -> answer["keyboard"]];
    ]

И выполним повторно развертывание облачного объекта:

deployWebhook[bot, handlerAbout[bot]];

Приверим, что получилось в чате с ботом. Но для начала очистим историю сообщений:

Расширение функциональности

Пока что принципиальных отличий от огромного множества уже существующих ботов нет. Может и смысла в его напиании тоже нет? Смысл всей проделанной выше работы будет, если понять в чем собственно преимущества такого бота! Ведь он может использовать все возможности Wolfram Language и Wolrfam Cloud. Необходимо, чтобы робот умел решать уравнения? Это очень легко! Надо всего-лишь доопределить ответ!

answer[text_String]["keyboard"] /;
  StringContainsQ := Automatic
answer[text_String]["answer"] /; StringContainsQ :=
    ToString[Flatten[Block[{args = StringSplit},
        Solve[ToExpression[args[[1]]], ToExpression[args[[2]]]]
    ]]]
deployWebhook[bot, handlerAbout[bot]];

Если у кого-то дополнительно появится интерес к возможностям облака — то хорошее описание его функциональности есть здесь.

Ограничения

Wolfram Cloud — платформа, которая позволяет использовать язык Wolfram бесплатно, в то время как основной продукт компании Wolfram Research — Mathematica стоит денег. Соответственно на использование есть ограничения и на мой взгяд они очень сильные. При использовании бесплатной версии Development Platform пользователю в месяц выдается 1000 облачных кредитов. Каждый облачный кредит дает время на вычисление различного типа. Так как в статье говоиртся про CloudDeploy + APIFunction — то такие объекты, хранящиеся в облаке тратят 1 кредит за 0.1 секунды вычислительного времени. Несложно подсчитать, что пользователю бесплатно выдается всего 1 минута и 40 секунд серверного времени на работу своего приложения (в данном случае бота). Мне здесь нечего добавить — это очень и очень мало. Основной упор на пользователей, которые работают в Development Platform самостоятельно с помощью браузера. Ведь в таком режиме нет никаких ограничений по времени, а только по длительности сессии и выделяемым ресурсам. При таком использовании Development Platform — это почти полноценная Mathematica, но не требующая установки и лицензии.

Статья в Wolfram Cloud

Tags:

Похожие публикации

Source: habr1

Метки:





Запись опубликована: 14.11.2018

Apple временно закрывает сервис Search Ads для российских разработчиков из-за «налога на Google»

  • Новость
image

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

К сожалению, пока неясно, когда сервис снова можно будет использовать — он закрыт на неопределенный срок. Корпорация объясняет свои действия изменениями в налоговом законодательстве России, которые станут актуальными с 1 января 2019 года.

1 января те иностранные компании, которые сотрудничают с российскими партнерами, должны встать на учет в ФНС, самостоятельно рассчитывая и выплачивая НДС с продаж в России. В настоящий момент это обязанность заказчиков.

Закрытие сервиса Search Ads повлияет на доходы и расходы отечественных iOS-разработчиков. Инструмент от Apple был эффективным и недорогим средством прорекламировать свое приложение. Теперь придется платить больше, например, из-за необходимости обращаться в агентства, которые предоставляют соответствующие услуги.

Пруф по приостановке Apple Search Ads для компаний из России. Временно, но в преддверии новогодних праздников мало приятного. pic.twitter.com/5c71F3uwTU

— Sergey Pakhandrin (@pakhandrin) November 13, 2018

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

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

Tags:

Source: habr1

Метки:





Запись опубликована: 14.11.2018

Apple: сопроцессор Т2 может блокировать DIY-ремонт новых MacBook и MacMini

Последнее мероприятие Apple, на котором были представлены обновленные модели MacBook Air и Mac mini, прошло с обычной для компании торжественностью. Оба устройства, равно, как и MacBook Pro и iMac Pro, показанные ранее, оснащены чипом Т2. Чип, по словам представителей корпорации, повышает уровень безопасности пользовательских данных. Т2, собственно, является сопроцессором, который «специализируется» на информационной безопасности.

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

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

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

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

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

Что касается «права на ремонт», то о блокировании возможности отремонтировать устройство своими силами Apple рассказала в одном из документов, которые попали в руки журналистов. Согласно этому документу, Т2 не даст загрузиться системе, если заменена, например, материнская плата или иной компонент. Ремонт провести можно только со специальным облачным ПО, доступ к которому есть лишь у сотрудников Apple и авторизованных центров. Сейчас известно, что компания против самостоятельной замены пользователями дисплея ноутбука, материнской платы, TouchID платы, флеш-накопителя. Скорее всего, не получится решить проблему с поломкой материнской платы, после введения в работу специализированного протокола с отремонтированным сторонним лицом или компанией ноутбуком работать будет нельзя.

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

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

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

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

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

Tags:

Похожие публикации

Source: habr1

Метки:





Запись опубликована: 14.11.2018

Перевод книги Эндрю Ына «Страсть к машинному обучению» Главы 28 — 29

предыдущие главы

Кривые обучения

28 Диагностирование смещения и разброса: Кривые обучения

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

image

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

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

Добавим желаемый уровень качества на нашу кривую обучения:
image

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

image

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

29 График ошибок обучения

Ошибки на валидационной (и тестовой) выборках должны уменьшаться по мере увеличения тренировочной выборки. Но на тренировочной выборке ошибка при добавлении данных обычно растет.

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

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

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

Давайте добавим график ошибок обучения к нашим предыдущим

image

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

Дальше давайте обсудим, как интерпретировать эти графики.

продолжение следует

Tags:

Похожие публикации

Source: habr1

Метки:





Запись опубликована: 14.11.2018

OpenSceneGraph: сборка из исходников и Hello World

  • Tutorial

Введение

OpenSceneGraph (далее OSG) — открытый кроссплатформенный фреймворк, написанный на C++ и представляющий собой графический движок, предоставляющий программисту объектный интерфейс к OpenGL. В нашей стране этот движок не особенно популярен, даже на Хабре я видел только одну более-менее приличную публикацию о нем. OSG применяется за рубежом много где, например он является основой для свободного авиасимулятора FlightGear, существует открытая реализация игры Morrowind, называемая OpenMW разработка которой так же перенесена на OSG с движка Ogre. Русскоязычной документации по нему исчезающе мало, а среди англоязычной можно отметить лишь серию книг от разработчиков: OpenSceneGraph 3.0. Beginner’s Guide и OpenSceneGraph 3. Cookbook.

Тем не менее, движок достаточно интересен по следующим причинам:

  1. Открытая кроссплатформенная реализация на C++.
  2. Модульная архитектура.
  3. Расширяемость за счет встроенной системы плагинов.
  4. Возможность многопоточной обработки графических данных и встроенный инструментарий для её реализации
  5. Управление динамической памятью через механизм умных указателей

Думаю, что читателям Хабра будет интересно более подробно ознакомится с этим проектом. Не лишним будет и пополнение русскоязычной базы знаний по OSG. Все материалы, которые будут публиковаться мной по данной теме основаны на книге OpenSceneGraph 3.0. Beginner’s Guide, но являются не её переводом, а скорее творческой переработкой изложенного там материала. Если вам интересна данная тема, прошу под кат

Единственным верным способом получить самую свежую версию OSG на своей машине — собрать библиотеку из исходных текстов. Существующий бинарный инсталлятор для Windows ориентируется на компилятор MS Visual C++. Мне же, для своих проектов необходимо использование компилятора GCC, вернее его варианта MinGW32, входящего в поставку средств разработки фреймворка Qt. Таким образом нам понадобится:

  1. Установленный и настроенный фреймворк Qt с компилятором MinGW32 версии 5.3 и IDE QtCreator
  2. Клиент Git для Windows
  3. Утилита сmake для Windows

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

1. Получение исходных текстов OpenSceneGraph

Создаем на своем жестком диске каталог, где будем производить сборку OSG, например по пути D:OSG

Перейдем в этот каталог и стянем туда исходники с официального репозитория OSG на Github

D:OSG> git clone https://github.com/openscenegraph/OpenSceneGraph.git

Длительность процесса скачивания зависит от того, насколько широк ваш канал доступа в Интернет. Рано или поздно мы получим у себя локальную копию репозитория OSG.

Скачав исходники создадим рядом каталог build-win32-debug

В этом каталоге мы будем осуществлять сборку отладочного комплекта OSG. Но прежде

2. Настройка cmake

Для корректной работы cmake нам следует отредактировать файл путь-установки-cmakesharecmake-3.13ModulesCMakeMinGWFindMake.cmake. По-умолчанию он выглядит так

# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.
find_program(CMAKE_MAKE_PROGRAM mingw32-make.exe PATHS
  "[HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsCurrentVersionUninstallMinGW;InstallLocation]/bin"
  c:/MinGW/bin /MinGW/bin
  "[HKEY_CURRENT_USERSoftwareCodeBlocks;Path]/MinGW/bin"
  )
find_program(CMAKE_SH sh.exe )
if(CMAKE_SH)
  message(FATAL_ERROR "sh.exe was found in your PATH, here:n${CMAKE_SH}nFor MinGW make to work correctly sh.exe must NOT be in your path.nRun cmake from a shell that does not have sh.exe in your PATH.nIf you want to use a UNIX shell, then use MSYS Makefiles.n")
  set(CMAKE_MAKE_PROGRAM NOTFOUND)
endif()
mark_as_advanced(CMAKE_MAKE_PROGRAM CMAKE_SH)

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

# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.
find_program(CMAKE_MAKE_PROGRAM mingw32-make.exe PATHS
  "[HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsCurrentVersionUninstallMinGW;InstallLocation]/bin"
  c:/MinGW/bin /MinGW/bin
  "[HKEY_CURRENT_USERSoftwareCodeBlocks;Path]/MinGW/bin"
  )
#find_program(CMAKE_SH sh.exe )
#if(CMAKE_SH)
#  message(FATAL_ERROR "sh.exe was found in your PATH, here:n${CMAKE_SH}nFor MinGW make to work correctly sh.exe must NOT be in your path.nRun cmake from a shell that does not have sh.exe in your PATH.nIf you want to use a UNIX shell, then use MSYS Makefiles.n")
#  set(CMAKE_MAKE_PROGRAM NOTFOUND)
#endif()
mark_as_advanced(CMAKE_MAKE_PROGRAM CMAKE_SH)

3. Сборка и установка отладочной и релизной версий движка

Теперь запускаем командный интерпретатор cmd, ярлык на который находится по пути Пуск->Программы->Qt->Qt 5.11.2->Qt 5.11.2 for Desktop (MinGW 5.3.0 32bit)

Запущенный сеанс командной строки настраивает всё окружение, необходимое для работы средств сборки mingw32. Переходим в каталог с исходниками OSG

C:QtQt5.11.2.11.2mingw53_32>D:
D:> cd OSGbuild-win32-debug

Даем команду

D:OSGbuild-win32-debug>cmake -G "MinGW Makefiles" -DCMAKE_INSTALL_PREFIX=E:AppsOSG -DCMAKE_BUILD_TYPE=DEBUG ../OpenSceneGraph

Разберем смысл параметров подробнее:

  • -G «MinGW Makefiles» — указывает, что необходимо сгенерировать Makefile для утилиты mingw32-make
  • -DCMAKE_INSTALL_PREFIX=E:AppsOSG — устанавливаем путь, по которому будет установлен OSG
  • -DCMAKE_BUILD_TYPE=DEBUG — указывает, что следует собирать отладочную версию движка.

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

Выхлоп cmake при настройке сборки OSG

-- The C compiler identification is GNU 5.3.0
-- The CXX compiler identification is GNU 5.3.0
-- Check for working C compiler: C:/Qt/Qt5.11.2/Tools/mingw530_32/bin/gcc.exe
-- Check for working C compiler: C:/Qt/Qt5.11.2/Tools/mingw530_32/bin/gcc.exe -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: C:/Qt/Qt5.11.2/Tools/mingw530_32/bin/g++.exe
-- Check for working CXX compiler: C:/Qt/Qt5.11.2/Tools/mingw530_32/bin/g++.exe -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Looking for pthread.h
-- Looking for pthread.h - found
-- Looking for pthread_create
-- Looking for pthread_create - found
-- Found Threads: TRUE
-- Found OpenGL: opengl32
-- Could NOT find EGL (missing: EGL_INCLUDE_DIR)
-- Checking windows version...
-- Performing Test GL_HEADER_HAS_GLINT64
-- Performing Test GL_HEADER_HAS_GLINT64 - Failed
-- Performing Test GL_HEADER_HAS_GLUINT64
-- Performing Test GL_HEADER_HAS_GLUINT64 - Failed
-- 32 bit architecture detected
-- Could NOT find Freetype (missing: FREETYPE_LIBRARY FREETYPE_INCLUDE_DIRS)
-- Could NOT find JPEG (missing: JPEG_LIBRARY JPEG_INCLUDE_DIR)
-- Could NOT find Jasper (missing: JASPER_LIBRARIES JASPER_INCLUDE_DIR JPEG_LIBRARIES)
-- Could NOT find LibXml2 (missing: LIBXML2_LIBRARY LIBXML2_INCLUDE_DIR)
-- Could NOT find ZLIB (missing: ZLIB_INCLUDE_DIR)
-- Could NOT find ZLIB (missing: ZLIB_INCLUDE_DIR)
-- Could NOT find GDAL (missing: GDAL_LIBRARY GDAL_INCLUDE_DIR)
-- Could NOT find PkgConfig (missing: PKG_CONFIG_EXECUTABLE)
-- Could NOT find CURL (missing: CURL_LIBRARY CURL_INCLUDE_DIR)
-- Trying to find DCMTK expecting DCMTKConfig.cmake
-- Trying to find DCMTK expecting DCMTKConfig.cmake - failed
-- Trying to find DCMTK relying on FindDCMTK.cmake
-- Please set DCMTK_DIR and re-run configure (missing: DCMTK_config_INCLUDE_DIR DCMTK_dcmdata_INCLUDE_DIR DCMTK_dcmimage_INCLUDE_DIR DCMTK_dcmimgle_INCLUDE_DIR DCMTK_dcmjpeg_INCLUDE_DIR DCMTK_dcmjpls_INCLUDE_DIR DCMTK_dcmnet_INCLUDE_DIR DCMTK_dcmpstat_INCLUDE_DIR DCMTK_dcmqrdb_INCLUDE_DIR DCMTK_dcmsign_INCLUDE_DIR DCMTK_dcmsr_INCLUDE_DIR DCMTK_dcmtls_INCLUDE_DIR DCMTK_ofstd_INCLUDE_DIR DCMTK_oflog_INCLUDE_DIR)
-- Could NOT find PkgConfig (missing: PKG_CONFIG_EXECUTABLE)
-- Could NOT find GStreamer (missing: GSTREAMER_INCLUDE_DIRS GSTREAMER_LIBRARIES GSTREAMER_VERSION GSTREAMER_BASE_INCLUDE_DIRS GSTREAMER_BASE_LIBRARIES GSTREAMER_APP_INCLUDE_DIRS GSTREAMER_APP_LIBRARIES GSTREAMER_PBUTILS_INCLUDE_DIRS GSTREAMER_PBUTILS_LIBRARIES) (found version "")
-- Could NOT find SDL2 (missing: SDL2_LIBRARY SDL2_INCLUDE_DIR)
-- Could NOT find SDL (missing: SDL_LIBRARY SDL_INCLUDE_DIR)
-- Could NOT find PkgConfig (missing: PKG_CONFIG_EXECUTABLE)
-- Could NOT find PkgConfig (missing: PKG_CONFIG_EXECUTABLE)
-- Could NOT find PkgConfig (missing: PKG_CONFIG_EXECUTABLE)
-- Could NOT find JPEG (missing: JPEG_LIBRARY JPEG_INCLUDE_DIR)
-- Could NOT find ZLIB (missing: ZLIB_INCLUDE_DIR)
-- Could NOT find PNG (missing: PNG_LIBRARY PNG_PNG_INCLUDE_DIR)
-- Could NOT find TIFF (missing: TIFF_LIBRARY TIFF_INCLUDE_DIR)
-- g++ version 5.3.0
-- Performing Test _OPENTHREADS_ATOMIC_USE_GCC_BUILTINS
-- Performing Test _OPENTHREADS_ATOMIC_USE_GCC_BUILTINS - Success
-- Performing Test _OPENTHREADS_ATOMIC_USE_MIPOSPRO_BUILTINS
-- Performing Test _OPENTHREADS_ATOMIC_USE_MIPOSPRO_BUILTINS - Failed
-- Performing Test _OPENTHREADS_ATOMIC_USE_SUN
-- Performing Test _OPENTHREADS_ATOMIC_USE_SUN - Failed
-- Performing Test _OPENTHREADS_ATOMIC_USE_WIN32_INTERLOCKED
-- Performing Test _OPENTHREADS_ATOMIC_USE_WIN32_INTERLOCKED - Success
-- Performing Test _OPENTHREADS_ATOMIC_USE_BSD_ATOMIC
-- Performing Test _OPENTHREADS_ATOMIC_USE_BSD_ATOMIC - Failed
-- Configuring done
-- Generating done
-- Build files have been written to: D:/OSG/build-win32-debug

говорит нам о том, что можно приступать к сборке. Даем команду

D:OSGbuild-win32-debug>mingw32-make -j9

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

По окончании сборки устанавливаем библиотеку

D:OSGbuild-win32-debug> mingw32-make install

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

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

D:OSGbuild-win32-debug>cd ..
D:OSG> mkdir build-win32-release
D:OSG>cd build-win32-release
D:OSGbuild-win32-release> cmake -G "MinGW Makefiles" -DCMAKE_INSTALL_PREFIX=E:AppsOSG ../OpenSceneGraph
D:OSGbuild-win32-release> mingw32-make -j9
D:OSGbuild-win32-release> mingw32-make install

4. Настройка переменных окружения

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

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

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

После этого процедуру установки OSG на наш компьютер можно считать оконченной.

5. Пишем Hello World в QtCreator

Знакомство с графическим движком OpenSceneGraph начнем с простейшего примера, как это обычно принято в программировании с некоего «Hello world!».

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

OSG-lessons/
	|-data/
	|-OSG-lessons/
		|
		|-hello/
			|-include/
			|-src/

В каталоге hello создадим файл hello.pro следующего содержания

Полный текст hello.pro

TEMPLATE = app
TARGET = hello
DESTDIR = ../../bin
win32 {
    OSG_LIB_DIRECTORY = $$(OSG_BIN_PATH)
    OSG_INCLUDE_DIRECTORY = $$(OSG_INCLUDE_PATH)
    CONFIG(debug, debug|release) {
        TARGET = $$join(TARGET,,,_d)
        LIBS += -L$$OSG_LIB_DIRECTORY -losgd
        LIBS += -L$$OSG_LIB_DIRECTORY -losgViewerd
        LIBS += -L$$OSG_LIB_DIRECTORY -losgDBd
        LIBS += -L$$OSG_LIB_DIRECTORY -lOpenThreadsd
    } else {
        LIBS += -L$$OSG_LIB_DIRECTORY -losg
        LIBS += -L$$OSG_LIB_DIRECTORY -losgViewer
        LIBS += -L$$OSG_LIB_DIRECTORY -losgDB
        LIBS += -L$$OSG_LIB_DIRECTORY -lOpenThreads
    }
    INCLUDEPATH += $$OSG_INCLUDE_DIRECTORY
}
unix {
    CONFIG(debug, debugrelease) {
        TARGET = $$join(TARGET,,,_d)
        LIBS += -losgd
        LIBS += -losgViewerd
        LIBS += -losgDBd
        LIBS += -lOpenThreadsd
    } else {
        LIBS +=  -losg
        LIBS +=  -losgViewer
        LIBS +=  -losgDB
        LIBS +=  -lOpenThreads
    }
}
INCLUDEPATH += ./include
HEADERS += $$files(./include/*.h)
SOURCES += $$files(./src/*.cpp)

Разберем эти письме подробнее

TEMPLATE = app
TARGET = hello
DESTDIR = ../../bin

Переменные задают шаблон проекта (app — приложение), имя исполняемого файла (hello) и каталог, куда исполняемый файл помещается после сборки.


win32 {
    OSG_LIB_DIRECTORY = $$(OSG_BIN_PATH)
    OSG_INCLUDE_DIRECTORY = $$(OSG_INCLUDE_PATH)
    CONFIG(debug, debugrelease) {
        TARGET = $$join(TARGET,,,_d)
        LIBS += -L$$OSG_LIB_DIRECTORY -losgd
        LIBS += -L$$OSG_LIB_DIRECTORY -losgViewerd
        LIBS += -L$$OSG_LIB_DIRECTORY -losgDBd
        LIBS += -L$$OSG_LIB_DIRECTORY -lOpenThreadsd
    } else {
        LIBS += -L$$OSG_LIB_DIRECTORY -losg
        LIBS += -L$$OSG_LIB_DIRECTORY -losgViewer
        LIBS += -L$$OSG_LIB_DIRECTORY -losgDB
        LIBS += -L$$OSG_LIB_DIRECTORY -lOpenThreads
    }
    INCLUDEPATH += $$OSG_INCLUDE_DIRECTORY
}

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


CONFIG(debug, debugrelease) {
    TARGET = $$join(TARGET,,,_d)
    LIBS += -L$$OSG_LIB_DIRECTORY -losgd
    LIBS += -L$$OSG_LIB_DIRECTORY -losgViewerd
    LIBS += -L$$OSG_LIB_DIRECTORY -losgDBd
    LIBS += -L$$OSG_LIB_DIRECTORY -lOpenThreadsd
} else {
    LIBS += -L$$OSG_LIB_DIRECTORY -losg
    LIBS += -L$$OSG_LIB_DIRECTORY -losgViewer
    LIBS += -L$$OSG_LIB_DIRECTORY -losgDB
    LIBS += -L$$OSG_LIB_DIRECTORY -lOpenThreads
}

Пишем сценарий для сборки в unix-подобных ОС


unix {
    CONFIG(debug, debugrelease) {
        TARGET = $$join(TARGET,,,_d)
        LIBS += -losgd
        LIBS += -losgViewerd
        LIBS += -losgDBd
        LIBS += -lOpenThreadsd
    } else {
        LIBS +=  -losg
        LIBS +=  -losgViewer
        LIBS +=  -losgDB
        LIBS +=  -lOpenThreads
    }
}

Здесь мы настраиваем имя исполняемого файла и указываем библиотеки, которые следует компоновать с нашей программой для различных вариантов сборки: как отладочной так и релизной. Отладочные библиотеки OSG имеют суффикс «d» после имени файла. Суффикс «_d» мы добавим так же и к исполняемому файлу проекта, дабы отличать отладочный вариант от релизного.


INCLUDEPATH += $$OSG_INCLUDE_DIRECTORY
INCLUDEPATH += ./include
HEADERS += $$files(./include/*.h)
SOURCES += $$files(./src/*.cpp)

Ну и наконец определяем пути поиска заголовочных файлов и файлы, включаемые в дерево проекта. Создаем в каталоге include/ пустой файл main.h, а в кталоге src/ — файл main.cpp. Открываем этот проект в QtCreator и настраиваем его так, как показано на скриншоте

После открытия проекта увидим следующую картину

Напишем такой код в файле main.h

#ifndef		MAIN_H
#define		MAIN_H
#include	<osgDB/ReadFile>
#include	<osgViewer/Viewer>
#endif

Далее реализуем основное тело программы в файле main.cpp

#include    "main.h"
int main(int argc, char *argv[])
{
    (void) argc;
    (void) argv;
    osg::ref_ptr<osg::Node> root = osgDB::readNodeFile("../data/cessna.osg");
    osgViewer::Viewer viewer;
    viewer.setSceneData(root.get());
    return viewer.run();
}

Файл с моделькой самолета необходимо скопировать в каталог data/. Этот файл, а так же многое из того, что будет использовано в данном цикле статей, можно скачать из репозитория OpenSceneGraph-Data

После компиляции и запуска мы получим что-то вроде этого

Первые две строчки нашего кода


(void) argc;
(void) argv;

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

osg::ref_ptr<osg::Node> root = osgDB::readNodeFile("../data/cessna.osg");

Потом создается экземпляр класса osgViewer::Viewer — так называемый «вьювер» — объект управляющий отображением сцены на экране. Вьюверу передаются данные сцены


viewer.setSceneData(root.get());

и запускается цикл отрисовки сцены


return viewer.run();

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

Заключение

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

Tags:

Похожие публикации

Source: habr1

Метки:





Запись опубликована: 13.11.2018

[Перевод] Еще одна причина, почему тормозят Docker контейнеры

Еще одна причина, почему тормозят Docker контейнеры

В последнем посте я рассказывал о Kubernetes, о том, как ThoughtSpot использует его для собственных нужд по поддержке разработки. Сегодня хотелось бы продолжить разговор о короткой, но от того не менее интересной истории отладки, которая произошла совсем недавно. Статья базируется на том, что containerization != virtualization. К тому же наглядно показывается, как контейнеризированные процессы конкурируют за ресурсы даже при оптимальных ограничениях по cgroup и высокой производительности машины.
image

Ранее мы запускали серии операций, связанных с разработкой b CI/CD, во внутренем кластре Kubernetes. Все бы ничего, да при запуске «докеризированного» приложения неожиданно сильно падала производительность. Мы не скупились: в каждом из контейнеров стояли ограничения по вычислительной мощности и памяти (5 CPU / 30 ГБ RAM), заданные через конфигурацию Pod. На виртуальной машине с такими параметрами все наши запросы из крошечного набора данных (10 Кб) для тестов летали бы. Однако в Docker & Kubernetes на 72 CPU / 512 ГБ RAM мы успевали запустить 3–4 копии продукта, а потом начинались тормоза. Запросы, которые раньше завершались за пару миллисекунд, теперь висели по 1–2 секунде, и это вызывало всевозможные сбои в CI-конвейере задач. Пришлось вплотную заняться отладкой.

Как правило, под подозрением — всевозможные ошибки конфигурации при упаковке приложения в Docker. Однако мы не нашли ничего, что могло бы вызвать хоть какое-либо замедление (если сравнивать с установками на голом железе или виртуальных машинах). С виду все правильно. Далее мы опробовали всевозможные тесты из пакета Sysbench. Проверили производительность ЦП, диска, памяти — все было таким же, как и на голом железе. Некоторые сервисы нашего продукта хранят подробную информацию обо всех действиях: ее потом можно использовать в профилировании производительности. Как правило, при нехватке какого-либо ресурса (ЦП, оперативной памяти, диска, сети) в некоторых вызовах отмечается значительный провал во времени — так мы обнаруживаем, что именно тормозит и где. Однако в данном случае ничего такого не произошло. Временные пропорции не отличались от исправной конфигурации — с той лишь разницей, что каждый вызов был значительно медленнее, чем на голом железе. Ничто не указывало на настоящий источник проблемы. Мы уже были готовы сдаться, как вдруг нашли вот это: https://sysdig.com/blog/container-isolation-gone-wrong/.

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

  1. Основная причина крылась в самом ядре Linux. Из-за структуры кэша-объектов dentry в ядре, поведение одного процесса сильно тормозило вызов ядра __d_lookup_loop, что прямым образом сказывалось на производительности другого.
  2. Автор использовал perf для обнаружения ошибки в ядре. Прекрасное средство отладки, которым мы никогда раньше не пользовались (а жаль!).

perf (иногда его называют perf_events или perf-инструменты; ранее был известен как Performance Counters for Linux, PCL) — это инструмент анализа производительности в Linux, доступный с версии ядра 2.6.31. Утилита управления пользовательским пространством, perf, доступна с командной строки и представляет собой набор подкоманд.

Она осуществляет статистическое профилирование целой системы (ядра и пространства пользователя). Данное средство поддерживает счетчики производительности аппаратной и программной (например, hrtimer) платформы, точки трассировки и динамические пробы (скажем, kprobes или uprobes). В 2012 году два инженера IBM признали perf (наряду с OProfile) одним из двух наиболее используемых инструментов профилирования счетчиков производительности в Linux.

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

Ниже приведены записи perf первых 10 секунд ThoughtSpot, работающего на здоровой (быстрой) машине (слева) и внутри контейнера (справа).
image

Сразу видно, что справа первые 5 вызовов связаны с ядром. Время, в основном, расходуется на пространство ядра, тогда как слева — большая часть времени идет на собственные процессы, выполняемые в пространстве пользователя. Но самое интересное, что все время занимает вызов posix_fadvise.

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

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

Это сторонняя библиотека логирования под названием glog. Мы пользовались ей для проекта. Конкретно эта строка (в LogFileObject::Write), наверное, самый критический путь всей библиотеки. Она вызывается для всех событий «запись лога в файл» (log to file), а многие экземпляры нашего продукта логинятся довольно часто. Беглый взгляд на исходный код подсказывает, что часть fadvise можно отключить, установив флажок --drop_log_memory=false:

 if (file_length_ >= logging::kPageSize) {
   // don’t evict the most recent page
   uint32 len = file_length_ & ~(logging::kPageSize — 1);
   posix_fadvise(fileno(file_), 0, len, POSIX_FADV_DONTNEED);
 }
}

что мы, конечно же, сделали и… в яблочко!
image

То, что раньше отнималло пару секунд, теперь выполняется за 8 (восемь!) миллисекунд. Немножко погуглив, мы нашли вот что: https://issues.apache.org/jira/browse/MESOS-920 и еще это: https://github.com/google/glog/pull/145, что в очередной раз подтвердило нашу догадку об истинной причине торможения. Скорее всего, то же самое происходило и на виртуальной машине/голом железе, но так как у нас было по 1 копии процесса на каждую машину/ядро, то интенсивность вызова fadvise была значительно ниже, чем и объяснялось отсутствие дополнительного потребления ресурсов. Увеличив процессы логирования в 3–4 раза и выделив им одно общее ядро, мы увидели, что это действительно застопорило fadvise.

И в заключение:

Информация эта не нова, но многие почему-то забывают главное: в случаях с контейнерами «изолированные» процессы конкурируют за все ресурсы ядра, а не только за ЦП, оперативную память, дисковое пространство и сеть. А поскольку ядро — это архисложная структура, то сбои могут происходить где угодно (как, например, в __d_lookup_loop из статьи Sysdig). Это, правда, не говорит о том, что контейнеры хуже или лучше традиционной виртуализации. Они — отличный инструмент, решающий свои задачи. Просто помните: ядро — это общий ресурс, и готовьтесь к отладке неожиданных конфликтов в пространстве ядра. Кроме того, такие конфликты — отличная возможность для злоумышленников прорваться через «истонченную» изоляцию и создать скрытые каналы между контейнерами. И, наконец, есть perf — отличное средство, которое покажет, что происходит в системе, и поможет отладить любые проблемы с производительностью. Если планируете запускать высоконагруженные приложения в Docker, то обязательно выделите время на изучение perf.

Source: habr1

Метки:





Запись опубликована: 13.11.2018

[Из песочницы] Быстрый Sin и Cos на встроенном ASM для Delphi

Всем привет!

Возникла потребность написать быстрое вычисление Sin и Cos. За основу для вычислений взял разложение по ряду Тейлора. Использую в 3D-системах (OpenGL и графическая библиотека своей разработки). К сожалению свести ряд «идеально» для Double не получается, но это компенсируется хорошим ускорением. Код написан на встроенном в Delphi XE6 ассемблере. Используется SSE2.

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

В итоге:

  1. Достигнутая точность результата равна: 10.e-13
  2. Максимальное расхождение с CPU — 0.000000000000045.
  3. Скорость увеличена в сравнении с CPU в 4.75 раза.
  4. Скорость увеличена в сравнении с Math.Sin и Math.Cos в 2.6 раза.

Для теста использовал процессор Intel Core-i7 6950X Extreme 3.0 ГГц.
Исходный текст на Delphi встроен в комментарии к ассемблеру.

Исходный код:

Заголовок спойлера

var
  gSinTab: array of Double;
  gSinAddr: UInt64;
const
  AbsMask64: UInt64 = ($7FFFFFFFFFFFFFFF);
  PI90: Double = (PI / 2.0);
  FSinTab: array[0..7] of Double =
  (1.0 / 6.0, //3!
   1.0 / 120.0, //5!
   1.0 / 5040.0, //7!
   1.0 / 362880.0, //9!
   1.0 / 39916800.0, //11!
   1.0 / 6227020800.0, //13!
   1.0 / 1307674368000.0, //15!
   1.0 / 355687428096000.0); //17!
  //Максимумы функции Sin для положительных значений
  QSinMaxP: array[0..3] of Double = (0.0, 1.0, 0.0, -1.0);
  //Максимумы функции Sin для отрицательных значений
  QSinMaxM: array[0..3] of Double = (0.0, -1.0, 0.0, 1.0);
  //Знаки квадрантов для положительных значений
  QSinSignP: array[0..3] of Double = (1.0, 1.0, -1.0, -1.0);
  //Знаки квадрантов для отрицательных значений
  QSinSignM: array[0..3] of Double = (-1.0, -1.0, 1.0, 1.0);
function Sinf(const A: Double): Double;
asm
  .NOFRAME
  // На входе A в xmm0
  // Четверть окружности
  // X := IntFMod(Abs(A), PI90, D);
  // Result := FNum - (Trunc(FNum / FDen) * FDen);
  pxor xmm3, xmm3
  comisd A, xmm3
  jnz @ANZ
  ret // Result := 0.0;
 @ANZ:
  //Флаг отрицательного угла
  //Minus := (A < 0.0);
  setc cl
  movsd xmm1, [AbsMask64] //Abs(A)
  pand A, xmm1
  movsd xmm1, [PI90] //PI90 = FDen
  movsd xmm2, A //Копия A = FNum
  divsd xmm2, xmm1 //(FNum / FDen)
  roundsd xmm2, xmm2, 11b //11b - Trunc
  cvtsd2si rax, xmm2 //Целая часть (X в EAX)
  mulsd xmm2, xmm1
  subsd xmm0, xmm2
  //-----------------------------------
  //Нормализация квадранта
  and rax, 3 // D := (D and 3);
  //-----------------------------------
  // Проверка на максимум функции
  // if (X = 0.0) then
  // begin
  //   if Minus then
  //     Exit(QSinMaxM[D]) else
  //     Exit(QSinMaxP[D]);
  // end;
  comisd xmm0, xmm3
  jnz @XNZ
  shl rax, 3 //умножить номер квадранта на размер Double (8 байт)
  cmp cl, 1 //Если угол отрицательынй, то переход
  je @MaxMinus
 @MaxPlus:
  lea rdx, qword ptr [QSinMaxP]
  movsd xmm0, qword ptr [rdx + rax]
  ret
 @MaxMinus:
  lea rdx, qword ptr [QSinMaxM]
  movsd xmm0, qword ptr [rdx + rax]
  ret
  //-----------------------------------
 @XNZ:
  //При нечетном квадранте нужно вычесть градусы для симметрии
  // if (D and 1) <> 0 then X := (PI90 - X);
  mov edx, eax
  and edx, 1
  cmp edx, 0
  je @DZ
  subsd xmm1, xmm0
  movsd xmm0, xmm1
  //-----------------------------------
 @DZ:
  // Result остается в xmm0
  // X в xmm0
  // N := (X * X) в xmm2
  // F := (N * X) в xmm1
  // N
  movsd xmm2, xmm0 // Копия X
  mulsd xmm2, xmm2 // N := (X * X)
  // F
  movsd xmm1, xmm2 // Копия N
  mulsd xmm1, xmm0 // F := (N * X)
  //Загружаем таблицу факториалов для синуса
  mov rdx, [gSinTab]
  movaps xmm4, dqword ptr [rdx]
  movaps xmm6, dqword ptr [rdx + 16]
  movaps xmm8, dqword ptr [rdx + 32]
  movaps xmm10, dqword ptr [rdx + 48]
  //Извлекаем нечетную часть таблицы
  movhlps xmm5, xmm4
  movhlps xmm7, xmm6
  movhlps xmm9, xmm8
  movhlps xmm11, xmm10
  // Result := X - F * PDouble(gSinAddr)^;
  mulsd xmm4, xmm1 //FSinTab[0]
  subsd xmm0, xmm4
  // F := (F * N);
  // Result := Result + F * PDouble(gSinAddr + 8)^;
  mulsd xmm1, xmm2
  mulsd xmm5, xmm1 //FSinTab[1]
  addsd xmm0, xmm5
  // F := (F * N);
  // Result := Result - F * PDouble(gSinAddr + 16)^;
  mulsd xmm1, xmm2
  mulsd xmm6, xmm1 //FSinTab[2]
  subsd xmm0, xmm6
  // F := (F * N);
  // Result := Result + F * PDouble(gSinAddr + 24)^;
  mulsd xmm1, xmm2
  mulsd xmm7, xmm1 //FSinTab[3]
  addsd xmm0, xmm7
  // F := (F * N);
  // Result := Result - F * PDouble(gSinAddr + 32)^;
  mulsd xmm1, xmm2
  mulsd xmm8, xmm1 //FSinTab[4]
  subsd xmm0, xmm8
  // F := (F * N);
  // Result := Result + F * PDouble(gSinAddr + 40)^;
  mulsd xmm1, xmm2
  mulsd xmm9, xmm1 //FSinTab[5]
  addsd xmm0, xmm9
  // F := (F * N);
  // Result := Result - F * PDouble(gSinAddr + 48)^;
  mulsd xmm1, xmm2
  mulsd xmm10, xmm1 //FSinTab[6]
  subsd xmm0, xmm10
  // F := (F * N);
  // Result := Result + F * PDouble(gSinAddr + 56)^;
  mulsd xmm1, xmm2
  mulsd xmm11, xmm1 //FSinTab[7]
  addsd xmm0, xmm11
  //-----------------------------------
  // if Minus then
  //   Result := (Result * QSinSignM[D]) else
  //   Result := (Result * QSinSignP[D]);
  shl rax, 3 //Умножаем на 8
  cmp cl, 1
  je @Minus
  lea rdx, qword ptr [QSinSignP]
  mulsd xmm0, qword ptr [rdx + rax]
  ret
 @Minus:
  lea rdx, qword ptr [QSinSignM]
  mulsd xmm0, qword ptr [rdx + rax]
end;
//------------------------------------
function Cosf(const A: Double): Double;
asm
  .NOFRAME
  // A в xmm0
  // Четверть окружности
  // X := IntFMod(AMod, PI90, D);
  // Result := FNum - (Trunc(FNum / FDen) * FDen);
  pxor xmm3, xmm3
  comisd A, xmm3
  jnz @ANZ
  movsd xmm0, [DoubleOne]
  ret // Result := 1.0;
  //-----------------------------------
 @ANZ:
  //Флаг отрицательного угла
  //Minus := (A < 0.0);
  setc cl
  movsd xmm1, [AbsMask64] //Abs(A)
  pand A, xmm1
  //-----------------------------------
  movsd xmm1, [PI90] //PI90 = FDen
  //-----------------------------------
  // if Minus then
  //   AMod := Abs(A) - PI90 else
  //   AMod := Abs(A) + PI90;
  cmp cl, 1
  je @SubPI90
  addsd A, xmm1
  jmp @IntFMod
 @SubPI90:
  subsd A, xmm1
  //-----------------------------------
 @IntFMod:
  movsd xmm2, A //Копия A = FNum
  divsd xmm2, xmm1 //(FNum / FDen)
  roundsd xmm2, xmm2, 11b //11b - Trunc
  cvtsd2si rax, xmm2 //Целая часть (X в EAX)
  mulsd xmm2, xmm1
  subsd xmm0, xmm2
  //-----------------------------------
  //Нормализация квадранта
  and rax, 3 // D := (D and 3);
  //-----------------------------------
  // Проверка на максимум функции
  // if (X = 0.0) then
  // begin
  //   if Minus then
  //     Exit(QSinMaxM[D]) else
  //     Exit(QSinMaxP[D]);
  // end;
  comisd xmm0, xmm3
  jnz @XNZ
  shl rax, 3 //умножить номер квадранта на размер Double (8 байт)
  cmp cl, 1 //Если угол отрицательынй, то переход
  je @MaxMinus
 @MaxPlus:
  lea rdx, qword ptr [QSinMaxP]
  movsd xmm0, qword ptr [rdx + rax]
  ret
 @MaxMinus:
  lea rdx, qword ptr [QSinMaxM]
  movsd xmm0, qword ptr [rdx + rax]
  ret
  //-----------------------------------
 @XNZ:
  //При нечетном квадранте нужно вычесть градусы для симметрии
  // if (D and 1) <> 0 then X := (PI90 - X);
  mov edx, eax
  and edx, 1
  cmp edx, 0
  je @DZ
  subsd xmm1, xmm0
  movsd xmm0, xmm1
 @DZ:
  // Result остается в xmm0
  // X в xmm0
  // N := (X * X) в xmm2
  // F := (N * X) в xmm1
  // N
  movsd xmm2, xmm0 // Копия X
  mulsd xmm2, xmm2 // N := (X * X)
  // F
  movsd xmm1, xmm2 // Копия N
  mulsd xmm1, xmm0 // F := (N * X)
  //Загружаем таблицу факториалов для синуса
  mov rdx, [gSinTab]
  movaps xmm4, dqword ptr [rdx]
  movaps xmm6, dqword ptr [rdx + 16]
  movaps xmm8, dqword ptr [rdx + 32]
  movaps xmm10, dqword ptr [rdx + 48]
  //Извлекаем нечетную часть таблицы
  movhlps xmm5, xmm4
  movhlps xmm7, xmm6
  movhlps xmm9, xmm8
  movhlps xmm11, xmm10
  // Result := X - F * PDouble(gSinAddr)^;
  mulsd xmm4, xmm1 //FSinTab[0]
  subsd xmm0, xmm4
  // F := (F * N);
  // Result := Result + F * PDouble(gSinAddr + 8)^;
  mulsd xmm1, xmm2
  mulsd xmm5, xmm1 //FSinTab[1]
  addsd xmm0, xmm5
  // F := (F * N);
  // Result := Result - F * PDouble(gSinAddr + 16)^;
  mulsd xmm1, xmm2
  mulsd xmm6, xmm1 //FSinTab[2]
  subsd xmm0, xmm6
  // F := (F * N);
  // Result := Result + F * PDouble(gSinAddr + 24)^;
  mulsd xmm1, xmm2
  mulsd xmm7, xmm1 //FSinTab[3]
  addsd xmm0, xmm7
  // F := (F * N);
  // Result := Result - F * PDouble(gSinAddr + 32)^;
  mulsd xmm1, xmm2
  mulsd xmm8, xmm1 //FSinTab[4]
  subsd xmm0, xmm8
  // F := (F * N);
  // Result := Result + F * PDouble(gSinAddr + 40)^;
  mulsd xmm1, xmm2
  mulsd xmm9, xmm1 //FSinTab[5]
  addsd xmm0, xmm9
  // F := (F * N);
  // Result := Result - F * PDouble(gSinAddr + 48)^;
  mulsd xmm1, xmm2
  mulsd xmm10, xmm1 //FSinTab[6]
  subsd xmm0, xmm10
  // F := (F * N);
  // Result := Result + F * PDouble(gSinAddr + 56)^;
  mulsd xmm1, xmm2
  mulsd xmm11, xmm1 //FSinTab[7]
  addsd xmm0, xmm11
  //-----------------------------------
  // if Minus then
  //   Result := (Result * QSinSignM[D]) else
  //   Result := (Result * QSinSignP[D]);
  shl rax, 3 //Умножаем на 8
  cmp cl, 1
  je @Minus
  lea rdx, qword ptr [QSinSignP]
  mulsd xmm0, qword ptr [rdx + rax]
  ret
 @Minus:
  lea rdx, qword ptr [QSinSignM]
  mulsd xmm0, qword ptr [rdx + rax]
end;
Initialization
  //Выровненный массив
  SetLength(gSinTab, 8);
  //Адрес таблицы
  gSinAddr := UInt64(@FSinTab[0]);
  //Копируем таблицу в выровненный массив
  Move(FSinTab[0], gSinTab[0], SizeOf(Double) * 8);
Finalization
  SetLength(gSinTab, 0);

Пример работы здесь

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

Tags:
Настройка языка
Интерфейс
Язык публикаций

Source: habr1

Метки:





Запись опубликована: 13.11.2018

[Перевод] Современный C++ != (Самый)Новый Стандарт

Ищем ошибки в C, C++ и C# на Windows, Linux, macOS

Современный C++ != (Самый)Новый Стандарт

  • Перевод

Термин «современный C++» часто используется как синоним выражения «код, использующий новый стандарт C++». Здесь «новый» может означать что угодно от C++11 до C++17, или даже то, что уже сейчас доступно из C++20. Я думаю, что современный C++ — это нечто большее, не ограничивающееся добавлением флага -std=c++17.

Что значит «современный»?

Если поискать значение слова «современный» в сети, одним из первых мы найдем определение из словаря Merriam-Webster. Вот две части, относящиеся к C++:

[…]

2: involving recent techniques, methods, or ideas: (up-to-date) modern methods of communication

3 capitalized: of, relating to, or having the characteristics of the present or most recent period of development of a language — Modern English

[…]

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

Можно поспорить, что возможности, существовавшие со времен C++98, не входят в современный C++, потому что они существуют слишком давно. Однако, нужно помнить, что самые активные люди в сообществе, которые говорят или пишут о «современном C++», это чаще всего первопроходцы. Большинство использует, изучает и даже преподает старый добрый «C с классами» из 90-х, что делает многие методы, которые там не используются, частью современного C++.

Помимо новых возможностей

Что же из доступного в C++98 я считаю принадлежащим к категории «современный C++»? Вот неполный список некоторых важных возможностей и идей:

RAII

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

RAII может использоваться для надежного управления многими вещами, такими как память (например, std::vector, std::string), дескрипторы файлов (std::fstream), сетевые соединения, мьютексы, соединения с базами данных, а также сущности, которые имеют отдаленное отношение к ресурсам. Если вам нужен надежный способ сделать некоторое действие, а потом отменить его на выходе из некоторой области видимости или при уничтожении объекта, RAII — то, что вам нужно.

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

Определенно, техника RAII входит в современный C++, хотя она и была доступна с самого начала.

Строгая типизация

Идея строгой типизации очень популярна в последнее время. В прошлом любые идентификаторы, размеры, почтовые индексы, цены и так далее представлялись через int или double, или другой арифметический тип. То, что они были совместимы, совершенно несвязанные друг с другом значения, которые по чистой случайности имеют один тип, было источником багов, но что поделать? По крайней мере, компилятор молча не преобразует числа и массивы в строки!

На деле получается, что система типов C++ и абстракции с нулевой стоимостью*, которые предоставляет нам компилятор, позволяют сделать многое. Просто создайте разные типы для идентификаторов, почтовых индексов, размеров (нет, без typedef, спасибо) и так далее. Если вам интересно, посмотрите один из докладов Björn Fahller, Jonathan Boccara или Jonathan Müller.

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

Если не считать некоторых недавних дополнений, <algorithm> был в стандартной библиотеке с самого начала. Но если взглянуть на код, выходит, что люди часто предпочитают писать циклы вручную. Причины разнятся от незнания того, какие стандартные алгоритмы доступны, до веры в то, что «шаблоны слишком медленные» (часто без объяснения, по сравнению с чем).

Программирование этапа компиляции

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

Да, большинство типажей в стандартной библиотеке появилось с приходом C++11, но писать их под свои нужды не слишком сложно, и некоторые наиболее общие из них были в Boost до C++11. Я считаю использование логики этапа компиляции частью современного C++, потому что оно отделяет C++ от вездесущего «C с классами».

Заключение

Современный C++ имеет отношение не к новым стандартам, а к тому, как мы пишем наши программы. Во-первых, на C++98 можно писать в более или менее современном стиле. Во-вторых, «C с классами и range-based for циклами» — это еще не современный C++. Новые возможности языка и библиотек помогают нам писать в стиле современного C++, но не они делают наш код современным C++.

Tags:

Похожие публикации

Настройка языка
Интерфейс
Язык публикаций

Source: habr1

Метки:





Запись опубликована: 13.11.2018

Конспект доклада «Монолит для сотен версий клиентов» (HL2018, Badoo, Владимир Янц)

Продолжаю серию конспектов с HL2018. В проверке этого конспекта мне помогали ребята из Badoo (Владимир Янц vyants и Николай Крапивный), за что им большой спасибо. Надеюсь, это положительно сказалось на качестве донесения идеи доклада.
image

Особенности процесса разработки:

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

Тесты

Unit тесты

Пишутся на phpunit.
Тестируют малую единицу. Не ходят ни в базу, ни в сервисы (не должны ни с чем взаимодействовать).
Легаси до сих пор есть и усложняет процесс тестирование.
Разработали библиотеку softMocks — перехватывает все include / require и подменяет на изменённый.
Можно мотать любые методы: статические, приватные, финальные.
Библиотека доступна в опен сорс.

Проблема: softmocks расслабляют и дают писать не тестируемый код (и все аврно покрыть его тестами).
Приняли правила:

  • Новый код должен быть легко тестируем phpunit
  • SoftMocks — крайний случай (старый код /долго / дорого / сложно)

На эти правила смотрим на код ревью

Качество тестов

Мутационное тестирование

  • Берём код
  • Берём code coverage
  • Парсим код и применяем мутации (меняем + => -; true => false и тп.)
  • Для каждой мутации прогоняем сюит (набор) тестов.
  • Если тесты упали, то ок. Если нет — они недостаточно эффективны. Разбираемся, меняем / дописываем тесты.

Есть готовые решения (Humbug, Infection), но они не подошли (не совместимы с softmocks, есть сложности с code coverage). Поэтому написали свое.
Мутационное тестирование пока недоступном для ручного теста. Доступно для запуска в ручную, из консоли. Сейчас внедряем в CI pipeline, выстраиваем процесс. Результат будет на хабре.

Интеграционные тесты

Тестируем работу нескольких компонентов в связке; проверяем работу с базой и/или сервисами.

Стандартный подход к тестированию БД (DBUnit):

  1. Поднимаем тестовую БД
  2. Заполняем ее
  3. Запускаем тест
  4. Очищаем БД

Проблемы:

  • Нужно поддерживать datatables и dataset (актуальность содержимого БД)
  • Требуется время на подготовку бд
  • Параллельные запуски делают тесты нестабильными и порождают дедлоки

Решение: библиотека DBMocks (своё решение)
Принцип работы:

  • Методы драйверов dB перехватываются с помощью SoftMocks на setUp теста
  • Из запроса парсим db + table
  • В tmpfs создаются временные таблицы с такой же схемой
  • Все запросы ходят только во временные таблицы
  • На TearDown они удаляются

Библиотека небольшая, но пока не запушена в опен сорс

Результаты:

  • Тесты не могут повредить данные в оригинальных таблицах
  • Тесты изолированы друг от друга (можно запускать параллельно)
  • Тестируется совместимость запроса с версией MySQL

API тесты

  • Имитируют клиентскую сессию
  • Умеют слать запросы к бэкенду
  • Бэкенд отвечает почти как реальному клиенту

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

Тестовые пользователя находятся в одном окружении с реальными, потому что devel != prod. Нужно изолировать тестовых и живых пользователей.

Для изоляции добавили флаг is_test_user у пользователя. И эти пользователи также исключаются из аналитики и результатов a/b тестов.
Можно сделать дешевле — отправить тестовых пользователей «в Антарктиду», где их никто не увидит (кроме пингвинов )

QA API

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

  • Хорошо документированные апи методы
  • Быстро и легко управляют данные
  • Пишут backend разработчики
  • Можно применять только к тестовым пользователям

Позволяют изменить пользователю неизменяемые данные (например, дату регистрации).

Требуется защита:

  • На уровне сети (доступно только из офисной сети)
  • С каждым запросом передаётся secret, валидность которого проверяется
  • Методы работают только с тестовыми пользователями

Есть BugsBounty программа на HackerOne. Платят за найденные уязвимости. Один косяк с QA API нашли с ее помощью.

Remote mocks

Моки для удаленного бэкенда.
Работают на Базе soft mocks. Тест просит backend инициализировать для сессии mock. При получении запроса бэкенд проверяет список моков для сессии и применяет их при помощи SoftMocks.

Пример теста:
image

API тесты слишком удобны. Есть соблазн писать их вместо Unit. Но API тесты сильно медленее.

Приняли набор правил:

  • Цель АПИ тестов — тестировать протокол и правильность интеграции
  • Допустимо проверять сложные флоу
  • Нельзя тестировать мелкую вариативность
  • На code review проверяем тесты тоже.

Ui тесты

Не пишет бэкенд команда.
Фича покрывается Ui тестами когда стабилизируется.
Используется selenium для веб. Для мобильных calabash.

Прогон тестов

100 000 юнит тестов. 6 000 интеграционных, 14 000 апи тестов.
В 1 поток время 40 мин / 90 мин / 10 часов.

Сделали TestCloud — облако для запуска тестов.
image

Распределение теста между потоками:

  • Можно поровну (плохо, все тесты разные, получается неравные по времени части)
  • Запустить несколько потоков и последовательно скармливать тесты phpunit по одному (накладные расходы на инициализацию. Долго!)

Решение:

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

Проблема с апи тестами — долго, много ресурсов и не дают выполнится другим.

Для решения разбили cloud на 2 части:

  1. Запускает только быстрые тесты
  2. Запускает оба типа тестов.

Результат — ускорение времени до:

  • Unit — 1 мин
  • Интеграционные — 5 мин
  • API — 15 минут.

Прогон по code coverage

Какие тесты выполнять? Покажет code coverage.

  1. Получаем branch diff
  2. Формируем список изменённых файлов
  3. Получаем список тестов для этих файлов
  4. запускаем прогон сюита только из этих тестов.

Coverage формируется раз в сутки, ночью, для master-ветки. Результаты (diff) складываем в базу.

Плюсы:

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

Минусы:

  • Релиз бэкенд 2 раза в день. После обеда coverage менее актуален (но при выкате била всегда гоняем полный сьют).
  • Апи тесты генерируют обширный coverage. Для них этот подход не дает большой экономии.

Если разработчику нужно сразу посмотреть code-coverage, то есть тулза которую можно запустить в консоле и сразу получить свежую метрику по coverege конкретного файла/компонента. Считается хитро: берется данные по coverege мастера, добавляются все измененную тесты, получается маленький сьют по которому уже и считается покрытие.

Итоги

  • Нужны все уровни тестов
  • Количество != качество. Делайте Code review и мутационное тестирование
  • Изолируйте тестовых пользователей от реальных
  • Бэкдоры в бэкенде упрощают и ускоряют написание тестов
  • Собирайте статистику по тестам.

Ссылки

Tags:
Настройка языка
Интерфейс
Язык публикаций

Source: habr1

Метки: