[ { "slug": "использование-jquery-в-webpack", "title": "JavaScript. Использование jQuery в Webpack", "date": "2019-01-12T21:54:18+00:00", "author": "DarkRiDDeR", "categories": [ "JavaScript" ], "cover": "/assets/illustrations/jquery-webpack.svg", "excerpt": "Webpack является одним из самых мощных и гибких инструментов для сборки фронтенд-проектов. Иногда необходимо включить в проект Webpack одну из самых популярных JavaScript библиотек jQuery. Для начала необходимо установить jQuery из репозитория npm командой: np", "contentHtml": "
Webpack является одним из самых мощных и гибких инструментов для сборки фронтенд-проектов. Иногда необходимо включить в проект Webpack одну из самых популярных JavaScript библиотек jQuery.
\nДля начала необходимо установить jQuery из репозитория npm командой:
\nnpm i jquery\nлибо (если используем менеджер пакетов Yarn):
\nyarn add jquery\nЧтобы jQuery стал доступным в глобальной области видимости в «бандле» (собираемом пакете, от bundle) можно использовать ProvidePlugin (см. официальную документацию https://webpack.js.org/plugins/provide-plugin/):
\n\nmodule.exports = {\n plugins: [\n new webpack.ProvidePlugin({\n $: 'jquery',\n jQuery: 'jquery'\n }),\n ]\n};Так библиотека jQuery будет доступна через глобальные переменные $, jQuery, window.jQuery.
\nИногда в проектах Webpack собираются несколько бандлов. Например, есть финальный скрипт, который подключается на все страницы и в нём необходима библиотека jQuery. А в других скриптах она уже не нужна, так как она будет уже подключена глобально. В таком случае только в модуле, который будет глобальным, необходимо подключить jQuery (для импорта модулей будем использовать стандарт ES6/ES2015):
import $ from 'jquery';\n \nglobal.jQuery = $;\nglobal.$ = $;Также jQuery можно подключить в проект Webpack через CDN с помощью плагина html-webpack-externals-plugin (https://www.npmjs.com/package/html-webpack-externals-plugin):
\n\nmodule.exports = {\n plugins: [\n new HtmlWebpackExternalsPlugin({ // optional plugin: inject cdn\n externals: [\n {\n module: 'jquery',\n entry: 'https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js'\n }\n ],\n }),\n ]\n};После чего можно использовать jQuery библиотеки, подключая их следующим образом:
\n\nrequire("inputmask/dist/inputmask/jquery.inputmask.js");Для старого проекта jQuery в Webpack лучше подключать явно: установить пакет, импортировать его в точке входа и отдельно решить вопрос с глобальными переменными. ProvidePlugin удобен, когда в модулях встречаются свободные идентификаторы $ или jQuery. Но если старый плагин лезет именно в window.jQuery, одного ProvidePlugin может быть мало — нужно положить jQuery в window самостоятельно.
\nimport $ from 'jquery';\n\nwindow.$ = $;\nwindow.jQuery = $;\nПосле этого можно добавить ProvidePlugin, чтобы не писать импорт в каждом файле:
\nconst webpack = require('webpack');\n\nmodule.exports = {\n plugins: [\n new webpack.ProvidePlugin({\n $: 'jquery',\n jQuery: 'jquery',\n }),\n ],\n};\nПорядок подключения имеет значение. Сначала в entry-файле импортируем jQuery и кладём его в window, потом импортируем старые плагины, которым нужен глобальный объект. Если плагин подключается через require, делаем это после установки window.jQuery.
\nimport './jquery-global';\nimport 'inputmask/dist/jquery.inputmask';\nimport './app';\nПроверка простая: в браузерной консоли должны существовать window.$ и window.jQuery, а в собранном bundle не должно быть двух разных копий jQuery. Если проект новый, лучше не тащить jQuery без необходимости. Если проект старый и плагины уже написаны под jQuery, такая схема делает зависимость явной и предсказуемой.
", "readingMinutes": 2 }, { "slug": "bitrix-api-add-foto-editor", "title": "Bitrix API. Вставка на страницу встроенного редактора картинок", "date": "2018-10-19T01:50:29+00:00", "author": "DarkRiDDeR", "categories": [ "Bitrix", "PHP" ], "cover": "/assets/illustrations/bitrix-photo-editor.svg", "excerpt": "В CMS Bitrix имеется довольно неплохой по функциональным возможностям и дизайну встроенный графический редактор картинок. Поэтому может возникнуть желании использовать его на сайте. Но как это сделать? На самом деле это не так сложно. Класс компонента графичес", "contentHtml": "В CMS Bitrix имеется довольно неплохой по функциональным возможностям и дизайну встроенный графический редактор картинок.
\n
Поэтому может возникнуть желании использовать его на сайте. Но как это сделать? На самом деле это не так сложно. Класс компонента графического редактора является \\Bitrix\\Main\\UI\\FileInput и располагается в файле /bitrix/modules/main/lib/ui/fileinput.php
\nДля вывода кода компонента необходимо создать экземляр класс с помощью статического метода createInstance. Затем получить код на вывод через метод show. Полный код вызова компонента будет иметь следующий вид:
\n\n<?=\\Bitrix\\Main\\UI\\FileInput::createInstance([\n\t"name" => "picture",\n\t"description" => true,\n\t"upload" => true,\n\t"allowUpload" => "I",\n\t"medialib" => true,\n\t"fileDialog" => true,\n\t"cloud" => true,\n\t"delete" => true,\n\t"maxCount" => 1\n\t])->show($id);\n?>Переменная $id содержит идентификатор картинки в системе.
\nПараметры:
\nТакже необходимо учитывать, что некоторые параметры будут работать только при определённых условиях, как, например, medialib и cloud.
\nПосле вывода компонента важно не забыть обработать результат формы: сохранить ID файла, проверить тип загруженного файла и права пользователя. Сам редактор решает задачу интерфейса, но безопасность и привязка к сущности остаются на стороне вашего кода.
\nВ параметрах компонента отдельно проверьте allowUpload, medialib, fileDialog, cloud, delete, edit и maxCount. Не все параметры будут иметь эффект в любой установке Bitrix: часть зависит от подключённых модулей, прав пользователя и контекста страницы.
\n$fileId = (int)$_POST['picture'];\n\nif ($fileId > 0) {\n $file = CFile::GetFileArray($fileId);\n if ($file && str_starts_with($file['CONTENT_TYPE'], 'image/')) {\n // сохраняем ID картинки в своей сущности\n }\n}\nЕсли редактор не открывается, проверьте подключение модулей main и fileman, права на загрузку, административные JS-расширения и наличие сессии пользователя. В публичной части сайта проблема часто оказывается не в FileInput, а в том, что на странице не подключены нужные скрипты Bitrix.
\nРабочая схема такая: выводим FileInput, разрешаем только картинки, ограничиваем количество, после отправки формы валидируем ID файла и сохраняем его в свою сущность. Тогда встроенный редактор становится нормальной частью формы, а не просто красивой кнопкой загрузки.
", "readingMinutes": 2 }, { "slug": "о-tilix-и-d-интервью-с-геральдом-нанном", "title": "О Tilix и D: интервью с Геральдом Нанном", "date": "2017-08-25T21:37:08+00:00", "author": "DarkRiDDeR", "categories": [ "DLang", "Интервью" ], "cover": "/assets/illustrations/tilix-cover.svg", "excerpt": "Йоаким — интервьюер-резидент блога о D. Он также брал интервью у членов D-сообщества для This Week in D и ответственен за портирование LDC для Android . Геральд Нанн — разработчик Tilix (ранее называвшийся Terminix). Tilix— продвинутый тайлинговый эмулятор тер", "contentHtml": "Йоаким — интервьюер-резидент блога о D. Он также брал интервью у членов D-сообщества для This Week in D и ответственен за портирование LDC для Android.
\nГеральд Нанн — разработчик Tilix (ранее называвшийся Terminix). Tilix— продвинутый тайлинговый эмулятор терминалов с открытым исходным кодом, который является самым «звёздным» проектом на основе D на GitHub, недавно даже обогнавший стандартный компилятор D, DMD. В этом году на DConf в Берлине он рассказывал о том, как использует D. Имеются слайды и видео. В своей повседневной работе, которая не имеет ничего общего с настольными графическими приложениями, он является старшим разработчиком промежуточных решений в Red Hat. [Подробнее об истории Джеральда в расширенном интервью — Ред.]
\nЙоаким: — Что такое тайлинговый эмулятор терминала?
\nГеральд: — Тайлинговый эмулятор терминала позволяет разделить терминал на несколько частей и распределить их в удобном порядке и месте, что наиболее удобно при работе над конкретной задачей. Люди, которые работают на нескольких терминалах одновременно, как правило, находят такие инструменты наиболее полезными, особенно с постоянно увеличивающимися размерами мониторов и разрешений.
\nНесмотря на то, что Tilix очень хорош, основной причиной, по которой я создал его, было то, что мне нужен был эмулятор терминала, который следовал бы концепции Gnome HIG (Human Interface Guidelines — рекомендации по созданию интуитивных, легко изучаемых и логичных интерфейсов взаимодействия с пользователем) и использовал CSD (Client-Side Decorations — отрисовку на стороне клиента). Tilix следует Gnome HIG, опубликованным здесь, что означает соблюдение интервалов, макетов и других рекомендаций. После HIG важно, чтобы приложение соответствовало всему рабочему столу в целом.
\nCSD ссылается на заголовок окна, где не диспетчер дисплея, а пользователь берёт на себя ответственность за него и может заполнять панель заголовка кнопками и другими элементами управления. Это часть концепции Gnome HIG, и большинство приложений Gnome (gedit, файлы, видео) по умолчанию используют этот подход. Единственное исключением является gnome-terminal, который вообще не использует CSD.
\nЙ.: — Можете привести несколько примеров того, как вы используете концепцию Gnome HIG?
\nГ.: — Gnome HIG определяет конкретный язык дизайна в отношении того, как приложения, работающие в Gnome, должны выглядеть и показывать себя. Некоторые примеры Tilix, следующие концепции HIG, включают использование CSD и меню приложений по рекомендациям, таким как интервалы, макеты и т.д. Кроме того, разработчики Gnome собрали множество макетов, как они думают, как должны выглядеть различные приложения. Tilix использует макеты, разработанные дизайнерами Gnome для терминала, где это возможно. Например, в Tilix диалог предпочтений и профилей использовался отдельно, однако один из дизайнеров Gnome предложил этот макет для gnome-терминала. Я пошел вперед и реализовал его в Tilix, гораздо лучше, чем раньше.
\nВ результате использования CSD и концепции Gnome HIG, надеюсь, что использование Tilix в Gnome более органичено для пользователей.
\nИнтересная вещь — это напряженность между людьми, которые используют Tilix на Gnome и тех, кто использует его в других дистрибутивах. Хотя я не имею никаких сомнений в том, что разработка под Gnome является моей основной целью, я стараюсь сделать Tilix лучше и в других средах рабочего стола, разрешив пользователю отключить CSD в пользу обычного заголовка, если они того пожелают.
\nЙ.: — Вы попали в D из среды Java. Вы все еще пишете код в стиле Java на D? Это было легко, т.е. сколько вам пришлось привыкать, чтобы писать на D?
\nГ.: — Да, чаще всего работаю с Java. Я нашёл это довольно интересным на DConf, когда я спросил, как много людей пришли не из среды C/C++ , только один человек поднял руку.
\nЕсли вы посмотрите на мой код в Tilix, то он очень похож на Java-код. Некоторое из этого связано с моей обычное средой, в которой я работаю, а некоторое из-за того, что GtkD является оболочкой классов.
\nЯ считаю, что переключение между D и Java является довольно плавным по большей части; между ними гораздо меньше когнитивного трения, чем, скажем, между переключением между Java и Python.
\nСамая важная идиома D, которую мне пришлось изучить, — это диапазоны, поскольку они являются основополагающей особенностью D. Однако это не было сильно сложным. Выполнение функций времени компиляции (CTFE) по-прежнему остаются для меня неестественными. Когда я использую их, мне приходится каждый раз искать про них информацию, и мои текущие попытки использования CTFE с точки зрения кода довольны глупы. Я хотел бы больше использовать CTFE в Tilix, поскольку я получаю всё больший опыт использования D.
\nНаконец, мой недостаток опыта работы с C выявляет другую проблему, с которой я немного борюсь — это взаимодействие с кодом на C. Хотя по большей части это довольно просто, но, когда мне приходится расшифровывать что-то сложное, оно становится не таким простым. Поддержка FlatPak (система изолированных контейнеров для графических приложений) в настоящее время есть, так как мне не приходилось так сильно напрягаться по некоторым вопросам, с которыми я сталкивался на C.
\nСказав это, у меня есть некоторый опыт разработки собственного кода, поскольку много лет назад я потратил много времени на разработку кода на Delphi и Object Pascal. Именно на это и приходится большая часть моего опыта работы с графическим интерфейсом.
\nЙ.: — И из вашего выступления на DConf вы явное не беспокоились о сборщике мусора (GC). Вам приходилось думать о нём, когда вы разрабатывали Tilix? Имелись ли проблемы задержек с GUI, вызванные GC?
\nГ.: — Придя из Java, GC для меня довольно естественен, и я определенно не считаю его плохим для D. Я думаю, что GC в D получает много плохой прессы на основе опыта Java, но важно помнить, что GC в D сильно отличается от того, что в Java. Самое большое различие для меня в том, что в D есть больше возможностей для его управления, так как он хорошо понимает, когда он может начать цикл GC. Я был очень рад видеть, как больше людей поднимают различные темы на reddit и форумах, жалуются на использование GC.
\nУ меня не было никаких проблем с GC с точки зрения пауз, и ни один пользователь Tilix не сообщал об этом. У меня было несколько проблем, связанных с GC, в основном связанных с утечкой памяти из-за хранения ссылок, но все они были ошибками программного кода, а не проблемой с реализацией GC в D. У меня есть другое приложение на GTK D, Visual Grep, где я столкнулся с ужасающей эффективностью при обработке большого количества совпадений в tight-цикле (цикл, который содержит несколько инструкций и повторяется много раз.). Тем не менее, просто отключив GC для этого раздела кода, ускорилось все.
\nЙ.: — Репозиторий github для Tilix замечательно чист, нет открытых запросов «pull request» (PR) и низкий процент проблем, которые все еще открыты. Сколько времени вы еженедельно тратите на Tilix? Является ли Tilix только хобби или он стал чем-то большим?
\nГ.: — Tilix — это просто хобби. Я, вероятно, провожу от 5 до 10 часов в неделю. На данный момент — это зрелое приложение, следовательно, относительно небольшое количество проблем. Я также уделяю приоритетное внимание исправлению ошибок при добавлении новых функций, что помогает сохранить список управляемым.
\nЧто касается запросов на «pull request» (PR), я твердо убежден в том, что я должен быть отзывчивым, поэтому я, как правило, отвечаю на PR в течение дня или двух. В качестве участника разработок я знаю, что ничего не убивает интерес, как видеть, что ваш PR томится в течение нескольких недель, месяцев или даже лет. Если вы хотите, чтобы люди вносили свой вклад, что я определенно делаю, то я чувствую, что вы обязаны участникам разработки своевременно реагировать на их PR.
\nНа данный момент Tiltx — это относительно небольшой проект, поэтому мне легко принять этот подход. Я понимаю, почему более крупные проекты могут иметь больше проблем в этой области.
\nЙ.: — D имеет много особенностей, насколько хорошо вы их знаете? Вы упомянули, что хотите использовать некоторые из возможностей времени компиляции. Как вы думаете за счёт каких особенностей D и что Tilix выиграет в будущем и как?
\nГ.: — Я не думаю, что знаю это, если честно, помимо основного набора особенностей, которые я использую в Tilix. Я всегда удивляюсь ребятам на форуме, которые могут утверждать достоинства/недостатки низкоуровневых деталей языка. Это определенно не я. Я бы хотел поправиться, но на самом деле я использую D только как хобби, поэтому я не могу тратить столько же времени, сколько и на Java. Кроме того, моя работа в Red Hat имеет больше компонентов инфраструктуры, чем мои предыдущие рабочие места, поэтому большая часть моего времени расходуется дома на обучение в этом направлении.
\nЧто касается возможностей, от которых Tilix выиграет, я думаю, что использование CTFE и диапазонов было бы очень полезно для того, чтобы улучшить некоторые моменты в GtkD более идиоматичным способом. У меня есть достаточное количество кода, где он может быть намного более кратким при соответствующем использовании CTFE. В качестве простого примера можно использовать диапазоны для поддержки итераций по различным артефактам с использованием foreach, а не классическим циклом. Тем не менее, я думаю, что некоторые из более сложных вариантов использования, таких как поддержка D-Bus (система межпроцессного взаимодействия, которая позволяет приложениям в операционной системе сообщаться друг с другом) и GObject, будут очень полезны.
\nДля тех, кто не знаком с GObject — это объект базового уровня в GTK и является счётчиком ссылок. Возможность легко создавать GObjects в D, как можно и в Python, упростит взаимодействие с некоторыми из API; прямо сейчас это эквивалентно написанию его в сыром C, и это несколько трудоемко. Майк Вей, сопровождающий GtkD, начал делать некоторые работы над этим.
\nЙ.: — Вы упомянули на DConf, что D имеет быстрый цикл компиляции: как вы используете это, то есть какие IDE, компиляторы, toolchain (набор программ, необходимых для создания других программ) вы используете как для разработки, так и для выпуска релизов?
\nГ.: — Я использую MS Visual Studio Code на Linux с отличным плагином code-d, написанным Jan «WebFreak» Jurzitza. Это дает мне все необходимые функции (автозаполнение кода, подсказки, рекомендации и т. Д.). Единственное, чего я не вижу, что есть в Java IDE, — это возможность рефакторинга. Для разработки я использую DMD, поскольку он имеет самое быстрое время компиляции. Сборку версий выполняю с использованием LDC, компилятора D с бэкэном LLVM, поскольку он генерирует меньшие и более быстрые двоичные файлы. Мне редко приходится запускать отладчик, но, когда я это делаю, я просто использую GDB из командной строки.
\nЙ.: — Какие проблемы у вас были с D? Какие его особенности вам не нравятся?
\nГ.: — Никаких серьёзных проблем с моей точки зрения. Я в целом очень доволен этим языком и считаю, что он несёт правильный баланс между простотой использования и возможностями. Наибольшее неудобство мне доставляла стандартная библиотека Phobos, а не сам язык, и эти неудобства напрямую соотносятся с нехваткой времени.
\nНикаких серьёзных проблем с Phobos, а скорее кучей раздражителей. Например, нельзя легко использовать immutable для отправки в std.concurrency, std.experimental.logger являющиеся все ещё экспериментальными, парсер json имеет проблемы, если в локализации установлена запятая для отделения десятичных знаков чисел и т.д. и т.п. Ни один из данных недочётов по себе не является особо важным, и большинство из скорее всего связаны из-за нехватки трудовых ресурсов. Я на самом деле несколько неохотно жалуюсь на них, потому что я, наверное, мог исправить их сам и отправить PR.
\nМеня больше раздражает негативность на форумах в отношении GC. Я чувствую, что иногда люди так поворачиваются в сторону D, что хотят видеть его идеальным системным языком (т.е. без GC, без безопасности памяти и т.д.), упуская из виду, что он очень хороший язык для создания приложений на данный момент. Пока D сравнивают с Rust, в некотором смысле сравнение с Go для меня более интересно. Оба языка основаны на GC и оба начинали как системные языки, однако Go опирается на GC и «удваивается» (от переводчика: скорее всего имеется в виду рост популярности), добиваясь успеха. Один из продуктов Red Hat, который я поддерживаю, OpenShift, использует Kubernetes (проект Google) для управления кластером контейнеров Linux как единой системой, и он написан на Go.
\nЯ думаю, что как язык D намного превосходит Go, и мне хотелось, что мы бы заявляли об этом громче вместо постоянного отрицательного обсуждения системного программирования. На данный момент надо сказать ради справедливости, что Go имеет крупного корпоративного спонсора, в отличие от D, однако контраст в позиционировании по-прежнему интересен мне.
\nЙ.: — Каковы ваши будущие планы в отношении Tilix?
\nГ.: — Две самые большие функции, которые я хотел бы добавить, это поддержка режима управления tmux и добавление возможности отображения боковой панели popout.
\nДля тех, кто не знаком с tmux, это терминальный мультиплексор; это, по сути, терминальный разделитель, но внутри самого терминала. Он также поддерживает ряд других функций, но наиболее интересным является сохранение терминальных сеансов, находящихся вне терминала. Поскольку он работает в терминале, он немного ухудшает производительность, а с точки зрения графического интерфейса он не может использовать собственные виджеты, такие как полосы прокрутки. Чтобы смягчить это, он поддерживает так называемый режим управления, который позволяет ему интегрироваться с эмулятором тайлингово терминала для создания новых терминалов, т.е. подтерминалов внутри одного терминала, управляющихся за пределами tmux. Это значительно улучшает его производительность, позволяя пользователям использовать другие функции, поддерживаемые tmux. На данный момент поддерживается только iterm2 на OSX насколько мне известно.
\nБоковая панель в Tilix является одним из наиболее противоречивых элементов интерфейса. Я решил не реализовывать интерфейс с вкладками, потому что я счел их бесполезными с точки зрения поиска открытия необходимой вкладки; там просто недостаточно места для вкладок, чтобы отличить их, и переименовывать их вручную — это «боль». Боковая панель — это моя попытка альтернативы, она отображает миниатюру каждого сеанса (известную также как ярлык) на боковой панели, которая может быть убрана по мере необходимости для переключения между сеансами.
\n
\nБоковая панель Tilix в действии.
У некоторых людей есть сильное предпочтение к тому, что постоянно доступно, но, к сожалению, все, что видно на боковой панели, непросто, так как генерация эскизов занимает значительное количество времени из-за структуры GTK. Существуют потенциальные способы заставить это работать быстрее, но требуется время на то, чтобы попробовать разные варианты, чтобы увидеть, что как отображается, а затем, что наиболее эффективно.
\nВ мечтах, я бы хотел переключиться на использование эмулятора терминала, написанного изначально в D, а не GTK VTE (Virtual Terminal Emulator), который написан на C, и который я использую сейчас. Для тех, кто не знаком с ним, VTE — это виджет эмуляции терминала, используемый терминалом Gnome и доступен в виде многоразового виджета. Многие эмуляторы терминала в Linux используют этот виджет (gnome-terminal, guake, terminator, tilix и т.д.), поскольку он обеспечивает полностью готовый к работке эмулятор, который прошел через огромное количество тестов.
\nНедостатком этого является то, что любые пользовательские функции, которые вы хотите реализовать, которые включают фактический уровень эмуляции терминала, требуют модификации VTE и получения этих изменений вверх по потоку (*upstream). У меня есть несколько патчей, которые Tilix поддерживает (для триггеров и значков), но, честно говоря, я провёл плохую работу по внедрению вверх по потоку (*upstream). Частично это связано с тем, что VTE написано на C и сделать быстрый и качественный патч на C занимает достаточно много времени.
\nТаким образом, наличие эмуляции терминала, написанного на D, сделало бы это намного проще, однако это огромные инвестиции времени, поскольку люди недооценивают объем работы. В эмуляции терминала много сложных случаев, плюс добавление всего материала, чтобы сделать его удобным для пользователей (поиск, клики по ссылкам и т.д.), это много, чтобы взять на себя. У меня просто нет времени, чтобы сделать это реальностью, если только я не выиграю в лотерею. Если кто-то захочет взять на себя это роль и создать виджет эмуляции терминала GTK, который имеет все необходимые функции и поддерживал бы его в течение длительного времени, я был бы рад работать с ним, чтобы интегрировать его с Tilix. Адам Рапп уже создал один, который работает достаточно хорошо по моим тестам; если кто-то хочет работать над преобразованием его в виджет GTK, добавит необходимые улучшения и согласится его поддерживать, то пусть не стесняется писать и надоедать сообщениями (*ping) мне. ?
\nЙ.: — Пожалуйста, расскажите из своего опыта, как вы впервые обнаружили D и использовали его для написания Tilix?
\nГ.: — На протяжении всей моей карьеры я всегда имел хобби. Некоторые вещи, над которыми я работал, включали популярную надстройку для Delphi под названием Gexperts, замену проводника файлов Windows, Java IDE под названием Gel и популярное приложение для Android под названием OnTrack Diabetes, которое я продал несколько лет назад. Пару лет назад я искал что-то новое для работы в качестве моей программы в качества хобби и остановился на идее создания десктопного приложения для Linux. Я знал, что нужно использовать инструментарий GTK, так как Gnome — моя предпочтительная среда рабочего стола, и особенно с моим прошлым опытом Delphi в графических интерфейсах, которые я мог бы использовать. Я также знал, что меня не интересует разработка на C или C ++, поэтому я посмотрел, какие альтернативы были.
\nЯ начал с Python, так как у него отличная поддержка GTK, и я немного поработал над программированием Jython в WebLogic, так как я использовал Weblogic Scripting Tool (WLST). Однако большая часть моей предыдущей работы была небольшими сценариями, и я быстро понял, что в больших масштабах Python не для меня. Я вообще предпочитаю статически типизированные языки, и динамический набор текста на Python приводил меня в замешательство, особенно в качестве хобби, где я постоянно нуждался в использование справочных материалов, так как я работал на нем нечасто.
\nЯ также смотрел в сторону Rust и Go, но в то время ни у одного из них не было полнофункциональных GTK-привязок. Кроме того, в то время у Rust было много позитивной прессы, но у него был плохой материал для убеждения в правильности его подхода, и я был не так сильно убежден в том, что управление безопасностью памяти с помощью контролера заимствований было лучшим подходом, чем GC.
\nЯ знал о D, поскольку я посматривал на D много лет назад и полюбил этот язык, но экосистема была настолько слабой, что это было не сильно полезно для практической работы. Я взглянул на него снова и обнаружил, что он значительно улучшился и даже удивительно: были доступны полные привязки GtkD. Это также помогло тому, чтобы D и Java были достаточно похожи, и чтобы собирать на D было невероятно легко. Как только я узнал, как работают диапазоны, было легко начать кодить.
\nСначала я создал небольшое приложение под названием Visual Grep, которое переносит grep в графический интерфейс. Когда я консультировался, мне часто приходилось собирать большие базы кода, ищущие конкретные шаблоны, и графический интерфейс, который делал просмотр шаблонов, был абсолютной необходимостью. Это было отличное первое приложение, так как я узнал немало вещей о D. Производительность приложения была изначально плохой с большими наборами результатов, потому что GC постоянно был перегружен при загрузке результатов из-за постоянного распределения. Отключение GC во время этого tight-цикла улучшило производительность неизмеримо. Я также узнал об интеграции GTK с возможностями многопоточности D.
\nЯ был вдохновлен пользовательским интерфейсом от Gnome Builder IDE, и подумал, что он хорошо подойдёт для эмулятора терминала. Таким образом, Tilix родился. Ну, на самом деле сначала он назывался Terminix, но как только он начал получать популярность, я получил вежливую просьбу на переименование из-за компании Terminix, американской компании по борьбе с вредителями. Будучи канадцем, я не слишком хорошо их знал, поэтому я не много думал об этом имени. Извлеченный урок: потратьте время на выбор хорошего имени, если ваше приложение станет более популярным, чем вы ожидаете.
\nЯ наслаждаюсь временем, проведённым с D, и это отличный язык для создания настольных приложений в GTK. Во многих отношениях, я чувствую, что D является естественным преемником Vala, который был языком, созданным специально для создания приложений GTK, но постепенно умирающим, в основном из-за его узкой направленности. Если вы не создаёте приложения GTK, вы не используете Vala, что означает, что количество людей, использующих его и работающий над ним, по определению очень мало.
\nЯ также считаю, что D является естественным преемником Delphi, по крайней мере с GtkD, как мощным инструментом для создания настольных приложений. Благодаря быстрому времени компиляции и легкому изучению языка, он приносит множество лучших атрибутов Delphi в современную эпоху. Плюс я могу ввести {и} вместо Begin и End и увеличить свою эффективность на 75% или около того. ?
\nОригинал (англ.) http://dlang.org/blog/2017/08/11/on-tilix-and-d-an-interview-with-gerald-nunn/
\nГлавный практический вывод прост: D может быть не только системным языком, но и удобным языком для прикладных desktop-инструментов. Tilix показывает, что связка D, GtkD и аккуратного следования GNOME HIG способна дать зрелое приложение, которым пользуются каждый день.
\nВ интервью хорошо видно, что выбор языка был не религиозным, а практическим. Автору не хотелось писать GUI на C или C++, Python оказался неудобен для крупного статически проверяемого приложения, а D дал знакомую после Java модель, нативную скорость и нормальные GTK-привязки. Это важный критерий выбора технологии: не «самый модный язык», а язык, на котором конкретный автор может быстро и спокойно делать продукт.
\nЕсли хочется повторить такой путь, начинать стоит не с большого терминала, а с маленькой GTK-утилиты: окно, настройки, несколько действий, сборка через DUB, упаковка и обновления. На таком проекте сразу проявятся реальные вопросы: работа с GtkD, структура проекта, взаимодействие с C-библиотеками, обработка событий и паузы GC.
\nИменно поэтому интервью полезно не только поклонникам D. Оно показывает нормальный инженерный путь: выбрать задачу, подобрать инструмент, сделать небольшую работающую версию, а затем постепенно доводить приложение до зрелого состояния.
", "readingMinutes": 19 }, { "slug": "bitrix-api-функция-для-генерации-кода-элемент", "title": "Bitrix API. Функция для генерации кода элемента из имени через транслит", "date": "2017-08-15T16:35:54+00:00", "author": "DarkRiDDeR", "categories": [ "Bitrix", "PHP" ], "cover": "/assets/illustrations/bitrix-translit-api.svg", "excerpt": "Часто в Bitrix необходимо сгенерировать код элемента из имени. Привожу пример реализации именно такой функции с помощью функции Bitrix API для транслита CUtil::translit :", "contentHtml": "Часто в Bitrix необходимо сгенерировать код элемента из имени.
\nПривожу пример реализации именно такой функции с помощью функции Bitrix API для транслита CUtil::translit:
function strToElementCode($str, $maxLength = 100) {\n\t$params = array(\n\t\t\t"max_len" => $maxLength,\n\t\t\t"change_case" => "L",\n\t\t\t"replace_space" => "_",\n\t\t\t"replace_other" => "_",\n\t\t\t"delete_repeat_replace" => "true",\n\t\t\t"use_google" => "false",\n\t\t);\n\treturn CUtil::translit($str, "ru", $params);\n}Транслитерация имени удобна, но код элемента должен оставаться уникальным. Поэтому после генерации проверяйте существующие символьные коды в инфоблоке и при совпадении добавляйте числовой суффикс. Иначе два товара с похожим названием могут получить один URL.
\ntelefon-samsung\ntelefon-samsung-2\ntelefon-samsung-3\nТакже стоит сразу нормализовать результат: привести к нижнему регистру, заменить пробелы на дефис, убрать повторяющиеся дефисы и обрезать дефис в начале или конце строки. Это мелочь, но именно такие мелочи потом портят адреса страниц.
\n$code = CUtil::translit($name, 'ru', [\n 'replace_space' => '-',\n 'replace_other' => '-',\n 'change_case' => 'L',\n]);\n\n$code = trim(preg_replace('/-+/', '-', $code), '-');\nПроверку уникальности лучше делать в том же инфоблоке, где будет создан элемент. Если сайт многоязычный или есть разделы с одинаковыми товарами, заранее решите правило: уникальность на весь инфоблок или только внутри раздела. Для SEO обычно проще и надёжнее уникальность на весь инфоблок.
\nВ итоге функция должна не просто транслитерировать строку, а возвращать готовый символьный код: чистый, нижнего регистра, без мусора и без совпадений с уже существующими элементами.
", "readingMinutes": 1 }, { "slug": "bitrix-api-создание-добавление-торгового-пре", "title": "Bitrix API. Создание (добавление) торгового предложения товара", "date": "2017-08-14T17:02:36+00:00", "author": "DarkRiDDeR", "categories": [ "Bitrix", "PHP" ], "cover": "/assets/illustrations/bitrix-offer-api.svg", "excerpt": "Хороший пример создания (добавления) торгового предложения в Bitrix через API :", "contentHtml": "Хороший пример создания (добавления) торгового предложения в Bitrix через API:
\n\nuse \\Bitrix\\Main\\Loader;\n \nif (!Loader::includeModule('iblock') || !Loader::includeModule('catalog'))\n{\n\tdie('Error loading module iblock or catalog');\n}\n \n$IBlockOffersCatalogId = 3; // ID инфоблока предложений (должен быть торговым каталогом)\n$productName = "Товар"; // наименование товара\n$offerName = "Торговое предложение"; // наименование торгового предложения\n$offerPrice = 100.50; // Цена торгового предложения\n \n \n$arCatalog = CCatalog::GetByID($IBlockOffersCatalogId);\n \n$IBlockCatalogId = $arCatalog['PRODUCT_IBLOCK_ID']; // ID инфоблока товаров\n$SKUPropertyId = $arCatalog['SKU_PROPERTY_ID']; // ID свойства в инфоблоке предложений типа "Привязка к товарам (SKU)"\n \n$obElement = new CIBlockElement();\n$arFields = array(\n 'NAME' => $productName,\n 'IBLOCK_ID' => $IBlockCatalogId,\n 'ACTIVE' => 'Y'\n);\n$productId = $obElement->Add($arFields); // добавили товар, получили ID\n \nif ($productId)\n{\n\t$obElement = new CIBlockElement();\n\t// свойства торгвоого предложения\n\t$arOfferProps = array(\n\t\t$SKUPropertyId => $productId,\n\t);\n\t$arOfferFields = array(\n\t\t'NAME' => $offerName,\n\t\t'IBLOCK_ID' => $IBlockOffersCatalogId,\n\t\t'ACTIVE' => 'Y',\n\t\t'PROPERTY_VALUES' => $arOfferProps\n\t);\n \n\t$offerId = $obElement->Add($arOfferFields); // ID торгового предложения\n \n\tif ($offerId)\n\t{\n\t\t// добавляем как товар и указываем цену\n\t\t$catalogProductAddResult =\tCCatalogProduct::Add(array(\n\t\t\t\t"ID" => $offersId,\n\t\t\t\t"VAT_INCLUDED" => "Y", //НДС входит в стоимость\n\t\t\t));\n\t\tif ($catalogProductAddResult && !CPrice::SetBasePrice($offerId, $offerPrice, "RUB"))\n\t\t\tthrow new Exception("Ошибка установки цены торгового предложения \\"{$offerId}\\"");\n\t\telse\n\t\t\tthrow new Exception("Ошибка добавления параметров торгового предложения \\"{$offerId}\\" в каталог товаров");\n\t}\n\telse\n\t{\n\t\tthrow new Exception("Ошибка добавления торгового предложения: " . $obElement->LAST_ERROR);\n\t}\n}\nelse\n{\n\tthrow new Exception("Ошибка добавления товара: " . $obElement->LAST_ERROR);\n}После добавления торгового предложения проверьте три связи: привязку к товару, цены и остатки. В Bitrix предложение может успешно создаться, но не появиться в публичной части, если не заполнены обязательные свойства SKU, не сохранён товарный каталог или не обновлены кеши.
\nМинимальная последовательность такая: создать элемент торгового предложения в инфоблоке SKU, записать связь с товаром, сохранить параметры товара, установить цену, установить количество. Если хотя бы один шаг пропущен, предложение может быть видно в админке, но не попадёт в публичный каталог.
\nCModule::IncludeModule('iblock');\nCModule::IncludeModule('catalog');\n\n$offerId = $el->Add($fields);\nCCatalogProduct::Add([\n 'ID' => $offerId,\n 'QUANTITY' => 10,\n]);\nCPrice::SetBasePrice($offerId, 990, 'RUB');\nНа боевом проекте обязательно логируйте результат Add и текст ошибки LAST_ERROR. Bitrix часто молча возвращает false, а настоящая причина находится именно там: обязательное поле, неправильный инфоблок, неверный код свойства или недостаточные права.
", "readingMinutes": 2 }, { "slug": "firefox-увеличение-ожидания-загрузки-timeout-стр", "title": "Firefox. Увеличение ожидания загрузки (timeout) страницы.", "date": "2017-08-04T16:24:11+00:00", "author": "DarkRiDDeR", "categories": [ "Firefox", "Настройки" ], "cover": "/assets/illustrations/firefox-timeout.svg", "excerpt": "При выполнении каких-либо ресурсоёмких операций на интернет ресурсах, когда время отклика может переваливать за минуты, а то и больше, браузеры прерывают соединение и сообщают, что сервер не доступен. К таким операция, например, можно отнести выгрузку дампа ба", "contentHtml": "При выполнении каких-либо ресурсоёмких операций на интернет ресурсах, когда время отклика может переваливать за минуты, а то и больше, браузеры прерывают соединение и сообщают, что сервер не доступен.
\nК таким операция, например, можно отнести выгрузку дампа базы данных в phpmyadmin, парсинг больших файлов и прочее.
\nМаксимальное время ожидания (timeout) в браузерах по умолчанию обычно настроено на несколько минут. В браузере Firefox, если я не ошибаюсь, это значение равно 5 минут.
\nНам, конечно, прерывать соединение не хочется, т.к. проводимая операция может прерваться, и, следовательно, возникнуть дальнейшие проблемы из-за неправильного выполнения операции.
\nБлаго данную настройку в Firefox можно изменить. Для этого:
\nP.S. Иногда данная опция оказывается даже очень полезной. В небезызвестном браузере Chrome данный параметр для настройки отсутствует.
\nУвеличение ожидания помогает при разовых административных операциях, но не должно маскировать медленный сайт. Если обычная страница требует минуты, правильнее вынести задачу в фон: очередь, cron, прогресс-бар и отдельная проверка статуса.
\nВажно не путать разные таймауты. Ожидание HTTP-ответа — это одно, долго выполняющийся JavaScript на странице — другое, а таймауты прокси или веб-сервера — третье. Если Firefox ждёт дольше, но nginx, Apache, PHP-FPM или upstream уже оборвал соединение, настройка браузера не поможет.
\nЕсли параметра в about:config нет, его можно создать вручную только тогда, когда вы понимаете, какая версия Firefox и какой именно timeout вам нужен. После завершения работы лучше вернуть настройку к обычному значению, чтобы браузер не висел слишком долго на реально недоступных сайтах.
\nИтог: увеличивать timeout можно как временный инструмент администратора. Для пользовательского сценария правильнее исправлять серверную часть, потому что долгий белый экран остаётся плохим интерфейсом даже тогда, когда браузер согласен ждать.
", "readingMinutes": 2 }, { "slug": "ошибка-php-ssl-certificate-error-unable-to-get-local-issuer-certificate", "title": "Ошибка PHP. SSL certificate error: unable to get local issuer certificate", "date": "2017-07-12T23:20:53+00:00", "author": "DarkRiDDeR", "categories": [ "PHP", "SSL" ], "cover": "/assets/illustrations/php-ssl-cacert.svg", "excerpt": "В PHP при загрузке или обмене данными с другим сервером через защищённое соединение может возникнуть ошибка: SSL certificate error: unable to get local issuer certificate Ошибка означает, что на сервере не установлен SSL сертификат. Чаще всего она наблюдается,", "contentHtml": "В PHP при загрузке или обмене данными с другим сервером через защищённое соединение может возникнуть ошибка:
\nSSL certificate error: unable to get local issuer certificate
\nОшибка означает, что на сервере не установлен SSL сертификат.
\nЧаще всего она наблюдается, когда мы ставим локальные платформы (сервера) быстрого развёртывания для веб-разработки, таких как WAMP, XAMPP и других.
\nДля решения проблемы нам необходимо установить SSL сертификат.
\nСертификат, например, можно взять отсюда (чтобы самим не генерировать ;):
\nhttps://curl.haxx.se/docs/caextract.html
\nПерезагружаем сервер. После чего ошибка должна быть решена.
\nПосле подключения файла сертификатов перезапустите веб-сервер или PHP-FPM и выполните тестовый HTTPS-запрос из того же окружения, где возникала ошибка. Важно проверять не браузер, а именно PHP: CLI и веб-сервер могут использовать разные php.ini.
\nphp --ini\nphp -i | grep -E \"curl.cainfo|openssl.cafile\"\nphp -r \"var_dump(file_get_contents('https://example.com') !== false);\"\nДля cURL указывается curl.cainfo, для потоков OpenSSL — openssl.cafile. На Windows путь лучше писать полностью и без относительных директорий. Если используется не глобальный php.ini, а отдельная настройка клиента, можно передать CA bundle прямо в cURL:
\ncurl_setopt($ch, CURLOPT_CAINFO, 'C:\\php\\extras\\ssl\\cacert.pem');\nНе отключайте проверку SSL через CURLOPT_SSL_VERIFYPEER = false как постоянное решение. Это скрывает проблему и делает соединение уязвимым. Такая настройка допустима только для короткой диагностики в локальной среде, и после проверки её нужно вернуть обратно.
\nЕсли CA bundle указан верно, но ошибка осталась, проверьте цепочку сертификатов на стороне удалённого сервера. Иногда браузер открывает сайт, потому что умеет достраивать цепочку, а PHP/cURL получает неполную цепочку и честно падает. В таком случае исправлять нужно сертификаты сервера, а не PHP-код.
\nПравильный итог: актуальный cacert.pem, явно указанные curl.cainfo и openssl.cafile, перезапуск PHP и тест именно из того окружения, где работает приложение.
", "readingMinutes": 2 }, { "slug": "dconf-2017-под-капотом-мусорщика-ди-дмитрий-ол", "title": "DConf-2017. Под капотом мусорщика D (Дмитрий Ольшанский)", "date": "2017-06-24T00:48:13+00:00", "author": "DarkRiDDeR", "categories": [ "DLang", "GC" ], "cover": "/assets/illustrations/dconf-gc-cover.svg", "excerpt": "DConf-2017. Дмитрий Ольшанский. 2017 июня 14 дня. Оригинал (англ.): http://olshansky.me/gc/runtime/dlang/2017/06/14/inside-d-gc.html Перевод: Глеб Куликов Оригинал перевода: https://yadi.sk/i/dftROrt33KLww6 Небольшие правочки: DarkRiDDeR Во время проходившего ", "contentHtml": "DConf-2017. Дмитрий Ольшанский. 2017 июня 14 дня.
\nОригинал (англ.): http://olshansky.me/gc/runtime/dlang/2017/06/14/inside-d-gc.html
\nПеревод: Глеб Куликов
\nОригинал перевода: https://yadi.sk/i/dftROrt33KLww6
\nНебольшие правочки: DarkRiDDeR
Во время проходившего на конференции DConf-2017 хакатона, я самоуверенно возглавил группу из двух человек, хакающих Ди’шный сборщик мусора (GC). После нескольких часов я уже не мог избавиться от навязчивой мысли «эгей, парень, это надо бы переписать!». Так что я решил отправиться в квест в поисках лучшего мусорщика для Ди, где первым шагом стал бы более быстрый, клаcсический сборщик, отмечающий достижимые объекты (mark-sweep).
\nДля пояснения моих мотивов, я намерен описать внутренности современного сборщика, отмечая промахи архитектуры. В конце концов, надо же понять, «куды тыкать»!
\nИгнорируя излишние детали, мусорщик — это просто массив объектов пула. Каждый пул — это кусочек отображённой (mmap) памяти + немного метаданных в куче (таблиц маркирующих бит, освобождающих бит ну и так далее). Выделение памяти происходит внутри пула. Если одиночный пул не в состоянии обслужить выделение памяти, заводится новый пул. Размер пула определяется арифметической прогрессией от числа пулов (или 150% от размера выделения, смотря, что окажется больше).
\nВажно, что пулы бывают двух типов, для больших и маленьких объектов. В «маленьких» пулах выделяются объекты размером до 2 Кб, всё остальное обслуживается «большими» пулами. На самом деле, маленькие пулы более интересны, так что давайте на них первым делом и взглянем.
\nПрежде всего, размер памяти для любого маленького выделения округляется до подходящего (по степеням двойки) класса размера — 16, 32, 64, 128, 256, 512, 1024, 2048. Затем для данного размера проверяется глобальный список свободных блоков и, если такового найти не получается, продолжается поиск маленького пула.
\nМаленький пул выделит новую страницу памяти и внесёт её в список свободных блоков данного размера. Вот и «вылезла» первая большая ошибка существующей архитектуры: класс размера назначается на страничной основе, поэтому нам требуется таблица (чтобы жизнь мёдом не казалась, называемая таблицей страниц), ставящая в соответствие каждому классу размера соответствующую страницу. Теперь, чтобы найти начало объекта по внутреннему указателю, мы сперва ищем страницу, к которой он принадлежит, затем ищем класс размера и, наконец, накладываем битовую маску. Более того, метаданные представляют собой кучу простых битовых таблиц, которые теперь должны покрывать страницы разного размера, поэтому на каждые 16 байт требуется примерно 7 бит независимо от размера объекта.
\nЧем была обусловлена эта архитектура? У меня на этот счёт есть две гипотезы. Первая связана с нежеланием резервировать память для недоиспользуемых пулов (что, на самом деле, не является проблемой, так как для виртуальной памяти работает ленивая фиксация). Вторая — с опасениями получить слишком много пулов, замедлив выделение и полезную маркировку. Последнее больше похоже на причину, так как во время фазы маркировки, мусорщик действительно довольно часто производит линейное сканирование по пулам и бинарный поиск для каждого предполагаемого указателя (!).
\nВот и вторая ошибка: поиск пула за log(P), где P — число пулов, и соответственно, отметка за N*log(P). Хэш–таблица могла бы малость сэкономить циклы.
\nЗавершая наш обзор маленького пула, мы также должны взглянуть на выбор классов размеров. Это третья погрешность (не ошибка, скорее, спорный выбор): размеры, следующие степеням двойки, гарантируют нам внутреннюю фрагментацию, доходящую до 50 %. Современные выделители, подобные jemalloc’у, обычно предлагают ещё один класс, размер которого лежит между степеням двойки. Деление по модулю на константу, не являющуюся степенью двойки, несколько медленнее одиночного битового, но и вполне приемлемо.
\nДавайте взглянем на пулы больших объектов. Первое, что нужно заметить, так это гранулярность страницы памяти (4 КБ) как для метаданных, так и собственно выделений памяти. Наборы свободных страниц внесены в один список, который линейно сканируется при каждом запросе на выделение памяти. Это четвёртая ошибка, которая, впрочем, не оказывает влияния на производительность выделения больших объектов. Для поиска начала объекта организована отдельная таблица, в которой для каждой страницы хранится индекс начала объекта, к которой он принадлежит.
\nСхема разумна до тех пор, пока не коснётся больших (более 100 МБ) выделений памяти. В этом случае, скорее всего, не удастся перераспределить память «по месту», в результате чего будет выделен новый пул и огромный объём памяти метаданных будет истрачен на всего один объект.
\nДо сих пор мы наблюдали конвейер выделения памяти, освобождение проходит примерно также. Более интересен автоматический возврат памяти, который и является смыслом сборщика мусора. Прежде всего позвольте мне отметить очевидное: во первых, сборщик мусора в Ди является консервативным, то есть, он не знает, является ли что-то указателем или нет. Во-вторых, он поддерживает финализаторы — действия, которые выполняются на объекте, прежде чем возвратить его память в общий пул. Эти два решения сильно ограничивают архитектуру сборщика.
\nС точки зрения высокого уровня, процесс сборки, как ни странно, представляет собой целостный 4-фазный процесс: подготовка — полная маркировка – подчистка — возврат памяти.
\nСтадия подготовки даёт наибольшие основания для сомнений. В сущности, для предотвращения сканирования свободной памяти, на этой стадии следовало бы скопировать биты–признаки свободных блоков в биты–признаки отметок. Однако всю малину портит то, что требуется вычислить полное свободное место, для чего перебрать списки свободных блоков. Это уже пятая(хм?) ошибка, потому что резкое распутывание бессчётного количества указателей — явно последнее, что нужно сделать во время «приостановки мира». Лучшей архитектурой было бы переворачивание битов–признаков свободных блоков во время выделения / освобождения памяти, тем более, что список свободных блоков поддерживает указатели на пул для каждого объекта, так что искать нужный пул не потребуется.
\nФаза маркировки сводится, на самом деле, к вызову MarkAll, который просто передаёт подходящие участки памяти в маркировочную функцию, заслуживающую более пристального рассмотрения.
\nСобственно, это всё, что нужно сказать о функции пометки, не считая забавных манипуляций с ограничением на стек (для того, чтобы избежать переполнения стека всё ещё пытаемся использовать выделение стека).
\nУпомянутый ранее поиск пула — далеко не единственный недостаток. Имеющее место смешение несканируемой памяти (без указателей) и нормальной памяти в одном и том же пуле, приводит к необходимости дополнительного просмотра битовой таблицы. А ведь если бы мы разделили пулы по классам размеров, можно было бы с лёгкостью избежать просмотра таблицы страниц. Не стоит даже упоминать сомнительную оптимизацию, касающуюся бита–признака «без внутреннего указателя», которая мало того, что делает код небезопасным (объект может быть сметён в мусор, хотя на него всё ещё ссылаются), но ещё и вводит несколько дополнительных проверок по критическому пути для всех больших объектов, включая возможный просмотр битовой таблицы.
\nУф, весьма запутанно. Однако, стоит иметь в виду, что фаза отметки — это воистину сердце любого сборщика.
\nПерейдём теперь к третье фазе — очистки. Ирония в том, что вопреки ожиданиям, в текущем Ди’шном сборщике очиститель вовсе не очищает списки свободных блоков. Всё, что его волнует, так это вызвать финализаторы, если таковые имеются, и установить биты–признаки «свободен» и прочие таблицы. Заметьте, что это требует поиска битов–отметок и соответственно, линейного прохода по памяти каждого пула.
\nПоследняя стадия — возврат освобождённой памяти. На самом деле, на этой стадии перестраиваются списки свободных блоков. и опять это линейный проход по (всем) пулам, но только по маленьким пулам. И опять нужен просмотр таблицы страниц для каждой страницы в пуле. И только для того, чтобы определить её размер. . . определённо, хочется плакать! Основной вопрос без ответа — зачем? Зачем лишний проход? Я долго пытался представить разумную причину, но так и не смог.
\nВозможно, слабое, но вполне возможное объяснение — «для простоты». По моему мнению, это последняя большая ошибка.
\nДо сих пор я критиковал то, что бросается в глаза. Пора перейти к тому, что попросту отсутствует.
\nКэш нитей — одно из таких больших упущений. Правда, следует помнить, что Ди’шный мусорщик родом из 2000–ых, так что это не удивительно. В любом современном распределителе есть хоть какая-то форма кэша нитей, некоторые даже пытаются поддерживать кэш для каждого процессора. Кэш работает для каждого потока, выделяя память скопом и придерживая сделанные выделения памяти на будущее. Такой подход уменьшает стоимость просмотра разделяемых структур данных кучи. Точнее говоря, мелкозернистые блокировки имеются, но не на уровне всего пула.
\nДругой пример современного подхода, безусловно ожидаемого в любом мусорщике, это параллельная отметка. Весьма популярны параллельные (или квази–паралелльные) сборщики, в которых отметка и гораздо более редкая очистка и компактификация (операция, которая преобразует топологические пространства в компактные) производятся во время выполнения нитей приложения.
\nЭто сообщение получилось гораздо длиннее и более подробное, чем бы мне хотелось. Тем не менее, сохраняется его главный посыл: Ди’шный мусорщик медленный не из-за каких–то фундаментальных ограничений, а просто потому, что почти половина решений в его реализации откровенно плоха. Действуя в том же духе легко можно было бы построить медленный и точный «поколенческий» мусорщик, просто из-за отсутствия хороших методик реализации.
\nПодводя итог, что пытается изменить моя первая попытка:
\nВторая итерация моих усилий будет сосредоточена на более вкусных вещах: кэш потока, параллельная маркировка и одновременная маркировка, использующая трюк с разветвлением (fork).
\nНа третьей итерации (если доживём), ожидаем совершенно новый дизайн, вдохновлённый коллегой immix1 — сборщик, основанный на отметки областей.
\nНа этой оптимистичной ноте я завершаю озвучивание моих планов. Предупреждаю, что собираюсь начать реализацию в Линукс–специфике, медленно доводя её до совместимости с POSIX. Поддержка Windows — отдалённая возможность.
\nДмитрий Ольшанский, исследователь и инженер–программист. Биографию см. в http://olshansky.me/about.html
\nСтатья важна не только для разработчиков компилятора. Она показывает, почему аллокатор и сборщик мусора нельзя оценивать одной фразой «GC медленный». Нужно смотреть на классы размеров, поиск пулов, стоимость маркировки и то, сколько работы выполняется во время остановки мира.
\nДля прикладного D-кода вывод такой: держите горячие циклы предсказуемыми, не создавайте лишние временные объекты в tight-loop и измеряйте паузы. Если участок действительно критичен, D позволяет локально управлять GC и заменить поток мелких аллокаций заранее подготовленными буферами.
\nimport core.memory : GC;\n\nGC.disable();\nscope(exit) GC.enable();\n\n// горячий участок без лишних временных объектов\nОтключать GC нужно аккуратно. Это не волшебная оптимизация, а договор: пока сборщик выключен, программа не должна бесконтрольно накапливать мусор. Поэтому такой приём подходит для короткого измеримого участка, а не для всего приложения.
\nГлавный смысл доклада в том, что производительность памяти — это архитектура. Если в программе много мелких объектов, непредсказуемых ссылок и больших пауз, проблема редко решается одной настройкой. Нужно менять модель данных, жизненный цикл объектов и место, где выделяется память.
", "readingMinutes": 11 }, { "slug": "пишем-аналог-функции-php-preg_match_all-на-языке-прог", "title": "Пишем аналог функции PHP preg_match_all на языке программирования D", "date": "2017-06-17T18:53:17+00:00", "author": "DarkRiDDeR", "categories": [ "DLang", "Регулярные выражения" ], "cover": "/assets/illustrations/preg-match-all-d.svg", "excerpt": "В PHP есть очень удобная функция для глобального поиска шаблона регулярного выражения в строке preg_match_all . Давайте напишем аналогичный класс статических методов для реализации этой функции с разными флагами на языке программирования D. Описание функции: i", "contentHtml": "В PHP есть очень удобная функция для глобального поиска шаблона регулярного выражения в строке preg_match_all. Давайте напишем аналогичный класс статических методов для реализации этой функции с разными флагами на языке программирования D.
\nОписание функции:
\n\nint preg_match_all ( string $pattern , string $subject [, array &$matches [, int $flags = PREG_PATTERN_ORDER [, int $offset = 0 ]]] )Функция ищет в строке subject все совпадения с шаблоном pattern и помещает результат в массив matches в порядке, определяемом комбинацией флагов flags.
\nПосле нахождения первого соответствия последующие поиски будут осуществляться не с начала строки, а от конца последнего найденного вхождения.
Данная функцию очень удобна и чаще всего она применяется с третьим параметром, чтобы обработать полученный массив совпадений шаблона. В D подобной регулярной функции нет. Мне вообще не сильно нравятся, как реализованы регулярные выражения в D. Ну да ладно.
\nПолную документацию по функции можете посмотреть здесь http://php.net/manual/ru/function.preg-match-all.php.
\nВозможные флаги функции preg_match_all:
\nНе будем заморачиваться с реализацией через передачу константы, как в PHP, или через шаблоны в D. Создадим для каждого флага PHP данной функции аналогичные статические методы. Параметр offset оставим за бортом. Класс давайте назовём PReg. Вырисовывается следующая структура:
\n\nclass PReg\n{\n\tpublic static:\n \n\t… matchAllPatternOrder (...) {...}\n\t… matchAllSetOrder (...){...}\n\t… matchAllOffsetCapture (…) {...}\n}Как параметры будем передавать строку для поиска (тип string), регулярное выражение (тип ???) и переменную для вывода количества совпадений шаблона регулярного выражения (тип out int). Тип регулярного выражения можно посмотреть, чтобы не капаться в модуле, применив такую хитрость:
\nwriteln(typeof(regex(`\\d+`)).stringof);\nТакже как аргумент вы без проблем сможете передавать compile-time регулярное выражение (ctRegex). Для данного типа создадим алиас типа, чтобы поудобнее было.
\nФункция matchAllPatternOrder и matchAllSetOrder будет возвращать двумерный массив строк, для них тоже создадим алиас типов (мне так кажется, что путаницы меньше). MatchAllOffsetCapture пока оставим на закуску.
В итоге у нас получается:
\n\nclass PReg\n{\n\tprivate static alias typeRegex = Regex!char;\n \n\tpublic static:\t\n\talias typePatternOrder = string[];\n\talias typeSetOrder = string[];\n \n\ttypePatternOrder[] matchAllPatternOrder (string subject, typeRegex obRegex, out int count) {..}\n\ttypeSetOrder[] matchAllSetOrder (string subject, typeRegex obRegex, out int count){...}\n\t… matchAllOffsetCapture (string subject, typeRegex obRegex, out int count)\t{...}\n}Привожу пример реализованной функции matchAllPatternOrder (основные моменты пояснены ниже):
\n\ntypePatternOrder[] matchAllPatternOrder (string subject, typeRegex obRegex, out int count)\n{\n\ttypePatternOrder[] matches;\n\tauto stdMatches = matchAll(subject, obRegex);\n \n\twhile (!stdMatches.empty) {\n\t\tmatches.length++;\n\t\tmatches[0] ~= stdMatches.front.hit;\n \n\t\tfor (int i = 1; i < stdMatches.front.length; i++) {\n\t\t\tmatches.length++;\n\t\t\tmatches[count+1] ~= stdMatches.front[i];\n\t\t}\n\t\tstdMatches.popFront();\n\t\tcount++;\n\t}\n\treturn matches;\n}Реализация функции matchAllSetOrder по сути ничем сильно не отличается кроме как позициями найденных строк в массиве (она даже проще). Приведу лишь реализованный вариант:
\n\ntypeSetOrder[] matchAllSetOrder (string subject, typeRegex obRegex, out int count)\n{\n\ttypeSetOrder[] matches;\n\tauto stdMatches = matchAll(subject, obRegex);\n \n\twhile (!stdMatches.empty) {\n\t\tfor (int i = 0; i < stdMatches.front.length; i++) {\n\t\t\tmatches.length++;\n\t\t\tmatches[count] ~= stdMatches.front[i];\n\t\t}\n\t\tstdMatches.popFront();\n\t\tcount++;\n\t}\n\treturn matches;\n}Реализация функции matchAllOffsetCapture имеет свои особенности. Найденные данные функции будем хранить в двумерном массиве картежа типов Tuple!(string, «text», int, «position»), по которому можно будет получить позицию и найденую строку. Реализация:
\n\nalias typeOffsetCapture = Tuple!(string, "text", int, "position")[];\n \ntypeOffsetCapture[] matchAllOffsetCapture (string subject, typeRegex obRegex, out int count)\n\t{\n\t\ttypeOffsetCapture[] matches;\n \n\t\tauto stdMatches = matchAll(subject, obRegex);\n\t\tmatches.length = stdMatches.front.length;\n \n\t\twhile (!stdMatches.empty) {\n\t\t\tTuple!(string, "text", int, "position") match;\n\t\t\tmatch.text = stdMatches.front.hit;\n\t\t\tmatch.position = cast(int)(match.text.ptr - subject.ptr);\n\t\t\tmatches[0].length = count+1;\n\t\t\tmatches[0][count] = match;\n\t\t\tfor (int i = 1; i < stdMatches.front.length; i++) {\n\t\t\t\tmatches[i].length = matches[0].length;\n\t\t\t\tmatch.text = stdMatches.front[i];\n\t\t\t\tmatch.position = cast(int)(stdMatches.front[i].ptr - subject.ptr);\n\t\t\t\tmatches[i][count] = match;\n\t\t\t}\n\t\t\tstdMatches.popFront();\n\t\t\tcount++;\n\t\t}\n\t\treturn matches;\n\t}Единственное, что здесь важно отметить, как определяются позиции, а именно:
\n\nmatch.position = cast(int)(match.text.ptr - subject.ptr);Позиция определяется через разность указателей позиций элементов массива. Также здесь выполняется приведение типа к int, так как многие процессоры уже использует 64-х битное представление, и указатели будут иметь тип long.
\nПолная реализация модуля с тестами представлена ниже:
\n\nmodule preg;\n \nimport std.string: format;\nimport std.typecons;\nimport std.regex: Regex, regex, ctRegex, matchFirst, matchAll;\n \n// analog of preg regex in php\n \nclass PReg\n{\n\tprivate static alias typeRegex = Regex!char;\n \n\tpublic static:\n \n\talias typePatternOrder = string[];\n\talias typeSetOrder = string[];\n\talias typeOffsetCapture = Tuple!(string, "text", int, "position")[];\n \n\ttypePatternOrder[] matchAllPatternOrder (string subject, typeRegex obRegex, out int count)\n\t{\n\t\ttypePatternOrder[] matches;\n\t\tauto stdMatches = matchAll(subject, obRegex);\n \n\t\twhile (!stdMatches.empty) {\n\t\t\tmatches.length++;\n\t\t\tmatches[0] ~= stdMatches.front.hit;\n \n\t\t\tfor (int i = 1; i < stdMatches.front.length; i++) {\n\t\t\t\tmatches.length++;\n\t\t\t\tmatches[count+1] ~= stdMatches.front[i];\n\t\t\t}\n\t\t\tstdMatches.popFront();\n\t\t\tcount++;\n\t\t}\n\t\treturn matches;\n\t}\n \n \n\ttypeSetOrder[] matchAllSetOrder (string subject, typeRegex obRegex, out int count)\n\t{\n\t\ttypeSetOrder[] matches;\n \n\t\tauto stdMatches = matchAll(subject, obRegex);\n \n\t\twhile (!stdMatches.empty) {\n\t\t\tfor (int i = 0; i < stdMatches.front.length; i++) {\n\t\t\t\tmatches.length++;\n\t\t\t\tmatches[count] ~= stdMatches.front[i];\n\t\t\t}\n\t\t\tstdMatches.popFront();\n\t\t\tcount++;\n\t\t}\n\t\treturn matches;\n\t}\n \n\ttypeOffsetCapture[] matchAllOffsetCapture (string subject, typeRegex obRegex, out int count)\n\t{\n\t\ttypeOffsetCapture[] matches;\n \n\t\tauto stdMatches = matchAll(subject, obRegex);\n\t\tmatches.length = stdMatches.front.length;\n \n\t\twhile (!stdMatches.empty) {\n\t\t\tTuple!(string, "text", int, "position") match;\n\t\t\tmatch.text = stdMatches.front.hit;\n\t\t\tmatch.position = cast(int)(match.text.ptr - subject.ptr);\n\t\t\tmatches[0].length = count+1;\n\t\t\tmatches[0][count] = match;\n\t\t\tfor (int i = 1; i < stdMatches.front.length; i++) {\n\t\t\t\tmatches[i].length = matches[0].length;\n\t\t\t\tmatch.text = stdMatches.front[i];\n\t\t\t\tmatch.position = cast(int)(stdMatches.front[i].ptr - subject.ptr);\n\t\t\t\tmatches[i][count] = match;\n\t\t\t}\n\t\t\tstdMatches.popFront();\n\t\t\tcount++;\n\t\t}\n\t\treturn matches;\n\t}\n}\n \nunittest\n{\n\timport std.stdio;\n\twriteln("test PReg.matchAll");\n \n\tint count;\n \n\tauto matches1 = PReg.matchAllPatternOrder(`one two`, regex(`(\\w)(\\w)\\w+`), count);\n\tassert(matches1[0][0] == `one`);\n\tassert(matches1[0][1] == `two`);\n\tassert(matches1[1][0] == `o`);\n\tassert(matches1[1][1] == `n`);\n\tassert(matches1[2][0] == `t`);\n\tassert(matches1[2][1] == `w`);\n\tassert(count == 2);\n \n\tauto matches2 = PReg.matchAllSetOrder(`one two`, regex(`(\\w)(\\w)\\w+`), count);\n\tassert(matches2[0][0] == `one`);\n\tassert(matches2[0][1] == `o`);\n\tassert(matches2[0][2] == `n`);\n\tassert(matches2[1][0] == `two`);\n\tassert(matches2[1][1] == `t`);\n\tassert(matches2[1][2] == `w`);\n\tassert(count == 2);\n \n\tauto matches3 = PReg.matchAllOffsetCapture(`one two`, ctRegex!(`(\\w)(\\w)\\w+`), count);\n\tassert(matches3[0][0].text == "one");\n\tassert(matches3[0][0].position == 0);\n\tassert(matches3[0][1].text == "two");\n\tassert(matches3[0][1].position == 4);\n \n\tassert(matches3[1][0].text == "o");\n\tassert(matches3[1][0].position == 0);\n\tassert(matches3[1][1].text == "t");\n\tassert(matches3[1][1].position == 4);\n \n\tassert(matches3[2][0].text == "n");\n\tassert(matches3[2][0].position == 1);\n\tassert(matches3[2][1].text == "w");\n\tassert(matches3[2][1].position == 5);\n\tassert(count == 2);\n}Надеюсь, что из этой статьи вы подчеркнули для себя что-то полезное и интересное. Всем спасибо!
\nПосле реализации флагов обязательно добавьте тесты на три случая: нет совпадений, одно совпадение с группами и несколько совпадений с позициями. Именно на позициях чаще всего появляются ошибки, потому что PHP возвращает смещения в исходной строке, а не в подстроке после очередного поиска.
\nassert(matchAll(r\"(w+)=(d+)\", \"a=1 b=22\").length == 2);\nassert(matchAll(r\"z+\", \"abc\").empty);\nВ D стоит явно решить, какой формат результата нужен. PHP поддерживает несколько режимов: PREG_PATTERN_ORDER, PREG_SET_ORDER и вариант с PREG_OFFSET_CAPTURE. Если пытаться сделать всё сразу, код быстро станет запутанным. Лучше сначала реализовать простой режим, затем режим группировки по совпадениям, и только после этого добавить позиции.
\nstruct MatchPart\n{\n string text;\n ptrdiff_t offset;\n}\n\nalias MatchSet = MatchPart[][];\nОтдельно проверьте Unicode. Если шаблон и строка содержат кириллицу, важно понимать, что именно возвращается в offset: позиция в байтах или позиция в символах. PHP для PREG_OFFSET_CAPTURE возвращает байтовое смещение. Если в D вы хотите повторить поведение PHP, нужно документировать именно байтовый offset, иначе результаты будут отличаться.
\nФинальный класс должен иметь маленькую поверхность API: метод без offset, метод с offset и понятный enum для порядка результата. Тогда аналог preg_match_all будет не просто копией PHP-функции, а удобным D-инструментом, который предсказуемо работает в тестах.
", "readingMinutes": 7 }, { "slug": "новый-движок-ctfe", "title": "Dlang. Новый движок CTFE", "date": "2017-05-26T11:51:22+00:00", "author": "DarkRiDDeR", "categories": [ "DLang", "Compile-time" ], "cover": "/assets/illustrations/ctfe-engine.svg", "excerpt": "В течение последних 9 месяцев велась работа над проектом под названием NewCTFE, в котором переписываются методы выполнения функций времени компиляции (СTFE) . СTFE считается одной из технологий способных изменить D. Как следует из названия, CTFE позволяет комп", "contentHtml": "В течение последних 9 месяцев велась работа над проектом под названием NewCTFE, в котором переписываются методы выполнения функций времени компиляции (СTFE). СTFE считается одной из технологий способных изменить D.
\nКак следует из названия, CTFE позволяет компилятору выполнять некоторые функции, когда он компилирует исходный код, в котором реализованы функции. Пока все аргументы функции доступны во время компиляции, а функция чиста (не имеет побочных эффектов), тогда функция квалифицируется как CTFE, и компилятор заменяет вызов функции результатом.
\n\nПоскольку это неотъемлемая часть языка, чистые функции могут быть вычислены везде, где может находиться константа времени компиляции. Простой пример можно найти в стандартном модуле std.uri, где CTFE используется для вычисления таблицы поиска. Это выглядит так:
\n\nprivate immutable ubyte[128] uri_flags = // indexed by character\n({\nubyte[128] uflags;\n// Compile time initialize\nuflags['#'] |= URI_Hash;\nforeach (c; 'A' .. 'Z' + 1)\n{\nuflags[c] |= URI_Alpha;\nuflags[c + 0x20] |= URI_Alpha; // lowercase letters\n}\nforeach (c; '0' .. '9' + 1) uflags[c] |= URI_Digit;\nforeach (c; ";/?:@&=+$,") uflags[c] |= URI_Reserved;\nforeach (c; "-_.!~*'()") uflags[c] |= URI_Mark;\nreturn uflags;\n})();Вместо заполнения таблицы магическими значениями используется простой экспрессивный литерал функции. Это намного проще понять и отладить, чем некоторые непрозрачные статические массивы. ({ запускает функцию-литерал, а }) закрывает ее. () в конце говорит компилятору немедленно вызвать этот литерал, чтобы uri_flags стал результатом литерала.
\nФункции выполняются только во время компиляции, если они необходимы. Uri_flags в приведенном выше фрагменте объявляется в области видимости модуля. Когда переменная области видимости модуля инициализируется таким образом, инициализатор должен быть доступен во время компиляции. В этом случае, поскольку инициализатор является функциональным литералом, будет предпринята попытка выполнить CTFE. Этот конкретный литерал не имеет аргументов и является чистым, поэтому попытка выполнена успешно.
\nБолее подробное обсуждение CTFE смотрите в статье H. S. Teoh D Wiki.
\nКонечно, подобный метод может быть применен и к более сложным проблемам. Например, std.regex можно использовать со специализированным автоматом для выполнения регулярного выражения во время компиляции с использованием CTFE. Однако, как только std.regex используется с CTFE для нетривиальных паттернов, то время компиляции может стать чрезвычайно длительным (в D все, что занимает больше секунды при компиляции — избыточность :)). В конце концов, по мере усложнения паттернов, у компилятора появится нехватка памяти, и, возможно, произойдёт крах всей системы.
\nПричина этого может крыться в текущей архитектуре интерпретатора CTFE. Это интерпретатор AST (абстрактного синтаксического дерева) — это означает, что он интерпретирует AST во время его обхода. Чтобы представить результат интерпретируемых выражений, он использует классы узлов DMD в AST. Это означает, что на каждое вновь встреченное выражение будет выделено один или несколько узлов AST. В ограниченном цикле интерпретатор может легко создать более 100.000.000 узлов и использовать несколько гигабайт оперативной памяти. Это может быстро израсходовать память.
\nВ issue 12844 имеет место проблема, что std.regex занимает более 16 ГБ ОЗУ для одного шаблона. Также есть issue 6498 , которая показывает, что выполняется простой от 0 до 10.000.000 узлов во время выполнения CTFE и что приводит к критичной нехватки памяти.
\nПростое освобождение узлов не устраняет проблему, так мы точно не знаем, какие узлы необходимо освободить, и что cделает весь компилятор очень медленным из-за сборщика мусора. К счастью, есть еще один подход, который не выделяет память для каждого вновь встреченного выражения. Он включает в себя компиляцию функции в виртуальную ISA (архитектуру набора инструкций). Эта виртуальная ISA, также известная как байт-код, затем передается выделенному интерпретатору для этой ISA (в случае, когда виртуальный ISA совпадает с ISA хоста, мы называем его JIT (Just in Time) интерпретатором).
\nПроект NewCTFE занимается реализацией такого интерпретатора байт-кода. Написание фактического интерпретатора (эмулятора CPU для виртуального CPU/ISA) достаточно просто. Однако компиляция кода для виртуального ISA выполняется в точности так же, как и при компиляции его в реальном ISA (хотя виртуальная ISA имеет дополнительное преимущество, которое может быть расширено для индивидуальных потребностей, но это затруднит выполнение JIT позже). Вот почему потребовался всего месяц, чтобы пучить первые простые примеры, работающие на новом движке CTFE, и почему немного более сложные из них все еще не работают даже после 9 месяцев разработки. В конце статьи вы найдете примерный график выполненной к настоящему времени работы (см. оригинал).
\nЯ буду выступать с презентацией на DConf 2017, где я расскажу о своем опыте внедрения движка и объясню некоторые технические детали, особенно относительно компромиссов и архитектурных решений, которые я применил. Текущая оценка состоит в том, что версия 1.0 не будет реализована к тому времени, но я буду заниматься разработкой, пока не закончу данный проект.
\nТе, кто хочет отслеживать разработку, могут сделать это на форуме D. В будущем я планирую написать еще одну статью о некоторых технических деталях реализации. К тому времени, я надеюсь, что следующий список поможет пролить свет на то, как много работы по реализации NewCTFE.
\nДанная статья является переводом статьи Штефана Коха, который является разработчиком sqlite-d, встроенного пакета в D для работы с sqlite, также он внес свой вклад в такие проекты, как SDC (Stupid D Compiler) и vibe.d. Он также был ответственен за 10% -ное повышение производительности в текущей реализации CTFE в D и в настоящее время пишет новый движок CTFE.
\nОригинал статьи смотрите по ссылке The D Blog/The New CTFE Engine.
\nCTFE полезен там, где результат можно посчитать один раз во время компиляции: таблицы констант, парсинг небольших DSL, генерация однотипного кода, предварительная подготовка строк и проверка инвариантов. Но не стоит превращать компиляцию в полноценный runtime. Чем проще входные данные и чем меньше побочных эффектов, тем легче будет сопровождать проект.
\nenum table = buildLookupTable();\n\nstatic assert(table.length == 256);\nХороший кандидат для CTFE — функция, которая зависит только от литералов, типов и конфигурации сборки. Плохой кандидат — код, которому нужен ввод-вывод, сеть, текущее время, состояние окружения или большая внешняя база данных. Такой код лучше оставить в runtime или вынести в отдельный генератор, который запускается до сборки.
\nДля контроля качества CTFE-кода полезно держать рядом static assert. Он сразу показывает, что вычисление действительно произошло на этапе компиляции и вернуло ожидаемый результат. Если compile-time функция стала слишком сложной, её стоит разбить на маленькие чистые функции: так компилятору проще, и человеку проще понять, что происходит.
\nПрактический вывод такой: CTFE в D — мощный инструмент, но сила его не в магии, а в переносе предсказуемых вычислений из runtime в compile-time. Используем его для констант, генерации и проверок. Не используем его как замену обычной программе.
", "readingMinutes": 6 }, { "slug": "особенности-vibe-d", "title": "Особенности Vibe.d", "date": "2017-05-23T17:55:03+00:00", "author": "DarkRiDDeR", "categories": [ "DLang", "Vibe.d" ], "cover": "/assets/illustrations/vibe-cover.svg", "excerpt": "Основная идея vibe.d заключалась в том, чтобы использовать быструю и ресурсоемкую асинхронную модель ввода-вывода (AIO) и сделать ее удобной в использовании. Некоторые другие программные платформы, такие как node.js, напрямую отображают интерфейс AIO с использ", "contentHtml": "Прежде всего, в node.js часто становится утомительным и запутанным писать последовательности кода (например, выполнять несколько последовательных запросов к базе данных). На каждом шаге будет представлен новый callback-вызов (обратный) с новой областью, callback-вызовы ошибок часто приходится обрабатывать отдельно. Это часто является причиной того, что возникает соблазн выполнить слабую обработку ошибок.
\nДругим следствием асинхронных callback-вызовов является отсутствие значимого стека вызовов. Мало того, что это может усложнить отладку, но такие функции, как исключения, не могут эффективно использоваться в такой среде.
\nПодход vibe.d заключается в использовании асинхронного ввода-вывода под оболочкой, но в то же время кажется, что все операции были синхронными и блокирующими, как и обычные операции ввода-вывода.
\nЭто делает возможным поддержку для так называемых волокон (также часто называемых совместными подпрограммами). Волокна ведут себя как потоки, просто они фактически работают в одном потоке. Как только бегущее волокно вызывает специальную функцию yield(), она возвращает управление функции, которая запустила волокно. Затем волокно может быть возобновлено в точно таком же положении и с тем же состоянием, которое было у него при вызове yield(). Таким образом, волокна могут мультиплексироваться вместе, выполняться квазипараллельно и максимально использовать каждую емкость нитей.
\nВсе это обычно скрыто за функциями API vibe.d, так что все кажется простой работой с обычными потоками и блокировкой операций. Все блокирующие функции, такие как sleep() или read(), останавливают работу, когда им необходимо ждать, и возобновляют её при возникновении события.
\nИспользуя эти инструменты, основанные на событиях, характер AIO полностью скрыт от пользователя библиотеки. Он также прекрасно сочетается со встроенной поддержкой многопоточности. Все параллельные операции выполняются с использованием так называемых задач. Каждая задача выполняется внутри одного волокна и мультиплексируется вместе с другими задачами, которые выполняются одновременно. Пользователю инструментария обычно нет видимой разницы между задачей и потоком.
\nСказав все это, vibe.d может также использоваться без волокон и без цикла обработки событий, если это необходимо, наподобие стандартной блокирующей модели ввода-вывода.
\nВстроенный балансировщик нагрузки, способный динамически создавать, запускать и тестировать новые процессы после того, как код был изменен, прежде чем переключать их в главный поток. Это позволяет осуществлять «бесшовные» и с низким уровнем риска изменения в работающих системах.
\nОбычно обработка исключений ограничивается локальными обработчиками ошибок в средах, основанных на событиях, так как невозможно поместить последовательность событий в блок try-catch, потому что каждая операция создает новую область, когда задействованы обратные вызовы. С другой стороны, vibe.d с его волоконно-ориентированным подходом обладает полной поддержкой обработки исключений.
\nВолокно ведет себя почти так же, как поток относительно стека: один согласованный стек вызовов существует во всех событиях, которые обрабатываются последовательно. Таким образом, становится возможно естественным образом использовать обработку исключений, и особенно в среде веб-служб, где исключения являются идеальной формой обработки ошибок.
\nИсключения имеют чрезвычайно полезную функцию, которую едва можно игнорировать. Таким образом, становится гораздо труднее создать трудноотлавливаемые ошибки и дыры в безопасности, непреднамеренно игнорируя условия ошибки. Любые неперехваченные исключения автоматически генерируют страницу ошибок и записывают всю полезную информацию в случае HTTP-сервисов.
\nЕсть также средства для автоматической генерации JSON / REST или HTML-интерфейсов на основе форм из классов D, что устраняет много работы и потенциальных ошибок из-за избежания стандартного кода. Генератор интерфейса REST поддерживает генерацию как сервера, так и клиентской части, и как таковой может использоваться как удобный механизм RPC.
\nСовместимость существующих драйверов легко сделать благодаря блокирующей природе API vibe.d. Единственное, что нужно сделать в большинстве случаев – заменить вызовы сокетов (send (), recv (), connect () и т. д.) соответствующими функциями vibe.d. В блоге дается обзор того, что нужно сделать, используя MySQL в качестве примера.
\nКонечно, «сырые» (необработанные) TCP/UDP и доступ к файлам поддерживаются набором инструментальных средств для включения пользовательских протоколов и форматов файлов. I/O происходит через интерфейс блокирующего потока, который эффективно скрывает тот факт, что базовые операции фактически основаны на событиях.
\nПомимо операций ввода-вывода поддерживаются все обычные инструменты для общих задач программирования:
\nВ отличие от большинства других сред, поддерживающих асинхронный ввод-вывод, vibe.d полностью интегрируется с циклом событий пользовательского интерфейса, поэтому его можно использовать для реализации приложений с графическим интерфейсом пользователя.
\nДля Windows существует собственная реализация драйвера событий (включается с VibeWin32Driver), которая использует функцию MsgWaitForMultipleObjectsEx для обработки оконных сообщений вместе с событиями ввода/вывода или параллелизма. Для систем, работающих под управлением X11, можно использовать createFileDescriptorEvent для прослушивания связи дисплея вместо использования XNextEvent.
\nИспользование асинхронного ввода-вывода имеет ряд преимуществ, главным из которых является то, что накладные расходы памяти намного ниже. Это связано с тем, что для обработки произвольного количества одновременных операций необходим только один аппаратный поток. Поток обычно остается в ожидании событий и разбужен операционной системой сразу же после поступления новых данных, установления нового соединения или возникновения ошибки. После каждой инициированной операции блокировки (например, записи данных в сокет) поток вернется в спящий режим и позволит операционной системе выполнить операцию в фоновом режиме.
\nДругим важным аспектом для скорости является то, что, поскольку требуется только один или несколько потоков, часто можно сохранить много дорогих контекстных переключателей потока. Однако, если приложение должно выполнять много вычислений помимо операций ввода-вывода, vibe.d имеет полную поддержку многопоточности, чтобы полностью использовать многоядерный процессор системы.
\nПоскольку использование модели на основе асинхронных событий часто больше применяется для реализации приложений, волокна используются вместе с планировщиком на основе событий, чтобы создать впечатление классических вызовов блокировки и многопоточности, тем самым скрывая дополнительные сложности и сохраняя при этом эксплуатационные характеристики асинхронности ввода-выводы.
\nХотя производительность, как правило, очень высока в однопоточном приложении из-за использования асинхронных операций ввода-вывода и волокон, есть приложения, которые могут извлечь большую пользу из использования нескольких ядер. Vibe.d поддерживает несколько способов использования этой дополнительной вычислительной мощности, оставляя решение по архитектуре потоков программисту.
\nПомимо разрешения использовать потоков D низкого уровня, предоставляется пул потоков, который используется для любой задачи, запущенной с помощью runWorkerTask вместо runTask. Это в основном полезно для выполнения вычислительно дорогостоящих операций, таких как декодирование изображений, наряду с обычным потоком программ, но оно также может быть использовано для лучшей производительности тяжелых задач ввода-вывода в некоторых случаях.
\nБиблиотека очень гибкая в том, что многопоточность может быть использована, и она гарантирует, что не могут возникнуть условия «низкоуровневых гонок», если не используются небезопасные операции, такие как cast () или __gshared переменные. В следующем списке показаны некоторые типичные архитектуры потоков:
\nHTTP-серверу (а также любому другому серверу на базе TCP) можно поручить обработку входящих соединений по рабочим потокам пула потоков, а не по основному потоку. Для приложений, которым не требуется разделять состояние между различными соединениями в этом процессе, это может увеличить линейное число запросов в секунду по количеству ядер в системе. Эта функция активируется с использованием настроек HTTPServerOption.distribute или TCPListenOptions.distribute.
\nВычислительные дорогостоящие задачи могут быть выгружены из основного потока, выполнив их с помощью runWorkerTask. Любая такая задача будет выполнена в пуле потоков. Этот подход полезен в тех случаях, когда приложение требует выполнения таких дорогостоящих задач, поскольку оно позволяет чистому выполнению операций ввода-вывода оставаться без изменений при большой вычислительной нагрузке. Вероятно, наиболее распространенным примером такой задачи является обработка/декодирование/кодирование изображений.
\nНормальные потоки D также могут использоваться вместе с vibe.d. Это важно, когда используются потоковые примитивы стандартной библиотеки D. Помимо основных потоков D, это std.parallelism и std.concurrency. Обратите внимание, что части стандартной библиотеки D не следует смешивать с функциями ввода-вывода vibe.d, поскольку они блокируют цикл обработки событий. В частности, функции передачи сообщений в std.concurrency_* в настоящее время несовместимы с циклом событий vibe.d и должны быть заменены функциями vibe.core.concurrency.
\nОбратите внимание, что vibe.d также включает в себя экспериментальную библиотеку поддерживающую изолированные и ограниченные ссылки. Это позволяет передавать изменяемые данные между потоками без использования мьютексов или аналогичных средств для синхронизации доступа к данным. Это особенно полезно при передаче данных, используемых в рабочих задачах между рабочим потоком и основным потоком.
\nНекоторые заметные отличия перечислены здесь, но есть много дополнительных вещей, которые выходят за рамки этого текста. Смотрите страницу функций D или книгу Programming in D для более тщательного обзора.
\nМетопрограммирование в D чрезвычайно эффективно. Шаблоны (сравнимые с шаблонами C ++) поддерживают параметры variadic, а также параметры строк и параметры-псевдонимы (alias) – это функции, которые обеспечивает очень удобные интерфейсы во время компиляции.
\nМощность языка дает возможность выполненять обычные функций во время компиляции, а также возможность компилировать строку, заданную во время компиляции как код D. Эта возможность похожа на функцию eval () в JavaScript: результат статически компилируется в машинный код (и без последствий безопасности eval ()).
\nЭти функции вместе с возможностью чтения файлов во время компиляции позволили создать мощный синтаксический анализатор шаблонов Diet.
\nСреда выполнения D предлагает встроенную сборку мусора, которая предоставляет некоторые интересные возможности, помимо очевидного преимущества отсутствия необходимости ручного управления памятью и связанного с этим риска утечек памяти и опасных потеряных ссылок.
\nВ частности, он позволяет работать с неизменяемыми данными, такими как строки или объекты, на которые можно безопасно ссылаться и передавать между потоками без каких-либо издержек синхронизации. При передаче данных между потоками статически закрепляется, что данные безопасны для ссылок из нескольких потоков. Это позволяет избежать целого класса трудно воспроизводимых и отслеживаемых ошибок, часто встречающихся в многопоточном коде многих других языков.
\nФункции, также известные из некоторых скриптовых и функциональных языков, а также в C # и некоторых других – замыкания и лямбда-функции. Они позволяют указывать функции обратного вызова очень компактным и читаемым способом. В свою очередь, они, как правило, оказывают сильное влияние на архитектуру API, так как они могут сделать вычислительно эффективные или безопасные API-интерфейсы действительно переносимыми (или даже приятными) в использовании.
\nСвойства – это функции, которые выглядят как поля класса или структуры. Они используются в API для получения логического и интуитивно понятного интерфейса без всякого рода ограничений и дополнительной типизации, необходимой для нормальных вызовов функций.
\nМассивы поддерживают встроенный функциональные возможности выполнения срезов с очень естественным синтаксисом. Совместно со сборщиком мусора они обеспечивают чрезвычайно быструю реализацию синтаксического анализатора строк. Данные операции безопасны (проверяются границы) и легко читаются.
\nСинтаксис в целом очень чистый и по смыслу – особенно в сравнение с его, возможно, самым близким родственником, C ++. Но в этом отношении также не нужно бояться современных скриптовых языков, таких как Ruby и Python. Помимо явной необходимости указывать явный типизации типы могут быть приведены автоматически при помощи оператора auto. Вы обнаружите, что использование auto работает предсказуемо, почти без промахов, и что разработка на D очень эффективна.
\n
\nОднако в vibe.d шаблоны могут содержать встроенный код D. Шаблоны читаются и анализируются, пока приложение компилируется и оптимизируется. Для них создается код на D. Это означает, что накладные расходы времени выполнения для этих шаблонов отсутствуют – не осуществляется доступ к диску, не выполняется синтаксический анализ, и нет необходимости в копировании строк, поскольку все это уже будет сделано до того, как приложение будет запущено.
\nВ то же время вы можете программировать на том же языке и остальную часть приложения, что обеспечивает очень последовательную разработку.
Перевод http://vibed.org/features
\nvibe.d хорошо подходит для небольших и средних веб-сервисов на D, где важны неблокирующий ввод-вывод, простая маршрутизация и единая экосистема DUB. Начать можно с минимального HTTP-сервера, затем добавить Diet-шаблоны, MongoDB или Redis и вынести повторяющиеся части в отдельные модули.
\nshared static this()\n{\n auto settings = new HTTPServerSettings;\n settings.port = 8080;\n listenHTTP(settings, &handleRequest);\n}\nПри проектировании сервиса главное не забывать, что удобный синтаксис не отменяет асинхронную модель. Долгие CPU-задачи нельзя выполнять прямо в обработчике запроса, иначе они будут мешать обработке ввода-вывода. Для таких задач лучше использовать отдельные worker-потоки, очередь или заранее подготовленные данные.
\nЕсли проект требует большого количества готовых интеграций, заранее проверьте наличие пакетов в DUB и состояние нужных драйверов. Если же нужна компактная серверная часть на D, vibe.d остаётся прямым и понятным выбором: один язык, быстрый нативный код, нормальная маршрутизация, шаблоны и асинхронный ввод-вывод.
", "readingMinutes": 13 }, { "slug": "вирус-самопроизвольный-запуск-брауз", "title": "Вирус. Самопроизвольный запуск браузера и перенаправление на вредоносные сайты.", "date": "2017-05-20T00:07:00+00:00", "author": "DarkRiDDeR", "categories": [ "Windows", "Вирусы" ], "cover": "/assets/illustrations/cover-virus.svg", "excerpt": "Недавно столкнулся с такой проблемой: браузер через приблизительно одинаковые промежутки времени сам запускался и открывал различные рекламные и вредоносные сайты. «О! Нет! Это вирус!» – воскликнули с ужасом в сердцах. Все мы напуганы зловредами, которые могут", "contentHtml": "Недавно столкнулся с такой проблемой: браузер через приблизительно одинаковые промежутки времени сам запускался и открывал различные рекламные и вредоносные сайты.
\n\n«О! Нет! Это вирус!» – воскликнули с ужасом в сердцах.
\nВсе мы напуганы зловредами, которые могут нанести ощутимый вред, особенно на фоне недавней атаки вируса шифровальщика WannaCry, который широко разгулялся и нанёс принёс ощутимые убытки большому числу компаний и организаций. Но в нашем случае всё не так страшно.
\nДанный вирус практически никакой угрозы не представляет, он даже вирус в кавычках. Основной его вред в том, что по большему счёту лишь надоедает и отвлекает. Сидишь ли делаешь работу, или может отдыхаешь, смотришь любимый сериал, и постоянно запускается браузер с каким-то левым сайтом, на котором находится нелицеприятный контент. Это начинает довольно сильно мешать.
\nПосле чего уже начинаешь задумываться. А какого фига данный вирус попал ко мне на компьютер? Как его удалить? Основная причина попадания таких вирусов – это установка какого-либо пиратского контента. Но бог с ним, как он к нам попал. Теперь его как-то надо удалять.
\nМы сначала запускаем свой любимый установленный на нашем компьютере антивирус. У меня лично стоит ESET NOD32 Smart Security (рекомендую). Но сканирование ничего не даёт. Что же делать? Порывшись в закоулках ОС Windows я нашёл причину этой проблемы. Это планировщик заданий, в котором было указано задание на запуск браузера с последующим переходом на зловредный сайт.
\nЧтобы попасть в планировщик заданий Windows запустите командную строку набором клавишь Win+R и введите команду taskschd.msc, которая и запустит его.
\nПросмотрев команды планировщика можно легко найти ту, которая запускает наш браузер (у меня Firefox).
\nВ задании видно, на какой сайт идёт перенаправление.
\nЭто по сути своей и не вирус – это лишь задание на запуск браузера с последующим перенаправлением, поэтому и антивирус прошёл мимо, не обратив внимания. Хотя добавить в антивирус подобного рода проверку было бы неплохо.
\nНещадно удаляем это задание. После чего проблема с самопроизвольным запуском браузера и последующим перенаправлением на вредоносные сайта решена.
\nВ данной статье описан один из возможных способов решения данной проблемы. Возможно в вашем случае имеет место та же проблема, и я надеюсь, что помог вам.
\nВсего хорошего!
\nПосле удаления задания перезагрузите компьютер и оставьте систему включённой на тот промежуток времени, через который раньше открывался браузер. Если окно больше не появляется, значит источник найден правильно. Если запуск повторяется, проверяем не только библиотеку планировщика, но и автозагрузку, ключи Run в реестре и расширения браузера.
\nВ планировщике заданий важно смотреть не только название задания. Название злоумышленник может сделать вполне безобидным: Update Service, Browser Check, Windows Helper. Смысл находится в действии. Если в действии указан путь к браузеру, а после него стоит адрес сайта, то перед нами как раз тот самый случай.
\nЕсли задача появляется снова, значит в системе остался установщик или служба, которая её восстанавливает. В таком случае дополнительно смотрим «Приложения и возможности», папки автозагрузки пользователя, ключи HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run и HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Run. После очистки нужно обновить антивирусные базы и выполнить полную проверку.
\nИтог простой: в этом случае лечится не браузер, а причина его запуска. Находим периодическое задание, проверяем действие, отключаем, убеждаемся, что оно не восстановилось, и только после этого удаляем. Так проблема закрывается полностью, а не до следующей перезагрузки.
", "readingMinutes": 3 }, { "slug": "компиляция-64-x-разрядных-программ-на-dmd-по", "title": "Компиляция 64-x разрядных программ на DMD под Windows x64", "date": "2017-05-04T18:13:42+00:00", "author": "DarkRiDDeR", "categories": [ "DLang", "DMD", "Windows" ], "cover": "/assets/illustrations/dmd-win64.svg", "excerpt": "Существуют проблемы совместимости с 32-x битными файлами DMD в Windows x64, так как они скомпилированы с использованием DMC фоновых программ, которые могут производить только OMF двоичные файлы. Поэтому для того, чтобы избежать проблем с подключением к внешним", "contentHtml": "Существуют проблемы совместимости с 32-x битными файлами DMD в Windows x64, так как они скомпилированы с использованием DMC фоновых программ, которые могут производить только OMF двоичные файлы. Поэтому для того, чтобы избежать проблем с подключением к внешним скомпилированным библиотекам, гораздо легче придерживаться использования 64-разрядных двоичных файлов. Это можно осуществить использованием Visual Studio компоновщика для DMD, который будет создавать совместимые COFF двоичный файлы. Еще одна проблема в том, что 32-битные DMD двоичные файлы связаны с устаревшими 32-битными библиотека WinAPI, в которых отсутствуют некоторые очень важные функции, в то время как 64-разрядные двоичные файлы должны быть связаны с 64-битными библиотеками, поставляемыми в SDK Windows. Решим эту проблему.
\nДля начала нужно установить Microsoft Windows SDK for Windows. Найти различные версии можно по ссылке http://www.microsoft.com/en-us/search/result.aspx?q=sdk%20windows. Скачиваем для своей версии Windows. Устанавливаем.
\nДиректория установки Windows SDK в Windows 7 по умолчанию — C:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\. Создадим системную переменную VCINSTALLDIR с данным адресом директории.
\nДалее нам будет предложено выбрать устанавливаемые средства разработки, нам важны только 64-x разрядные библиотеки и компилятор Visual Studio, выберем данные пункты.
\nДалее создадим ещё одну системную переменную DEV_DIR_WINSDK, в которой будет храниться путь к Windows SDK. В моё случае это C:\\Program Files\\Microsoft SDKs\\Windows\\v7.1\\.
\nТеперь необходимо отредактировать файл конфигурации компилятора DMD, он находится по пути dmd2\\windows\\bin\\sc.ini. Файл разбит на разделы, которые отмечены квадратными скобками, на потребуется изменить данные в разделе Environment64:
\nНа этом настройка завершена, попробуем скомпилировать проект (запись блога ), открываем командную строку, пишем:
\nrdmd -m64 C:\\D\\projects\\test\\hello.d
\nЕсли всё прошло удачно, будет выведено «Hello, World!». Поздравляю, теперь вы можете компилировать 64-х разрядные приложения на D для Windows.
\nОсновная информация взята с http://wiki.dlang.org/Installing_DMD_on_64-bit_Windows_7_%28COFF-compatible%29
\nГлавная мысль остаётся той же: нельзя смешивать 32-битные OMF-библиотеки и 64-битную сборку. В старых установках DMD под Windows это часто упиралось в ручную настройку sc.ini, Windows SDK и Visual Studio linker. В более новых установках официальный установщик DMD обычно делает большую часть этой настройки сам, а для нормальной работы достаточно иметь Visual Studio Build Tools или установленную Visual Studio с C++ tooling.
\nДля простой 64-битной сборки используем явную архитектуру:
\ndmd -m64 main.d -of=app.exe\ndub build --arch=x86_64\nЕсли используется старая 32-битная сборка, смотрим, какой формат объектных файлов нужен проекту. Старый OMF и MS-COFF несовместимы между собой. Для современных библиотек Windows чаще нужен MS-COFF, а для 64-битной сборки это фактически обычный путь.
\nТипичная ошибка выглядит так: код компилируется, а линковка падает на внешней библиотеке. В большинстве случаев виноват не D-код, а то, что рядом лежит библиотека другого формата или другой разрядности. Сначала проверяем toolchain и зависимости, и только потом ищем ошибку в исходниках.
", "readingMinutes": 3 } ]