Hugo Valmat's Personal Blog 2025-10-23T01:37:11+03:00 https://valmat.ru/ Печь для бани с дожигом пиролизных газов https://valmat.ru/posts/2025/10/furnace/ 2025-10-23T00:07:49+03:00 2025-10-23T00:07:49+03:00 <p>В прошлом году моя тоненькая печка начала прогорать. В какой-то момент, сидя на полке, можно было наблюдать, как в ней горит пламя прямо через прогоревшие щели.<br> До этого я уже ремонтировал в ней отвалившуюся перегородку, посадив её на уголки болтами, но диагноз был однозначным: печь пора менять.</p> <p>Найти готовую печь для бани на Кипре казалось нереальной затеей, а заказать сварку — слишком дорого.<br> Кроме того, моя текущая печь органично вписывалась в габариты моей бани, и хотелось сохранить эту гармонию.<br> Решение было принято: буду варить себе новую печь.<br> Кроме того, эта идея обещала массу удовольствия от самого процесса.</p> <p>Я сам не настоящий сварщик. Варить меня когда-то учил отец. Я немного баловался электродной сваркой, но ничего серьёзного никогда не делал. Но жизнь заставит.</p> <p>Первым делом купил MIG-проволочный сварочник в Lidl и маску — это обошлось мне примерно в 100 €.</p> <p><img src="1.jpg" alt="сварочник Lidl" /> <img src="2.jpg" alt="маска Lidl" /></p> <p>На металлобазе нашёл 4-мм листовое железо. Оно продавалось листами 2×1 м² — пришлось купить лист целиком.<br> <img src="3.jpg" alt="железо для печи" /><br> Лист оказался неподъёмным. Чтобы его поднять, пришлось сразу распилить пополам.<br> Лист обошёлся ещё примерно в 100 €. Половину этого листа я потом удачно продал за 50 €.<br> Ну и купил сразу пачку катушек со сварочной проволокой.<br> Итого себестоимость моей печи можно условно считать ~200 €.</p> <p>Немного потренировался и начал варить.</p> <p>Габариты печи я хотел сделать такими же, как у моей предыдущей, чтобы использовать кожух из нержавейки от старой печи и стеклянную дверку. И чтобы она вписалась в мою баню так же органично, как старая печка.</p> <p>Но решил добавить изюминку: дожиг пиролизных газов.<br> Рассчитал все габариты, и получилась вот такая схема:<br> <img src="scheme.jpg" alt="схема печи с дожигом пиролизных газов" /><br> В чем здесь суть: задняя стенка двойная. Полость шириной 2 см. Через неё снизу печки, из внешнего пространства, поступает воздух прямо в камеру дожига. При этом воздух разогревается от горящих дров и поступает разогретым, не остужая газовоздушную смесь, а дополнительно поджигая её. Таким образом, в камере дожига происходит дожиг и горение. Это дополнительно повышает температуру непосредственно под верхней перекладиной, на которой лежат камни.</p> <p>Важно, чтобы воздух поступал ровно на входе в камеру дожига.</p> <p>Был некоторый риск, что схема не сработает. Но я решил, что если что-то пойдёт не так, то просто закрою входное отверстие или в крайнем случае заварю его. В принципе риск был небольшой и оправданный.</p> <p>В итоге всё получилось очень даже хорошо. Об этом ниже.</p> <p>Вот немного фоток процесса изготовления:</p> <p><img src="4.jpg" alt="" /><br> <img src="5.jpg" alt="" /><br> <img src="6.jpg" alt="" /><br> <img src="7.jpg" alt="" /></p> <p>Сделал съёмную корзину для камней:<br> <img src="8.jpg" alt="" /><br> <img src="9.jpg" alt="" /></p> <p>А на верхнюю перегородку приварил рёбра жёсткости, которые одновременно являются дополнительными радиаторами:<br> <img src="10.jpg" alt="" /><br> <img src="11.jpg" alt="" /><br> <img src="12.jpg" alt="" /></p> <p>Первые испытания проводил на улице:<br> <img src="14.jpg" alt="" /></p> <p>После прогрева дыма вообще не видно:<br> <img src="15.jpg" alt="" /></p> <p>Сейчас печь уже установлена у меня в бане и выглядит вот так:<br> <img src="furnace.jpg" alt="" /></p> <p>Прогревается довольно быстро. Заметно быстрее предыдущей печи. После прогрева видимый дым полностью исчезает.<br> И, как будто, даже золы стало меньше. Или я просто стал тратить меньше дров.<br> Из минусов: камни ближе к задней стенке прогреваются заметно хуже. Основной жар — чуть дальше. Видимо, это связано с динамикой горения. Я не специалист в области теплотехники и горения. Допускаю, что мог сделать не оптимально.</p> <p>В общем, я очень доволен результатом. Это был в каком-то смысле эксперимент. И он удался.</p> Lost in the Middle. Перевод знаменитой статьи https://valmat.ru/posts/2025/08/lost-in-the-middle/ 2025-08-30T22:38:23+03:00 2025-08-30T22:38:23+03:00 <p>Ниже представлен перевод знаменитой <a class="gblog-markdown__link" href="https://arxiv.org/abs/2307.03172" >статьи Lost in the Middle</a> о том, что номинальная длина контекстного окна &ndash; это совсем не то же самое, что и эффективная.</p> <p>Ссылки:</p> <ul> <li><a class="gblog-markdown__link" href="Lost_in_the_Middle-2307.03172v3-en.pdf" >PDF оригинальной статьи</a></li> <li><a class="gblog-markdown__link" href="Lost_in_the_Middle-2307.03172v3-ru.pdf" >PDF перевода статьи</a></li> <li>Источник: <a class="gblog-markdown__link" href="https://arxiv.org/abs/2307.03172" >https://arxiv.org/abs/2307.03172</a></li> </ul> <div class="flex align-center gblog-post__anchorwrap"> <h1 id="потерянные-в-середине-как-языковые-модели-используют-длинные-контексты" > Потерянные в середине: как языковые модели используют длинные контексты </h1> <a data-clipboard-text="https://valmat.ru/posts/2025/08/lost-in-the-middle/#потерянные-в-середине-как-языковые-модели-используют-длинные-контексты" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Потерянные в середине: как языковые модели используют длинные контексты" href="#%d0%bf%d0%be%d1%82%d0%b5%d1%80%d1%8f%d0%bd%d0%bd%d1%8b%d0%b5-%d0%b2-%d1%81%d0%b5%d1%80%d0%b5%d0%b4%d0%b8%d0%bd%d0%b5-%d0%ba%d0%b0%d0%ba-%d1%8f%d0%b7%d1%8b%d0%ba%d0%be%d0%b2%d1%8b%d0%b5-%d0%bc%d0%be%d0%b4%d0%b5%d0%bb%d0%b8-%d0%b8%d1%81%d0%bf%d0%be%d0%bb%d1%8c%d0%b7%d1%83%d1%8e%d1%82-%d0%b4%d0%bb%d0%b8%d0%bd%d0%bd%d1%8b%d0%b5-%d0%ba%d0%be%d0%bd%d1%82%d0%b5%d0%ba%d1%81%d1%82%d1%8b"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p><em>Lost in the Middle: How Language Models Use Long Contexts</em></p> <p><strong>Авторы:</strong><br> Nelson F. Liu*, Kevin Lin, John Hewitt, Ashwin Paranjape, Michele Bevilacqua, Fabio Petroni, Percy Liang<br> *Работа частично выполнена в качестве стажёра в Samaya AI.</p> <div class="flex align-center gblog-post__anchorwrap"> <h2 id="аннотация" > Аннотация </h2> <a data-clipboard-text="https://valmat.ru/posts/2025/08/lost-in-the-middle/#аннотация" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Аннотация" href="#%d0%b0%d0%bd%d0%bd%d0%be%d1%82%d0%b0%d1%86%d0%b8%d1%8f"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Хотя современные языковые модели могут принимать длинные контексты в качестве входных данных, относительно мало известно о том, насколько хорошо они <em>используют</em> более длинные контексты.<br> Мы анализируем производительность языковых моделей в двух задачах, требующих идентификации релевантной информации в их входных контекстах: многодокументный вопросно-ответный анализ и извлечение ключевых значений.<br> Мы обнаруживаем, что производительность может значительно ухудшаться при изменении позиции релевантной информации, что указывает на то, что текущие языковые модели не могут надежно использовать информацию в длинных входных контекстах.<br> В частности, мы наблюдаем, что производительность часто максимальна, когда релевантная информация находится в начале или в конце входного контекста, и значительно ухудшается, когда модели должны получать доступ к релевантной информации в середине длинных контекстов, даже для моделей с явно длинным контекстом.<br> Наш анализ дает лучшее понимание того, как языковые модели используют свой входной контекст, и предлагает новые протоколы оценки для будущих моделей с длинным контекстом.</p> <hr> <div class="flex align-center gblog-post__anchorwrap"> <h2 id="введение" > Введение </h2> <a data-clipboard-text="https://valmat.ru/posts/2025/08/lost-in-the-middle/#введение" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Введение" href="#%d0%b2%d0%b2%d0%b5%d0%b4%d0%b5%d0%bd%d0%b8%d0%b5"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p><img src="figures/figure1.png" alt="U-образная кривая производительности" /> <em>Изменение местоположения релевантной информации (позиции отрывка, который отвечает на входной вопрос) в контексте входных данных языковой модели приводит к U-образной кривой производительности — модели лучше используют релевантную информацию, которая находится в самом начале (эффект первичности) или в конце её входного контекста (эффект недавности), а производительность значительно ухудшается, когда модели должны получать доступ и использовать информацию, расположенную в середине её входного контекста.</em></p> <p>Языковые модели стали важным и гибким строительным блоком в различных языковых технологиях, ориентированных на пользователя, включая разговорные интерфейсы, поиск и суммаризацию, а также совместное написание [Shuster et al., 2022; Thoppilan et al., 2022; Lee et al., 2022].<br> Эти модели выполняют задачи нижнего уровня в основном через подсказки: вся релевантная спецификация задачи и данные для обработки форматируются как текстовый входной контекст, и модель возвращает сгенерированное текстовое завершение.<br> Эти входные контексты могут содержать тысячи токенов, особенно когда языковые модели используются для обработки длинных документов (например, юридических или научных документов, истории разговоров и т. д.) или когда языковые модели дополняются внешней информацией (например, релевантными документами из поисковой системы, результатами запросов к базе данных и т. д.; [Petroni et al., 2020; Ram et al., 2023; Shi et al., 2023; Mallen et al., 2023; Schick et al., 2023]).</p> <p>Обработка этих случаев использования требует, чтобы языковые модели успешно работали с длинными последовательностями.<br> Существующие языковые модели обычно реализуются с помощью трансформеров [Vaswani et al., 2017], которые требуют памяти и вычислений, увеличивающихся квадратично в зависимости от длины последовательности.<br> В результате трансформерные языковые модели часто обучались с относительно небольшими оконными контекстами (от 512 до 2048 токенов).<br> Недавние улучшения в аппаратном обеспечении (например, более быстрые графические процессоры с большим объемом памяти) и алгоритмах [Dai et al., 2019; Dao et al., 2022; Poli et al., 2023; Rubin et al., 2023] привели к появлению языковых моделей с большими оконными контекстами (например, 4096, 32K и даже 100K токенов), но остается неясным, как эти модели с расширенным контекстом используют свои входные контексты при выполнении задач нижнего уровня.</p> <p>Мы эмпирически исследуем этот вопрос с помощью контролируемых экспериментов с различными современными открытыми (MPT-30B-Instruct, LongChat-13B (16K)) и закрытыми (OpenAI&rsquo;s GPT-3.5-Turbo и Anthropic&rsquo;s Claude-1.3) языковыми моделями в условиях, требующих доступа и использования информации в пределах входного контекста.<br> В частности, в наших экспериментах вносятся контролируемые изменения в размер входного контекста и положение релевантной информации в пределах входного контекста, и изучаются их эффекты на производительность языковой модели.<br> Если языковые модели могут надежно использовать информацию в пределах длинных входных контекстов, то их производительность должна быть <em>минимально подвержена</em> влиянию положения релевантной информации в контексте входных данных.</p> <p>Сначала мы экспериментируем с многодокументным вопросно-ответным анализом, который требует от моделей анализа предоставленных документов для нахождения релевантной информации и использования её для ответа на заданный вопрос; эта задача имитирует настройку генерации с дополнением поиска, лежащую в основе многих коммерческих приложений генеративного поиска и вопросно-ответного анализа (например, Bing Chat).<br> В этом контексте мы контролируем (i)~длину входного контекста, изменяя количество документов в контексте входных данных (аналогично извлечению большего или меньшего количества документов в генерации с дополнением поиска), и (ii)~контролируем положение релевантной информации в пределах входного контекста, изменяя порядок документов, чтобы разместить релевантный документ в начале, середине или конце контекста.</p> <p>Мы обнаруживаем, что изменение положения релевантной информации в контексте входных данных может существенно повлиять на производительность модели, что указывает на то, что текущие языковые модели не могут надежно получать доступ и использовать информацию в длинных входных контекстах.<br> Более того, мы наблюдаем характерную U-образную кривую производительности (см. рисунок выше); производительность языковой модели наивысшая, когда релевантная информация находится в самом начале (эффект первичности) или в конце её входного контекста (эффект недавности), и производительность значительно ухудшается, когда модели должны получать доступ и использовать информацию в середине своих входных контекстов (§ QA Results).<br> Например, когда релевантная информация размещена в середине её входного контекста, производительность GPT-3.5-Turbo на задаче многодокументного вопросно-ответного анализа ниже, чем её производительность при прогнозировании <em>без каких-либо документов</em> (т.е. в закрытой книге; 56.1%).<br> Кроме того, мы обнаруживаем, что модели часто имеют идентичную производительность с их аналогами с расширенным контекстом, что указывает на то, что модели с расширенным контекстом не обязательно лучше используют свой входной контекст (§ QA Results).</p> <p>Учитывая, что языковые модели испытывают трудности с извлечением и использованием релевантной информации в задаче многодокументного вопросно-ответного анализа, в какой степени языковые модели вообще могут <em>извлекать</em> из своих входных контекстов?<br> Мы изучаем этот вопрос с помощью синтетической задачи извлечения ключевых значений, которая предназначена для минимального тестирования базовой способности извлекать совпадающие токены из входного контекста.<br> В этой задаче моделям предоставляется коллекция пар ключ-значение в формате JSON, и они должны вернуть значение, связанное с определенным ключом.<br> Подобно задаче многодокументного вопросно-ответного анализа, задача извлечения ключевых значений допускает контролируемые изменения длины входного контекста (добавление большего количества пар ключ-значение) и положения релевантной информации.<br> Хотя некоторые модели выполняют синтетическую задачу извлечения ключевых значений идеально, другие модели испытывают трудности даже с простым извлечением совпадающих токенов, которые встречаются в середине их входного контекста, и продолжают демонстрировать U-образную кривую производительности.</p> <p>Чтобы лучше понять, почему языковые модели испытывают трудности с надежным доступом и использованием информации в своих входных контекстах, мы изучаем роль архитектуры модели (только декодер против кодер-декодер), контекстуализации с учетом запроса и тонкой настройки инструкций (§ Why U-shape). Мы обнаруживаем, что:</p> <ul> <li>Кодер-декодер модели относительно устойчивы к изменениям положения релевантной информации в их входном контексте, но только при оценке последовательностей в пределах их максимальной длины последовательности на этапе обучения. При оценке последовательностей, превышающих те, что были видны во время обучения, мы наблюдаем U-образную кривую производительности (§ Architecture).</li> <li>Контекстуализация с учетом запроса (размещение запроса перед <em>и</em> после документов или пар ключ-значение) обеспечивает почти идеальную производительность в синтетической задаче извлечения ключевых значений, но минимально изменяет тенденции в многодокументном вопросно-ответном анализе (§ Pre-conditioning).</li> <li>Даже базовые языковые модели (т.е. без тонкой настройки инструкций) демонстрируют U-образную кривую производительности при изменении положения релевантной информации в контексте входных данных.</li> </ul> <p>Наши результаты показывают, что предоставление языковым моделям более длинных входных контекстов — это компромисс: предоставление языковой модели большего объема информации может помочь ей выполнить задачу нижнего уровня, но также увеличивает объем контента, который модель должна анализировать, что может снизить точность.<br> Чтобы лучше понять этот компромисс на практике, мы проводим тематическое исследование с моделями извлечения-читателя на открытом вопросно-ответном анализе (§ ODQA Case Study).<br> В отличие от нашей контролируемой задачи многодокументного вопросно-ответного анализа, где контекст всегда содержит ровно <em>один</em> документ, который отвечает на вопрос, ни один или многие из топ $k$ документов могут не содержать ответа в настройке открытого вопросно-ответного анализа.<br> Когда мы извлекаем из Википедии, чтобы ответить на запросы из NaturalQuestions-Open, мы обнаруживаем, что производительность модели насыщается задолго до насыщения извлечения, что указывает на то, что текущие модели не могут эффективно использовать дополнительные извлеченные документы — использование 50 документов вместо 20 извлеченных документов лишь незначительно улучшает производительность ($\sim$1.5% для GPT-3.5-Turbo и $\sim$1% для Claude-1.3).</p> <p>Наш анализ дает лучшее понимание того, как языковые модели используют свой входной контекст, и вводит новые протоколы оценки для будущих моделей с длинным контекстом; чтобы утверждать, что языковая модель может надежно использовать информацию в пределах длинных входных контекстов, необходимо показать, что её производительность минимально подвержена влиянию положения релевантной информации в контексте входных данных (например, минимальная разница в наилучшей и наихудшей производительности).<br> Чтобы способствовать дальнейшей работе по пониманию и улучшению того, как языковые модели используют свой входной контекст, мы выпускаем наш код и данные оценки.<br> <a class="gblog-markdown__link" href="https://nelsonliu.me/papers/lost-in-the-middle" >https://nelsonliu.me/papers/lost-in-the-middle</a></p> <hr> <div class="flex align-center gblog-post__anchorwrap"> <h2 id="многодокументный-вопросно-ответный-анализ" > Многодокументный вопросно-ответный анализ </h2> <a data-clipboard-text="https://valmat.ru/posts/2025/08/lost-in-the-middle/#многодокументный-вопросно-ответный-анализ" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Многодокументный вопросно-ответный анализ" href="#%d0%bc%d0%bd%d0%be%d0%b3%d0%be%d0%b4%d0%be%d0%ba%d1%83%d0%bc%d0%b5%d0%bd%d1%82%d0%bd%d1%8b%d0%b9-%d0%b2%d0%be%d0%bf%d1%80%d0%be%d1%81%d0%bd%d0%be-%d0%be%d1%82%d0%b2%d0%b5%d1%82%d0%bd%d1%8b%d0%b9-%d0%b0%d0%bd%d0%b0%d0%bb%d0%b8%d0%b7"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="экспериментальная-установка" > Экспериментальная установка </h3> <a data-clipboard-text="https://valmat.ru/posts/2025/08/lost-in-the-middle/#экспериментальная-установка" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Экспериментальная установка" href="#%d1%8d%d0%ba%d1%81%d0%bf%d0%b5%d1%80%d0%b8%d0%bc%d0%b5%d0%bd%d1%82%d0%b0%d0%bb%d1%8c%d0%bd%d0%b0%d1%8f-%d1%83%d1%81%d1%82%d0%b0%d0%bd%d0%be%d0%b2%d0%ba%d0%b0"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>В задаче многодокументного вопросно-ответного анализа входные данные модели включают (i)<del>вопрос, на который нужно ответить, и (ii)</del>$k$ документов (например, отрывки из Википедии), где <em>ровно один</em> из документов содержит ответ на вопрос, а $k - 1$ «отвлекающих» документов не содержат.<br> Эта задача требует от модели доступа к документу, содержащему ответ, в пределах её входного контекста и использования его для ответа на вопрос.</p> <!-- ![Пример задачи многодокументного вопросно-ответного анализа](figures/qa_example.png) --> <div class="flex align-center gblog-post__anchorwrap"> <h4 id="пример-задачи-многодокументного-вопросно-ответного-анализа" > Пример задачи многодокументного вопросно-ответного анализа </h4> <a data-clipboard-text="https://valmat.ru/posts/2025/08/lost-in-the-middle/#пример-задачи-многодокументного-вопросно-ответного-анализа" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Пример задачи многодокументного вопросно-ответного анализа" href="#%d0%bf%d1%80%d0%b8%d0%bc%d0%b5%d1%80-%d0%b7%d0%b0%d0%b4%d0%b0%d1%87%d0%b8-%d0%bc%d0%bd%d0%be%d0%b3%d0%be%d0%b4%d0%be%d0%ba%d1%83%d0%bc%d0%b5%d0%bd%d1%82%d0%bd%d0%be%d0%b3%d0%be-%d0%b2%d0%be%d0%bf%d1%80%d0%be%d1%81%d0%bd%d0%be-%d0%be%d1%82%d0%b2%d0%b5%d1%82%d0%bd%d0%be%d0%b3%d0%be-%d0%b0%d0%bd%d0%b0%d0%bb%d0%b8%d0%b7%d0%b0"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p><strong>Input Context</strong></p> <blockquote> <p>Write a high-quality answer for the given question using only the provided search results (some of which might be irrelevant).</p> <p>Document [1](Title: Asian Americans in science and technology) Prize in physics for discovery of the subatomic particle J/ψ. Subrahmanyan Chandrasekhar shared&hellip;</p> <p><strong>Document [2](Title: List of Nobel laureates in Physics) The first Nobel Prize in Physics was awarded in 1901 to Wilhelm Conrad Röntgen, of Germany, who received&hellip;</strong></p> <p>Document [3](Title: Scientist) and pursued through a unique method, was essentially in place. Ramón y Cajal won the Nobel Prize in 1906 for his remarkable&hellip;</p> <p>Question: who got the first nobel prize in physics <br> Answer:</p> </blockquote> <p><strong>Desired Answer</strong></p> <blockquote> <p>Wilhelm Conrad Röntgen</p> </blockquote> <p><em>Документ, содержащий ответ, выделен для ясности.</em></p> <p>Мы реализуем эту задачу с данными из NaturalQuestions-Open [Lee et al., 2019; Kwiatkowski et al., 2019], которые содержат исторические запросы, отправленные в поисковую систему Google, в сочетании с аннотированными людьми ответами, извлеченными из Википедии.<br> В частности, мы берем 2655 запросов, где аннотированный длинный ответ является абзацем (в отличие от списка или таблицы).<br> Мы используем отрывки (кусочки не более 100 токенов) из Википедии в качестве документов в пределах наших входных контекстов.<br> Для каждого из запросов нам нужен документ, содержащий ответ, и $k-1$ отвлекающих документов, которые не содержат ответа.<br> Чтобы получить документ, который отвечает на вопрос, мы используем абзац Википедии, содержащий ответ из аннотаций NaturalQuestions.</p> <p>Чтобы собрать $k-1$ отвлекающих документов, которые не содержат ответа, мы используем систему извлечения (Contriever, дообученную на MS-MARCO; [Izacard et al., 2021]) для извлечения $k-1$ отрывков из Википедии, которые наиболее релевантны запросу и не содержат ни одного из аннотированных ответов NaturalQuestions.</p> <p>Чтобы модулировать положение релевантной информации в пределах входного контекста, мы изменяем порядок документов, чтобы изменить положение документа, содержащего ответ.</p> <!-- ![Модуляция положения релевантной информации](figures/qa_changing_position.png) --> <div class="flex align-center gblog-post__anchorwrap"> <h4 id="модуляция-положения-релевантной-информации" > Модуляция положения релевантной информации </h4> <a data-clipboard-text="https://valmat.ru/posts/2025/08/lost-in-the-middle/#модуляция-положения-релевантной-информации" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Модуляция положения релевантной информации" href="#%d0%bc%d0%be%d0%b4%d1%83%d0%bb%d1%8f%d1%86%d0%b8%d1%8f-%d0%bf%d0%be%d0%bb%d0%be%d0%b6%d0%b5%d0%bd%d0%b8%d1%8f-%d1%80%d0%b5%d0%bb%d0%b5%d0%b2%d0%b0%d0%bd%d1%82%d0%bd%d0%be%d0%b9-%d0%b8%d0%bd%d1%84%d0%be%d1%80%d0%bc%d0%b0%d1%86%d0%b8%d0%b8"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p><strong>Input Context</strong></p> <blockquote> <p>Write a high-quality answer for the given question using only the provided search results (some of which might be irrelevant).</p> <p><strong>Document [1](Title: List of Nobel laureates in Physics) &hellip;</strong> <br> Document [2](Title: Asian Americans in science and technology) &hellip; <br> Document [3](Title: Scientist) &hellip;</p> <p>Question: who got the first nobel prize in physics <br> Answer:</p> </blockquote> <p><strong>Desired Answer</strong></p> <blockquote> <p>Wilhelm Conrad Röntgen</p> </blockquote> <p>Чтобы модулировать длину входного контекста в этой задаче, мы увеличиваем или уменьшаем количество извлеченных документов, не содержащих ответа.</p> <!-- ![Модуляция длины входного контекста](figures/qa_changing_length.png) --> <div class="flex align-center gblog-post__anchorwrap"> <h4 id="модуляция-длины-входного-контекста" > Модуляция длины входного контекста </h4> <a data-clipboard-text="https://valmat.ru/posts/2025/08/lost-in-the-middle/#модуляция-длины-входного-контекста" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Модуляция длины входного контекста" href="#%d0%bc%d0%be%d0%b4%d1%83%d0%bb%d1%8f%d1%86%d0%b8%d1%8f-%d0%b4%d0%bb%d0%b8%d0%bd%d1%8b-%d0%b2%d1%85%d0%be%d0%b4%d0%bd%d0%be%d0%b3%d0%be-%d0%ba%d0%be%d0%bd%d1%82%d0%b5%d0%ba%d1%81%d1%82%d0%b0"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p><strong>Input Context</strong></p> <blockquote> <p>Write a high-quality answer for the given question using only the provided search results (some of which might be irrelevant).</p> <p>Document [1](Title: Asian Americans in science and technology) &hellip; <br> <strong>Document [2](Title: List of Nobel laureates in Physics) &hellip;</strong> <br> Document [3](Title: Scientist) &hellip; <br> Document [4](Title: Norwegian Americans) &hellip; <br> Document [5](Title: Maria Goeppert Mayer) &hellip;</p> <p>Question: who got the first nobel prize in physics <br> Answer:</p> </blockquote> <p><strong>Desired Answer</strong></p> <blockquote> <p>Wilhelm Conrad Röntgen</p> </blockquote> <p>Следуя [Kandpal et al., 2022; Mallen et al., 2023], мы используем точность в качестве нашего основного показателя оценки, оценивая, появляется ли какой-либо из правильных ответов (как взято из аннотаций NaturalQuestions) в предсказанном выводе.</p> <hr> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="модели" > Модели </h3> <a data-clipboard-text="https://valmat.ru/posts/2025/08/lost-in-the-middle/#модели" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Модели" href="#%d0%bc%d0%be%d0%b4%d0%b5%d0%bb%d0%b8"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Мы анализируем несколько современных открытых и закрытых языковых моделей.<br> Мы используем жадное декодирование при генерации выводов и оставляем изучение других методов декодирования для будущей работы.<br> Мы используем стандартный набор подсказок для каждой модели.</p> <p><strong>Открытые модели:</strong></p> <ul> <li>MPT-30B-Instruct (максимальная длина контекста 8192 токена, ALiBi позиционирование)</li> <li>LongChat-13B (16K) (расширенное окно контекста LLaMA-13B до 16384 токенов)</li> </ul> <p><strong>Закрытые модели:</strong></p> <ul> <li>GPT-3.5-Turbo (4K токенов) и GPT-3.5-Turbo (16K)</li> <li>Claude-1.3 (8K токенов) и Claude-1.3 (100K токенов)</li> <li>GPT-4 (8K) — только подмножество экспериментов</li> </ul> <hr> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="результаты-и-обсуждение" > Результаты и обсуждение </h3> <a data-clipboard-text="https://valmat.ru/posts/2025/08/lost-in-the-middle/#результаты-и-обсуждение" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Результаты и обсуждение" href="#%d1%80%d0%b5%d0%b7%d1%83%d0%bb%d1%8c%d1%82%d0%b0%d1%82%d1%8b-%d0%b8-%d0%be%d0%b1%d1%81%d1%83%d0%b6%d0%b4%d0%b5%d0%bd%d0%b8%d0%b5"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Мы экспериментируем с входными контекстами, содержащими 10, 20 и 30 документов.<br> Ниже — производительность многодокументного вопросно-ответного анализа при изменении положения релевантной информации в пределах входного контекста.</p> <p><img src="figures/qa.png" alt="Влияние положения релевантной информации" /></p> <div class="flex align-center gblog-post__anchorwrap"> <h4 id="таблица-точность-языковых-моделей-в-закрытой-книге-и-оракульской-настройке" > Таблица: Точность языковых моделей в закрытой книге и оракульской настройке </h4> <a data-clipboard-text="https://valmat.ru/posts/2025/08/lost-in-the-middle/#таблица-точность-языковых-моделей-в-закрытой-книге-и-оракульской-настройке" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Таблица: Точность языковых моделей в закрытой книге и оракульской настройке" href="#%d1%82%d0%b0%d0%b1%d0%bb%d0%b8%d1%86%d0%b0-%d1%82%d0%be%d1%87%d0%bd%d0%be%d1%81%d1%82%d1%8c-%d1%8f%d0%b7%d1%8b%d0%ba%d0%be%d0%b2%d1%8b%d1%85-%d0%bc%d0%be%d0%b4%d0%b5%d0%bb%d0%b5%d0%b9-%d0%b2-%d0%b7%d0%b0%d0%ba%d1%80%d1%8b%d1%82%d0%be%d0%b9-%d0%ba%d0%bd%d0%b8%d0%b3%d0%b5-%d0%b8-%d0%be%d1%80%d0%b0%d0%ba%d1%83%d0%bb%d1%8c%d1%81%d0%ba%d0%be%d0%b9-%d0%bd%d0%b0%d1%81%d1%82%d1%80%d0%be%d0%b9%d0%ba%d0%b5"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <table> <thead> <tr> <th>Модель</th> <th style="text-align: center">Закрытая книга</th> <th style="text-align: center">Оракул</th> </tr> </thead> <tbody> <tr> <td>LongChat-13B (16K)</td> <td style="text-align: center">35.0%</td> <td style="text-align: center">83.4%</td> </tr> <tr> <td>MPT-30B-Instruct</td> <td style="text-align: center">31.5%</td> <td style="text-align: center">81.9%</td> </tr> <tr> <td>GPT-3.5-Turbo</td> <td style="text-align: center">56.1%</td> <td style="text-align: center">88.3%</td> </tr> <tr> <td>GPT-3.5-Turbo (16K)</td> <td style="text-align: center">56.0%</td> <td style="text-align: center">88.6%</td> </tr> <tr> <td>Claude-1.3</td> <td style="text-align: center">48.3%</td> <td style="text-align: center">76.1%</td> </tr> <tr> <td>Claude-1.3 (100K)</td> <td style="text-align: center">48.2%</td> <td style="text-align: center">76.4%</td> </tr> </tbody> </table> <div class="flex align-center gblog-post__anchorwrap"> <h4 id="основные-выводы" > Основные выводы </h4> <a data-clipboard-text="https://valmat.ru/posts/2025/08/lost-in-the-middle/#основные-выводы" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Основные выводы" href="#%d0%be%d1%81%d0%bd%d0%be%d0%b2%d0%bd%d1%8b%d0%b5-%d0%b2%d1%8b%d0%b2%d0%be%d0%b4%d1%8b"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <ul> <li><strong>Производительность максимальна, когда релевантная информация в начале или конце контекста.</strong><br> U-образная кривая производительности: модели лучше используют релевантную информацию, находящуюся в начале (эффект первичности) или в конце (эффект недавности) контекста, и производительность значительно ухудшается, когда информация в середине.</li> <li><strong>Модели с расширенным контекстом не обязательно лучше используют входной контекст.</strong><br> Производительность между обычной и расширенной версией модели почти идентична, если входной контекст помещается в их окно.</li> </ul> <hr> <div class="flex align-center gblog-post__anchorwrap"> <h2 id="насколько-хорошо-языковые-модели-могут-извлекать-из-входных-контекстов" > Насколько хорошо языковые модели могут извлекать из входных контекстов? </h2> <a data-clipboard-text="https://valmat.ru/posts/2025/08/lost-in-the-middle/#насколько-хорошо-языковые-модели-могут-извлекать-из-входных-контекстов" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Насколько хорошо языковые модели могут извлекать из входных контекстов?" href="#%d0%bd%d0%b0%d1%81%d0%ba%d0%be%d0%bb%d1%8c%d0%ba%d0%be-%d1%85%d0%be%d1%80%d0%be%d1%88%d0%be-%d1%8f%d0%b7%d1%8b%d0%ba%d0%be%d0%b2%d1%8b%d0%b5-%d0%bc%d0%be%d0%b4%d0%b5%d0%bb%d0%b8-%d0%bc%d0%be%d0%b3%d1%83%d1%82-%d0%b8%d0%b7%d0%b2%d0%bb%d0%b5%d0%ba%d0%b0%d1%82%d1%8c-%d0%b8%d0%b7-%d0%b2%d1%85%d0%be%d0%b4%d0%bd%d1%8b%d1%85-%d0%ba%d0%be%d0%bd%d1%82%d0%b5%d0%ba%d1%81%d1%82%d0%be%d0%b2"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Учитывая, что языковые модели испытывают трудности с извлечением и использованием информации из середины их входных контекстов в задаче многодокументного вопросно-ответного анализа, в какой степени они могут просто <em>извлекать</em> из входных контекстов?<br> Мы изучаем этот вопрос с помощью синтетической задачи извлечения ключевых значений.</p> <!-- ![Пример задачи извлечения ключевых значений](figures/kv_retrieval_example.png) --> <p><strong>Input Context</strong></p> <blockquote> <p>Extract the value corresponding to the specified key in the JSON object below.</p> <p>JSON data:</p> </blockquote> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span><span class="nt">&#34;2a8d601d-1d69-4e64-9f90-8ad825a74195&#34;</span><span class="p">:</span> <span class="s2">&#34;bb3ba2a5-7de8-434b-a86e-a88bb9fa7289&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;a54e2eed-e625-4570-9f74-3624e77d6684&#34;</span><span class="p">:</span> <span class="s2">&#34;d1ff29be-4e2a-4208-a182-0cea716be3d4&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;9f4a92b9-5f69-4725-ba1e-403f08dea695&#34;</span><span class="p">:</span> <span class="s2">&#34;703a7ce5-f17f-4e6d-b895-5836ba5ec71c&#34;</span><span class="p">,</span> <span class="c1">// &lt;-- </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="nt">&#34;52a9c80c-da51-4fc9-bf70-4a4901bc2ac3&#34;</span><span class="p">:</span> <span class="s2">&#34;b2f8ea3d-4b1b-49e0-a141-b9823991ebeb&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;f4eb1c53-af0a-4dc4-a3a5-c2d50851a178&#34;</span><span class="p">:</span> <span class="s2">&#34;d733b0d2-6af3-44e1-8592-e5637fdb76fb&#34;</span><span class="p">}</span> </span></span></code></pre></div><blockquote> <p>Key: &ldquo;<strong>9f4a92b9-5f69-4725-ba1e-403f08dea695</strong>&rdquo; Corresponding value:</p> </blockquote> <p><strong>Desired Output</strong></p> <blockquote> <p>703a7ce5-f17f-4e6d-b895-5836ba5ec71c</p> </blockquote> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="экспериментальная-установка-1" > Экспериментальная установка </h3> <a data-clipboard-text="https://valmat.ru/posts/2025/08/lost-in-the-middle/#экспериментальная-установка-1" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Экспериментальная установка" href="#%d1%8d%d0%ba%d1%81%d0%bf%d0%b5%d1%80%d0%b8%d0%bc%d0%b5%d0%bd%d1%82%d0%b0%d0%bb%d1%8c%d0%bd%d0%b0%d1%8f-%d1%83%d1%81%d1%82%d0%b0%d0%bd%d0%be%d0%b2%d0%ba%d0%b0-1"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Входные данные включают:</p> <ul> <li>сериализованный объект JSON с $k$ парами ключ-значение (UUID)</li> <li>ключ, для которого нужно вернуть значение</li> </ul> <p>Мы измеряем точность, оценивая, появляется ли правильное значение в предсказанном выводе.</p> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="результаты" > Результаты </h3> <a data-clipboard-text="https://valmat.ru/posts/2025/08/lost-in-the-middle/#результаты" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Результаты" href="#%d1%80%d0%b5%d0%b7%d1%83%d0%bb%d1%8c%d1%82%d0%b0%d1%82%d1%8b"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p><img src="figures/kv_records.png" alt="Влияние длины и положения релевантной информации" /></p> <ul> <li>Claude-1.3 и Claude-1.3 (100K) почти идеально выполняют задачу на всех длинах контекста.</li> <li>GPT-3.5-Turbo, GPT-3.5-Turbo (16K) и MPT-30B-Instruct испытывают трудности, особенно при большом количестве пар.</li> <li>U-образная кривая производительности сохраняется: наименьшая точность — при необходимости извлекать из середины контекста.</li> </ul> <hr> <div class="flex align-center gblog-post__anchorwrap"> <h2 id="почему-языковые-модели-не-устойчивы-к-изменениям-положения-релевантной-информации" > Почему языковые модели не устойчивы к изменениям положения релевантной информации? </h2> <a data-clipboard-text="https://valmat.ru/posts/2025/08/lost-in-the-middle/#почему-языковые-модели-не-устойчивы-к-изменениям-положения-релевантной-информации" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Почему языковые модели не устойчивы к изменениям положения релевантной информации?" href="#%d0%bf%d0%be%d1%87%d0%b5%d0%bc%d1%83-%d1%8f%d0%b7%d1%8b%d0%ba%d0%be%d0%b2%d1%8b%d0%b5-%d0%bc%d0%be%d0%b4%d0%b5%d0%bb%d0%b8-%d0%bd%d0%b5-%d1%83%d1%81%d1%82%d0%be%d0%b9%d1%87%d0%b8%d0%b2%d1%8b-%d0%ba-%d0%b8%d0%b7%d0%bc%d0%b5%d0%bd%d0%b5%d0%bd%d0%b8%d1%8f%d0%bc-%d0%bf%d0%be%d0%bb%d0%be%d0%b6%d0%b5%d0%bd%d0%b8%d1%8f-%d1%80%d0%b5%d0%bb%d0%b5%d0%b2%d0%b0%d0%bd%d1%82%d0%bd%d0%be%d0%b9-%d0%b8%d0%bd%d1%84%d0%be%d1%80%d0%bc%d0%b0%d1%86%d0%b8%d0%b8"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Мы исследуем роль архитектуры модели, контекстуализации с учетом запроса и тонкой настройки инструкций.</p> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="влияние-архитектуры-модели" > Влияние архитектуры модели </h3> <a data-clipboard-text="https://valmat.ru/posts/2025/08/lost-in-the-middle/#влияние-архитектуры-модели" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Влияние архитектуры модели" href="#%d0%b2%d0%bb%d0%b8%d1%8f%d0%bd%d0%b8%d0%b5-%d0%b0%d1%80%d1%85%d0%b8%d1%82%d0%b5%d0%ba%d1%82%d1%83%d1%80%d1%8b-%d0%bc%d0%be%d0%b4%d0%b5%d0%bb%d0%b8"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p><img src="figures/qa_decoder_only_vs_encoder_decoder.png" alt="Сравнение моделей только-декодера и кодер-декодер" /></p> <ul> <li>Кодер-декодер модели (Flan-UL2, Flan-T5-XXL) устойчивы к изменению позиции релевантной информации, если длина последовательности не превышает ту, что была на обучении.</li> <li>При более длинных последовательностях появляется U-образная кривая.</li> </ul> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="влияние-контекстуализации-с-учетом-запроса" > Влияние контекстуализации с учетом запроса </h3> <a data-clipboard-text="https://valmat.ru/posts/2025/08/lost-in-the-middle/#влияние-контекстуализации-с-учетом-запроса" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Влияние контекстуализации с учетом запроса" href="#%d0%b2%d0%bb%d0%b8%d1%8f%d0%bd%d0%b8%d0%b5-%d0%ba%d0%be%d0%bd%d1%82%d0%b5%d0%ba%d1%81%d1%82%d1%83%d0%b0%d0%bb%d0%b8%d0%b7%d0%b0%d1%86%d0%b8%d0%b8-%d1%81-%d1%83%d1%87%d0%b5%d1%82%d0%be%d0%bc-%d0%b7%d0%b0%d0%bf%d1%80%d0%be%d1%81%d0%b0"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p><img src="figures/20_total_documents_precondition_with_question.jpg" alt="Контекстуализация с учетом запроса" /></p> <ul> <li>Размещение запроса перед <em>и</em> после данных почти не влияет на тенденции в многодокументном вопросно-ответном анализе, но помогает в синтетической задаче извлечения ключевых значений.</li> </ul> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="влияние-тонкой-настройки-инструкций" > Влияние тонкой настройки инструкций </h3> <a data-clipboard-text="https://valmat.ru/posts/2025/08/lost-in-the-middle/#влияние-тонкой-настройки-инструкций" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Влияние тонкой настройки инструкций" href="#%d0%b2%d0%bb%d0%b8%d1%8f%d0%bd%d0%b8%d0%b5-%d1%82%d0%be%d0%bd%d0%ba%d0%be%d0%b9-%d0%bd%d0%b0%d1%81%d1%82%d1%80%d0%be%d0%b9%d0%ba%d0%b8-%d0%b8%d0%bd%d1%81%d1%82%d1%80%d1%83%d0%ba%d1%86%d0%b8%d0%b9"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p><img src="figures/base_vs_instruction_tuned.jpg" alt="Базовая модель vs. после тонкой настройки инструкций" /></p> <ul> <li>U-образная кривая наблюдается как у базовой модели, так и у модели после тонкой настройки инструкций.</li> <li>Тонкая настройка инструкций слегка уменьшает разницу между наилучшей и наихудшей производительностью.</li> </ul> <hr> <div class="flex align-center gblog-post__anchorwrap"> <h2 id="всегда-ли-больше-контекста-лучше-тематическое-исследование-с-открытым-вопросно-ответным-анализом" > Всегда ли больше контекста лучше? Тематическое исследование с открытым вопросно-ответным анализом </h2> <a data-clipboard-text="https://valmat.ru/posts/2025/08/lost-in-the-middle/#всегда-ли-больше-контекста-лучше-тематическое-исследование-с-открытым-вопросно-ответным-анализом" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Всегда ли больше контекста лучше? Тематическое исследование с открытым вопросно-ответным анализом" href="#%d0%b2%d1%81%d0%b5%d0%b3%d0%b4%d0%b0-%d0%bb%d0%b8-%d0%b1%d0%be%d0%bb%d1%8c%d1%88%d0%b5-%d0%ba%d0%be%d0%bd%d1%82%d0%b5%d0%ba%d1%81%d1%82%d0%b0-%d0%bb%d1%83%d1%87%d1%88%d0%b5-%d1%82%d0%b5%d0%bc%d0%b0%d1%82%d0%b8%d1%87%d0%b5%d1%81%d0%ba%d0%be%d0%b5-%d0%b8%d1%81%d1%81%d0%bb%d0%b5%d0%b4%d0%be%d0%b2%d0%b0%d0%bd%d0%b8%d0%b5-%d1%81-%d0%be%d1%82%d0%ba%d1%80%d1%8b%d1%82%d1%8b%d0%bc-%d0%b2%d0%be%d0%bf%d1%80%d0%be%d1%81%d0%bd%d0%be-%d0%be%d1%82%d0%b2%d0%b5%d1%82%d0%bd%d1%8b%d0%bc-%d0%b0%d0%bd%d0%b0%d0%bb%d0%b8%d0%b7%d0%be%d0%bc"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <ul> <li>Предоставление языковой модели большего объема информации может помочь, но также увеличивает объем контента для анализа, что может снизить точность.</li> <li>В экспериментах с NaturalQuestions-Open производительность модели насыщается задолго до насыщения извлечения: использование 50 документов вместо 20 улучшает точность лишь на 1–1.5%.</li> </ul> <p><img src="figures/odqa.jpg" alt="Извлечение и производительность модели в зависимости от количества документов" /></p> <hr> <div class="flex align-center gblog-post__anchorwrap"> <h2 id="связанные-работы" > Связанные работы </h2> <a data-clipboard-text="https://valmat.ru/posts/2025/08/lost-in-the-middle/#связанные-работы" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Связанные работы" href="#%d1%81%d0%b2%d1%8f%d0%b7%d0%b0%d0%bd%d0%bd%d1%8b%d0%b5-%d1%80%d0%b0%d0%b1%d0%be%d1%82%d1%8b"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="языковые-модели-с-длинным-контекстом" > Языковые модели с длинным контекстом </h3> <a data-clipboard-text="https://valmat.ru/posts/2025/08/lost-in-the-middle/#языковые-модели-с-длинным-контекстом" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Языковые модели с длинным контекстом" href="#%d1%8f%d0%b7%d1%8b%d0%ba%d0%be%d0%b2%d1%8b%d0%b5-%d0%bc%d0%be%d0%b4%d0%b5%d0%bb%d0%b8-%d1%81-%d0%b4%d0%bb%d0%b8%d0%bd%d0%bd%d1%8b%d0%bc-%d0%ba%d0%be%d0%bd%d1%82%d0%b5%d0%ba%d1%81%d1%82%d0%be%d0%bc"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Много работ посвящено масштабированию трансформеров по длине контекста: модификации внимания (рекуррентность, аппроксимации, свертки, линейные RNN), ускоренные реализации (FlashAttention), отказ от внимания (RWKV, S4, Hyena).<br> Оценка часто проводится по перплексии, но точный доступ к знаниям на длинных контекстах — отдельная задача.</p> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="как-языковые-модели-используют-контекст" > Как языковые модели используют контекст? </h3> <a data-clipboard-text="https://valmat.ru/posts/2025/08/lost-in-the-middle/#как-языковые-модели-используют-контекст" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Как языковые модели используют контекст?" href="#%d0%ba%d0%b0%d0%ba-%d1%8f%d0%b7%d1%8b%d0%ba%d0%be%d0%b2%d1%8b%d0%b5-%d0%bc%d0%be%d0%b4%d0%b5%d0%bb%d0%b8-%d0%b8%d1%81%d0%bf%d0%be%d0%bb%d1%8c%d0%b7%d1%83%d1%8e%d1%82-%d0%ba%d0%be%d0%bd%d1%82%d0%b5%d0%ba%d1%81%d1%82"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Ранние работы показали, что LSTM используют долгосрочный контекст все менее эффективно; внимательные LSTM склонны к недавности; трансформеры часто не используют долгосрочный контекст эффективно; длинный контекст помогает только для некоторых токенов.</p> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="эффект-серийной-позиции" > Эффект серийной позиции </h3> <a data-clipboard-text="https://valmat.ru/posts/2025/08/lost-in-the-middle/#эффект-серийной-позиции" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Эффект серийной позиции" href="#%d1%8d%d1%84%d1%84%d0%b5%d0%ba%d1%82-%d1%81%d0%b5%d1%80%d0%b8%d0%b9%d0%bd%d0%be%d0%b9-%d0%bf%d0%be%d0%b7%d0%b8%d1%86%d0%b8%d0%b8"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>U-образная кривая соответствует эффекту серийной позиции в психологии: люди лучше запоминают первые и последние элементы списка.<br> В трансформерах, несмотря на техническую возможность извлекать любой токен, наблюдается аналогичный эффект.</p> <hr> <div class="flex align-center gblog-post__anchorwrap"> <h2 id="заключение" > Заключение </h2> <a data-clipboard-text="https://valmat.ru/posts/2025/08/lost-in-the-middle/#заключение" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Заключение" href="#%d0%b7%d0%b0%d0%ba%d0%bb%d1%8e%d1%87%d0%b5%d0%bd%d0%b8%d0%b5"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Мы эмпирически изучаем, как языковые модели используют длинные входные контексты.<br> Показываем, что производительность моделей значительно ухудшается при изменении положения релевантной информации — особенно в середине длинных контекстов.<br> Проводим исследование роли архитектуры, контекстуализации с учетом запроса и тонкой настройки инструкций.<br> В тематическом исследовании ODQA обнаруживаем, что производительность насыщается задолго до насыщения извлечения.<br> Наши результаты дают лучшее понимание того, как языковые модели используют контекст, и предлагают новые протоколы оценки для будущих моделей с длинным контекстом.</p> Размышления о природе случайности https://valmat.ru/posts/2025/08/randomness/ 2025-08-22T00:49:34+03:00 2025-08-22T00:49:34+03:00 <p>Тема случайности меня волнует давно.<br> Существует ли физически объективная случайность, не обусловленная никакими внешними признаками? А если да, то как её определить и почему она существует?<br> Или всё, что мы воспринимаем как случайное, обусловлено лишь нашей мерой незнания? Как только мы получаем новую информацию, случайности становится меньше — просто потому, что мы узнали больше об объективных закономерностях в системе, которые на самом деле не являются случайными, а лишь отражают принципиальную сложность системы.</p> <div class="flex align-center gblog-post__anchorwrap"> <h2 id="что-такое-хаотическая-и-стохастическая-случайность" > Что такое хаотическая и стохастическая случайность? </h2> <a data-clipboard-text="https://valmat.ru/posts/2025/08/randomness/#что-такое-хаотическая-и-стохастическая-случайность" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Что такое хаотическая и стохастическая случайность?" href="#%d1%87%d1%82%d0%be-%d1%82%d0%b0%d0%ba%d0%be%d0%b5-%d1%85%d0%b0%d0%be%d1%82%d0%b8%d1%87%d0%b5%d1%81%d0%ba%d0%b0%d1%8f-%d0%b8-%d1%81%d1%82%d0%be%d1%85%d0%b0%d1%81%d1%82%d0%b8%d1%87%d0%b5%d1%81%d0%ba%d0%b0%d1%8f-%d1%81%d0%bb%d1%83%d1%87%d0%b0%d0%b9%d0%bd%d0%be%d1%81%d1%82%d1%8c"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>О хаотических системах известно уже давно. По ним написано множество научных и популярных работ.<br> Если не вдаваться в детали, хаотическими можно назвать такие системы, для которых малые изменения в начальных условиях ведут к катастрофически большим различиям на дистанции.<br> Это хорошо известные примеры: динамика трёх тел, двойной маятник, турбулентное движение воды, образование пятен на солнце и даже уровни рек или курсы акций — всё это классические примеры хаотических систем.</p> <p>Существуют определённые методы выявления хаотичности и поиска закономерностей: фрактальные размерности траекторий, показатели Ляпунова и Херста, а также широкий набор алгоритмов теории информации позволяют исследовать хаотические системы и выявлять меру их детерминированности. Но они не дают полного ответа на вопрос, чем обусловлена случайность той или иной системы.</p> <p>Если смотреть чуть шире, к хаотическим можно отнести и системы, которые работают детерминированно, но под влиянием скрытых от нас параметров, которые мы не можем измерить.</p> <p>Стохастическая или истинная случайность — это когда исход фундаментально непредсказуем. Нет скрытых причин, нет &ldquo;глубже&rdquo; уровня, где всё детерминировано. Объективная — значит, не из-за нашего незнания, а реально встроенная в ткань реальности. В классической физике такого нет, но квантовая механика намекает, что, возможно, да.</p> <p>Само определение стохастической системы очень размыто. По сути, мы считаем систему стохастической, если не можем найти в ней хаотическую (потенциально детерминированную) составляющую.</p> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="неравенства-белла-почему-они-заставляют-думать-о-случайности" > Неравенства Белла: почему они заставляют думать о случайности </h3> <a data-clipboard-text="https://valmat.ru/posts/2025/08/randomness/#неравенства-белла-почему-они-заставляют-думать-о-случайности" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Неравенства Белла: почему они заставляют думать о случайности" href="#%d0%bd%d0%b5%d1%80%d0%b0%d0%b2%d0%b5%d0%bd%d1%81%d1%82%d0%b2%d0%b0-%d0%b1%d0%b5%d0%bb%d0%bb%d0%b0-%d0%bf%d0%be%d1%87%d0%b5%d0%bc%d1%83-%d0%be%d0%bd%d0%b8-%d0%b7%d0%b0%d1%81%d1%82%d0%b0%d0%b2%d0%bb%d1%8f%d1%8e%d1%82-%d0%b4%d1%83%d0%bc%d0%b0%d1%82%d1%8c-%d0%be-%d1%81%d0%bb%d1%83%d1%87%d0%b0%d0%b9%d0%bd%d0%be%d1%81%d1%82%d0%b8"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Неравенства Белла — это математический тест, придуманный Джоном Беллом в 1964 году, чтобы проверить, совместима ли квантовая механика со &ldquo;здравым смыслом&rdquo; Эйнштейна. Эйнштейн не любил идею случайности (как и я) и &ldquo;спонтанной связи&rdquo; в квантовой механике, где запутанные частицы (например, два электрона, &ldquo;связанных&rdquo; на расстоянии) вроде бы &ldquo;общаются&rdquo; мгновенно.</p> <p>В упрощённом виде: представьте две частицы, разлетающиеся в разные стороны. Их свойства (спин или поляризация) запутаны — измеришь одну и сразу узнаёшь про вторую. Белл показал: если мир &ldquo;<em>локально-реалистичен</em>&rdquo; (свойства существуют заранее, и ничто не влияет быстрее света), то корреляции между измерениями ограничены формулой вроде $|E_1 + E_2 + E_3 - E_4| \leq 2$ (где $E$ — средние корреляции).</p> <p>Но эксперименты (с 1980-х по 2020-е, включая &ldquo;без лазеек&rdquo; в 2015+) показывают значение до $2\cdot\sqrt{2} \approx 2.8$! Это нарушает неравенство. Вывод: либо нет реализма (свойства не существуют до измерения, и исход случаен), либо нет локальности (есть &ldquo;нелокальное&rdquo; влияние), либо оба. Для многих это доказательство &ldquo;объективной случайности&rdquo; — в квантовой механике вероятности фундаментальны.</p> <p>Звучит обескураживающе, правда? Но давайте не спешить с выводами — это не конец истории.</p> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="альтернативные-взгляды-почему-неравенства-белла-не-закрывают-вопрос" > Альтернативные взгляды: почему неравенства Белла не закрывают вопрос </h3> <a data-clipboard-text="https://valmat.ru/posts/2025/08/randomness/#альтернативные-взгляды-почему-неравенства-белла-не-закрывают-вопрос" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Альтернативные взгляды: почему неравенства Белла не закрывают вопрос" href="#%d0%b0%d0%bb%d1%8c%d1%82%d0%b5%d1%80%d0%bd%d0%b0%d1%82%d0%b8%d0%b2%d0%bd%d1%8b%d0%b5-%d0%b2%d0%b7%d0%b3%d0%bb%d1%8f%d0%b4%d1%8b-%d0%bf%d0%be%d1%87%d0%b5%d0%bc%d1%83-%d0%bd%d0%b5%d1%80%d0%b0%d0%b2%d0%b5%d0%bd%d1%81%d1%82%d0%b2%d0%b0-%d0%b1%d0%b5%d0%bb%d0%bb%d0%b0-%d0%bd%d0%b5-%d0%b7%d0%b0%d0%ba%d1%80%d1%8b%d0%b2%d0%b0%d1%8e%d1%82-%d0%b2%d0%be%d0%bf%d1%80%d0%be%d1%81"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Хотя сейчас принято считать, что нарушения неравенств Белла доказывают существование объективной случайности, среди физиков нет полного единства по этому поводу. Эксперименты действительно подтверждают нарушение этих неравенств, но вопрос, что из этого следует, открыт для интерпретации.</p> <p>Суть в том, что неравенства Белла основаны сразу на трёх предположениях:</p> <ul> <li>что свойства системы существуют заранее (реализм),</li> <li>что никакое событие не может влиять на другое мгновенно на расстоянии (локальность),</li> <li>и что выбор того, что измерять, не зависит от самой системы (независимость).</li> </ul> <p>Эксперименты показывают: все три вместе не работают. Но какой из кирпичиков &ldquo;убрать&rdquo; — решает каждая интерпретация по-своему. Вот основные альтернативные взгляды:</p> <ul> <li> <p><strong>Супердетерминизм</strong>. Предполагается, что всё в мире, включая наш выбор, уже предопределено. Тогда нарушается независимость эксперимента, и неравенства Белла теряют силу. Эта идея непопулярна, потому что кажется слишком &ldquo;жёсткой&rdquo;, но теоретически её нельзя исключить.</p> </li> <li> <p><strong>Модели с контекстом или скрытыми переменными</strong>. Некоторые варианты скрытых переменных предполагают, что свойства частиц могут зависеть от самого процесса измерения или от условий эксперимента. Такие модели сложнее, но полностью их не исключили.</p> </li> <li> <p><strong>Влияние из будущего (ретрокаузальность)</strong>. Есть гипотезы, где будущие события могут как-то влиять на прошлое. Это странно, но такие идеи тоже обсуждаются.</p> </li> <li> <p><strong>Вероятности как наши знания</strong>. Есть взгляды, что квантовая &ldquo;случайность&rdquo; — это не свойство самой природы, а просто отражение нашего незнания (информационные интерпретации).</p> </li> <li> <p><strong>Фундаментальная случайность</strong>. Можно принять, что случайные события действительно &ldquo;встроены&rdquo; в законы природы, и на этом остановиться. Но это уже отдельная гипотеза, а не прямой вывод из экспериментов.</p> </li> </ul> <p>Важно понимать: современные эксперименты очень аккуратны, все основные &ldquo;лазейки&rdquo; (например, влияние детекторов) почти полностью закрыты. Но остаётся принципиальный вопрос — действительно ли наш выбор измерения никак не связан с состоянием системы? Проверить это до конца невозможно.</p> <p>В итоге, неравенства Белла показывают, что привычный набор предположений о мире не совместим с квантовой физикой. Но какой из этих кирпичиков переставить или убрать — вопрос интерпретации. Поэтому тема случайности в физике остаётся открытой: разные взгляды пока равноправны, и никто не может сказать, что вопрос полностью закрыт.</p> <div class="flex align-center gblog-post__anchorwrap"> <h2 id="как-объяснить-объективную-случайность" > Как объяснить объективную случайность </h2> <a data-clipboard-text="https://valmat.ru/posts/2025/08/randomness/#как-объяснить-объективную-случайность" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Как объяснить объективную случайность" href="#%d0%ba%d0%b0%d0%ba-%d0%be%d0%b1%d1%8a%d1%8f%d1%81%d0%bd%d0%b8%d1%82%d1%8c-%d0%be%d0%b1%d1%8a%d0%b5%d0%ba%d1%82%d0%b8%d0%b2%d0%bd%d1%83%d1%8e-%d1%81%d0%bb%d1%83%d1%87%d0%b0%d0%b9%d0%bd%d0%be%d1%81%d1%82%d1%8c"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Предположим, что всё-таки Белл прав и объективная случайность действительно существует. Я не сторонник такой идеи, но вполне допускаю, что это может быть правдой, что бы это ни значило.</p> <p>Допустим, случайность реальна. Как её вписать в картину мира без лишней &ldquo;магии&rdquo; и избыточных предположений?</p> <p>Здесь вступает в силу бритва Оккама: выбираем объяснение с минимальными допущениями. Вместо введения &ldquo;фундаментальной случайности&rdquo; как примитива или нелокальности как хака, давайте подумаем шире.</p> <p>Предположение, что в системе могут возникать непроизвольные и вообще ничем не обусловленные изменения — это очень сильное допущение. Обычный ответ: так устроена квантовая механика — мы не можем это объяснить, но это так, и нужно просто принять. Это плохое объяснение с точки зрения бритвы Оккама.</p> <p>Я предлагаю одно простое и минималистичное допущение: <strong>всё, что объективно существует, — это информация</strong>.<br> И больше нет ничего.</p> <p>Это совершенно минималистичное допущение. Ни у кого не возникает сомнений, что математика объективна и не нуждается в носителе.<br> Математика никак не ограничена фундаментальными физическими константами нашей Вселенной. Она вне сомнения одинакова в любой точке нашей Вселенной. И если бы мы когда-нибудь выяснили, что другие вселенные существуют, то, вне всякого сомнения, в них была бы та же самая математика.</p> <p>Все возможные миры — это просто реализации континуума комбинаторных возможностей того, как информация может комбинироваться. Они существовали всегда, поскольку информация в комбинаторном многообразии не нуждается ни в создании, ни в создателе, ни в каких-либо избыточных предположениях. Да, это антропный принцип. Мы наблюдаем нашу Вселенную, потому что возникли именно в такой комбинации информации. Это идея математичности всего.<br> Она может казаться сложной только на первый взгляд. Но на самом деле она очень простая.</p> <p>Все возможные миры существуют вечно, изолированно, без взаимодействия.<br> Они не создаются, не появляются, а просто есть как одна из континуума возможных комбинаций информации.</p> <p>В такой интерпретации нам уже не нужно предположение о случайности как о немотивированном изменении.<br> Любое изменение, которое выглядит как случайное, — это просто реализация комбинаторного многообразия. В бесчисленном количестве изолированных и &ldquo;параллельных&rdquo; вселенных были другие изменения, которых в нашей Вселенной могло не быть.</p> <p>Ещё раз: в нашей &ldquo;ветви&rdquo; (мире) исход квантового измерения кажется случайным, но глобально все варианты реализованы. Мы наблюдаем эту вселенную, потому что только в совместимых с жизнью комбинациях возникают наблюдатели (антропный принцип). Нет коллапса, нет ветвления (как в многомировой интерпретации) — просто статичные мультивселенные.</p> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="в-чём-отличие-от-многомировой-интерпретации-эверетта" > В чём отличие от многомировой интерпретации Эверетта </h3> <a data-clipboard-text="https://valmat.ru/posts/2025/08/randomness/#в-чём-отличие-от-многомировой-интерпретации-эверетта" class="gblog-post__anchor clip flex align-center" aria-label="Anchor В чём отличие от многомировой интерпретации Эверетта" href="#%d0%b2-%d1%87%d1%91%d0%bc-%d0%be%d1%82%d0%bb%d0%b8%d1%87%d0%b8%d0%b5-%d0%be%d1%82-%d0%bc%d0%bd%d0%be%d0%b3%d0%be%d0%bc%d0%b8%d1%80%d0%be%d0%b2%d0%be%d0%b9-%d0%b8%d0%bd%d1%82%d0%b5%d1%80%d0%bf%d1%80%d0%b5%d1%82%d0%b0%d1%86%d0%b8%d0%b8-%d1%8d%d0%b2%d0%b5%d1%80%d0%b5%d1%82%d1%82%d0%b0"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>На первый взгляд идея, которую я тут предлагаю, очень похожа на интерпретацию Эверетта.<br> Но есть важное отличие.<br> Никакого разветвления не происходит. Наблюдатель ни на что не влияет и вообще не требуется ни наблюдатель, ни измерение.<br> Коллапс волновой функции снова имеет разумное объяснение как просто байесовское уточнение информации, а не как магический процесс.</p> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="почему-это-минималистично-по-оккаму" > Почему это минималистично по Оккаму? </h3> <a data-clipboard-text="https://valmat.ru/posts/2025/08/randomness/#почему-это-минималистично-по-оккаму" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Почему это минималистично по Оккаму?" href="#%d0%bf%d0%be%d1%87%d0%b5%d0%bc%d1%83-%d1%8d%d1%82%d0%be-%d0%bc%d0%b8%d0%bd%d0%b8%d0%bc%d0%b0%d0%bb%d0%b8%d1%81%d1%82%d0%b8%d1%87%d0%bd%d0%be-%d0%bf%d0%be-%d0%be%d0%ba%d0%ba%d0%b0%d0%bc%d1%83"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <ul> <li><strong>Меньше допущений</strong>: Вместо &ldquo;случайности как сущности&rdquo; или &ldquo;нелокальности&rdquo; — неоспоримая фундаментальность математики. В основе всего — просто комбинаторика.</li> <li><strong>Вечность и простота</strong>: Нет начала и конца. Информация вечна и вообще вне времени.</li> </ul> <div class="flex align-center gblog-post__anchorwrap"> <h2 id="выводы" > Выводы </h2> <a data-clipboard-text="https://valmat.ru/posts/2025/08/randomness/#выводы" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Выводы" href="#%d0%b2%d1%8b%d0%b2%d0%be%d0%b4%d1%8b"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Разумеется, то, что я тут написал, — это не научная теория.<br> Всё это нефальсифицируемо, как и многие другие интерпретации квантовой механики.<br> Но эта идея логична и минималистична по Оккаму.</p> <p>Возможно, она кажется сложной из-за бесконечности. Но стоит вспомнить о фракталах — простое правило может порождать сложное.</p> <p>В общем, мы не знаем, существует ли объективная случайность, а если существует, то как она реализуется.<br> А может быть, Белл просто подсвечивает наши пробелы в понимании. Это размышления, а не истина.</p> LittleVec — легковесная векторная база данных https://valmat.ru/posts/2024/06/little-vec/ 2025-06-24T12:00:00+03:00 2025-06-24T12:00:00+03:00 <p>В последние годы векторные базы данных стали неотъемлемой частью современных ИИ-проектов: поиск по embedding, RAG-пайплайны, быстрый семантический поиск и многое другое. Однако, если вы когда-либо пробовали развернуть подобную БД — будь то FAISS, Milvus, Qdrant или аналогичные решения — вы наверняка сталкивались с тем, что даже для небольших экспериментов и pet-проектов такие системы требуют довольно мощного железа. В итоге приходится либо платить за дорогой сервер, либо мириться с низкой скоростью и неудобствами.</p> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="мотивация-и-цели" > Мотивация и цели </h3> <a data-clipboard-text="https://valmat.ru/posts/2024/06/little-vec/#мотивация-и-цели" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Мотивация и цели" href="#%d0%bc%d0%be%d1%82%d0%b8%d0%b2%d0%b0%d1%86%d0%b8%d1%8f-%d0%b8-%d1%86%d0%b5%d0%bb%d0%b8"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Я регулярно разрабатываю прототипы и экспериментирую с RAG-пайплайнами, поэтому мне остро не хватало простой, быстрой и лёгкой векторной БД, которую можно было бы запустить на любом VPS или даже на домашнем мини-компьютере.<br> Мои цели были такие:</p> <ul> <li><strong>Минимальные требования к ресурсам.</strong> Максимум эффективности: чтобы можно было держать миллионы векторов даже на слабых серверах.</li> <li><strong>Простота запуска и интеграции.</strong>.</li> <li><strong>Надёжность хранения.</strong> Не хочется терять данные из-за сбоя — значит, нужна проверенная СУБД в основе.</li> <li><strong>Гибкость.</strong> Возможность использовать разные метрики расстояния, хранить дополнительную информацию (payload), работать с несколькими базами одновременно.</li> </ul> <p>Так и родился <a class="gblog-markdown__link" href="https://github.com/valmat/little-vec" >LittleVec</a> — минималистичная векторная база данных, работающая как плагин для <a class="gblog-markdown__link" href="https://github.com/valmat/RocksServer" >RocksServer</a> и использующая в качестве хранилища сверхнадёжную <a class="gblog-markdown__link" href="https://rocksdb.org/" >RocksDB</a>.</p> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="почему-это-может-быть-полезно-вам" > Почему это может быть полезно вам? </h3> <a data-clipboard-text="https://valmat.ru/posts/2024/06/little-vec/#почему-это-может-быть-полезно-вам" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Почему это может быть полезно вам?" href="#%d0%bf%d0%be%d1%87%d0%b5%d0%bc%d1%83-%d1%8d%d1%82%d0%be-%d0%bc%d0%be%d0%b6%d0%b5%d1%82-%d0%b1%d1%8b%d1%82%d1%8c-%d0%bf%d0%be%d0%bb%d0%b5%d0%b7%d0%bd%d0%be-%d0%b2%d0%b0%d0%bc"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <ul> <li><strong>Экономия ресурсов.</strong> LittleVec потребляет всего 15–50 Мб ОЗУ даже на миллионах записей. Это значит, что вы можете запускать полноценную векторную БД даже на бюджетных VPS или в контейнере на локальной машине.</li> <li><strong>Быстрая интеграция.</strong> Минималистичный и понятный API, готовые Docker-образы, поддержка DEB-пакетов — всё, чтобы вы могли быстро начать использовать LittleVec в своём проекте.</li> <li><strong>Надёжность и скорость.</strong> Благодаря RocksDB и оптимизациям на уровне хранения, поиск по миллиону векторов занимает ~60 мс на обычном сервере.</li> </ul> <hr> <div class="flex align-center gblog-post__anchorwrap"> <h2 id="как-использовать-littlevec" > Как использовать LittleVec </h2> <a data-clipboard-text="https://valmat.ru/posts/2024/06/little-vec/#как-использовать-littlevec" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Как использовать LittleVec" href="#%d0%ba%d0%b0%d0%ba-%d0%b8%d1%81%d0%bf%d0%be%d0%bb%d1%8c%d0%b7%d0%be%d0%b2%d0%b0%d1%82%d1%8c-littlevec"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="1-установка-и-запуск" > 1. Установка и запуск </h3> <a data-clipboard-text="https://valmat.ru/posts/2024/06/little-vec/#1-установка-и-запуск" class="gblog-post__anchor clip flex align-center" aria-label="Anchor 1. Установка и запуск" href="#1-%d1%83%d1%81%d1%82%d0%b0%d0%bd%d0%be%d0%b2%d0%ba%d0%b0-%d0%b8-%d0%b7%d0%b0%d0%bf%d1%83%d1%81%d0%ba"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <div class="flex align-center gblog-post__anchorwrap"> <h4 id="вариант-1-docker" > Вариант 1: Docker </h4> <a data-clipboard-text="https://valmat.ru/posts/2024/06/little-vec/#вариант-1-docker" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Вариант 1: Docker" href="#%d0%b2%d0%b0%d1%80%d0%b8%d0%b0%d0%bd%d1%82-1-docker"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Самый простой способ — воспользоваться Docker-образом:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker pull valmatdocker/littlevec </span></span><span class="line"><span class="cl">docker run -d -p 5577:5577 --name littlevec valmatdocker/littlevec </span></span></code></pre></div><p>После запуска API будет доступно на <code>127.0.0.1:5577</code>.</p> <div class="flex align-center gblog-post__anchorwrap"> <h4 id="вариант-2-установка-через-deb-пакет" > Вариант 2: Установка через DEB-пакет </h4> <a data-clipboard-text="https://valmat.ru/posts/2024/06/little-vec/#вариант-2-установка-через-deb-пакет" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Вариант 2: Установка через DEB-пакет" href="#%d0%b2%d0%b0%d1%80%d0%b8%d0%b0%d0%bd%d1%82-2-%d1%83%d1%81%d1%82%d0%b0%d0%bd%d0%be%d0%b2%d0%ba%d0%b0-%d1%87%d0%b5%d1%80%d0%b5%d0%b7-deb-%d0%bf%d0%b0%d0%ba%d0%b5%d1%82"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Если вы предпочитаете запускать сервис напрямую:</p> <ol> <li>Установите <a class="gblog-markdown__link" href="https://github.com/valmat/RocksServer/releases" >RocksServer</a>.</li> <li>Клонируйте репозиторий LittleVec и соберите DEB-пакет:</li> </ol> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone https://github.com/valmat/little-vec.git </span></span><span class="line"><span class="cl"><span class="nb">cd</span> little-vec/build_deb </span></span><span class="line"><span class="cl">./build.sh </span></span><span class="line"><span class="cl">sudo dpkg -i littlevec_&lt;version&gt;_amd64.deb </span></span></code></pre></div><ol start="3"> <li>Перезапустите RocksServer:</li> </ol> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo /etc/init.d/rocksserver restart </span></span></code></pre></div><div class="flex align-center gblog-post__anchorwrap"> <h4 id="вариант-3-сборка-вручную" > Вариант 3: Сборка вручную </h4> <a data-clipboard-text="https://valmat.ru/posts/2024/06/little-vec/#вариант-3-сборка-вручную" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Вариант 3: Сборка вручную" href="#%d0%b2%d0%b0%d1%80%d0%b8%d0%b0%d0%bd%d1%82-3-%d1%81%d0%b1%d0%be%d1%80%d0%ba%d0%b0-%d0%b2%d1%80%d1%83%d1%87%d0%bd%d1%83%d1%8e"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">cd</span> src </span></span><span class="line"><span class="cl">make -j </span></span><span class="line"><span class="cl"><span class="c1"># Поместите little_vec.so в каталог плагинов RocksServer (обычно /usr/lib/rocksserver/plugins)</span> </span></span></code></pre></div><hr> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="2-быстрый-старт-с-api" > 2. Быстрый старт с API </h3> <a data-clipboard-text="https://valmat.ru/posts/2024/06/little-vec/#2-быстрый-старт-с-api" class="gblog-post__anchor clip flex align-center" aria-label="Anchor 2. Быстрый старт с API" href="#2-%d0%b1%d1%8b%d1%81%d1%82%d1%80%d1%8b%d0%b9-%d1%81%d1%82%d0%b0%d1%80%d1%82-%d1%81-api"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <div class="flex align-center gblog-post__anchorwrap"> <h4 id="создание-базы-данных" > Создание базы данных </h4> <a data-clipboard-text="https://valmat.ru/posts/2024/06/little-vec/#создание-базы-данных" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Создание базы данных" href="#%d1%81%d0%be%d0%b7%d0%b4%d0%b0%d0%bd%d0%b8%d0%b5-%d0%b1%d0%b0%d0%b7%d1%8b-%d0%b4%d0%b0%d0%bd%d0%bd%d1%8b%d1%85"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-http" data-lang="http"><span class="line"><span class="cl"><span class="err">POST /vecdb/create </span></span></span><span class="line"><span class="cl"><span class="err">Content-Type: application/json </span></span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;db_name&#34;</span><span class="p">:</span> <span class="s2">&#34;my_vectors&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;dim&#34;</span><span class="p">:</span> <span class="mi">128</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;dist&#34;</span><span class="p">:</span> <span class="s2">&#34;cos&#34;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><ul> <li><code>db_name</code> — имя вашей БД (любая строка)</li> <li><code>dim</code> — размерность векторов</li> <li><code>dist</code> — метрика: <code>cos</code>, <code>qcos</code>, <code>dot_prod</code>, <code>l1</code>, <code>l2</code> (опционально)</li> </ul> <div class="flex align-center gblog-post__anchorwrap"> <h4 id="добавление-векторов" > Добавление векторов </h4> <a data-clipboard-text="https://valmat.ru/posts/2024/06/little-vec/#добавление-векторов" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Добавление векторов" href="#%d0%b4%d0%be%d0%b1%d0%b0%d0%b2%d0%bb%d0%b5%d0%bd%d0%b8%d0%b5-%d0%b2%d0%b5%d0%ba%d1%82%d0%be%d1%80%d0%be%d0%b2"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-http" data-lang="http"><span class="line"><span class="cl"><span class="err">POST /vectors/set </span></span></span><span class="line"><span class="cl"><span class="err">Content-Type: application/json </span></span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;db_name&#34;</span><span class="p">:</span> <span class="s2">&#34;my_vectors&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;data&#34;</span><span class="p">:</span> <span class="p">[</span> </span></span><span class="line"><span class="cl"> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="s2">&#34;vec1&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;vector&#34;</span><span class="p">:</span> <span class="p">[</span><span class="mf">0.1</span><span class="p">,</span> <span class="mf">0.2</span><span class="p">,</span> <span class="err">...</span><span class="p">],</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;payload&#34;</span><span class="p">:</span> <span class="p">{</span><span class="nt">&#34;text&#34;</span><span class="p">:</span> <span class="s2">&#34;пример&#34;</span><span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">]</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><div class="flex align-center gblog-post__anchorwrap"> <h4 id="поиск-ближайших-векторов" > Поиск ближайших векторов </h4> <a data-clipboard-text="https://valmat.ru/posts/2024/06/little-vec/#поиск-ближайших-векторов" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Поиск ближайших векторов" href="#%d0%bf%d0%be%d0%b8%d1%81%d0%ba-%d0%b1%d0%bb%d0%b8%d0%b6%d0%b0%d0%b9%d1%88%d0%b8%d1%85-%d0%b2%d0%b5%d0%ba%d1%82%d0%be%d1%80%d0%be%d0%b2"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-http" data-lang="http"><span class="line"><span class="cl"><span class="err">POST /vectors/get/nearest </span></span></span><span class="line"><span class="cl"><span class="err">Content-Type: application/json </span></span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;db_name&#34;</span><span class="p">:</span> <span class="s2">&#34;my_vectors&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;vector&#34;</span><span class="p">:</span> <span class="p">[</span><span class="mf">0.1</span><span class="p">,</span> <span class="mf">0.2</span><span class="p">,</span> <span class="err">...</span><span class="p">],</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;top_k&#34;</span><span class="p">:</span> <span class="mi">5</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>Ответ:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;data&#34;</span><span class="p">:</span> <span class="p">[</span> </span></span><span class="line"><span class="cl"> <span class="p">{</span> <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="s2">&#34;vec1&#34;</span><span class="p">,</span> <span class="nt">&#34;distance&#34;</span><span class="p">:</span> <span class="mf">0.123</span><span class="p">,</span> <span class="nt">&#34;payload&#34;</span><span class="p">:</span> <span class="p">{</span><span class="nt">&#34;text&#34;</span><span class="p">:</span> <span class="s2">&#34;пример&#34;</span><span class="p">}</span> <span class="p">},</span> </span></span><span class="line"><span class="cl"> <span class="err">...</span> </span></span><span class="line"><span class="cl"> <span class="p">]</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><div class="flex align-center gblog-post__anchorwrap"> <h4 id="удаление-векторов" > Удаление векторов </h4> <a data-clipboard-text="https://valmat.ru/posts/2024/06/little-vec/#удаление-векторов" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Удаление векторов" href="#%d1%83%d0%b4%d0%b0%d0%bb%d0%b5%d0%bd%d0%b8%d0%b5-%d0%b2%d0%b5%d0%ba%d1%82%d0%be%d1%80%d0%be%d0%b2"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-http" data-lang="http"><span class="line"><span class="cl"><span class="err">POST /vectors/delete </span></span></span><span class="line"><span class="cl"><span class="err">Content-Type: application/json </span></span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;db_name&#34;</span><span class="p">:</span> <span class="s2">&#34;my_vectors&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;data&#34;</span><span class="p">:</span> <span class="p">[</span> </span></span><span class="line"><span class="cl"> <span class="p">{</span> <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="s2">&#34;vec1&#34;</span> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">]</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><hr> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="3-дополнительные-возможности" > 3. Дополнительные возможности </h3> <a data-clipboard-text="https://valmat.ru/posts/2024/06/little-vec/#3-дополнительные-возможности" class="gblog-post__anchor clip flex align-center" aria-label="Anchor 3. Дополнительные возможности" href="#3-%d0%b4%d0%be%d0%bf%d0%be%d0%bb%d0%bd%d0%b8%d1%82%d0%b5%d0%bb%d1%8c%d0%bd%d1%8b%d0%b5-%d0%b2%d0%be%d0%b7%d0%bc%d0%be%d0%b6%d0%bd%d0%be%d1%81%d1%82%d0%b8"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <ul> <li>Можно создавать несколько независимых векторных баз.</li> <li>В каждом запросе указывать свою метрику поиска.</li> <li>Хранить произвольный payload для каждого объекта.</li> <li>Получать distance между любыми векторами и объектами по id.</li> <li>Всё это — по простому HTTP API, легко интегрируется с любым языком.</li> </ul> <hr> <div class="flex align-center gblog-post__anchorwrap"> <h2 id="заключение" > Заключение </h2> <a data-clipboard-text="https://valmat.ru/posts/2024/06/little-vec/#заключение" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Заключение" href="#%d0%b7%d0%b0%d0%ba%d0%bb%d1%8e%d1%87%d0%b5%d0%bd%d0%b8%d0%b5"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p><strong>LittleVec</strong> — минималистичная, быстрая, экономная и надёжная векторная база данных для экспериментов, прототипов или даже небольших production-сервисов.</p> <p>Всё открыто, просто и прозрачно — пробуйте, присылайте фидбек и pull requests!</p> <ul> <li><a class="gblog-markdown__link" href="https://github.com/valmat/little-vec" >GitHub: LittleVec</a></li> <li><a class="gblog-markdown__link" href="https://github.com/valmat/little-vec#readme" >Документация и примеры</a></li> </ul> <hr> <p>Все вопросы в issue на GitHub!</p> AI git commit generator https://valmat.ru/posts/2024/02/git-ai/ 2024-02-07T12:00:00+03:00 2024-02-07T12:00:00+03:00 <p>В создании коммитов, да и тегов, для гита самое сложное — придумать ёмкое описание, чтобы оно включало все важные аспекты изменений. А ещё это нужно сделать в правильном формате и на грамотном английском.</p> <p>Поэтому в какой-то момент я просто взял и сделал bash-утилиты, использующие AI для этих вещей: <strong>gitai</strong> для коммитов и <strong>gitaitag</strong> для тегов. О них и расскажу ниже.</p> <hr> <div class="flex align-center gblog-post__anchorwrap"> <h2 id="ai-для-коммитов-и-тегов-gitai-и-gitaitag" > AI для коммитов и тегов: <code>gitai</code> и <code>gitaitag</code> </h2> <a data-clipboard-text="https://valmat.ru/posts/2024/02/git-ai/#ai-для-коммитов-и-тегов-gitai-и-gitaitag" class="gblog-post__anchor clip flex align-center" aria-label="Anchor AI для коммитов и тегов: gitai и gitaitag" href="#ai-%d0%b4%d0%bb%d1%8f-%d0%ba%d0%be%d0%bc%d0%bc%d0%b8%d1%82%d0%be%d0%b2-%d0%b8-%d1%82%d0%b5%d0%b3%d0%be%d0%b2-gitai-%d0%b8-gitaitag"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="что-это-такое" > Что это такое? </h3> <a data-clipboard-text="https://valmat.ru/posts/2024/02/git-ai/#что-это-такое" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Что это такое?" href="#%d1%87%d1%82%d0%be-%d1%8d%d1%82%d0%be-%d1%82%d0%b0%d0%ba%d0%be%d0%b5"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p><a class="gblog-markdown__link" href="https://gist.github.com/valmat/44822e1b7c6884bebb25b3ff005117fe" >gitai</a> — это bash-скрипт для Linux и macOS, который помогает создавать информативные git commit-сообщения с помощью OpenAI (или совместимых API). Скрипт не только пишет лаконичные заголовки в прошедшем времени и краткое описание изменений, но и добавляет эмодзи, список затронутых файлов и даже проводит AI-код-ревью с подсветкой потенциальных проблем или улучшений — всё на грамотном английском.</p> <p><a class="gblog-markdown__link" href="https://gist.github.com/valmat/cd64141685f2655c4a02d59902962ca3" >gitaitag</a> — похожий bash-скрипт для генерации содержательных сообщений к git-тегам и автоматического подбора следующей версии. Он анализирует все коммиты с прошлого тега, резюмирует изменения, добавляет релевантные эмодзи и оформляет красивое англоязычное описание релиза.</p> <hr> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="ключевые-возможности" > Ключевые возможности </h3> <a data-clipboard-text="https://valmat.ru/posts/2024/02/git-ai/#ключевые-возможности" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Ключевые возможности" href="#%d0%ba%d0%bb%d1%8e%d1%87%d0%b5%d0%b2%d1%8b%d0%b5-%d0%b2%d0%be%d0%b7%d0%bc%d0%be%d0%b6%d0%bd%d0%be%d1%81%d1%82%d0%b8"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <div class="flex align-center gblog-post__anchorwrap"> <h4 id="gitai" > <code>gitai</code>: </h4> <a data-clipboard-text="https://valmat.ru/posts/2024/02/git-ai/#gitai" class="gblog-post__anchor clip flex align-center" aria-label="Anchor gitai:" href="#gitai"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <ul> <li>✨ Генерирует commit-сообщения через OpenAI или совместимый API (можно быстро переключиться на xAI и т.д.)</li> <li>🐞 Проводит AI-код-ревью и вставляет комментарии о возможных багах или улучшениях прямо в коммит</li> <li>📝 Автоматически резюмирует изменения (diff)</li> <li>💬 Украсит сообщение релевантными эмодзи</li> <li>📄 Перечисляет изменённые файлы</li> <li>💡 Принимает подсказку (hint) для дополнительного контекста</li> <li>🏷️ Всегда пишет на английском</li> <li>🔒 Не даст закоммитить, если нет подготовленных изменений</li> <li>🖊️ Даёт возможность отредактировать сообщение перед коммитом</li> </ul> <div class="flex align-center gblog-post__anchorwrap"> <h4 id="gitaitag" > <code>gitaitag</code>: </h4> <a data-clipboard-text="https://valmat.ru/posts/2024/02/git-ai/#gitaitag" class="gblog-post__anchor clip flex align-center" aria-label="Anchor gitaitag:" href="#gitaitag"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <ul> <li>🚀 Использует любой OpenAI-совместимый API для генерации описания тега</li> <li>🏷️ Автоматически подбирает следующую версию тега (например, “v1.2.3” → “v1.2.4”)</li> <li>✍️ Генерирует подробный changelog по всем коммитам с прошлого тега</li> <li>🤖 Добавляет эмодзи и делает сообщение приятным для чтения</li> <li>💡 Можно добавить свой hint для более точного описания релиза</li> <li>🏷️ Всегда генерирует сообщение на английском</li> <li>✏️ Позволяет вручную подправить текст перед созданием тега</li> <li>🔒 Не создаёт тег, если нет новых коммитов с прошлого релиза</li> </ul> <hr> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="требования" > Требования </h3> <a data-clipboard-text="https://valmat.ru/posts/2024/02/git-ai/#требования" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Требования" href="#%d1%82%d1%80%d0%b5%d0%b1%d0%be%d0%b2%d0%b0%d0%bd%d0%b8%d1%8f"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Обе утилиты требуют:</p> <ul> <li><strong>ОС:</strong> Linux или macOS (Windows не поддерживается)</li> <li><strong>Зависимости:</strong> <ul> <li><a class="gblog-markdown__link--code" href="https://stedolan.github.io/jq/" ><code>jq</code></a> (парсинг JSON)</li> <li><a class="gblog-markdown__link--code" href="https://curl.se/" ><code>curl</code></a> (обычно уже установлен)</li> <li><a class="gblog-markdown__link--code" href="https://www.nano-editor.org/" ><code>nano</code></a> (или замените на любимый редактор в скрипте)</li> <li><strong>API-ключ OpenAI:</strong> переменная окружения <code>$OPENAI_API_KEY</code></li> </ul> </li> </ul> <hr> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="установка" > Установка </h3> <a data-clipboard-text="https://valmat.ru/posts/2024/02/git-ai/#установка" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Установка" href="#%d1%83%d1%81%d1%82%d0%b0%d0%bd%d0%be%d0%b2%d0%ba%d0%b0"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <ol> <li> <p><strong>Установите зависимости:</strong></p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="c1"># Для Debian/Ubuntu и производных</span> </span></span><span class="line"><span class="cl">sudo apt-get install jq curl nano </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c1"># Для macOS (через Homebrew)</span> </span></span><span class="line"><span class="cl">brew install jq curl nano </span></span></code></pre></div></li> <li> <p><strong>Скачайте скрипты:</strong></p> <ul> <li><strong>gitai:</strong> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">curl -o ~/bin/gitai https://gist.github.com/valmat/44822e1b7c6884bebb25b3ff005117fe/raw/gitai.sh </span></span><span class="line"><span class="cl">chmod +x ~/bin/gitai </span></span></code></pre></div></li> <li><strong>gitaitag:</strong> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">curl -o ~/bin/gitaitag https://gist.github.com/valmat/cd64141685f2655c4a02d59902962ca3/raw/gitaitag.sh </span></span><span class="line"><span class="cl">chmod +x ~/bin/gitaitag </span></span></code></pre></div></li> </ul> </li> <li> <p><strong>Добавьте <code>~/bin</code> в <code>$PATH</code>:</strong></p> <p>Добавьте в <code>~/.bashrc</code>, <code>~/.zshrc</code> или аналогичный файл:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">PATH</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/bin:</span><span class="nv">$PATH</span><span class="s2">&#34;</span> </span></span></code></pre></div><p>Перезапустите терминал или выполните <code>source ~/.bashrc</code> (или соответствующий файл).</p> </li> <li> <p><strong>Установите переменную с OpenAI API-ключом:</strong></p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">OPENAI_API_KEY</span><span class="o">=</span><span class="s2">&#34;sk-...your key here...&#34;</span> </span></span></code></pre></div><p>Для удобства добавьте эту строку в настройки вашей оболочки.</p> </li> </ol> <hr> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="использование" > Использование </h3> <a data-clipboard-text="https://valmat.ru/posts/2024/02/git-ai/#использование" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Использование" href="#%d0%b8%d1%81%d0%bf%d0%be%d0%bb%d1%8c%d0%b7%d0%be%d0%b2%d0%b0%d0%bd%d0%b8%d0%b5"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <div class="flex align-center gblog-post__anchorwrap"> <h4 id="для-коммитов--gitai" > Для коммитов — <code>gitai</code>: </h4> <a data-clipboard-text="https://valmat.ru/posts/2024/02/git-ai/#для-коммитов--gitai" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Для коммитов — gitai:" href="#%d0%b4%d0%bb%d1%8f-%d0%ba%d0%be%d0%bc%d0%bc%d0%b8%d1%82%d0%be%d0%b2--gitai"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <ol> <li> <p>Перейдите в директорию git-проекта:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="nb">cd</span> /path/to/your/project </span></span></code></pre></div></li> <li> <p>Запустите скрипт:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">gitai </span></span></code></pre></div><p>Скрипт:</p> <ul> <li>автоматически выполнит <code>git add</code> для всех изменений,</li> <li>сгенерирует commit-сообщение с помощью AI,</li> <li>откроет его в редакторе для финального редактирования,</li> <li>закоммитит после сохранения.</li> </ul> </li> <li> <p>(Опционально) Можно добавить hint для AI:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">gitai <span class="s2">&#34;Fix for production crash on startup&#34;</span> </span></span></code></pre></div></li> </ol> <div class="flex align-center gblog-post__anchorwrap"> <h4 id="для-тегов--gitaitag" > Для тегов — <code>gitaitag</code>: </h4> <a data-clipboard-text="https://valmat.ru/posts/2024/02/git-ai/#для-тегов--gitaitag" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Для тегов — gitaitag:" href="#%d0%b4%d0%bb%d1%8f-%d1%82%d0%b5%d0%b3%d0%be%d0%b2--gitaitag"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <ol> <li> <p>Перейдите в директорию git-проекта:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="nb">cd</span> /path/to/your/project </span></span></code></pre></div></li> <li> <p>Запустите скрипт:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">gitaitag </span></span></code></pre></div><p>Скрипт:</p> <ul> <li>найдет последний тег и предложит следующий (например, “v1.2.3” → “v1.2.4”),</li> <li>проанализирует все коммиты после прошлого тега,</li> <li>сгенерирует AI-описание релиза с эмодзи,</li> <li>откроет его в редакторе для вашего финального взгляда,</li> <li>создаст аннотированный тег с этим сообщением.</li> </ul> </li> <li> <p>(Опционально) Можно добавить hint для AI:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">gitaitag <span class="s2">&#34;Major refactor and new API integration&#34;</span> </span></span></code></pre></div></li> </ol> <hr> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="примечания" > Примечания </h3> <a data-clipboard-text="https://valmat.ru/posts/2024/02/git-ai/#примечания" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Примечания" href="#%d0%bf%d1%80%d0%b8%d0%bc%d0%b5%d1%87%d0%b0%d0%bd%d0%b8%d1%8f"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <ul> <li>Скрипты не поддерживают Windows.</li> <li>Редактор по умолчанию — <code>nano</code>, можно заменить в скрипте на любой другой.</li> <li>Если нет изменений (для <code>gitai</code>) или новых коммитов с прошлого тега (для <code>gitaitag</code>), скрипты не будут ничего делать.</li> <li>Каждый запрос к AI учитывается в биллинге вашего провайдера.</li> </ul> <hr> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="примеры" > Примеры </h3> <a data-clipboard-text="https://valmat.ru/posts/2024/02/git-ai/#примеры" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Примеры" href="#%d0%bf%d1%80%d0%b8%d0%bc%d0%b5%d1%80%d1%8b"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="c1"># Обычный коммит с помощью AI</span> </span></span><span class="line"><span class="cl">gitai </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c1"># Коммит с подсказкой для AI</span> </span></span><span class="line"><span class="cl">gitai <span class="s2">&#34;Refactored backend integration&#34;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c1"># Обычное создание тега с описанием через AI</span> </span></span><span class="line"><span class="cl">gitaitag </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c1"># Тег с подсказкой для AI</span> </span></span><span class="line"><span class="cl">gitaitag <span class="s2">&#34;Initial stable version for production&#34;</span> </span></span></code></pre></div><hr> <div class="flex align-center gblog-post__anchorwrap"> <h2 id="ссылки" > Ссылки </h2> <a data-clipboard-text="https://valmat.ru/posts/2024/02/git-ai/#ссылки" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Ссылки" href="#%d1%81%d1%81%d1%8b%d0%bb%d0%ba%d0%b8"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <ul> <li><a class="gblog-markdown__link" href="https://gist.github.com/valmat/44822e1b7c6884bebb25b3ff005117fe" >gitai</a>.</li> <li><a class="gblog-markdown__link" href="https://gist.github.com/valmat/cd64141685f2655c4a02d59902962ca3" >gitaitag</a>.</li> </ul> Утилиты для извлечения изображений и текста из PDF https://valmat.ru/posts/2023/11/pdf-extract/ 2023-11-16T12:00:00+03:00 2023-11-16T12:00:00+03:00 <p>В повседневной работе часто возникает задача быстро извлечь изображения или текст из PDF-документов — будь то подготовка презентаций, анализ документов, создание датасетов или автоматизация обработки большого количества файлов. Стандартные графические редакторы или онлайн-сервисы либо требуют ручной работы, либо работают медленно, либо не позволяют автоматизировать процесс.</p> <p>Чтобы упростить и ускорить решение этих задач, я написал набор утилит на C++ — <strong>PDF2Images</strong>. Они позволяют:</p> <ul> <li>Мгновенно извлекать все изображения из PDF-файлов в нужном формате (png, jpg, tiff и др.)</li> <li>Получать текст с разбивкой по страницам или в один файл</li> <li>Гибко настраивать параметры извлечения: диапазон страниц, формат выходных файлов, разрешение и пр.</li> <li>Использовать утилиты в автоматических скриптах и пайплайнах</li> </ul> <p>Я сам ежедневно использую эти инструменты для подготовки скриншотов и текстовых выборок из PDF, а также для построения RAG (Retrieval-Augmented Generation) — когда нужно быстро получить текстовую базу для дальнейшей работы с LLM.</p> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="установка" > Установка </h3> <a data-clipboard-text="https://valmat.ru/posts/2023/11/pdf-extract/#установка" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Установка" href="#%d1%83%d1%81%d1%82%d0%b0%d0%bd%d0%be%d0%b2%d0%ba%d0%b0"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Для работы потребуется установленная библиотека poppler:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">apt install libpoppler-dev </span></span></code></pre></div><p>Далее клонируем репозиторий:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone --recursive https://github.com/valmat/pdf2images </span></span><span class="line"><span class="cl"><span class="nb">cd</span> pdf2images </span></span></code></pre></div><p>или, если без <code>--recursive</code>:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone https://github.com/valmat/pdf2images </span></span><span class="line"><span class="cl"><span class="nb">cd</span> pdf2images </span></span><span class="line"><span class="cl">git submodule update --init --recursive </span></span></code></pre></div><p>Переходим в папку <code>src</code> и собираем проект:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">cd</span> src </span></span><span class="line"><span class="cl">make release </span></span></code></pre></div><p>Для debug-сборки можно использовать:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">make -j </span></span></code></pre></div><div class="flex align-center gblog-post__anchorwrap"> <h3 id="извлечение-изображений" > Извлечение изображений </h3> <a data-clipboard-text="https://valmat.ru/posts/2023/11/pdf-extract/#извлечение-изображений" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Извлечение изображений" href="#%d0%b8%d0%b7%d0%b2%d0%bb%d0%b5%d1%87%d0%b5%d0%bd%d0%b8%d0%b5-%d0%b8%d0%b7%d0%be%d0%b1%d1%80%d0%b0%d0%b6%d0%b5%d0%bd%d0%b8%d0%b9"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">bin/extract_imgs.bin &lt;input_file.pdf&gt; <span class="o">[</span>options<span class="o">]</span> </span></span></code></pre></div><p><strong>Основные опции:</strong></p> <ul> <li><code>-i</code>, <code>--input</code> — путь к PDF-файлу</li> <li><code>-o</code>, <code>--output</code> — папка для сохранения изображений (по умолчанию <code>.</code>)</li> <li><code>-e</code>, <code>--ext</code> — формат изображений (по умолчанию <code>png</code>, можно <code>jpg</code>, <code>tiff</code> и др.)</li> <li><code>-f</code>, <code>--from</code> — первая страница для обработки (по умолчанию 1)</li> <li><code>-l</code>, <code>--lim</code> — количество страниц для обработки (по умолчанию нет ограничений)</li> <li><code>-x</code>, <code>--xres</code>, <code>-y</code>, <code>--yres</code>, <code>-d</code>, <code>--dpi</code> — разрешение и dpi</li> <li><code>-g</code>, <code>--gray</code> — черно-белый режим</li> <li><code>-q</code>, <code>--quiet</code> — тихий режим (без вывода прогресса)</li> </ul> <p>Пример:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">bin/extract_imgs.bin -i mydoc.pdf -o imgs -e jpg -f <span class="m">2</span> -l <span class="m">5</span> </span></span></code></pre></div><p>Извлечет изображения со 2 по 6 страницу в папку <code>imgs</code> в формате jpg.</p> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="извлечение-текста" > Извлечение текста </h3> <a data-clipboard-text="https://valmat.ru/posts/2023/11/pdf-extract/#извлечение-текста" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Извлечение текста" href="#%d0%b8%d0%b7%d0%b2%d0%bb%d0%b5%d1%87%d0%b5%d0%bd%d0%b8%d0%b5-%d1%82%d0%b5%d0%ba%d1%81%d1%82%d0%b0"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">bin/extract_txts.bin -i &lt;input_file.pdf&gt; <span class="o">[</span>options<span class="o">]</span> </span></span></code></pre></div><p><strong>Основные опции:</strong></p> <ul> <li><code>-i</code>, <code>--input</code> — входной PDF (обязательно)</li> <li><code>-o</code>, <code>--out-dir</code> — папка для сохранения текстов (по умолчанию <code>./</code>)</li> <li><code>-O</code>, <code>--out-file</code> — имя выходного файла (если указано, все страницы будут в одном файле)</li> <li><code>-f</code>, <code>--from</code> — первая страница для извлечения (по умолчанию 1)</li> <li><code>-l</code>, <code>--limit</code> — сколько страниц извлекать (по умолчанию без ограничения)</li> <li><code>-n</code>, <code>--nopagebreak</code> — не добавлять разделитель между страницами (актуально при сохранении в один файл)</li> </ul> <p>Пример:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">bin/extract_txts.bin -i mydoc.pdf -O all_text.txt -f <span class="m">1</span> -l <span class="m">10</span> </span></span></code></pre></div><p>Извлечет текст с 1 по 10 страницу в файл <code>all_text.txt</code>.</p> <hr> <p>Утилиты распространяются под лицензией MIT, исходники доступны на <a class="gblog-markdown__link" href="https://github.com/valmat/pdf2images" >GitHub</a>.</p> Получение распределений в задачах регрессии https://valmat.ru/posts/2022/09/regress-distr/ 2022-09-20T12:00:00+03:00 2022-09-20T12:00:00+03:00 <p>Приводится алгоритм нахождения функций распределения в качестве решения задачи регрессии.</p> <p>В общем виде задачу регрессии можно сформулировать как восстановление зависимости<br> $\phi: X \to L_1(\Omega)$,<br> сопоставляющей элементам некоторого фазового пространства $X$ случайную величину $\xi \in L_1(\Omega)$.</p> <p>Классический подход к решению задачи регрессии состоит в нахождении среднего значения $E[\phi(x)]$ для каждого $x \in X$.</p> <p>В статье предлагается простой алгоритм оценки распределений случайных величин $\phi(x) \in L_1(\Omega)$.</p> <p><a class="gblog-markdown__link" href="https://github.com/valmat" >GitHub</a></p> <hr> <div class="gblog-toc gblog-toc__level--6"> <nav id="TableOfContents"> <ul> <li> <ul> <li><a href="#мотивация">Мотивация</a></li> <li><a href="#описание-подхода">Описание подхода</a> <ul> <li><a href="#постановка-задачи">Постановка задачи</a></li> <li><a href="#построение-модели">Построение модели</a></li> <li><a href="#ограничения">Ограничения</a></li> </ul> </li> <li><a href="#итоговый-алгоритм">Итоговый алгоритм</a></li> <li><a href="#валидация">Валидация</a></li> <li><a href="#эксперименты">Эксперименты</a></li> <li><a href="#заключение">Заключение</a></li> </ul> </li> </ul> </nav> <hr /> </div> <hr> <div class="flex align-center gblog-post__anchorwrap"> <h2 id="мотивация" > Мотивация </h2> <a data-clipboard-text="https://valmat.ru/posts/2022/09/regress-distr/#мотивация" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Мотивация" href="#%d0%bc%d0%be%d1%82%d0%b8%d0%b2%d0%b0%d1%86%d0%b8%d1%8f"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>В анализе данных значительное место занимают два класса задач — задачи классификации и регрессии.</p> <p>Так сложилось, что, хотя эти задачи очень похожи, подход к их решению отличается.</p> <p>Большинство алгоритмов решения задач <strong>классификации</strong> позволяют не просто оценить среднее значение $E[\phi(x)]$ для каждого элемента фазового пространства $X$, но и найти плотность распределения.</p> <p>Для задач <strong>регрессии</strong> обычно находят лишь некоторую числовую оценку $\widehat{\phi(x)}$, которая, чаще всего, является средним значением, но не находят плотность распределения.</p> <p>Знание плотности распределения даёт гораздо больше возможностей для принятия решений, чем просто оценка среднего.</p> <p>Например, для заданной точки $x \in X$ мы можем:</p> <ul> <li>Оценить уверенность прогноза в каждой конкретной точке.</li> <li>Найти не среднее, а наиболее вероятное значение случайной величины. Это особенно актуально, если распределение $\phi(x)$ является мультимодальным.</li> <li>Определить доверительный интервал возможных значений оценки $\widehat{\phi(x)}$.</li> <li>Вычислить любые характеристики распределения, определяемые конкретной задачей и позволяющие более взвешенно и точно принимать решения на основе прогноза модели.</li> </ul> <hr> <div class="flex align-center gblog-post__anchorwrap"> <h2 id="описание-подхода" > Описание подхода </h2> <a data-clipboard-text="https://valmat.ru/posts/2022/09/regress-distr/#описание-подхода" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Описание подхода" href="#%d0%be%d0%bf%d0%b8%d1%81%d0%b0%d0%bd%d0%b8%d0%b5-%d0%bf%d0%be%d0%b4%d1%85%d0%be%d0%b4%d0%b0"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="постановка-задачи" > Постановка задачи </h3> <a data-clipboard-text="https://valmat.ru/posts/2022/09/regress-distr/#постановка-задачи" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Постановка задачи" href="#%d0%bf%d0%be%d1%81%d1%82%d0%b0%d0%bd%d0%be%d0%b2%d0%ba%d0%b0-%d0%b7%d0%b0%d0%b4%d0%b0%d1%87%d0%b8"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Для простоты опишу подход для одномерной задачи регрессии. В многомерном случае подход аналогичен.</p> <p>Имеем некоторое фазовое пространство $X$ и закономерность</p> <p>$$ \phi: X \to L_1(\Omega, \mathbb{R}) $$</p> <p>$\phi$ сопоставляет случайные величины из $L_1(\Omega)$ точкам фазового пространства $X$.</p> <p>Таким образом, мы имеем семейство вероятностных мер $\lbrace P_x\rbrace_{x \in X}$, порождаемых закономерностью $\phi$.</p> <p>Нам нужно построить модель, порождающую параметрическое семейство вероятностных мер</p> <p>$$ \lbrace Q_{x, \theta}\rbrace_{x \in X, \theta \in \Theta} $$</p> <p>и найти оптимальное значение параметра $\theta_0 \in \Theta$, дающее наилучшее, в некотором смысле, приближение реальных распределений $\lbrace P_x\rbrace_{x \in X}$:</p> <p>$$ Q_{x, \theta_0} \sim P_x $$</p> <p>При этом мы располагаем выборкой точек $\lbrace(x_i, y_i)\rbrace_{i=1}^N$, порожденной $N$ независимыми испытаниями: $x_i \in X$, $y_i = \phi(x_i)$.</p> <p>$y_i \in L_1(\Omega)$ — являются независимыми случайными величинами. $x_i \in X$, в общем случае, случайными величинами могут и не быть.</p> <p>Чтобы понять как строить модель, решающую поставленную задачу, посмотрим как она решается в случае задач классификации.</p> <p>В приведенной выше постановке задачи единственным отличием задачи классификации от задачи регрессии является то, что для задач классификации вероятностное пространство $L_1(\Omega)$ является дискретным.</p> <p>Когда задача моделирования распределения решается для дискретного $L_1(\Omega)$, т.е. для классификации, реальную плотность распределения приближают функциями вида</p> <p>$$ \sum_{k=1}^K \mathbf{1}_{A_k} $$</p> <p>где $A_k \subseteq \Omega$, $\mathbf{1}_{A}$ — характеристическая функция множества $A$.</p> <p>Именно так мы и поступим.</p> <p>Только для решения задачи регрессии моделировать лучше не плотность, а функцию распределения. На это есть ряд причин.</p> <p>Во-первых, использование функции распределения является более робастным, чем использование плотности.</p> <p>Во-вторых, плотность распределения должна удовлетворять свойству $\int_{\mathbb{R}} p(t) dt = 1$. Это свойство может быть сложнее удовлетворить при построении модели, чем соответствующее ограничение на функцию распределения:</p> <p>$$ \lim\limits_{t \to -\infty}F(t) = 0, \ \lim\limits_{t \to +\infty}F(t) = 1. $$</p> <hr> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="построение-модели" > Построение модели </h3> <a data-clipboard-text="https://valmat.ru/posts/2022/09/regress-distr/#построение-модели" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Построение модели" href="#%d0%bf%d0%be%d1%81%d1%82%d1%80%d0%be%d0%b5%d0%bd%d0%b8%d0%b5-%d0%bc%d0%be%d0%b4%d0%b5%d0%bb%d0%b8"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Вместо привычной для регрессии модели</p> <p>$$ M_{\theta}: X \to \mathbb{R} $$</p> <p>и последующего нахождения $\theta$ путём оптимизации, будем строить модель, сразу приближающую функции распределения:</p> <p>$$ M_{\theta}: X \to (\mathbb{R} \to [0, 1]) $$</p> <p>или, что то же самое:</p> <p>$$ M_{\theta}: X \times \mathbb{R} \to [0, 1] $$</p> <p>То есть каждой паре $(x, t)$, $x \in X, t \in \mathbb{R}$, наша модель будет сопоставлять число в интервале $[0, 1]$.</p> <p>Например, для нейронных сетей этого легко добиться, поместив сигмоиду последним слоем сети.</p> <p>Информация о реальном семействе распределений $\lbrace P_x \rbrace_{x \in X}$, которой мы располагаем, отражена в имеющейся у нас обучающей выборке $\lbrace(x_i, y_i)\rbrace_{i=1}^N$.</p> <p>Эта обучающая выборка порождает набор тривиальных функций распределения $\lbrace F_i\rbrace_{i=1}^N$:</p> <p>$$ F_i(t) = \begin{cases} 1, &amp; t \geqslant y_i |\ 0, &amp; t &lt; y_i \end{cases} $$</p> <p>$F_i(t) = 1$, при $t \geqslant y_i$, и $F_i(t) = 0$, при $t &lt; y_i$.</p> <p>Чтобы уйти от задачи построения модели, аппроксимирующей выборку функций, к хорошо изученной задаче построения модели, аппроксимирующей выборку точек, перейдем от выборки $\lbrace(x_i, y_i)\rbrace_{i=1}^N$ к выборке</p> <p>$$ \bigcup\limits_{i=1}^N \lbrace(x_i, t_j, F_i(t_j))\rbrace_{j \in J_i} $$</p> <p>Для этого для каждого $i = 1&hellip;N$ случайным образом подберём числа $t_j$ для $j \in J_i$ из некоторого диапазона допустимых значений $y$.</p> <p>Таким образом, мы снова приходим к классической задаче регрессии, но фазовым пространством для нее будет не исходное пространство $X$, а пространство $X \times Y$, где $Y \subseteq \mathbb{R}$ — множество допустимых значений $y$.</p> <p>То есть мы получили обычную задачу регрессии для выборки $\lbrace(z_k, u_k)\rbrace_{k=1}^M$, где<br> $z_k = (x_l, t_s)$, а $u_k = F_l(t_s) \in [0, 1]$, для некоторых $l$ и $s$.</p> <p>Для решения этой задачи можно применить любой алгоритм обучения с учителем из арсенала методов решения задач регрессии.</p> <hr> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="ограничения" > Ограничения </h3> <a data-clipboard-text="https://valmat.ru/posts/2022/09/regress-distr/#ограничения" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Ограничения" href="#%d0%be%d0%b3%d1%80%d0%b0%d0%bd%d0%b8%d1%87%d0%b5%d0%bd%d0%b8%d1%8f"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Поскольку описанный выше способ моделирует построение функций распределения, наша модель должна удовлетворять некоторым дополнительным ограничениям.</p> <p>Пусть</p> <p>$$ M_{\theta}: X \times Y \to [0, 1], \theta \in \Theta $$</p> <p>— параметрическое семейство моделей, и $\theta_0$ — оптимальная оценка параметра, дающая приближение реального семейства распределений $\lbrace P_x\rbrace_{x \in X}$, и</p> <p>$$ M = \lim\limits_{\theta \to \theta_0, \theta \in \Theta} M_{\theta} $$</p> <p>— итоговая модель.</p> <p>Тогда должны быть выполнены требования:</p> <ul> <li>Для каждого $x \in X$ $M(x, \cdot): t \to [0, 1] $ — является функцией некоторого распределения.</li> </ul> <p>То есть должны быть удовлетворены следующие условия:</p> <ol> <li>$\lim\limits_{t \to -\infty} M(x ,t) = 0$,<br> $\lim\limits_{t \to +\infty} M(x ,t) = 1$</li> <li>$t_1 \leqslant t_2 \Rightarrow M(x, t_1) \leqslant M(x, t_2)$</li> <li>$M(x, t) \in [0, 1],, \forall t \in \mathbb{R}$</li> </ol> <p>Все эти условия, в общем случае, не обязаны выполняться по построению моделей $M_{\theta}$ способом, описанным выше.</p> <p>Условие (3) может быть удовлетворено путём наложения ограничений на саму модель. Например, для нейронных сетей можно последним слоем разместить сигмоиду.</p> <p>Практика показала, что для правильно построенной модели при достаточном объеме обучающей выборки условия (1) и (2) будут выполнены автоматически. Но эти условия должны быть вынесены на этап валидации в качестве дополнительного обязательного критерия правильности построения модели.</p> <hr> <div class="flex align-center gblog-post__anchorwrap"> <h2 id="итоговый-алгоритм" > Итоговый алгоритм </h2> <a data-clipboard-text="https://valmat.ru/posts/2022/09/regress-distr/#итоговый-алгоритм" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Итоговый алгоритм" href="#%d0%b8%d1%82%d0%be%d0%b3%d0%be%d0%b2%d1%8b%d0%b9-%d0%b0%d0%bb%d0%b3%d0%be%d1%80%d0%b8%d1%82%d0%bc"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Кратко опишем алгоритм.</p> <p>Дана обучающая выборка $\lbrace(x_i, y_i)\rbrace_{i=1}^N$.</p> <ol> <li> <p><strong>Находим диапазон допустимых значений $Y$.</strong><br> Например, $$ Y = [\min\limits_{i} y_i - a, \max\limits_{i} y_i + a], $$ где $a$ — некоторое число, подбираемое исследователем.</p> </li> <li> <p><strong>Для каждой пары $(x_i, y_i)$ случайно генерируем набор точек $\lbrace t_j\rbrace_{j \in J_i} \subseteq Y$.</strong><br> $\lbrace t_j\rbrace$ нужно генерировать так, чтобы было достаточно точек, лежащих левее $y_i$ и достаточно точек, лежащих правее $y_i$.<br> Можно задать разбиение $\lbrace t_j\rbrace_{j \in J} \subseteq Y$ одинаковое для всех $i$, но тогда мы теряем разнообразие обучающей выборки в тех случаях, когда $(x_i, y_i)$ и $(x_k, y_k)$ — близкие, но не совпадающие точки.</p> </li> <li> <p><strong>После того, как точки $\lbrace t_j\rbrace_{j \in J_i}$ сгенерированы, генерируем новую обучающую выборку, как объединение выборок:</strong></p> <p>$$ \bigcup\limits_{i=1}^N \lbrace(x_i, t_j, u_{i j})\rbrace_{j \in J_i} $$</p> <p>где</p> <p>$$ u_{i j} = \begin{cases} 1, &amp; t_j \geqslant y_i |\ 0, &amp; t_j &lt; y_i \end{cases} $$</p> <p>$u_{i j} = 1$, при $t_j \geqslant y_i$, и $u_{i j} = 0$, при $t_j &lt; y_i$.</p> <p>Для удобства обозначим $z_{i j} = (x_i, t_j)$.<br> $z_{i j}$ будут лежать в области определения нашей модели, т.е. будут являться признаками, а $u_{i j}$ в области значений, т.е. будут являться таргетами.</p> </li> <li> <p><strong>Новую полученную выборку лучше случайно перемешать, перед тем как приступать к обучению модели.</strong></p> </li> <li> <p><strong>Строим модель обучения с учителем на обучающей выборке $\lbrace(z_{i j}, u_{i j})\rbrace$ как для обычной задачи регрессии.</strong></p> </li> <li> <p><strong>Проводим валидацию модели.</strong><br> В частности, на удовлетворение условия того, что $M(x, t)$ является функцией распределения по $t$ для каждого $x \in X$, т.е. проверяем (1), (2), (3).</p> </li> </ol> <hr> <div class="flex align-center gblog-post__anchorwrap"> <h2 id="валидация" > Валидация </h2> <a data-clipboard-text="https://valmat.ru/posts/2022/09/regress-distr/#валидация" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Валидация" href="#%d0%b2%d0%b0%d0%bb%d0%b8%d0%b4%d0%b0%d1%86%d0%b8%d1%8f"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Как и для обычных задач регрессии, невозможно дать какие-то универсальные критерии оценки качества построения модели. Но можно дать несколько рекомендаций, позволяющих оценить это качество.</p> <p>В любом случае, модель $M(x, t)$ должна быть функцией распределения по $t$ для всех $x \in X$. Если ограничения (1), (2), (3) не выполнены для $M(x, \cdot)$, то такую модель следует отвергнуть как некачественную.</p> <p>Сам алгоритм по построению является обычной задачей регрессии. И к его результатам применимы все метрики качества, применяемые к задачам регрессии.</p> <p>Для получения этих метрик тестовую выборку $\lbrace(x_i, y_i)\rbrace$ нужно привести к виду $\lbrace(z_{i j}, u_{i j})\rbrace$ тем же способом, что и обучающую.</p> <p>Кроме того, мы можем перейти на уровень исходных данных и для каждой $x_i$ из тестовой выборки посчитать среднее значение $\widehat{y_i}$ как</p> <p>$$ \widehat{y_i} = \int\limits_{t \in Y} t, dM(x_i, t) $$</p> <p>Таким образом, мы можем оценивать качество модели так, как если бы мы не строили распределения, а решали обычную задачу регрессии.</p> <p>Замечу, что в некоторых случаях вместо оценки среднего $\widehat{y}$ более уместным будет оценивать наиболее вероятное значение $y$:</p> <p>$$ \widehat{y_i} = \arg\max \limits_{t \in Y} \frac{\partial M(x_i, t)}{\partial t} $$</p> <p>В целом, подход с моделированием распределений вместо моделирования значений даёт не меньше, а даже больше способов оценки качества модели.</p> <hr> <div class="flex align-center gblog-post__anchorwrap"> <h2 id="эксперименты" > Эксперименты </h2> <a data-clipboard-text="https://valmat.ru/posts/2022/09/regress-distr/#эксперименты" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Эксперименты" href="#%d1%8d%d0%ba%d1%81%d0%bf%d0%b5%d1%80%d0%b8%d0%bc%d0%b5%d0%bd%d1%82%d1%8b"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>В качестве базовой закономерности возьмём функцию</p> <p>$$ f(x) = 1 - x^2 + \frac{3}{2} x - \sin(2 \pi x^2) $$</p> <p>на отрезке $x \in [0, 1]$.</p> <p>Моделируем<br> <a class="gblog-markdown__link" href="https://github.com/valmat/regress_distr/blob/master/experements.ipynb" >Исходный код экспериментов</a>.</p> <p>Закономерность определяется выражением выше плюс нормальный шум $\mathcal{N}(f(x), \sigma(x))$, где</p> <p>$$ \sigma(x) = 0.05 + \frac{x}{2} $$</p> <p>То есть для каждой точки $x \in [0, 1]$ нашего фазового пространства значения соответствующей случайной величины, определяемой моделируемой закономерностью, распределены по закону:</p> <p>$$ \mathcal{N}(f(x), \sigma(x)) $$</p> <p><em>На рисунках ниже:</em></p> <ul> <li>(a) моделируемая закономерность</li> <li>(b) решение обычной задачи регрессии</li> </ul> <p><img src="ex1_pic2.png" alt="Моделируемая закономерность и решение обычной задачи регрессии" /> <img src="ex1_pic4.png" alt="Функция распределения и плотность распределения" /> <img src="ex1_pic6.png" alt="Средние и наиболее вероятные значения, модельная функция распределения $F(x, y)$" /></p> <p>Все функции распределения для всех точек: <img src="ex1_pic7.png" alt="Полная функция распределения" /></p> <p>Если решать обычную задачу регрессии с помощью нейронной сети, то можно увидеть, что выдаваемые моделью ответы будут довольно хорошо ложиться на средние значения, как это и ожидалось.</p> <p>Нахождение распределений методом, описанным в настоящей статье, тоже даёт хорошие результаты.</p> <hr> <div class="flex align-center gblog-post__anchorwrap"> <h2 id="заключение" > Заключение </h2> <a data-clipboard-text="https://valmat.ru/posts/2022/09/regress-distr/#заключение" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Заключение" href="#%d0%b7%d0%b0%d0%ba%d0%bb%d1%8e%d1%87%d0%b5%d0%bd%d0%b8%d0%b5"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>На практике, при достаточном объеме обучающей выборки, непрерывные алгоритмы машинного обучения, такие как нейронные сети, дают хорошее приближение для функций распределения.</p> <p>В обучающей выборке могут быть образцы с близкими значениями признака $x$, но различными значениями таргета $y$. Все они вносят вклад в обучение функций распределения.</p> <p>Эксперименты и практический опыт показывают, что ограничения, накладываемые на функцию распределения, удовлетворяются.</p> <p>Прогнозирование распределений вместо прогнозирования средних значений даёт намного более богатые возможности для принятия решений.</p> <p>Моделирование распределений вместо моделирования значений требует меньше дополнительных и часто невыполнимых ограничений.</p> <p>Например, если рассмотреть решение одной и той же задачи моделированием распределений<br> $M_{\theta}(x, t) \in [0, 1]$ и моделированием значений $R_{\theta}(x) \in \mathbb{R}$, то применение МНК, то есть MSE в качестве функции потерь, для $R_{\theta}(x)$ равносильно предположению</p> <p>$$ M_{\theta}(x, t) \sim \mathcal{N}(t , \widehat{y}, \sigma) $$</p> <p>что, чаще всего, неверно.</p> <p>Конечно, для нахождения оптимальной модели $M_{\theta_0}(x, t)$ мы тоже вынуждены сделать некоторое предположение на вид распределения ошибки $M_{\theta_0}(x, t) - \widehat{u}$ но это предположение ограничивает нас менее жёстко.</p> <p>Платой за преимущества, даваемые моделью, предсказывающей распределения, является необходимость обучать более ёмкую модель. А следовательно, более медленная скорость сходимости по сравнению с классическим подходом.</p> <p>Действительно, нам нужно выучить не просто среднее, но и дополнительную информацию о форме распределения.</p> <p>Кроме того, мы вынуждены искусственно увеличить объём обучающей выборки, выполняя пополнение её таким образом, как это было описано выше.</p> <p>Это дополнительно приводит к замедлению обучения и требует больше вычислительных ресурсов.</p> Бенчмарк энтропий Шеннона и Реньи на C++ https://valmat.ru/posts/2022/02/entropy-benchmark/ 2022-02-08T12:00:00+03:00 2022-02-08T12:00:00+03:00 <p>Решил сравнить как различные показатели энтропии Реньи влияют на производительность в плане вычислений.</p> <p>В реальных задачах часто возникает необходимость быстро и эффективно вычислять энтропию для большого количества данных.</p> <p>Для этого важно понимать, как различные показатели энтропиивлияют на время выполнения.</p> <script src="https://gist.github.com/valmat/6a737cc3783449c4f7a829e77c77393e.js"></script> Определение угла наклона текста на сканированных изображениях https://valmat.ru/posts/2022/02/slope-detection/ 2022-02-08T12:00:00+03:00 2022-02-08T12:00:00+03:00 <p>При оптическом распознавании текста на сканированных документах качество распознавания зависит от того, наклонён ли текст в документе. У выровненных документов качество распознавания заметно лучше. Соответственно, возникает практическая необходимость в средствах автоматического выравнивания угла наклона текста.</p> <p>В статье предлагается простой, универсальный и достаточно эффективный алгоритм выравнивания наклона текста, основанный на идее минимизации средней энтропии строк и столбцов растрового изображения.</p> <hr> <div class="gblog-toc gblog-toc__level--6"> <nav id="TableOfContents"> <ul> <li> <ul> <li><a href="#идея">Идея</a></li> <li><a href="#эксперимент">Эксперимент</a></li> <li><a href="#алгоритм">Алгоритм</a></li> <li><a href="#ссылки">Ссылки</a></li> </ul> </li> </ul> </nav> <hr /> </div> <hr> <div class="flex align-center gblog-post__anchorwrap"> <h2 id="идея" > Идея </h2> <a data-clipboard-text="https://valmat.ru/posts/2022/02/slope-detection/#идея" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Идея" href="#%d0%b8%d0%b4%d0%b5%d1%8f"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Базовая идея алгоритма состоит в том, что при повороте текста на сканированном изображении средняя, по строкам и столбцам, энтропия распределения пикселей должна возрасти.</p> <p>Предположим, нам дан чёрно-белый скан изображения. То есть, каждый пиксель может принимать только два значения: 0 или 1. Как известно, энтропия равномерного распределения максимальна. Если изображение повёрнуто, то в среднем, распределение чёрных и белых пикселей по строкам (и столбцам) будет ближе к равномерному, чем у неповёрнутого изображения. У выровненного изображения распределение пикселей в среднем должно быть менее равномерным.</p> <p><img src="img_1.png" alt="Пример изображения" /></p> <p>Гипотеза состоит в том, чтобы вычислить среднюю по строкам и столбцам энтропию распределения пикселей для разных углов поворота и найти такой угол, при котором эта усреднённая энтропия примет минимальное значение.</p> <p>Для проверки этой гипотезы в интернете был собран набор данных различных видов сканированных изображений, после чего предположения были проверены экспериментально.</p> <p>Предложенный подход работает и позволяет абсолютно точно определить угол поворота в 83% случаев и с точностью до 1° — в 98% случаев.</p> <p>Хотя, на первый взгляд, энтропия Шеннона хорошо подходит для этой задачи, было бы разумно не ограничиваться только ей, а рассмотреть весь спектр энтропий Реньи. И с учётом полученных результатов, а также вычислительной сложности, выбрать оптимальное значение параметра энтропии Реньи.</p> <p>Энтропия Реньи вычисляется по формуле:</p> <p>$$ R_{\alpha} = \frac{1}{1-\alpha} \log\left( \sum_{i=1}^{n}p_i^{\alpha} \right), $$</p> <p>где $p_i$ — вероятности, соответствующие распределению (в нашем случае — частоты чёрных и белых пикселей).</p> <p>В случае $\alpha = 1$ это превращается в энтропию Шеннона:</p> <p>$$ R_{1} = H = - \sum_{i=1}^{n} p_i \log(p_i) $$</p> <hr> <div class="flex align-center gblog-post__anchorwrap"> <h2 id="эксперимент" > Эксперимент </h2> <a data-clipboard-text="https://valmat.ru/posts/2022/02/slope-detection/#эксперимент" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Эксперимент" href="#%d1%8d%d0%ba%d1%81%d0%bf%d0%b5%d1%80%d0%b8%d0%bc%d0%b5%d0%bd%d1%82"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Для проведения эксперимента был собран набор различных документов в сети Интернет. Каждое изображение из набора было повёрнуто на случайный угол в интервале от -45° до 45°, после чего был вычислен угол поворота с помощью предложенного алгоритма.</p> <p><img src="img_2.png" alt="Пример повёрнутого изображения" /></p> <p>В таблице ниже представлены результаты для различных значений параметра энтропии Реньи $\alpha$:</p> <table> <thead> <tr> <th>Параметр энтропии Реньи $\alpha$</th> <th>1/8</th> <th>1/4</th> <th>1/2</th> <th>3/4</th> <th>1</th> <th>2</th> <th>5</th> </tr> </thead> <tbody> <tr> <td>Среднее абсолютное отклонение</td> <td>0.498</td> <td>0.299</td> <td><strong>0.211</strong></td> <td>0.283</td> <td>0.240</td> <td>5.827</td> <td>41.099</td> </tr> <tr> <td>Доля полных совпадений (точность)</td> <td>0.822</td> <td><strong>0.834</strong></td> <td>0.828</td> <td>0.815</td> <td>0.805</td> <td>0.641</td> <td>0.009</td> </tr> <tr> <td>Доля совпадений с точностью до 1°</td> <td>0.942</td> <td>0.969</td> <td><strong>0.980</strong></td> <td><strong>0.982</strong></td> <td><strong>0.983</strong></td> <td>0.822</td> <td>0.015</td> </tr> <tr> <td>Доля совпадений с точностью до 2°</td> <td>0.967</td> <td>0.983</td> <td><strong>0.991</strong></td> <td><strong>0.993</strong></td> <td><strong>0.994</strong></td> <td>0.841</td> <td>0.015</td> </tr> </tbody> </table> <p>Всего было обработано 1665 документов.</p> <p><strong>Выводы из таблицы:</strong></p> <ul> <li>Наименьшее среднее абсолютное отклонение достигается при $\alpha = \frac{1}{2}$.</li> <li>Наилучшая точность (доля полных совпадений) — при $\alpha = \frac{1}{4}$.</li> <li>Наилучшая приемлемая точность (доля совпадений с точностью до 1° и до 2°) — при $\alpha \in {1, \frac{3}{4}, \frac{1}{2}}$.</li> </ul> <p>Если рассматривать методику как часть комплекса оптического распознавания документов, то наилучшим значением оказывается $\alpha = \frac{1}{2}$.</p> <p>При $\alpha = \frac{1}{2}$ среднее абсолютное отклонение составит всего $0.211^\circ$. При этом достигается оптимальная доля совпадений с точностью до 1°.</p> <p>Есть ещё одна причина выбрать $\alpha = \frac{1}{2}$: при этом значении достигается оптимальная вычислительная сложность.</p> <p>Ниже представлены результаты бенчмарка многократного вычисления энтропий для различных значений параметра $\alpha$:</p> <table> <thead> <tr> <th>$\alpha$</th> <th>nanoseconds</th> <th>miliseconds</th> <th>% of Shannon</th> </tr> </thead> <tbody> <tr> <td>1</td> <td>10249895206</td> <td>10249</td> <td>100</td> </tr> <tr> <td>1/2</td> <td>8677368472</td> <td>8677</td> <td>84.66</td> </tr> <tr> <td>1/4</td> <td>10421639934</td> <td>10421</td> <td>120.1</td> </tr> <tr> <td>1/8</td> <td>13235709810</td> <td>13235</td> <td>127</td> </tr> <tr> <td>3/4</td> <td>11403406522</td> <td>11403</td> <td>86.16</td> </tr> <tr> <td>2</td> <td>7245386547</td> <td>7245</td> <td>63.54</td> </tr> <tr> <td>5</td> <td>7771674801</td> <td>7771</td> <td>107.26</td> </tr> <tr> <td>10</td> <td>10809162384</td> <td>10809</td> <td>139.08</td> </tr> </tbody> </table> <p>Из таблицы видно, что среди подходящих значений $\alpha$ наилучшая производительность достигается при $\alpha = 1/2$.</p> <hr> <div class="flex align-center gblog-post__anchorwrap"> <h2 id="алгоритм" > Алгоритм </h2> <a data-clipboard-text="https://valmat.ru/posts/2022/02/slope-detection/#алгоритм" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Алгоритм" href="#%d0%b0%d0%bb%d0%b3%d0%be%d1%80%d0%b8%d1%82%d0%bc"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <blockquote> <p><strong>Замечание:</strong><br> Предлагаемый ниже алгоритм (<a class="gblog-markdown__link" href="https://github.com/valmat/rotate_detection" >исходный код на GitHub</a>) предполагает, что для определения угла поворота мы используем бинарное чёрно-белое растровое изображение, в котором каждый пиксель может принимать два значения: 0 или 1.</p> <p>Для применения алгоритма необходимо получить бинаризованную копию изображения.</p> <p>Я реализовал алгоритм с применением библиотеки <em>libleptonica</em> (используется в TesseractOCR). Для этого использовал последовательное преобразование <code>pixContrastTRC</code> с <code>contrast_factor = 1.0</code> и затем <code>pixConvertTo1</code> с <code>threshold = 170</code>.</p> </blockquote> <p>Пусть $h$ — высота, $w$ — ширина исходного изображения. Пусть $d = \sqrt{w^2 + h^2}$ — длина диагонали.</p> <p>Будем поворачивать изображение на угол $\phi$ относительно центра изображения и считать среднюю энтропию по строкам и столбцам. Чтобы не выйти за границы, мысленно расширим полотно до размеров $d \times d$.</p> <p>Определим:</p> <ul> <li>$x_{from} = \frac{d}{2} - \frac{h |\sin(\phi)| + w |\cos(\phi)|}{2}$,</li> <li>$x_{to} = d - x_{from}$,</li> <li>$y_{from} = \frac{d}{2} - \frac{h |\cos(\phi)| + w |\sin(\phi)|}{2}$,</li> <li>$y_{to} = d - y_{from}$</li> </ul> <p>где $x_{from}$ и $x_{to}$ — границы по ширине, $y_{from}$ и $y_{to}$ — по высоте.</p> <p>Нужно посчитать среднюю энтропию по строкам (и аналогично по столбцам).</p> <p>Пусть $V(x, y)$ — цвет пикселя $(x, y)$ (0 или 1), $R({p, q})$ — энтропия распределения ${p, q}$.</p> <p>Алгоритм расчёта средней энтропии $S_{\phi}$ для угла $\phi$ (по строкам):</p> <pre tabindex="0"><code class="language-pseudo" data-lang="pseudo">S_phi = 0 for y in y_from .. y_to: b = 0 // количество чёрных пикселей в строке for x in x_from .. x_to: x_tilde = x - d/2 y_tilde = y - d/2 x&#39; = x_tilde * cos(phi) - y_tilde * sin(phi) + w/2 y&#39; = x_tilde * sin(phi) + y_tilde * cos(phi) + h/2 if x&#39; &gt;= 0 and x&#39; &lt; w and y&#39; &gt;= 0 and y&#39; &lt; h: b = b + V(x&#39;, y&#39;) p = b / d q = 1 - p S_phi = S_phi + R({p, q}) / d </code></pre><p>Полученное значение $S_\phi$ — средняя энтропия для угла поворота $\phi$.</p> <p>Для нахождения искомого угла поворота $\phi_0$ нужно найти минимум $S_\phi$:</p> <p>$$ \phi_0 = -\arg\min_\phi S_\phi $$</p> <p>Знак минус берётся потому, что для выравнивания изображения нужно повернуть его в обратную сторону.</p> <hr> <div class="flex align-center gblog-post__anchorwrap"> <h2 id="ссылки" > Ссылки </h2> <a data-clipboard-text="https://valmat.ru/posts/2022/02/slope-detection/#ссылки" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Ссылки" href="#%d1%81%d1%81%d1%8b%d0%bb%d0%ba%d0%b8"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <ul> <li><a class="gblog-markdown__link" href="https://ru.wikipedia.org/wiki/%d0%ad%d0%bd%d1%82%d1%80%d0%be%d0%bf%d0%b8%d1%8f_%d0%a0%d0%b5%d0%bd%d1%8c%d0%b8" >WikiPedia: Энтропия Реньи (ru)</a></li> <li><a class="gblog-markdown__link" href="https://github.com/valmat/rotate_detection" >Исходный код алгоритма на GitHub (C++)</a></li> <li><a class="gblog-markdown__link" href="https://gist.github.com/valmat/6a737cc3783449c4f7a829e77c77393e" >Исходный код бенчмарка производительности энтропий на C++</a></li> <li><a class="gblog-markdown__link" href="/posts/2022/02/entropy-benchmark/" >Бенчмарк производительности энтропий</a></li> </ul> Получение целочисленного типа по его длине https://valmat.ru/posts/2021/09/cpp-int-by-size/ 2019-09-12T12:00:00+03:00 2019-09-12T12:00:00+03:00 <div class="flex align-center gblog-post__anchorwrap"> <h1 id="простой-способ-получить-целочисленный-тип-по-его-длине" > Простой способ получить целочисленный тип по его длине </h1> <a data-clipboard-text="https://valmat.ru/posts/2021/09/cpp-int-by-size/#простой-способ-получить-целочисленный-тип-по-его-длине" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Простой способ получить целочисленный тип по его длине" href="#%d0%bf%d1%80%d0%be%d1%81%d1%82%d0%be%d0%b9-%d1%81%d0%bf%d0%be%d1%81%d0%be%d0%b1-%d0%bf%d0%be%d0%bb%d1%83%d1%87%d0%b8%d1%82%d1%8c-%d1%86%d0%b5%d0%bb%d0%be%d1%87%d0%b8%d1%81%d0%bb%d0%b5%d0%bd%d0%bd%d1%8b%d0%b9-%d1%82%d0%b8%d0%bf-%d0%bf%d0%be-%d0%b5%d0%b3%d0%be-%d0%b4%d0%bb%d0%b8%d0%bd%d0%b5"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <script src="https://gist.github.com/valmat/9c3d3c14fa7695b35e56086be9140475.js"></script> Пробуем контрактное программирование С++20 уже сейчас https://valmat.ru/posts/archive/2019/03/cpp-contracts/ 2019-03-14T12:00:00+03:00 2019-03-14T12:00:00+03:00 <div class="flex align-center gblog-post__anchorwrap"> <h1 id="пробуем-контрактное-программирование-с20-уже-сейчас" > Пробуем контрактное программирование С++20 уже сейчас </h1> <a data-clipboard-text="https://valmat.ru/posts/archive/2019/03/cpp-contracts/#пробуем-контрактное-программирование-с20-уже-сейчас" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Пробуем контрактное программирование С&#43;&#43;20 уже сейчас" href="#%d0%bf%d1%80%d0%be%d0%b1%d1%83%d0%b5%d0%bc-%d0%ba%d0%be%d0%bd%d1%82%d1%80%d0%b0%d0%ba%d1%82%d0%bd%d0%be%d0%b5-%d0%bf%d1%80%d0%be%d0%b3%d1%80%d0%b0%d0%bc%d0%bc%d0%b8%d1%80%d0%be%d0%b2%d0%b0%d0%bd%d0%b8%d0%b5-%d1%8120-%d1%83%d0%b6%d0%b5-%d1%81%d0%b5%d0%b9%d1%87%d0%b0%d1%81"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p><img src="pic.png" alt="" /></p> <p>В С++20 появилось контрактное программирование. На текущий момент ни один компилятор ещё не реализовал поддержку этой возможности.</p> <p>Но есть способ уже сейчас попробовать использовать контракты из C++20, так как это описано в стандарте. <cut text="Заинтересовавшихся прошу под кат" /></p> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="tldr" > TL;DR </h3> <a data-clipboard-text="https://valmat.ru/posts/archive/2019/03/cpp-contracts/#tldr" class="gblog-post__anchor clip flex align-center" aria-label="Anchor TL;DR" href="#tldr"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p><em>Есть форк clang, поддерживающий контракты. На его примере я рассказываю как пользоваться контрактами, чтобы как только фича появилась в вашем любимом компиляторе, вы сразу же могли начать её использовать.</em></p> <p>Про контрактное программирование уже написано много, но в двух словах расскажу что это такое и для чего нужно.</p> <div class="flex align-center gblog-post__anchorwrap"> <h2 id="логика-хоара" > Логика Хоара </h2> <a data-clipboard-text="https://valmat.ru/posts/archive/2019/03/cpp-contracts/#логика-хоара" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Логика Хоара" href="#%d0%bb%d0%be%d0%b3%d0%b8%d0%ba%d0%b0-%d1%85%d0%be%d0%b0%d1%80%d0%b0"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>В основе парадигмы контрактов лежит логика Хоара (<a class="gblog-markdown__link" href="https://ru.wikipedia.org/wiki/%D0%9B%D0%BE%D0%B3%D0%B8%D0%BA%D0%B0_%D0%A5%D0%BE%D0%B0%D1%80%D0%B0" >1</a>, <a class="gblog-markdown__link" href="https://habr.com/ru/post/268013/" >2</a>).</p> <p>Логика Хоара – это способ формального доказательства корректности алгоритма. Она оперирует такими понятиями, как предусловие, постусловие и инвариант. С практической точки зрения, использование логики Хоара это, во-первых, способ формального доказательства корректности программы в тех случаях, когда ошибки могут привести к катастрофе или гибели людей. Во-вторых, способ повысить надёжность программы, наряду со статическим анализом и тестированием.</p> <div class="flex align-center gblog-post__anchorwrap"> <h2 id="контрактное-программирование" > Контрактное программирование </h2> <a data-clipboard-text="https://valmat.ru/posts/archive/2019/03/cpp-contracts/#контрактное-программирование" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Контрактное программирование" href="#%d0%ba%d0%be%d0%bd%d1%82%d1%80%d0%b0%d0%ba%d1%82%d0%bd%d0%be%d0%b5-%d0%bf%d1%80%d0%be%d0%b3%d1%80%d0%b0%d0%bc%d0%bc%d0%b8%d1%80%d0%be%d0%b2%d0%b0%d0%bd%d0%b8%d0%b5"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>(<a class="gblog-markdown__link" href="https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BD%D1%82%D1%80%D0%B0%D0%BA%D1%82%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5" >1</a>, <a class="gblog-markdown__link" href="https://habr.com/ru/post/38612/" >2</a>)</p> <p>Основная идея контрактов в том, что по аналогии с контрактами в бизнесе, для каждой функции или метода описываются договорённости. Эти договорённости должны соблюдать как вызывающая сторона, так и вызываемая. Неотъемлемой частью контрактов является как минимум два режима сборки – отладочный и продуктовый. В зависимости от режима сборки контракты должны себя вести по разному. Наиболее распространённой практикой является проверка контрактов в отладочной сборке и их игнорирование в продуктовой.</p> <p>Иногда в продуктовой сборке контракты тоже проверяются и их невыполнение может, например, вести к генерации исключения.</p> <p>Основное отличие использования контрактов от «классического» подхода в том, что вызывающая сторона должна соблюдать предусловия вызываемой стороны, которые описываются в контракте, а вызываемая должна соблюдать свои постусловия и инварианты. Соответственно, вызываемая сторона не обязана проверять корректность передаваемых её параметров. Эта обязанность возлагается контрактом на вызывающую сторону.</p> <p>Несоблюдение контрактов должно быть обнаружено на этапе тестирования и дополняет все виды тестов: модульные интеграционные и т. д.</p> <p>На первый взгляд, использование контрактов ведёт к усложнению разработки и ухудшает читаемость кода. На самом деле, всё как раз наоборот. Приверженцам статической типизации будет проще всего оценить пользу контрактов, потому что простейшим их вариантом является описание типов в сигнатуре методов и функций.</p> <p>Итак, какую пользу дают контракты:</p> <ul> <li>Улучшают читаемость кода за счёт явного документирования.</li> <li>Повышают надёжность кода, дополняя собой тестирование.</li> <li>Позволяют компиляторам использовать низкоуровневые оптимизации и генерировать более быстрый код в расчёте на соблюдение контракта. В последнем случае несоблюдение контракта в релизной сборке может вести к UB.</li> </ul> <div class="flex align-center gblog-post__anchorwrap"> <h2 id="контрактное-программирование-в-c" > Контрактное программирование в C++ </h2> <a data-clipboard-text="https://valmat.ru/posts/archive/2019/03/cpp-contracts/#контрактное-программирование-в-c" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Контрактное программирование в C&#43;&#43;" href="#%d0%ba%d0%be%d0%bd%d1%82%d1%80%d0%b0%d0%ba%d1%82%d0%bd%d0%be%d0%b5-%d0%bf%d1%80%d0%be%d0%b3%d1%80%d0%b0%d0%bc%d0%bc%d0%b8%d1%80%d0%be%d0%b2%d0%b0%d0%bd%d0%b8%d0%b5-%d0%b2-c"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Контрактное программирование реализовано во многих языках. Наиболее яркие примеры, это <a class="gblog-markdown__link" href="https://www.eiffel.org/doc/eiffel/ET-_Design_by_Contract_%28tm%29%2C_Assertions_and_Exceptions" >Eiffel</a>, где парадигма была впервые реализована, и <a class="gblog-markdown__link" href="https://dlang.org/spec/contracts.html" >D</a>, в D контракты являются частью языка.</p> <p>В C++, до стандарта C++20, контракты можно было использовать в виде отдельных библиотек.</p> <p>Такой подход имеет ряд недостатков:</p> <ul> <li>Весьма неуклюжий синтаксис с использованием макросов.</li> <li>Отсутствие единого стиля.</li> <li>Невозможность использования контрактов компилятором для оптимизации кода.</li> </ul> <p>В основе библиотечных реализаций обычно лежит использование старого доброго assert&rsquo;а и препроцессорных директив, проверяющих наличие флага компиляции.</p> <p>Использование контрактов в таком виде, действительно делает код уродливым и нечитаемым. Это одна из причин, почему использование контрактов в C++ мало практикуется.</p> <p>Забегая вперёд, покажу как в C++20 будет выглядеть использование контрактов. А затем, разберём всё это подробнее:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">f</span><span class="p">(</span><span class="kt">int</span> <span class="n">x</span><span class="p">,</span> <span class="kt">int</span> <span class="n">y</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="na">[[ expects: x &gt; 0 ]]</span> <span class="c1">// precondition </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="na">[[ expects: y &gt; 0 ]]</span> <span class="c1">// precondition </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="na">[[ ensures r: r &lt; x + y ]]</span> <span class="c1">// postcondition </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kt">int</span> <span class="n">z</span> <span class="o">=</span> <span class="p">(</span><span class="n">x</span> <span class="o">-</span> <span class="n">x</span><span class="o">%</span><span class="n">y</span><span class="p">)</span> <span class="o">/</span> <span class="n">y</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="na">[[ assert: z &gt;= 0 ]]</span><span class="p">;</span> <span class="c1">// assertion </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="k">return</span> <span class="n">z</span> <span class="o">+</span> <span class="n">y</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><div class="flex align-center gblog-post__anchorwrap"> <h2 id="пробуем" > Пробуем </h2> <a data-clipboard-text="https://valmat.ru/posts/archive/2019/03/cpp-contracts/#пробуем" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Пробуем" href="#%d0%bf%d1%80%d0%be%d0%b1%d1%83%d0%b5%d0%bc"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>К сожалению, на текущий момент ни один из широко используемых компиляторов ещё не реализовал поддержку контрактов. Но есть выход.</p> <p><em>ARCOS research group</em> из <em>Universidad Carlos III de Madrid</em> реализовали экспериментальную поддержку контрактов в форке clang++.</p> <p>Чтобы не «писать код на бумажке», а иметь возможность сразу же попробовать новые возможности в деле, мы можем собрать этот форк и с его помощью пробовать приводимые ниже примеры.</p> <p>Инструкция по сборке описана в readme репозитория на Гитхабе <a class="gblog-markdown__link" href="https://github.com/arcosuc3m/clang-contracts" >https://github.com/arcosuc3m/clang-contracts</a></p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone https://github.com/arcosuc3m/clang-contracts/ </span></span><span class="line"><span class="cl">mkdir -p clang-contracts/build/ <span class="o">&amp;&amp;</span> <span class="nb">cd</span> clang-contracts/build/ </span></span><span class="line"><span class="cl">cmake -G <span class="s2">&#34;Unix Makefiles&#34;</span> -DLLVM_USE_LINKER<span class="o">=</span>gold -DBUILD_SHARED_LIBS<span class="o">=</span>ON -DLLVM_USE_SPLIT_DWARF<span class="o">=</span>ON -DLLVM_OPTIMIZED_TABLEGEN<span class="o">=</span>ON ../ </span></span><span class="line"><span class="cl">make -j8 </span></span></code></pre></div><p>У меня не возникло проблем при сборке, но компиляция исходников занимает очень много времени.</p> <p>Для компиляции примеров вам нужно будет явно указать путь к бинарнику clang++. Например, у меня это выглядит примерно так</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">/home/valmat/work/git/clang-contracts/build/bin/clang++ -std<span class="o">=</span>c++2a -build-level<span class="o">=</span>audit -g test.cpp -o test.bin </span></span></code></pre></div><p>Я подготовил примеры, чтобы вам было удобно исследовать контракты на примерах реального кода. Предлагаю, прежде чем приступить к чению следующего раздела, склонировать и скомпилировать примеры.</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone https://github.com/valmat/cpp20-contracts-examples/ </span></span><span class="line"><span class="cl"><span class="nb">cd</span> cpp20-contracts-examples </span></span><span class="line"><span class="cl">make <span class="nv">CPP</span><span class="o">=</span>/path/to/clang++ </span></span></code></pre></div><p>Здесь <code>/path/to/clang++</code> путь к бинарнику <code>clang++</code> вашей сборки экспериментального компилятора.</p> <p>Кроме самого компилятора, ARCOS research group подготовили свою версию <a class="gblog-markdown__link" href="http://fragata.arcos.inf.uc3m.es/" >Compiler Explorer</a> для своего форка.</p> <div class="flex align-center gblog-post__anchorwrap"> <h2 id="контрактное-программирование-в-c20" > Контрактное программирование в C++20 </h2> <a data-clipboard-text="https://valmat.ru/posts/archive/2019/03/cpp-contracts/#контрактное-программирование-в-c20" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Контрактное программирование в C&#43;&#43;20" href="#%d0%ba%d0%be%d0%bd%d1%82%d1%80%d0%b0%d0%ba%d1%82%d0%bd%d0%be%d0%b5-%d0%bf%d1%80%d0%be%d0%b3%d1%80%d0%b0%d0%bc%d0%bc%d0%b8%d1%80%d0%be%d0%b2%d0%b0%d0%bd%d0%b8%d0%b5-%d0%b2-c20"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Теперь ничего не мешает нам приступить к исследованию возможностей, которые даёт контрактное программирование, и сразу пробовать эти возможности в деле.</p> <p>Как уже было сказано выше, контракты строятся из предусловий, постусловий и инвариантов (утверждений).</p> <p>В C++20 для этого используются атрибуты со следующим синтаксисом</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="na">[[contract-attribute modifier identifier: conditional-expression]]</span> </span></span></code></pre></div><p>Где <code>contract-attribute</code> может принимать одно из следующих значений: <strong>expects</strong>, <strong>ensures</strong> или <strong>assert</strong>.</p> <p><code>expects</code> используется для предусловий, <code>ensures</code> для постусловий и <code>assert</code> для утверждений.</p> <p><code>conditional-expression</code> – это булево выражение, проверяемый в контракте предикат. <code>modifier</code> и <code>identifier</code> могут быть опущены.</p> <p>Зачем нужен <code>modifier</code> я напишу чуть ниже.</p> <p><code>identifier</code> используется только с <code>ensures</code> и служит для представления возвращаемого значения.</p> <p>Предусловия имеют доступ к аргументам.</p> <p>Постусловия имеют доступ к возвращаемому функцией значению. Для этого используется синтаксис</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="na">[[ensures return_variable: expr(return_variable)]]</span> </span></span></code></pre></div><p>Где <code>return_variable</code> любое валидное выражение для переменной.</p> <p>Другими словами, предусловия предназначены, чтобы объявлять ограничения, накладываемые на принимаемые функцией аргументы, а постусловия для того, чтобы объявлять ограничения, накладываемые на возвращаемое функцией значение.</p> <p>Считается, что <em>предусловия</em> и <em>постусловия</em> являются частью интерфейса функции, в то время как <em>утверждения</em> являются частью её реализации.</p> <p>Предикаты предусловий всегда вычисляются непосредственно перед выполнением функции. Постусловия выполняются сразу же после передачи функцией управления вызывающему коду.</p> <p>Если в функции происходит выброс исключения, то постусловия не будет проверяться. Постусловия проверяются только в случае нормального завершения функции.</p> <p>Если при проверке выражения в контракте возникло ислючение, то будет вызван <code>std::terminate()</code>.</p> <p>Предусловия и постусловия всегда описываются вне тела функции и не могут иметь доступ к локальным переменным.</p> <p>Если предусловия и постусловия описывают контракт для публичного метода класса, они не могут иметь доступ к приватным и защищённым полям класса. Если метод класса защищённый, то к защищённым и публичным данным класса доступ есть, а к приватным нет. Последнее ограничение совершенно логично, если учесть, что контракт является частью интерфейса метода.</p> <p>Утверждения (инварианты) всегда описываются в теле функции или метода. По дизайну они являются частью реализации. И, соответственно, могут иметь доступ ко всем доступным данным. В том числе, к локальным переменным функции и приватным и защищённым полям класса.</p> <p><a class="gblog-markdown__link" href="https://github.com/valmat/cpp20-contracts-examples/blob/master/example1.cpp" >пример 1</a></p> <p>Определим два предусловия, одно постусловие и один инвариант:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">foo</span><span class="p">(</span><span class="kt">int</span> <span class="n">x</span><span class="p">,</span> <span class="kt">int</span> <span class="n">y</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="na">[[ expects: x &gt; y ]]</span> <span class="c1">// precondition #1 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="na">[[ expects: y &gt; 0 ]]</span> <span class="c1">// precondition #2 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="na">[[ ensures r: r &lt; x ]]</span> <span class="c1">// postcondition #3 </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kt">int</span> <span class="n">z</span> <span class="o">=</span> <span class="p">(</span><span class="n">x</span> <span class="o">-</span> <span class="n">x</span><span class="o">%</span><span class="n">y</span><span class="p">)</span> <span class="o">/</span> <span class="n">y</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="na">[[ assert: z &gt;= 0 ]]</span><span class="p">;</span> <span class="c1">// assertion </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="k">return</span> <span class="n">z</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> </span></span><span class="line"><span class="cl"><span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="n">foo</span><span class="p">(</span><span class="mi">117</span><span class="p">,</span> <span class="mi">20</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="n">foo</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="mi">20</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span> <span class="c1">// &lt;-- contract violation #1 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="n">foo</span><span class="p">(</span><span class="mi">100</span><span class="p">,</span> <span class="o">-</span><span class="mi">5</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span> <span class="c1">// &lt;-- contract violation #2 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p><a class="gblog-markdown__link" href="https://github.com/valmat/cpp20-contracts-examples/blob/master/example2.cpp" >пример 2</a></p> <p>Предусловие публичного метода не может ссылаться на защищённое или приватное поле:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="k">struct</span> <span class="nc">X</span> </span></span><span class="line"><span class="cl"><span class="p">{</span> </span></span><span class="line"><span class="cl"><span class="c1">//protected: </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kt">int</span> <span class="n">m</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="k">public</span><span class="o">:</span> </span></span><span class="line"><span class="cl"> <span class="kt">int</span> <span class="n">foo</span><span class="p">(</span><span class="kt">int</span> <span class="n">n</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="na">[[expects: n &lt; m]]</span> </span></span><span class="line"><span class="cl"> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="n">n</span><span class="o">*</span><span class="n">n</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">};</span> </span></span></code></pre></div><p>Не допускается модификация переменных внутри выражений, описываемых атрибутами контракта. Если это нарушено, будет UB.</p> <p>Выражения, описываемые в контрактах, не должны иметь побочных эффектов. Хотя компиляторы могот это проверять, такая обязанность на них не возлагается. Нарушение этого требования считается неопределённым поведением.</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="k">struct</span> <span class="nc">X</span> </span></span><span class="line"><span class="cl"><span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kt">int</span> <span class="n">m</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="kt">int</span> <span class="nf">foo</span><span class="p">(</span><span class="kt">int</span> <span class="n">n</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="na">[[ expects: n &lt; m++ ]]</span> <span class="c1">// UB: Modifies variable m </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kt">int</span> <span class="n">k</span> <span class="o">=</span> <span class="n">n</span><span class="o">*</span><span class="n">n</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="na">[[ assert: ++k &lt; 100 ]]</span> <span class="c1">// UB: Modifies variable k </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="k">return</span> <span class="n">n</span><span class="o">*</span><span class="n">n</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">};</span> </span></span></code></pre></div><p>Требование не изменять состояние программы в выражениях контрактов станет очевидно чуть ниже, когда я расскажу про уровни модификаторов контрактов и режимы сборки.</p> <p>Сейчас просто отмечу, что корректная программа должна работать так же, как если бы контрактов вообще не было.</p> <p>Как я отмечал выше, в контракте можно указывать сколько угодно предусловий и постусловий. Все они будут проверены по порядку. Но предусловия всегда проверяются до выполнения функции, а постусловия сразу после выхода из неё.</p> <p>Это означает, что в первую очередь всегда проверяются предусловия, как проиллюстрировано в следующем примере:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">foo</span><span class="p">(</span><span class="kt">int</span> <span class="n">n</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="na">[[ expects: expr(n) ]]</span> <span class="c1">// # 1 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="na">[[ ensures r: expr(r) ]]</span> <span class="c1">// # 4 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="na">[[ expects: expr(n) ]]</span> <span class="c1">// # 2 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="na">[[ expects: expr(n) ]]</span> <span class="c1">// # 3 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="na">[[ ensures r: expr(r) ]]</span> <span class="c1">// # 5 </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">{...}</span> </span></span></code></pre></div><p>Выражения в постусловиях могут ссылаться не только на возвращаемое функцией значение, но и на аргументы функции.</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">foo</span><span class="p">(</span><span class="kt">int</span> <span class="o">&amp;</span><span class="n">n</span><span class="p">)</span> <span class="na">[[ ensures: expr(r) ]]</span><span class="p">;</span> </span></span></code></pre></div><p>В этом случае можно опустить идентификатор возвращаемого значения.</p> <p>Если постусловие ссылается на аргумент функции, то этот аргумент рассматривается <em>в точке выхода из функции</em>, а не в точке входа, как в случае с предусловиями.</p> <p>Нет никакого способа ссылаться на оригинальное (в точке входа в функцию) значение в постусловии.</p> <p><a class="gblog-markdown__link" href="https://github.com/valmat/cpp20-contracts-examples/blob/master/example3.cpp" >пример</a>:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="kt">void</span> <span class="nf">incr</span><span class="p">(</span><span class="kt">int</span> <span class="o">&amp;</span><span class="n">n</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="na">[[ expects: 3 == n ]]</span> </span></span><span class="line"><span class="cl"> <span class="na">[[ ensures: 4 == n ]]</span> </span></span><span class="line"><span class="cl"><span class="p">{</span><span class="o">++</span><span class="n">n</span><span class="p">;}</span> </span></span></code></pre></div><p>Предикаты в контрактах могут ссылаться на локальные переменные, только если время жизни этих переменных соответствует времени вычисления предиката.</p> <p>Например, для <code>constexpr</code> функции нельзя ссылаться на локальные переменные, если только они не известны во время компиляции.</p> <p><a class="gblog-markdown__link" href="https://github.com/valmat/cpp20-contracts-examples/blob/master/example4.cpp" >пример</a>:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="kt">int</span> <span class="n">a</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="k">constexpr</span> <span class="kt">int</span> <span class="n">b</span> <span class="o">=</span> <span class="mi">100</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">constexpr</span> <span class="kt">int</span> <span class="nf">foo</span><span class="p">(</span><span class="kt">int</span> <span class="n">n</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="na">[[ expects: a &lt;= n ]]</span> <span class="c1">// error: `a` is not constexpr </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="na">[[ expects: n &lt; b ]]</span> <span class="c1">// OK </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="na">[[assert: n &gt; 2*a]]</span><span class="p">;</span> <span class="c1">// error: `a` is not constexpr </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="na">[[assert: n &lt; 2*b]]</span><span class="p">;</span> <span class="c1">// OK </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="k">return</span> <span class="mi">2</span><span class="o">*</span><span class="n">n</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><div class="flex align-center gblog-post__anchorwrap"> <h3 id="контракты-для-указателей-на-функцию" > Контракты для указателей на функцию </h3> <a data-clipboard-text="https://valmat.ru/posts/archive/2019/03/cpp-contracts/#контракты-для-указателей-на-функцию" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Контракты для указателей на функцию" href="#%d0%ba%d0%be%d0%bd%d1%82%d1%80%d0%b0%d0%ba%d1%82%d1%8b-%d0%b4%d0%bb%d1%8f-%d1%83%d0%ba%d0%b0%d0%b7%d0%b0%d1%82%d0%b5%d0%bb%d0%b5%d0%b9-%d0%bd%d0%b0-%d1%84%d1%83%d0%bd%d0%ba%d1%86%d0%b8%d1%8e"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Нельзя определить контракты для указателя на функцию, но указателю на функцию можно присвоить адрес функции, для которой определён контракт.</p> <p><a class="gblog-markdown__link" href="https://github.com/valmat/cpp20-contracts-examples/blob/master/example5.cpp" >пример</a>:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">foo</span><span class="p">(</span><span class="kt">int</span> <span class="n">n</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="na">[[expects: n &lt; 10]]</span> </span></span><span class="line"><span class="cl"><span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="n">n</span><span class="o">*</span><span class="n">n</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="p">(</span><span class="o">*</span><span class="n">pfoo</span><span class="p">)(</span><span class="kt">int</span> <span class="n">n</span><span class="p">)</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">foo</span><span class="p">;</span> </span></span></code></pre></div><p>Вызов <code>pfoo(100)</code> приведёт к нарушению контракта.</p> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="контракты-при-наследовании" > Контракты при наследовании </h3> <a data-clipboard-text="https://valmat.ru/posts/archive/2019/03/cpp-contracts/#контракты-при-наследовании" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Контракты при наследовании" href="#%d0%ba%d0%be%d0%bd%d1%82%d1%80%d0%b0%d0%ba%d1%82%d1%8b-%d0%bf%d1%80%d0%b8-%d0%bd%d0%b0%d1%81%d0%bb%d0%b5%d0%b4%d0%be%d0%b2%d0%b0%d0%bd%d0%b8%d0%b8"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Классическая реализация концепции контрактов предполагает, что предусловия могут быть ослаблены в подклассах, постусловия и инварианты могут быть усилены в подклассах.</p> <p>В реализации C++20 это не так.</p> <p>Во-первых, инварианты в C++20 являются частью реализации, а не интерфейса. По этой причине, их можно как усилить, так и ослабить. Если в реализации виртуальной функции <code>assert</code> отсутствует, то он не будет унаследован.</p> <p>Во-вторых, требуется, чтобы при наследовании функции были <a class="gblog-markdown__link" href="https://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%B0%D0%B2%D0%B8%D0%BB%D0%BE_%D0%BE%D0%B4%D0%BD%D0%BE%D0%B3%D0%BE_%D0%BE%D0%BF%D1%80%D0%B5%D0%B4%D0%B5%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F" >ODR</a> идентичны. А, поскольку предусловия и постусловия являются частью интерфейса, то в наследнике они должны в точности совпадать.</p> <p>При этом, описание предусловий и постусловий при наследовании можно опустить. Но если они объявлены, то должны в точности совпадать с определением в базовом классе.</p> <p><a class="gblog-markdown__link" href="https://github.com/valmat/cpp20-contracts-examples/blob/master/example6-inheritance.cpp" >пример</a>:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="k">struct</span> <span class="nc">Base</span> </span></span><span class="line"><span class="cl"><span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">virtual</span> <span class="kt">int</span> <span class="nf">foo</span><span class="p">(</span><span class="kt">int</span> <span class="n">n</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="na">[[ expects: n &lt; 10 ]]</span> </span></span><span class="line"><span class="cl"> <span class="na">[[ ensures r: r &gt; 100 ]]</span> </span></span><span class="line"><span class="cl"> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="n">n</span><span class="o">*</span><span class="n">n</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">};</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="nc">Derived1</span> <span class="o">:</span> <span class="n">Base</span> </span></span><span class="line"><span class="cl"><span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">virtual</span> <span class="kt">int</span> <span class="nf">foo</span><span class="p">(</span><span class="kt">int</span> <span class="n">n</span><span class="p">)</span> <span class="k">override</span> </span></span><span class="line"><span class="cl"> <span class="na">[[ expects: n &lt; 10 ]]</span> </span></span><span class="line"><span class="cl"> <span class="na">[[ ensures r: r &gt; 100 ]]</span> </span></span><span class="line"><span class="cl"> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="n">n</span><span class="o">*</span><span class="n">n</span><span class="o">*</span><span class="mi">2</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">};</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="nc">Derived2</span> <span class="o">:</span> <span class="n">Base</span> </span></span><span class="line"><span class="cl"><span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="c1">// Inherits contracts from Base </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="k">virtual</span> <span class="kt">int</span> <span class="nf">foo</span><span class="p">(</span><span class="kt">int</span> <span class="n">n</span><span class="p">)</span> <span class="k">override</span> </span></span><span class="line"><span class="cl"> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="n">n</span><span class="o">*</span><span class="mi">3</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">};</span> </span></span></code></pre></div><p><code>&lt;spoiler title=&quot;Замечание&quot;&gt;</code></p> <p>К сожалению, пример выше <a class="gblog-markdown__link" href="https://github.com/arcosuc3m/clang-contracts/issues/18" >не работает</a> в экспериментальном компиляторе как ожидается.</p> <p>Если у <code>foo</code> из <code>Derived2</code> опустить контракт, то он не будет унаследован из базового класса. Кроме того, компилятор позволяет определить для подкласса контракт несовпадающий с контрактом базового.</p> <p>Ещё одна <a class="gblog-markdown__link" href="https://github.com/arcosuc3m/clang-contracts/issues/20" >ошибка</a> экспериментального компилятора: синтаксически правильной должна быть запись</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="k">virtual</span> <span class="kt">int</span> <span class="nf">foo</span><span class="p">(</span><span class="kt">int</span> <span class="n">n</span><span class="p">)</span> <span class="k">override</span> </span></span><span class="line"><span class="cl"> <span class="na">[[expects: n &lt; 10]]</span> </span></span><span class="line"><span class="cl"><span class="p">{...}</span> </span></span></code></pre></div><p>Однако в таком виде я получил ошибку компиляции</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">inheritance1.cpp:20:36: error: expected <span class="s1">&#39;;&#39;</span> at end of declaration list </span></span><span class="line"><span class="cl"> virtual int foo<span class="o">(</span>int n<span class="o">)</span> override </span></span><span class="line"><span class="cl"> ^ </span></span><span class="line"><span class="cl"> <span class="p">;</span> </span></span></code></pre></div><p>и пришлось заменить на</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="k">virtual</span> <span class="kt">int</span> <span class="nf">foo</span><span class="p">(</span><span class="kt">int</span> <span class="n">n</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="na">[[expects: n &lt; 10]]</span> </span></span><span class="line"><span class="cl"><span class="k">override</span> </span></span><span class="line"><span class="cl"><span class="p">{...}</span> </span></span></code></pre></div><p>Думаю, это связано с особенностью экспериментального компилятора, и в релизных версиях компиляторов будет работать синтаксически верный код.</p> <p><code>&lt;/spoiler&gt;</code></p> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="модификаторы-контрактов" > Модификаторы контрактов </h3> <a data-clipboard-text="https://valmat.ru/posts/archive/2019/03/cpp-contracts/#модификаторы-контрактов" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Модификаторы контрактов" href="#%d0%bc%d0%be%d0%b4%d0%b8%d1%84%d0%b8%d0%ba%d0%b0%d1%82%d0%be%d1%80%d1%8b-%d0%ba%d0%be%d0%bd%d1%82%d1%80%d0%b0%d0%ba%d1%82%d0%be%d0%b2"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Проверки предикатов контрактов могут нести дополнительные вычислительные расходы. Поэтому распространённой практикой является проверка контрактов в девелоперской и тестовой сборках и их игнорирование в релизной сборке.</p> <p>Для этх целей стандарт предлагает три уровня модификаторов контрактов. С помощью модификаторов и ключей компилятора программист может управлять тем, какие контакты будут проверяться в сборке, а какие игнорироваться.</p> <ul> <li><code>default</code> – этот модификатор используется по умолчанию. Предполагается, что вычислительная стоимость проверки выполнения выражения с этим модификатором <em>небольшая</em>, по сравнению со стоимостью вычисления самой функции.</li> <li><code>audit</code> – этот модификатор предполагает, что вычислительная стоимость проверки выполнения выражения <em>значительна</em> по сравнению со стоимостью вычисления самой функции.</li> <li><code>axiom</code> – этот модификатор используется, если выражение носит декларативный характер. Не проверяется во время выполнения. Служит для документирования интерфейса функции, использования статическими анализаторами и оптимизатором компилятора. Выражения с модификатором <code>axiom</code> никогда не вычисляются во время выполнения.</li> </ul> <p>Пример</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="na">[[expects: expr]]</span> <span class="c1">// Неявно default </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="na">[[expects default: expr]]</span> <span class="c1">// Явно default </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="na">[[expects axiom : expr]]</span> <span class="c1">// Run-time проверки не выполняются </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="na">[[expects audit : expr]]</span> <span class="c1">// Вычислительно дорогая проверка </span></span></span></code></pre></div><p>Используя модификаторы, можно определить какие проверки в каких версиях ваших сборок будут использоваться, а какие будут отключены.</p> <p>Стоит отметить, что если даже проверка не выполняется, компилятор вправе использовать контракт для низкоуровневых оптимизаций. И хотя проверка контракта может быть отключена флагом компиляции, нарушение контракта ведёт к неопределённому поведению программы.</p> <p>На усмотрение компилятора, могут быть предоставлены средства для включения проверок выражений, помеченных как <code>axiom</code>.</p> <p>В нашем случае, это опция компилятора</p> <pre tabindex="0"><code>-axiom-mode=&lt;mode&gt; </code></pre><p><code>-axiom-mode=on</code> <em>включает</em> режим аксиом и, соответственно, <em>выключает</em> проверку утверждений с идентификатором <code>axiom</code>,</p> <p><code>-axiom-mode=off</code> <em>выключает</em> режим аксиом и, соответственно, <em>включает</em> проверку утверждений с идентификатором <code>axiom</code>.</p> <p><a class="gblog-markdown__link" href="https://github.com/valmat/cpp20-contracts-examples/blob/master/example7-axiom.cpp" >пример</a>:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">foo</span><span class="p">(</span><span class="kt">int</span> <span class="n">n</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="na">[[expects axiom: n &lt; 10]]</span> </span></span><span class="line"><span class="cl"><span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="n">n</span><span class="o">*</span><span class="n">n</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>Программа может быть скомпилирована с тремя разными уровнями проверки:</p> <ul> <li><code>off</code> выключает все проверки выражений в контрактах</li> <li><code>default</code> проверяются только выражения с модификатором <code>default</code></li> <li><code>audit</code> расширенный режим, когда выполняются все проверки с модификатором <code>default</code> и <code>audit</code></li> </ul> <p>Как именно реализовывать установку уровня проверки отводится на усмотрение разработчиков компилятора.</p> <p>В нашем случае, для этого используется опция компилятора</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">-build-level<span class="o">=</span>&lt;off<span class="p">|</span>default<span class="p">|</span>audit&gt; </span></span></code></pre></div><p>По умолчанию используется <code>-build-level=default</code></p> <p>Как уже было сказано, компилятор может использовать контракты для низкоуровневых оптимизаций. По этой причине, не смотря на то, что во время выполнения некоторые предикаты в контрактах (в зависимости от уровня проверки) могут не вычисляться, их невыполнение ведёт к неопределённому поведению.</p> <p>Примеры применения уровней сборки отложу до следующего раздела, там их можно будет сделать наглядными.</p> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="перехват-нарушения-контракта" > Перехват нарушения контракта </h3> <a data-clipboard-text="https://valmat.ru/posts/archive/2019/03/cpp-contracts/#перехват-нарушения-контракта" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Перехват нарушения контракта" href="#%d0%bf%d0%b5%d1%80%d0%b5%d1%85%d0%b2%d0%b0%d1%82-%d0%bd%d0%b0%d1%80%d1%83%d1%88%d0%b5%d0%bd%d0%b8%d1%8f-%d0%ba%d0%be%d0%bd%d1%82%d1%80%d0%b0%d0%ba%d1%82%d0%b0"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>В зависимости от того, с какими опциями собирается программа, в случае нарушения контракта могут быть разные сценарии поведения.</p> <p>По умолчанию нарушение контракта ведёт к падению программы, вызову <code>std::termenate()</code>. Но программист может переопределить это поведение, предоставив свой обработчик и указав компилятору на необходимость продолжать работу программы после нарушения контракта.</p> <p>При компиляции можно установить обработчик <em>violation handler</em>, вызываемый при нарушении контракта.</p> <p>Способ реализация установки обработчика отводится на усмотрение создателей компилятора.</p> <p>В нашем случае это</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">-contract-violation-handler<span class="o">=</span>&lt;violation_handler&gt; </span></span></code></pre></div><p>Сигнатура обработчика должна иметь вид</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="kt">void</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">contract_violation</span><span class="o">&amp;</span> <span class="n">info</span><span class="p">)</span> </span></span></code></pre></div><p>или</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="kt">void</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">contract_violation</span><span class="o">&amp;</span> <span class="n">info</span><span class="p">)</span> <span class="k">noexcept</span> </span></span></code></pre></div><p><code>std::contract_violation</code> эквивалентна следующему определению:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="k">struct</span> <span class="nc">contract_violation</span> </span></span><span class="line"><span class="cl"><span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kt">uint_least32_t</span> <span class="nf">line_number</span><span class="p">()</span> <span class="k">const</span> <span class="k">noexcept</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">std</span><span class="o">::</span><span class="n">string_view</span> <span class="n">file_name</span><span class="p">()</span> <span class="k">const</span> <span class="k">noexcept</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">std</span><span class="o">::</span><span class="n">string_view</span> <span class="n">function_name</span><span class="p">()</span> <span class="k">const</span> <span class="k">noexcept</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">std</span><span class="o">::</span><span class="n">string_view</span> <span class="n">comment</span><span class="p">()</span> <span class="k">const</span> <span class="k">noexcept</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">std</span><span class="o">::</span><span class="n">string_view</span> <span class="n">assertion_level</span><span class="p">()</span> <span class="k">const</span> <span class="k">noexcept</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="p">};</span> </span></span></code></pre></div><p>Таким образом, обработчик позволяет получить достаточно исчерпывающую информацию о том, где именно и при каких условиях произошло нарушение контракта.</p> <p>Если обработчик <em>violation handler</em> задан, то, в случае нарушения контракта, по умолчанию, сразу после его выполнения будет вызван <code>std::abort()</code> (Без указания обработчика вызывается <code>std::terminate()</code>).</p> <p>Стандарт предполагает, что компиляторы предоставляют средства, позволяющие программистам продолжить выполнение программы после нарушения контракта.</p> <p>Способ реализации этих средств остаётся на усмотрение разработчиков компилятора. В нашем случае, это опция компилятора</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">-fcontinue-after-violation </span></span></code></pre></div><p>Опции <code>-fcontinue-after-violation</code> и <code>-contract-violation-handler</code> могут быть установлены независимо друг от друга. Например, можно установить <code>-fcontinue-after-violation</code>, но не устанавливать <code>-contract-violation-handler</code>. В последнем случае, после нарушения контракта программа просто продолжит работу.</p> <p>Возможность продолжения работы программы после нарушения контракта специфицирована стандартом, но нужно подходить с осторожностью к этой возможности.</p> <p>Технически, поведение программы после нарушения контракта не определено, даже если программист явно указал, что программа должна продолжать работать.</p> <p>Это связано с возможностью компилятора выполнять низкоуровневые оптимизации в рассчёте на выполнение конрактов.</p> <p>В идеале, если произошло нарушение конракта, нужно как можно скорее записать диагностическую информацию и завершить работу программы. Нужно точно понимать, что вы делаете позволяя программе работать после violation.</p> <p>Определим <a class="gblog-markdown__link" href="https://github.com/valmat/cpp20-contracts-examples/blob/master/violation_handler.h" >свой обработчик</a> и с его помощью перехватим нарушение конракта</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="kt">void</span> <span class="nf">violation_handler</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">contract_violation</span><span class="o">&amp;</span> <span class="n">info</span><span class="p">)</span> </span></span><span class="line"><span class="cl"><span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">std</span><span class="o">::</span><span class="n">cerr</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;line_number : &#34;</span> <span class="o">&lt;&lt;</span> <span class="n">info</span><span class="p">.</span><span class="n">line_number</span><span class="p">()</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">std</span><span class="o">::</span><span class="n">cerr</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;file_name : &#34;</span> <span class="o">&lt;&lt;</span> <span class="n">info</span><span class="p">.</span><span class="n">file_name</span><span class="p">()</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">std</span><span class="o">::</span><span class="n">cerr</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;function_name : &#34;</span> <span class="o">&lt;&lt;</span> <span class="n">info</span><span class="p">.</span><span class="n">function_name</span><span class="p">()</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">std</span><span class="o">::</span><span class="n">cerr</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;comment : &#34;</span> <span class="o">&lt;&lt;</span> <span class="n">info</span><span class="p">.</span><span class="n">comment</span><span class="p">()</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">std</span><span class="o">::</span><span class="n">cerr</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;assertion_level : &#34;</span> <span class="o">&lt;&lt;</span> <span class="n">info</span><span class="p">.</span><span class="n">assertion_level</span><span class="p">()</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>И рассмотрим <a class="gblog-markdown__link" href="https://github.com/valmat/cpp20-contracts-examples/blob/master/example8-handling.cpp" >пример</a> нарушения конракта:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&#34;violation_handler.h&#34;</span><span class="cp"> </span></span></span><span class="line"><span class="cl"><span class="cp"></span> </span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">foo</span><span class="p">(</span><span class="kt">int</span> <span class="n">n</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="na">[[expects: n &lt; 10]]</span> </span></span><span class="line"><span class="cl"><span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="n">n</span><span class="o">*</span><span class="n">n</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> </span></span><span class="line"><span class="cl"><span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">foo</span><span class="p">(</span><span class="mi">100</span><span class="p">);</span> <span class="c1">// &lt;-- contract violation </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>Скомпилируем программу с опциями <code>-contract-violation-handler=violation_handler</code> и <code>-fcontinue-after-violation</code> и запустим</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ bin/example8-handling.bin </span></span><span class="line"><span class="cl">line_number : <span class="m">4</span> </span></span><span class="line"><span class="cl">file_name : example8-handling.cpp </span></span><span class="line"><span class="cl">function_name : foo </span></span><span class="line"><span class="cl">comment : n &lt; <span class="m">10</span> </span></span><span class="line"><span class="cl">assertion_level : default </span></span></code></pre></div><p>Теперь можно привести примеры, демонстрирующие поведение программы при нарушении контракта при разных уровнях сборки и режимах контрактов.</p> <p>Рассмотрим следующий <a class="gblog-markdown__link" href="https://github.com/valmat/cpp20-contracts-examples/blob/master/example9.cpp" >пример</a>:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&#34;violation_handler.h&#34;</span><span class="cp"> </span></span></span><span class="line"><span class="cl"><span class="cp"></span> </span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">foo</span><span class="p">(</span><span class="kt">int</span> <span class="n">n</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="na">[[ expects axiom : n &lt; 100 ]]</span> </span></span><span class="line"><span class="cl"> <span class="na">[[ expects default : n &lt; 200 ]]</span> </span></span><span class="line"><span class="cl"> <span class="na">[[ expects audit : n &lt; 300 ]]</span> </span></span><span class="line"><span class="cl"><span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">n</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> </span></span><span class="line"><span class="cl"><span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">foo</span><span class="p">(</span><span class="mi">350</span><span class="p">);</span> <span class="c1">// audit </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="n">foo</span><span class="p">(</span><span class="mi">250</span><span class="p">);</span> <span class="c1">// default </span></span></span><span class="line"><span class="cl"><span class="c1"></span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>Если собрать его с опцией <code>-build-level=off</code> то как и ожидается, конракты не будут проверяться.</p> <p>Собрав с уровнем <code>default</code> (с опцией <code>-build-level=default</code>), получим следующий вывод:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ bin/example9-default.bin </span></span><span class="line"><span class="cl">line_number : <span class="m">5</span> </span></span><span class="line"><span class="cl">file_name : example9.cpp </span></span><span class="line"><span class="cl">function_name : foo </span></span><span class="line"><span class="cl">comment : n &lt; <span class="m">200</span> </span></span><span class="line"><span class="cl">assertion_level : default </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl">line_number : <span class="m">5</span> </span></span><span class="line"><span class="cl">file_name : example9.cpp </span></span><span class="line"><span class="cl">function_name : foo </span></span><span class="line"><span class="cl">comment : n &lt; <span class="m">200</span> </span></span><span class="line"><span class="cl">assertion_level : default </span></span></code></pre></div><p>И сборка с уровнем <code>audit</code> даст:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"> $ bin/example9-audit.bin </span></span><span class="line"><span class="cl">line_number : <span class="m">5</span> </span></span><span class="line"><span class="cl">file_name : example9.cpp </span></span><span class="line"><span class="cl">function_name : foo </span></span><span class="line"><span class="cl">comment : n &lt; <span class="m">200</span> </span></span><span class="line"><span class="cl">assertion_level : default </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl">line_number : <span class="m">6</span> </span></span><span class="line"><span class="cl">file_name : example9.cpp </span></span><span class="line"><span class="cl">function_name : foo </span></span><span class="line"><span class="cl">comment : n &lt; <span class="m">300</span> </span></span><span class="line"><span class="cl">assertion_level : audit </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl">line_number : <span class="m">5</span> </span></span><span class="line"><span class="cl">file_name : example9.cpp </span></span><span class="line"><span class="cl">function_name : foo </span></span><span class="line"><span class="cl">comment : n &lt; <span class="m">200</span> </span></span><span class="line"><span class="cl">assertion_level : default </span></span></code></pre></div><div class="flex align-center gblog-post__anchorwrap"> <h4 id="замечания" > Замечания </h4> <a data-clipboard-text="https://valmat.ru/posts/archive/2019/03/cpp-contracts/#замечания" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Замечания" href="#%d0%b7%d0%b0%d0%bc%d0%b5%d1%87%d0%b0%d0%bd%d0%b8%d1%8f"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p><code>violation_handler</code> может бросать исключения. В этом случае можно настроить программу так, чтобы нарушение контракта вело к выбросу исключения.</p> <p>Если функция, у которой описаны контракты, помечена как <code>noexcept</code> и при проверке контракта вызван <code>violation_handler</code>, который бросает исключение, то будет вызван <code>std::terminate()</code>.</p> <p><a class="gblog-markdown__link" href="https://github.com/valmat/cpp20-contracts-examples/blob/master/example11.cpp" >Пример</a></p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="kt">void</span> <span class="nf">violation_handler</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">contract_violation</span><span class="o">&amp;</span><span class="p">)</span> </span></span><span class="line"><span class="cl"><span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">throw</span> <span class="n">std</span><span class="o">::</span><span class="n">exception</span><span class="p">();</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">foo</span><span class="p">(</span><span class="kt">int</span> <span class="n">n</span><span class="p">)</span> <span class="k">noexcept</span> </span></span><span class="line"><span class="cl"> <span class="na">[[ expects: n &gt; 0 ]]</span> </span></span><span class="line"><span class="cl"><span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="n">n</span><span class="o">*</span><span class="n">n</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> </span></span><span class="line"><span class="cl"><span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">foo</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span> <span class="c1">// &lt;-- std::terminate() when violation handler throws an exception </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>Если компилятору передан флаг: не продолжать выполнение программы после нарушения контракта (<code>continuation mode=off</code>), но обработчик violation handler бросает исключение, то будет принудительно вызвана <code>std::terminate()</code>.</p> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="заключение" > Заключение </h3> <a data-clipboard-text="https://valmat.ru/posts/archive/2019/03/cpp-contracts/#заключение" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Заключение" href="#%d0%b7%d0%b0%d0%ba%d0%bb%d1%8e%d1%87%d0%b5%d0%bd%d0%b8%d0%b5"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Контракты относятся к неинтрузивным проверкам времени выполнения. Они играют очень важную роль в обеспечении качества выпускаемого программного обеспечения.</p> <p>C++ используется очень широко. И наверняка найдётся достаточное количество притензий к спецификации контрактов. На мой субъективный взгляд, реализация получилась довольно удобной и наглядной.</p> <p>Контракты C++20 позволят сделать наши программы ещё более надёжными, быстрыми и понятными. С нетерпением жду их реализацию в компиляторах.</p> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="ссылки" > Ссылки </h3> <a data-clipboard-text="https://valmat.ru/posts/archive/2019/03/cpp-contracts/#ссылки" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Ссылки" href="#%d1%81%d1%81%d1%8b%d0%bb%d0%ba%d0%b8"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <ul> <li><a class="gblog-markdown__link" href="https://gist.github.com/valmat/91b5a7eae275b9ed9e6824d975da151f" >Gist</a></li> <li><a class="gblog-markdown__link" href="https://habr.com/ru/articles/443766/" >Habr</a></li> </ul> Установка сертификатов LetsEncript https://valmat.ru/posts/archive/2018/05/letsencript/ 2018-05-20T12:00:00+03:00 2018-05-20T12:00:00+03:00 <div class="flex align-center gblog-post__anchorwrap"> <h1 id="установка-сертификатов-letsencript" > Установка сертификатов LetsEncript </h1> <a data-clipboard-text="https://valmat.ru/posts/archive/2018/05/letsencript/#установка-сертификатов-letsencript" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Установка сертификатов LetsEncript" href="#%d1%83%d1%81%d1%82%d0%b0%d0%bd%d0%be%d0%b2%d0%ba%d0%b0-%d1%81%d0%b5%d1%80%d1%82%d0%b8%d1%84%d0%b8%d0%ba%d0%b0%d1%82%d0%be%d0%b2-letsencript"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Можно установить несколько сертификатов для разных доменов. Если по каким то причинам конфиг Nginx не позволяет вычленить домены то в site-avaible нужно поместить временный конфиг, в котором перечислены домены. потом его убрать и всё будет работать</p> <p>Описание установки тут:</p> <ul> <li><a class="gblog-markdown__link" href="https://certbot.eff.org/#ubuntuxenial-nginx" >https://certbot.eff.org/#ubuntuxenial-nginx</a></li> <li><a class="gblog-markdown__link" href="https://certbot.eff.org/docs/using.html#renewal" >https://certbot.eff.org/docs/using.html#renewal</a></li> </ul> <p>Установка пакетов:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">apt-get install software-properties-common </span></span><span class="line"><span class="cl">add-apt-repository ppa:certbot/certbot </span></span><span class="line"><span class="cl">apt-get update </span></span><span class="line"><span class="cl">apt-get install certbot python-certbot-nginx </span></span></code></pre></div><p>Затем можно устанавливать сертификат Что бы получить список опций certbot набираем certbot &ndash;help</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">certbot <span class="o">[</span>SUBCOMMAND<span class="o">]</span> <span class="o">[</span>options<span class="o">]</span> <span class="o">[</span>-d DOMAIN<span class="o">]</span> <span class="o">[</span>-d DOMAIN<span class="o">]</span> ... </span></span></code></pre></div><p>Установка тодлко сертификата без правок конфига:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">certbot --nginx certonly </span></span></code></pre></div><p>При создании сертификата можно указать домены:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">certbot --nginx certonly -d avtogs.ru -d www.avtogs.ru -d msk.avtogs.ru </span></span></code></pre></div><p>Если набрать <code>certbot --nginx</code> То certbot попытается в конец конфига дописать включение сертификата</p> <p>Проверка обновления:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">certbot renew --dry-run </span></span></code></pre></div><p>Ручное обновление:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">certbot renew </span></span></code></pre></div><p>Ручное обновление с перезапуском конфигов:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">certbot -q renew --post-hook <span class="s2">&#34;service nginx reload&#34;</span> </span></span><span class="line"><span class="cl">certbot renew --pre-hook <span class="s2">&#34;service nginx stop&#34;</span> --post-hook <span class="s2">&#34;service nginx start&#34;</span> </span></span><span class="line"><span class="cl">certbot renew -a nginx --cert-name /etc/letsencrypt/renewal/my-domain.org </span></span></code></pre></div><p>посмотреть сертификаты</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">certbot certificates </span></span></code></pre></div><p>Проверим полученный сертификат</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">openssl x509 -text -in /etc/letsencrypt/live/avtogs.ru/cert.pem </span></span></code></pre></div><p>После установки можно попдправить Cron скрипт в /etc/cron.d ( /etc/cron.d/certbot )</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="m">22</span> */12 * * * root <span class="nb">test</span> -x /usr/bin/certbot -a <span class="se">\!</span> -d /run/systemd/system <span class="o">&amp;&amp;</span> perl -e <span class="s1">&#39;sleep int(rand(3600))&#39;</span> <span class="o">&amp;&amp;</span> certbot -q renew --post-hook <span class="s2">&#34;service nginx reload&#34;</span> </span></span></code></pre></div><p>Что бы сертификаты заработали в конфиг Nginx нужно добваить</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="n">server</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">listen</span> <span class="mi">443</span> <span class="n">ssl</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">ssl_certificate</span> <span class="o">/</span><span class="n">etc</span><span class="o">/</span><span class="n">letsencrypt</span><span class="o">/</span><span class="n">live</span><span class="o">/</span><span class="n">biotrapeza.ru</span><span class="o">/</span><span class="n">fullchain.pem</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">ssl_certificate_key</span> <span class="o">/</span><span class="n">etc</span><span class="o">/</span><span class="n">letsencrypt</span><span class="o">/</span><span class="n">live</span><span class="o">/</span><span class="n">biotrapeza.ru</span><span class="o">/</span><span class="n">privkey.pem</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">include</span> <span class="o">/</span><span class="n">etc</span><span class="o">/</span><span class="n">letsencrypt</span><span class="o">/</span><span class="n">options</span><span class="o">-</span><span class="n">ssl</span><span class="o">-</span><span class="n">nginx.conf</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="p">...</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="n">server</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">listen</span> <span class="mi">80</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">server_name</span> <span class="n">plazan.ru</span> <span class="n">en.plazan</span><span class="p">.</span><span class="n">ru</span> <span class="n">www.plazan</span><span class="p">.</span><span class="n">ru</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="kr">return</span> <span class="mi">301</span> <span class="n">https</span><span class="p">:</span><span class="o">//</span><span class="err">$</span><span class="n">host</span><span class="err">$</span><span class="n">request_uri</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>&ndash;</p> <p>См ещё: <a class="gblog-markdown__link" href="https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-16-04" >https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-16-04</a></p> <p><a class="gblog-markdown__link" href="https://gist.github.com/valmat/1a1b29459a815cb85ea9dd0e0db9cf6c" >Источник</a></p> Proxy для Telegram https://valmat.ru/posts/archive/2018/04/proxy/ 2018-04-14T12:00:00+03:00 2018-04-14T12:00:00+03:00 <div class="flex align-center gblog-post__anchorwrap"> <h1 id="в-связи-с-попытками-блокировок-telegram" > В связи с попытками блокировок Telegram </h1> <a data-clipboard-text="https://valmat.ru/posts/archive/2018/04/proxy/#в-связи-с-попытками-блокировок-telegram" class="gblog-post__anchor clip flex align-center" aria-label="Anchor В связи с попытками блокировок Telegram" href="#%d0%b2-%d1%81%d0%b2%d1%8f%d0%b7%d0%b8-%d1%81-%d0%bf%d0%be%d0%bf%d1%8b%d1%82%d0%ba%d0%b0%d0%bc%d0%b8-%d0%b1%d0%bb%d0%be%d0%ba%d0%b8%d1%80%d0%be%d0%b2%d0%be%d0%ba-telegram"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Проверено на 5 баксовом тарифе DigitalOcean</p> <hr> <ol> <li></li> </ol> <p>Создаём proxy пользователя для аутентификации по паролю:</p> <pre tabindex="0"><code>useradd -d /dev/null teleg passwd teleg </code></pre><hr> <ol start="2"> <li></li> </ol> <p>Сразу же закрываем этому пользователю вход по SSH: (ещё лучше всегда менять ssh порт с дефолтного на кастомный)</p> <p><code>nano /etc/ssh/sshd_config</code></p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1">#Port 22</span> </span></span><span class="line"><span class="cl">Port <span class="m">4251</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl">Match User teleg </span></span><span class="line"><span class="cl">PasswordAuthentication no </span></span><span class="line"><span class="cl">Match all </span></span></code></pre></div><p>Рестартим ssh:</p> <pre tabindex="0"><code>/etc/init.d/ssh restart </code></pre><p>Проверяем:</p> <pre tabindex="0"><code>ssh -p4251 teleg@&lt;ip&gt; </code></pre><p>Должно быть</p> <pre tabindex="0"><code>Permission denied (publickey). </code></pre><hr> <ol start="3"> <li></li> </ol> <p>В репах Убунту старый и глючный <code>dante-server</code> Поэтому берём свежий пакет</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">cd</span> /tmp </span></span><span class="line"><span class="cl">wget http://ppa.launchpad.net/dajhorn/dante/ubuntu/pool/main/d/dante/dante-server_1.4.1-1_amd64.deb </span></span><span class="line"><span class="cl">dpkg -i dante-server_1.4.1-1_amd64.deb </span></span><span class="line"><span class="cl">rm dante-server_1.4.1-1_amd64.deb </span></span></code></pre></div><p>Редактируем настройки Данте-сервера:</p> <pre tabindex="0"><code>cp /etc/danted.conf /etc/danted~.conf nano /etc/danted.conf </code></pre><p>Конфиг:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">logoutput: syslog /var/log/danted.log </span></span><span class="line"><span class="cl">user.privileged: root </span></span><span class="line"><span class="cl">user.unprivileged: teleg </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c1"># The listening network interface or address.</span> </span></span><span class="line"><span class="cl">internal: 0.0.0.0 <span class="nv">port</span><span class="o">=</span><span class="m">1180</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c1"># The proxying network interface or address.</span> </span></span><span class="line"><span class="cl">external: eth0 </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c1"># socks-rules determine what is proxied through the external interface.</span> </span></span><span class="line"><span class="cl"><span class="c1"># The default of &#34;none&#34; permits anonymous access.</span> </span></span><span class="line"><span class="cl">socksmethod: username </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c1"># client-rules determine who can connect to the internal interface.</span> </span></span><span class="line"><span class="cl"><span class="c1"># The default of &#34;none&#34; permits anonymous access.</span> </span></span><span class="line"><span class="cl">clientmethod: none </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl">client pass <span class="o">{</span> </span></span><span class="line"><span class="cl"> from: 0.0.0.0/0 to: 0.0.0.0/0 </span></span><span class="line"><span class="cl"> log: connect disconnect error </span></span><span class="line"><span class="cl"><span class="o">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl">socks pass <span class="o">{</span> </span></span><span class="line"><span class="cl"> from: 0.0.0.0/0 to: 0.0.0.0/0 </span></span><span class="line"><span class="cl"> log: connect disconnect error </span></span><span class="line"><span class="cl"><span class="o">}</span> </span></span></code></pre></div><p>Здесь <code>user.unprivileged: teleg</code> &ndash; имя пользователя, которого мы создали выше <code>port=1180</code> мржете указать сами. Номер должен быть больше 1000</p> <p>Сетевой интерфейс: <code>external: eth0</code> Имя сетевого интерфейса может отличаться. Обычно eth0 Что бы посмотреть имя используемого сетевого интерфейса нужно набрать</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ifconfig </span></span></code></pre></div><p>Перезапускаем dante-server:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">/etc/init.d/danted stop </span></span><span class="line"><span class="cl">/etc/init.d/danted start </span></span></code></pre></div><p>Проверяем работает ли proxy:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -v -x socks5://teleg:&lt;psw&gt;@&lt;ip&gt;:&lt;port&gt; http://ya.ru/ </span></span></code></pre></div><p><psw>,<ip> и <port> нужно указать свои</p> <p>Если всё нормально, то Ok Ссылка для включения прокси в телеграме:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">https://t.me/socks?server<span class="o">=</span>&lt;ip&gt;<span class="p">&amp;</span><span class="nv">port</span><span class="o">=</span>&lt;port&gt;<span class="p">&amp;</span><span class="nv">user</span><span class="o">=</span>teleg<span class="p">&amp;</span><span class="nv">pass</span><span class="o">=</span>&lt;psw&gt; </span></span></code></pre></div><p>Если что-то пошло не так, то смотрим логи:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cat /var/log/danted.log </span></span></code></pre></div><hr> <p>Инструкцию эту собрал сам из разных источников. Лично мной проверена.</p> <p><a class="gblog-markdown__link" href="https://gist.github.com/valmat/9b49ad02dac3941e47c442221ce852bb" >Источник</a></p> CRTP и двойной статическая полиморфизм C++ https://valmat.ru/posts/archive/2018/03/crtp-cpp/ 2018-03-27T12:00:00+03:00 2018-03-27T12:00:00+03:00 <div class="flex align-center gblog-post__anchorwrap"> <h2 id="crtp-и-двойной-статическая-полиморфизм" > CRTP и двойной статическая полиморфизм </h2> <a data-clipboard-text="https://valmat.ru/posts/archive/2018/03/crtp-cpp/#crtp-и-двойной-статическая-полиморфизм" class="gblog-post__anchor clip flex align-center" aria-label="Anchor CRTP и двойной статическая полиморфизм" href="#crtp-%d0%b8-%d0%b4%d0%b2%d0%be%d0%b9%d0%bd%d0%be%d0%b9-%d1%81%d1%82%d0%b0%d1%82%d0%b8%d1%87%d0%b5%d1%81%d0%ba%d0%b0%d1%8f-%d0%bf%d0%be%d0%bb%d0%b8%d0%bc%d0%be%d1%80%d1%84%d0%b8%d0%b7%d0%bc"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Ниже представлен паттерн проектирования на C++, известный как <strong>CRTP (Curiously Recurring Template Pattern)</strong>.</p> <p>С его помощью реализовать двойной статический полиморфизм. Пример ниже демонстрирует, как можно строить иерархии классов с использованием шаблонов для достижения поведения, похожего на виртуальные функции, но без накладных расходов раннего связывания.</p> <script src="https://gist.github.com/valmat/5c630e0c0a25f9087abc180a616ced4c.js"></script> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="зачем-нужен-этот-код" > Зачем нужен этот код? </h3> <a data-clipboard-text="https://valmat.ru/posts/archive/2018/03/crtp-cpp/#зачем-нужен-этот-код" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Зачем нужен этот код?" href="#%d0%b7%d0%b0%d1%87%d0%b5%d0%bc-%d0%bd%d1%83%d0%b6%d0%b5%d0%bd-%d1%8d%d1%82%d0%be%d1%82-%d0%ba%d0%be%d0%b4"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Обычный полиморфизм в C++ реализуется через виртуальные функции и наследование, что приводит к использованию виртуальной таблицы (vtable) и некоторым накладным расходам. Однако иногда нам требуется полиморфизм без этих издержек, особенно если все типы известны на этапе компиляции. Здесь на помощь приходит CRTP — паттерн, при котором класс-наследник передаёт себя как параметр шаблона базовому классу.</p> <p>В данном примере мы не просто реализуем классический CRTP, но и демонстрируем <strong>двойной статический полиморфизм</strong>: шаблонные базовые классы зависят сразу от двух параметров, что позволяет гибко комбинировать реализацию и интерфейс.</p> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="что-делает-этот-код" > Что делает этот код? </h3> <a data-clipboard-text="https://valmat.ru/posts/archive/2018/03/crtp-cpp/#что-делает-этот-код" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Что делает этот код?" href="#%d1%87%d1%82%d0%be-%d0%b4%d0%b5%d0%bb%d0%b0%d0%b5%d1%82-%d1%8d%d1%82%d0%be%d1%82-%d0%ba%d0%be%d0%b4"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <ol> <li><strong>WordGetterBase</strong> — базовый шаблонный класс, реализующий интерфейс для получения строки (<code>getWord()</code>) и вывода её в консоль (<code>printWord()</code>). Реализация метода <code>getWord()</code> делегируется классу-наследнику через CRTP.</li> <li><strong>WordGetterW1</strong> и <strong>WordGetterW2</strong> — конкретные реализации, возвращающие разные строки.</li> <li><strong>StrPrinterBase</strong> — ещё один шаблонный базовый класс, который наследуется от конкретного WordGetter и расширяет интерфейс методом <code>printStr()</code>. Он также использует CRTP для вызова метода наследника.</li> <li><strong>StrPrinter1</strong> и <strong>StrPrinter2</strong> — конкретные реализации принтера, которые форматируют и выводят строку, полученную от соответствующего WordGetter.</li> <li>В функции <code>main</code> демонстрируется использование этих классов: выводятся строки напрямую через WordGetter&rsquo;ы и с помощью различных StrPrinter&rsquo;ов.</li> </ol> Haskell https://valmat.ru/posts/archive/2016/11/haskell/ 2016-11-14T12:49:58+03:00 2016-11-14T12:49:58+03:00 <p>Мои эксперементы с Haskell</p> <p>Сделал тоже самое что описано в <a class="gblog-markdown__link" href="/posts/archive/2014/11/cpp-tolist/" >посте про C++</a></p> <script src="https://gist.github.com/valmat/dfde28138a143f4e339eb79233a887ea.js"></script> <p>Haskell &ndash; классный язык программирования. Но писатьна нём что то в одиночку очень тяжело.</p> Мой первый Arduino проект https://valmat.ru/posts/archive/2016/02/battery-sensor/ 2016-02-21T15:35:00+00:00 2016-02-21T15:35:00+00:00 <p>Пару лет назад подарили отцу на день рождения лодочный электромотор и литиевый тяговый аккумулятор к нему. Хорошая вещь, сплошные положительные эмоции от использования. Но, как оказалось, мотор не рассчитан на работу с этим аккумулятором. В том смысле, что индикация уровня заряда на корпусе мотора оказалась совершенно неадекватной.</p> <p>Решил попробовать сделать необходимый девайс на Arduino.</p> <p>До этого с микроконтроллерами дела не имел. Оказалось, это совсем не сложно и даже интересно.</p> <p>Исходники выложил на GitHub: <a class="gblog-markdown__link" href="https://github.com/valmat/BatterySensor" >https://github.com/valmat/BatterySensor</a></p> <p>Документация там же.</p> <p>Прибор умеет отображать уровень заряда батареи в процентах, с помощью значка, а также тремя RGB светодиодами, которые постепенно меняют свои цвета с зелёного на жёлтый, потом на красный — в зависимости от уровня заряда.</p> <p>На корпусе расположил выключатель и кнопку. Кнопка выключает светодиоды, а долгое нажатие кнопки отключает подсветку экрана. Для экономии батареи в солнечный день и чтобы не слепило ночью.</p> <p>Кроме того, подключил датчик BMP180. Устройство показывает температуру и давление. Давление на рыбалке знать необходимо.</p> <p>Корпус сделал герметичным. Всё проклеил резиновым клеем, а сверху заклеил алюминиевым скотчем.</p> <p>Работает надёжно. Показания точные.</p> <p><img src="./3.JPG" alt="Корпус устройства, общий вид" /></p> <p><img src="./4.JPG" alt="Вид устройства с другого ракурса" /></p> <p><img src="./5.JPG" alt="Внутренности устройства" /></p> <p><img src="./10.jpg" alt="Экран устройства в работе" /></p> Определение по списку в C++ https://valmat.ru/posts/archive/2014/11/cpp-tolist/ 2014-11-18T16:38:00+00:00 2014-11-18T16:38:00+00:00 <p>В PHP есть возможность присвоить переменным значения, используя массив:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">list</span><span class="p">(</span><span class="nv">$a</span><span class="p">,</span> <span class="nv">$b</span><span class="p">)</span> <span class="o">=</span> <span class="k">array</span><span class="p">(</span><span class="s1">&#39;str1&#39;</span><span class="p">,</span> <span class="s1">&#39;str2&#39;</span><span class="p">);</span> </span></span></code></pre></div><p>В Python это выглядит так:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">a</span><span class="p">,</span> <span class="n">b</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;str1&#39;</span><span class="p">,</span> <span class="s1">&#39;str2&#39;</span><span class="p">]</span> </span></span></code></pre></div><p>А вот в C++ такой языковой конструкции нет. Но это совершенно не проблема, потому что ее можно сделать самому.</p> <p>Вот что у меня получилось:</p> <script src="https://gist.github.com/valmat/455d9f9c56237b87f7e2.js"></script> <!-- ```cpp /** * This is a helper class. * It can be used only inside the function ListInitializer tolist(Args&& ...args) * ListInitializer list(Args&& ...args) * In all other cases, use it not possible * size is the number of arguments with which the constructor was called */ template<typename DataType, unsigned size> class ListInitializer { public: /** * Assignment operator * Ref-qualified forbids such things: * ListInitializer c(a,b); * c = arr; * You can only use this form: * ListInitializer(a,b) = arr; * VecType must be convertable to DataType */ template<typename ContainerType> ListInitializer& operator=(ContainerType&& arr) && { auto it = arr.begin(), itend = arr.end(); unsigned i = 0; while(it != itend && i < size) { *parr[i] = *it; ++it; ++i; } return *this; } /** * Deleted constructors. Forbids such things: * tolist q(a,b,c,d,e),w(a,b,c,d,e); * w = q; * You can only use this form: * tolist(a,b,c,d,e) = arr; */ ListInitializer(const ListInitializer& that) = delete; ListInitializer() = delete; private: /** * Constructor with one argument */ explicit ListInitializer(DataType& arg) { helper(0, arg); } /** * Constructor with a variable (>1) number of arguments * You can use any number of arguments of type DataType in the constructor. * tolist(a,b,c,d) = arr; * tolist(a,b) = arr; */ template<typename... Args> ListInitializer(DataType& arg0, Args&... args) { helper(0, arg0, args...); } /** * Move constructor */ ListInitializer(ListInitializer&& that) { for(unsigned i = 0; i < size; ++i) { parr[i] = that.parr[i]; that.parr[i] = nullptr; } } /** * Helper method. * Allows to initialize the list of any number of arguments. * Alternately, one by one makes pointers to the arguments into the internal array. */ template<typename... Args> void helper(int ind, DataType& arg0, Args&... args) { helper(ind, arg0); helper(++ind, args...); } /** * Helper method. */ void helper(int ind, DataType& arg0) { parr[ind] = &arg0; } template<typename T, typename... Args> friend ListInitializer<T, sizeof...(Args)+1> tolist(T& arg0, Args&... args); // Internal array of pointers to pointers to arguments DataType* parr[size]; }; template<typename DataType, typename... Args> ListInitializer<DataType, sizeof...(Args)+1> tolist(DataType& arg0, Args&... args) { return ListInitializer<DataType, sizeof...(Args)+1>(arg0, args...); } // Check #include <iostream> #include <vector> int main() { std::vector<std::string> arr{"str1", "str2", "str3", "str4", "str5", "str6"}; std::string a, b, c, d, e; tolist(b) = arr; std::cout << std::endl << a << " " << b << " " << c << " " << d << " " << e << std::endl; tolist(a, b, c, d, e) = arr; std::cout << std::endl << a << " " << b << " " << c << " " << d << " " << e << std::endl; return 0; } /* * g++ tolist.cpp -std=c++11 && ./a.out * or * clang++ tolist.cpp -std=c++11 && ./a.out * * |output: * | str1 * | * |str1 str2 str3 str4 str5 * */ ``` --> <p>См также <a class="gblog-markdown__link" href="https://gist.github.com/valmat/fdb37d1b741d5c12336d" >Optimized string concatenation: strjoin.cpp</a></p> <p>Нужен C++11, т.к. используются Ref-qualifiers.</p> <p>P.S. blogger.com как всегда портит код. Надо менять блогодвижок.</p> Микро-бенчмарк RocksDB server https://valmat.ru/posts/archive/2014/08/rocksdb-mikro-bench/ 2014-08-21T06:00:00+00:00 2014-08-21T06:00:00+00:00 <p>В полноценном смысле то, что я тут хочу написать, конечно, бенчмарком не является. Но вполне способно дать понимание области применения RocksDB.</p> <p>RocksDB — это довольно крутое хранилище, являющееся (на данный момент) встраиваемым решением. Главной фишкой RocksDB является то, что она рассчитана на использование на flash-накопителях, то есть на SSD-дисках.</p> <p>Лично я давно ждал появления таких решений. Поскольку память всё ещё дорогая, а жёсткие диски медленные, использование SSD для хранения данных — очень логичный шаг.</p> <p>Как я уже говорил, RocksDB — встраиваемое решение и не является сервером. Я честно ждал почти год, когда кто-нибудь напишет или хотя бы начнёт писать серверную обёртку над ней. Но то, что появилось за это время, по разным причинам категорически меня не устраивает.</p> <p>Поэтому я написал собственную серверную обёртку: <a class="gblog-markdown__link" href="https://github.com/valmat/RocksServer" >https://github.com/valmat/RocksServer</a>. В настоящее время она вполне функциональна, протестирована и готова к работе. Но есть ещё моменты, требующие улучшения. Эти моменты никак не связаны с её пригодностью к использованию, поэтому ничто не мешает уже сейчас произвести замеры производительности.</p> <p>Замеры я осуществлял с помощью идущего в комплекте с сервером драйвера для PHP. Во-первых, мне так было удобнее, а во-вторых, использовать я его в ближайшее время буду именно из PHP.</p> <p>Итак, табличка.</p> <p><strong>MultiGet со случайными ключами.</strong></p> <p><strong>Условия проведения эксперимента:</strong></p> <ul> <li>В базе данных 1 000 000 ключей.</li> <li>Выбирается набор случайных ключей в случайном порядке.</li> <li>Кэш не используется. Прямо перед экспериментом я перезагрузил компьютер, чтобы полностью исключить возможность использования файлового кэша.</li> <li>Хранимые значения имеют длину 50 ±5 байт.</li> </ul> <table> <thead> <tr> <th>Количество ключей в выборке</th> <th>SSD</th> <th>HDD</th> </tr> </thead> <tbody> <tr> <td>50</td> <td>4.7 ms</td> <td>131.3 ms</td> </tr> <tr> <td>300</td> <td>12.6 ms</td> <td>2371.4 ms (~2s)</td> </tr> <tr> <td>1000</td> <td>29.5 ms</td> <td>7918.4 ms (~8s)</td> </tr> <tr> <td>10 000</td> <td>124.6 ms</td> <td>45229.5 ms (~45s)</td> </tr> <tr> <td>100 000</td> <td>2346.6 ms</td> <td>51855.8 ms (~56s)</td> </tr> </tbody> </table> <p>Следует отметить, что после попадания ключей в кэш скорость выборки на HDD возрастает и приближается к скорости выборки на SSD.</p> <p>Какие выводы можно сделать из таблички? RocksDB действительно очень быстрое хранилище и подходит для использования на SSD-дисках.</p> <hr> <p><strong>P.S.</strong> Сама RocksDB обладает очень богатыми возможностями. В настоящий момент я реализовал лишь базовый необходимый мне функционал. В дальнейшем, постепенно, я планирую наращивать функциональные возможности RocksServer.</p> Clang vs gcc performance https://valmat.ru/posts/archive/2014/05/clang-vs-gcc-performance/ 2014-05-31T21:18:00+00:00 2014-05-31T21:18:00+00:00 <p>Стало мне интересно, и решил я провести такую глупую проверку: сравнить производительность программ, откомпилированных Clang&rsquo;ом и gcc.</p> <p>Для эксперимента взял первую попавшуюся реализацию пузырьковой сортировки с GitHub&rsquo;а.</p> <p>Компилировал с опциями <code>-g</code>, <code>-O1</code>, <code>-O2</code>, <code>-O3</code> и без опций.</p> <p>Получилось интересно и неожиданно.</p> <p>Для запуска тестов использовал такой скрипт:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/bash </span></span></span><span class="line"><span class="cl"><span class="cp"></span> </span></span><span class="line"><span class="cl"><span class="nv">OPT</span><span class="o">=</span><span class="s2">&#34;-O3&#34;</span> </span></span><span class="line"><span class="cl">g++ <span class="nv">$OPT</span> bubble.cpp -o bubble1 </span></span><span class="line"><span class="cl">clang++ <span class="nv">$OPT</span> bubble.cpp -o bubble2 <span class="c1"># -stdlib=libstdc++</span> </span></span><span class="line"><span class="cl">clang++ <span class="nv">$OPT</span> -stdlib<span class="o">=</span>libc++ bubble.cpp -o bubble3 <span class="c1"># -stdlib=libc++</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c1"># ls -slh</span> </span></span><span class="line"><span class="cl"><span class="c1"># exit;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl">sleep <span class="m">3</span> </span></span><span class="line"><span class="cl"><span class="nb">time</span> ./bubble1 &gt; /dev/null </span></span><span class="line"><span class="cl">sleep <span class="m">3</span> </span></span><span class="line"><span class="cl"><span class="nb">time</span> ./bubble2 &gt; /dev/null </span></span><span class="line"><span class="cl">sleep <span class="m">3</span> </span></span><span class="line"><span class="cl"><span class="nb">time</span> ./bubble3 &gt; /dev/null </span></span></code></pre></div><p>Меняя параметр <code>OPT</code>.</p> <p>Поясню, что здесь компилируется:</p> <ul> <li> <p><code>g++ $OPT bubble.cpp -o bubble1</code><br> Используется <strong>gcc</strong></p> </li> <li> <p><code>clang++ $OPT bubble.cpp -o bubble2</code><br> Используется <strong>clang</strong> со стандартной библиотекой <strong>libstdc++</strong> от gcc</p> </li> <li> <p><code>clang++ $OPT -stdlib=libc++ bubble.cpp -o bubble3</code><br> Используется <strong>clang</strong> со своей собственной стандартной библиотекой <strong>libc++</strong></p> </li> </ul> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="результаты-производительности" > Результаты производительности </h3> <a data-clipboard-text="https://valmat.ru/posts/archive/2014/05/clang-vs-gcc-performance/#результаты-производительности" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Результаты производительности" href="#%d1%80%d0%b5%d0%b7%d1%83%d0%bb%d1%8c%d1%82%d0%b0%d1%82%d1%8b-%d0%bf%d1%80%d0%be%d0%b8%d0%b7%d0%b2%d0%be%d0%b4%d0%b8%d1%82%d0%b5%d0%bb%d1%8c%d0%bd%d0%be%d1%81%d1%82%d0%b8"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <table> <thead> <tr> <th>Опция</th> <th>gcc</th> <th>clang -stdlib=libstdc++ (gcc)</th> <th>clang -stdlib=libc++</th> </tr> </thead> <tbody> <tr> <td>-g</td> <td>real <strong>0m3.337</strong>s<br>user 0m3.335s<br>sys 0m0.004s</td> <td>real <strong>0m3.294</strong>s<br>user 0m3.296s<br>sys 0m0.000s</td> <td>real <strong>0m3.323</strong>s<br>user 0m3.325s<br>sys 0m0.000s</td> </tr> <tr> <td>без опций</td> <td>real <strong>0m3.334</strong>s<br>user 0m3.336s<br>sys 0m0.000s</td> <td>real <strong>0m3.293</strong>s<br>user 0m3.295s<br>sys 0m0.000s</td> <td>real <strong>0m3.320</strong>s<br>user 0m3.318s<br>sys 0m0.004s</td> </tr> <tr> <td>-O1</td> <td>real <strong>0m1.735</strong>s<br>user 0m1.735s<br>sys 0m0.000s</td> <td>real <strong>0m1.485</strong>s<br>user 0m1.486s<br>sys 0m0.000s</td> <td>real <strong>0m1.493</strong>s<br>user 0m1.494s<br>sys 0m0.000s</td> </tr> <tr> <td>-O2</td> <td>real <strong>0m1.516</strong>s<br>user 0m1.517s<br>sys 0m0.000s</td> <td>real <strong>0m1.522</strong>s<br>user 0m1.523s<br>sys 0m0.000s</td> <td>real <strong>0m1.495</strong>s<br>user 0m1.492s<br>sys 0m0.004s</td> </tr> <tr> <td>-O3</td> <td>real <strong>0m1.510</strong>s<br>user 0m1.506s<br>sys 0m0.004s</td> <td>real <strong>0m1.534</strong>s<br>user 0m1.535s<br>sys 0m0.000s</td> <td>real <strong>0m1.498</strong>s<br>user 0m1.499s<br>sys 0m0.000s</td> </tr> </tbody> </table> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="размеры-бинарников" > Размеры бинарников </h3> <a data-clipboard-text="https://valmat.ru/posts/archive/2014/05/clang-vs-gcc-performance/#размеры-бинарников" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Размеры бинарников" href="#%d1%80%d0%b0%d0%b7%d0%bc%d0%b5%d1%80%d1%8b-%d0%b1%d0%b8%d0%bd%d0%b0%d1%80%d0%bd%d0%b8%d0%ba%d0%be%d0%b2"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <table> <thead> <tr> <th>Опция</th> <th>gcc</th> <th>clang -stdlib=libstdc++ (gcc)</th> <th>clang -stdlib=libc++</th> </tr> </thead> <tbody> <tr> <td>-g</td> <td>22K</td> <td>28K</td> <td>65K</td> </tr> <tr> <td>без опций</td> <td>9,2K</td> <td>8,3K</td> <td>15K</td> </tr> <tr> <td>-O1</td> <td>8,9K</td> <td>8,4K</td> <td>12K</td> </tr> <tr> <td>-O2</td> <td>8,9K</td> <td>8,1K</td> <td>12K</td> </tr> <tr> <td>-O3</td> <td>8,9K</td> <td>8,1K</td> <td>12K</td> </tr> </tbody> </table> <p>Понятно, что по такому примеру корректно сравнивать компиляторы нельзя, но для меня, как для приверженца GNU компилятора, результаты получились неожиданными.</p> <p>По сути, clang превзошёл gcc во всех направлениях.</p> <p>Для меня основной вывод такой:<br> Clang заслуживает внимания и заслуживает того, чтобы к нему присмотреться. Тем более, сообщения об ошибках, которые он выдаёт, информативнее, чем сообщения, выдаваемые gcc.</p> <p>До этого я никогда не пользовался клэнгом (силангом). Столкнулся с ним по необходимости. Думаю, что стоит попробовать использовать его в своей работе.</p> <p>Мои версии clang и gcc:</p> <ul> <li><strong>gcc version 4.8.1</strong> (Ubuntu/Linaro 4.8.1-10ubuntu9)</li> <li>Debian <strong>clang version 3.2</strong>-7ubuntu1 (tags/RELEASE_32/final) (based on LLVM 3.2)</li> </ul> Отчетность в налоговую на Linux https://valmat.ru/posts/archive/2014/03/linux/ 2014-03-20T10:43:00+00:00 2014-03-20T10:43:00+00:00 <p>Как я готовлю отчетность в налоговую.</p> <p>Выписки у меня достаются в таком формате:</p> <ul> <li><code>2014.01.20.rtf</code></li> <li><code>2014.01.20-1.rtf</code></li> <li>&hellip;</li> </ul> <p>В первую очередь, нужно упорядочить по дате, поэтому переименовываем:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="k">for</span> i in <span class="sb">`</span>find . -type f -name <span class="s2">&#34;*.rtf*&#34;</span><span class="sb">`</span><span class="p">;</span> <span class="k">do</span> </span></span><span class="line"><span class="cl"> <span class="nv">dst</span><span class="o">=</span><span class="sb">`</span><span class="nb">echo</span> <span class="nv">$i</span> <span class="p">|</span> sed -e :a -e <span class="s1">&#39;s/\(.*\)\([0-9]\{2\}\)\.\([0-9]\{2\}\)\.\([0-9]\{4\}\)\(.*\)/\1\4.\3.\2\5/;ta&#39;</span><span class="sb">`</span> </span></span><span class="line"><span class="cl"> <span class="nb">echo</span> mv <span class="nv">$i</span> <span class="nv">$dst</span> </span></span><span class="line"><span class="cl"><span class="k">done</span> </span></span></code></pre></div><p>Потом конвертируем в PDF:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">libreoffice --invisible --convert-to pdf *.rtf </span></span></code></pre></div><p>И соединяем все в один файл:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">gs -dNOPAUSE -sDEVICE<span class="o">=</span>pdfwrite -sOUTPUTFILE<span class="o">=</span>toprint.pdf -dBATCH <span class="sb">`</span>find . -type f -name <span class="s2">&#34;*.pdf&#34;</span> <span class="p">|</span> sort<span class="sb">`</span> </span></span></code></pre></div><p>Всё.</p> Документация PHP-CPP https://valmat.ru/posts/archive/2014/03/documentation-php-cpp-start/ 2014-03-10T18:42:00+00:00 2014-03-10T18:42:00+00:00 <p>У PHP-CPP появился раздел с <a class="gblog-markdown__link" href="http://www.php-cpp.com/documentation" >документацией</a>.</p> <p>Вообще разработка библиотеки идет семимильными шагами, и, похоже, недолго осталось ждать первого рабочего релиза. Когда будет готово, напишу подробнее про саму библиотеку и её возможности.</p> <hr> <p>PS: Вместо того чтобы что-то писать здесь про библиотеку, я просто взял и начал переводить документацию на русский язык: <a class="gblog-markdown__link" href="http://phpcpp.ru/" >http://phpcpp.ru/</a></p> <p>Основная цель этого перевода — познакомить русскоязычное сообщество с библиотекой.</p> <p>Перевод вольный. Поскольку многие вещи в библиотеке так или иначе сделаны с моим участием (да и просто потому что я уже достаточно хорошо в ней разобрался), мой перевод во многих местах дополняет и расширяет оригинальный текст.</p> Сравнение производительности C++ php-расширения с нативным кодом https://valmat.ru/posts/archive/2014/01/php-cpp-pagination/ 2014-01-12T15:11:00+00:00 2014-01-12T15:11:00+00:00 <p>Давно меня интересовал вопрос: насколько увеличивается производительность при переписывании нативного кода в php-расширение.</p> <p>И вот я решил провести сравнение.</p> <p>В качестве платформы для написания расширения была выбрана библиотека <a class="gblog-markdown__link" href="https://github.com/CopernicaMarketingSoftware/PHP-CPP" >PHP-CPP</a>.</p> <p><strong>Пару слов о самой библиотеке PHP-CPP.</strong><br> Довольно неплохая библиотека. Работать с ней приятно и удобно. Честно говоря, никогда еще не было так легко писать расширения для php.<br> Из минусов могу отметить недостаток документации. Чтобы разобраться с некоторыми вещами, недостаточно даже примеров, которыми автор снабжает код — приходится смотреть исходники. В частности, я так и не разобрался, как перенести статический метод класса из C++ в статический же метод в php.<br> Основана она на C++11, т.е. на старых дистрибутивах может потребоваться обновить gcc.</p> <p>В качестве подопытного был выбран модуль постраничного разбиения.<br> Исходники на GitHub: <a class="gblog-markdown__link" href="https://github.com/valmat/myscrnav" >https://github.com/valmat/myscrnav</a><br> Я написал два идентичных класса: один на php, другой на C++ (в виде расширения к php).</p> <p><strong>Пару слов как пользоваться.</strong><br> Пример использования есть в исходниках: <a class="gblog-markdown__link" href="https://github.com/valmat/myscrnav/blob/master/screennav_test.php" >https://github.com/valmat/myscrnav/blob/master/screennav_test.php</a></p> <p>Создаем pagination-объект из расширения:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nv">$scr</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">myScrNav</span><span class="p">(</span><span class="nv">$pageNom</span><span class="p">,</span> <span class="nv">$Count</span><span class="p">,</span> <span class="s1">&#39;/url/to/page/&#39;</span><span class="p">);</span> </span></span></code></pre></div><p>Или из php-класса:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">require</span> <span class="s1">&#39;php/class.screennav.php&#39;</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="nv">$scr</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ScreenNav</span><span class="p">(</span><span class="nv">$pageNom</span><span class="p">,</span> <span class="nv">$Count</span><span class="p">,</span> <span class="s1">&#39;/url/to/page/&#39;</span><span class="p">);</span> </span></span></code></pre></div><p>Задаём необходимые параметры (если необходимо, можно не задавать):</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nv">$scr</span><span class="o">-&gt;</span><span class="na">setInterval</span><span class="p">(</span><span class="mi">10</span><span class="p">);</span> <span class="c1">// Сколько объектов на странице </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nv">$scr</span><span class="o">-&gt;</span><span class="na">setPrefix</span><span class="p">(</span><span class="s1">&#39;?qwe&amp;part=&#39;</span><span class="p">);</span> <span class="c1">// URL prefix </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nv">$scr</span><span class="o">-&gt;</span><span class="na">setPostfix</span><span class="p">(</span><span class="s1">&#39;&amp;prm=132&#39;</span><span class="p">);</span> <span class="c1">// URL postfix </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nv">$scr</span><span class="o">-&gt;</span><span class="na">setSpace</span><span class="p">(</span><span class="s1">&#39;&lt;space&gt;...&lt;/space&gt;&#39;</span><span class="p">);</span> <span class="c1">// Разделитель блоков табов </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nv">$scr</span><span class="o">-&gt;</span><span class="na">setCssName</span><span class="p">(</span><span class="s1">&#39;newClassName&#39;</span><span class="p">);</span> <span class="c1">// Имя класса css блока управления постраничным выводом </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nv">$scr</span><span class="o">-&gt;</span><span class="na">setMidTab</span><span class="p">(</span><span class="mi">15</span><span class="p">);</span> <span class="c1">// см. info.png </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nv">$scr</span><span class="o">-&gt;</span><span class="na">setMaxTab</span><span class="p">(</span><span class="mi">5</span><span class="p">);</span> <span class="c1">// см. info.png </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nv">$scr</span><span class="o">-&gt;</span><span class="na">showCount</span><span class="p">(</span><span class="k">true</span><span class="p">);</span> <span class="c1">// Показывать ли общее количество элементов </span></span></span></code></pre></div><p><img src="./info.png" alt="info.png" /></p> <p>Кроме того, доступны следующие методы для получения вычисленных данных:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nx">getStartPos</span><span class="p">();</span> <span class="c1">// Номер начального элемента на текущей странице (для выборки из БД) </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">getLimitPos</span><span class="p">();</span> <span class="c1">// Длина списка элементов на странице на текущей странице (для выборки из БД) </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">getPageCnt</span><span class="p">();</span> <span class="c1">// Количество страниц при разбивке на части </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">getStartPos</span><span class="p">();</span> <span class="c1">// Номер (вычисленный) текущей страницы </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">show</span><span class="p">();</span> <span class="c1">// Возвращает собственно сам элемент управления постраничной разбивкой (html) </span></span></span></code></pre></div><p>То есть можно управлять постраничной разбивкой и делать запросы к БД на основе этой разбивки. Внешний вид, разумеется, полностью настраивается через css.</p> <p><strong>Теперь сами результаты сравнения.</strong><br> PHP тестируется с включённым опкешером (apc). Без него смысла тестировать не вижу, ибо тестировать нужно так, как используется на рабочей системе.</p> <p><strong>1</strong><br> Во-первых, сравним просто функции.</p> <p>На php будет:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">function</span> <span class="nf">ScreenNav_pageNo</span><span class="p">(</span><span class="nv">$var</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="p">(</span><span class="nx">isset</span><span class="p">(</span><span class="nv">$_GET</span><span class="p">[</span><span class="nv">$var</span><span class="p">]))</span> <span class="o">?</span> <span class="p">((</span><span class="nx">int</span><span class="p">)</span><span class="nv">$_GET</span><span class="p">[</span><span class="nv">$var</span><span class="p">]</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">:</span> <span class="mi">0</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>На C++:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="n">Php</span><span class="o">::</span><span class="n">Value</span> <span class="n">GETpageNom</span><span class="p">(</span><span class="n">Php</span><span class="o">::</span><span class="n">Parameters</span> <span class="o">&amp;</span><span class="n">params</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="n">params</span><span class="p">.</span><span class="n">size</span><span class="p">()</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">string</span> <span class="n">var</span> <span class="o">=</span> <span class="p">(</span><span class="k">new</span> <span class="n">Php</span><span class="o">::</span><span class="n">Value</span><span class="p">(</span><span class="n">params</span><span class="p">[</span><span class="mi">0</span><span class="p">]))</span><span class="o">-&gt;</span><span class="n">stringValue</span><span class="p">();</span> </span></span><span class="line"><span class="cl"> <span class="n">string</span> <span class="n">get</span> <span class="o">=</span> <span class="n">Php</span><span class="o">::</span><span class="n">globals</span><span class="p">[</span><span class="s">&#34;_GET&#34;</span><span class="p">][</span><span class="n">var</span><span class="p">];</span> </span></span><span class="line"><span class="cl"> <span class="kt">long</span> <span class="kt">int</span> <span class="n">rez</span> <span class="o">=</span> <span class="p">(</span><span class="k">new</span> <span class="n">Php</span><span class="o">::</span><span class="n">Value</span><span class="p">(</span><span class="n">get</span><span class="p">))</span><span class="o">-&gt;</span><span class="n">numericValue</span><span class="p">();</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="n">rez</span> <span class="o">?</span> <span class="p">(</span><span class="n">rez</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">:</span> <span class="mi">0</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p><strong>php</strong></p> <ul> <li>memory usage: 0.56Kb</li> <li>memory peak_usage: 1.1Kb</li> <li>Вычисленное: time: 50·10⁻⁶ sec</li> <li>ab -n 10000 &hellip; : Time per request: 0.39 [ms] (mean)</li> </ul> <p><strong>C++</strong></p> <ul> <li>memory usage: 1.26Kb</li> <li>memory peak_usage: 1.8Kb</li> <li>Вычисленное: time: 60·10⁻⁶ sec</li> <li>ab -n 10000 &hellip; : Time per request: 0.4 [ms] (mean)</li> </ul> <p>Как видно, на таком простом примере расширение не выигрывает, а даже проигрывает нативному коду.</p> <hr> <p><strong>2</strong><br> Теперь сравним классы.</p> <p><strong>php</strong></p> <ul> <li>time: 215·10⁻⁶ sec</li> <li>memory usage: 16.5Kb</li> <li>memory peak_usage: 22.7Kb</li> <li>ab -n 10000 &hellip; : Time per request: 0.533 [ms] (mean)</li> </ul> <p><strong>C++</strong></p> <ul> <li>time: 170·10⁻⁶ sec</li> <li>memory usage: 2.3Kb</li> <li>memory peak_usage: 4.6Kb</li> <li>ab -n 10000 &hellip; : Time per request: 0.49 [ms] (mean)</li> </ul> <hr> <div class="flex align-center gblog-post__anchorwrap"> <h2 id="выводы" > Выводы </h2> <a data-clipboard-text="https://valmat.ru/posts/archive/2014/01/php-cpp-pagination/#выводы" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Выводы" href="#%d0%b2%d1%8b%d0%b2%d0%be%d0%b4%d1%8b"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Разница во времени порядка 10⁻⁵ sec — не то, ради чего нужно переписывать с php на C++.<br> С другой стороны, само то, что она проявилась на такой незначительной задаче — уже результат.<br> Приведённый пример позволяет понять порядок выигрыша и оценить целесообразность переписывания нативного кода в C++ расширение.</p> <p>Обращает на себя внимание разница в потреблении памяти. Причём, если расширение выгрузить, то потребление памяти нативным кодом не уменьшается.</p> Сериализация в PHP https://valmat.ru/posts/archive/2013/12/serialize-php/ 2013-12-15T19:08:00+00:00 2013-12-15T19:08:00+00:00 <p>Будут сравниваться 4 способа сериализации:</p> <ol> <li>Стандартная сериализация <code>serialize</code></li> <li>JSON</li> <li>msgpack</li> <li>igbinary</li> </ol> <div class="gblog-toc gblog-toc__level--6"> <nav id="TableOfContents"> <ul> <li> <ul> <li><a href="#кратко-об-установке">Кратко об установке</a></li> <li><a href="#что-еще-важно-отметить">Что еще важно отметить</a></li> <li><a href="#тесты">Тесты</a> <ul> <li><a href="#1-массив-ассоциативный-вложенные-массивы-и-строки">1. Массив ассоциативный: вложенные массивы и строки</a></li> <li><a href="#2-массив-числовой-большие-массивы-целых-чисел">2. Массив числовой: большие массивы целых чисел</a></li> <li><a href="#3-массив-числовой-большие-массивы-длинных-целых-чисел-int64">3. Массив числовой: большие массивы длинных целых чисел (int64)</a></li> <li><a href="#4-массив-числовой-большие-массивы-чисел-с-плавающей-точкой-float">4. Массив числовой: большие массивы чисел с плавающей точкой (float)</a></li> <li><a href="#5-массив-строковый-большие-массивы-длинных-строк">5. Массив строковый: большие массивы длинных строк</a></li> </ul> </li> </ul> </li> </ul> </nav> <hr /> </div> <div class="flex align-center gblog-post__anchorwrap"> <h2 id="кратко-об-установке" > Кратко об установке </h2> <a data-clipboard-text="https://valmat.ru/posts/archive/2013/12/serialize-php/#кратко-об-установке" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Кратко об установке" href="#%d0%ba%d1%80%d0%b0%d1%82%d0%ba%d0%be-%d0%be%d0%b1-%d1%83%d1%81%d1%82%d0%b0%d0%bd%d0%be%d0%b2%d0%ba%d0%b5"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p><strong>JSON</strong> раньше шел в стандартной поставке PHP. Сейчас нужно поставить дополнительное расширение <code>php5-json</code>.</p> <p><strong>msgpack</strong><br> Сайт: <a class="gblog-markdown__link" href="http://msgpack.org/" >http://msgpack.org/</a><br> Исходники: <a class="gblog-markdown__link" href="https://github.com/msgpack/msgpack-php" >github.com/msgpack/msgpack-php</a> и <a class="gblog-markdown__link" href="http://pecl.php.net/package/msgpack" >http://pecl.php.net/package/msgpack</a><br> Ставим:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="nb">cd</span> /tmp </span></span><span class="line"><span class="cl">wget http://pecl.php.net/get/msgpack-0.5.5.tgz </span></span><span class="line"><span class="cl">tar xzf msgpack-0.5.5.tgz </span></span><span class="line"><span class="cl"><span class="nb">cd</span> msgpack-0.5.5 </span></span><span class="line"><span class="cl">phpize </span></span><span class="line"><span class="cl">./configure </span></span><span class="line"><span class="cl">make </span></span><span class="line"><span class="cl">make <span class="nb">test</span> </span></span></code></pre></div><p>Если тесты прошли нормально, то создаем пакет и ставим:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo checkinstall -D --install<span class="o">=</span>no </span></span><span class="line"><span class="cl">sudo dpkg -i msgpack_0.5.5-1_amd64.deb </span></span></code></pre></div><p><strong>igbinary</strong><br> Исходники: <a class="gblog-markdown__link" href="https://github.com/phadej/igbinary/tree/master" >github.com/phadej/igbinary</a> и <a class="gblog-markdown__link" href="http://pecl.php.net/package/igbinary" >http://pecl.php.net/package/igbinary</a><br> Далее опять:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">cd</span> /tmp </span></span><span class="line"><span class="cl">wget http://pecl.php.net/get/igbinary-1.1.1.tgz </span></span><span class="line"><span class="cl">tar xzf igbinary-1.1.1.tgz </span></span><span class="line"><span class="cl"><span class="nb">cd</span> igbinary-1.1.1 </span></span><span class="line"><span class="cl">phpize </span></span><span class="line"><span class="cl">./configure </span></span><span class="line"><span class="cl">make </span></span><span class="line"><span class="cl">make <span class="nb">test</span> </span></span><span class="line"><span class="cl">sudo checkinstall -D --install<span class="o">=</span>no </span></span><span class="line"><span class="cl">sudo dpkg -i igbinary_1.1.1-1_amd64.deb </span></span></code></pre></div><p>msgpack добавляет функции:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nx">BinData</span> <span class="nx">msgpack_pack</span><span class="p">(</span><span class="nx">phpValue</span><span class="p">);</span> </span></span><span class="line"><span class="cl"><span class="nx">phpValue</span> <span class="nx">msgpack_unpack</span><span class="p">(</span><span class="nx">BinData</span><span class="p">);</span> </span></span></code></pre></div><p>igbinary добавляет функции:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nx">BinData</span> <span class="nx">igbinary_serialize</span><span class="p">(</span><span class="nx">phpValue</span><span class="p">);</span> </span></span><span class="line"><span class="cl"><span class="nx">phpValue</span> <span class="nx">igbinary_unserialize</span><span class="p">(</span><span class="nv">$BinData</span><span class="p">);</span> </span></span></code></pre></div><p>Чтобы они заработали, нужно не забыть включить их в php.ini:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[igbinary]</span> </span></span><span class="line"><span class="cl"><span class="na">extension</span><span class="o">=</span><span class="s">igbinary.so</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c1">; Enable or disable compacting of duplicate strings</span> </span></span><span class="line"><span class="cl"><span class="c1">; The default is On.</span> </span></span><span class="line"><span class="cl"><span class="c1">;igbinary.compact_strings=On</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">[msgpack]</span> </span></span><span class="line"><span class="cl"><span class="na">extension</span><span class="o">=</span><span class="s">msgpack.so</span> </span></span></code></pre></div><div class="flex align-center gblog-post__anchorwrap"> <h2 id="что-еще-важно-отметить" > Что еще важно отметить </h2> <a data-clipboard-text="https://valmat.ru/posts/archive/2013/12/serialize-php/#что-еще-важно-отметить" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Что еще важно отметить" href="#%d1%87%d1%82%d0%be-%d0%b5%d1%89%d0%b5-%d0%b2%d0%b0%d0%b6%d0%bd%d0%be-%d0%be%d1%82%d0%bc%d0%b5%d1%82%d0%b8%d1%82%d1%8c"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <ul> <li><code>serialize</code> — стандартная функция PHP.</li> <li>JSON — старое и стабильное расширение.</li> <li>igbinary — тоже достаточно старая библиотека, давно вышедшая в стабильную ветку.</li> <li>msgpack — на данный момент все еще находится в стадии beta. С msgpack мне реально доводилось ловить глюки в ее предыдущих релизах. И если я решусь внедрять ее в продакшен, то только там, где ее ошибки не принесут фатального ущерба.</li> </ul> <div class="flex align-center gblog-post__anchorwrap"> <h2 id="тесты" > Тесты </h2> <a data-clipboard-text="https://valmat.ru/posts/archive/2013/12/serialize-php/#тесты" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Тесты" href="#%d1%82%d0%b5%d1%81%d1%82%d1%8b"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <hr> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="1-массив-ассоциативный-вложенные-массивы-и-строки" > 1. Массив ассоциативный: вложенные массивы и строки </h3> <a data-clipboard-text="https://valmat.ru/posts/archive/2013/12/serialize-php/#1-массив-ассоциативный-вложенные-массивы-и-строки" class="gblog-post__anchor clip flex align-center" aria-label="Anchor 1. Массив ассоциативный: вложенные массивы и строки" href="#1-%d0%bc%d0%b0%d1%81%d1%81%d0%b8%d0%b2-%d0%b0%d1%81%d1%81%d0%be%d1%86%d0%b8%d0%b0%d1%82%d0%b8%d0%b2%d0%bd%d1%8b%d0%b9-%d0%b2%d0%bb%d0%be%d0%b6%d0%b5%d0%bd%d0%bd%d1%8b%d0%b5-%d0%bc%d0%b0%d1%81%d1%81%d0%b8%d0%b2%d1%8b-%d0%b8-%d1%81%d1%82%d1%80%d0%be%d0%ba%d0%b8"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">array</span> <span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="s1">&#39;v0&#39;</span> <span class="o">=&gt;</span> </span></span><span class="line"><span class="cl"> <span class="k">array</span> <span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="mi">0</span> <span class="o">=&gt;</span> <span class="mi">0</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="mi">1</span> <span class="o">=&gt;</span> <span class="mi">1</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="mi">2</span> <span class="o">=&gt;</span> <span class="mi">2</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="o">...</span> </span></span><span class="line"><span class="cl"> <span class="mi">3</span> <span class="o">=&gt;</span> <span class="mi">3</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="mi">23</span> <span class="o">=&gt;</span> <span class="mi">23</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="mi">24</span> <span class="o">=&gt;</span> <span class="mi">24</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="p">),</span> </span></span><span class="line"><span class="cl"> <span class="s1">&#39;rnd0&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;2e0c883df6e2cb771103f4409f053549094d6787&#39;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="o">...</span> </span></span><span class="line"><span class="cl"><span class="p">)</span> </span></span></code></pre></div><p>c 16384 элементами</p> <table> <thead> <tr> <th></th> <th style="text-align: center">Время сериализации (msec)</th> <th style="text-align: center">Время десериализации (msec)</th> <th style="text-align: center">Размер упакованных данных (Kb)</th> </tr> </thead> <tbody> <tr> <td>MessagePack</td> <td style="text-align: center">9</td> <td style="text-align: center">25</td> <td style="text-align: center">678</td> </tr> <tr> <td>igbinary<br>compact_strings=Off</td> <td style="text-align: center">9</td> <td style="text-align: center">32</td> <td style="text-align: center">1278</td> </tr> <tr> <td>igbinary<br>compact_strings=On</td> <td style="text-align: center">16</td> <td style="text-align: center">32</td> <td style="text-align: center">1120</td> </tr> <tr> <td>JSON</td> <td style="text-align: center">14</td> <td style="text-align: center">318</td> <td style="text-align: center">1022</td> </tr> <tr> <td>SERIALIZE</td> <td style="text-align: center">62</td> <td style="text-align: center">39</td> <td style="text-align: center">2486</td> </tr> </tbody> </table> <hr> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="2-массив-числовой-большие-массивы-целых-чисел" > 2. Массив числовой: большие массивы целых чисел </h3> <a data-clipboard-text="https://valmat.ru/posts/archive/2013/12/serialize-php/#2-массив-числовой-большие-массивы-целых-чисел" class="gblog-post__anchor clip flex align-center" aria-label="Anchor 2. Массив числовой: большие массивы целых чисел" href="#2-%d0%bc%d0%b0%d1%81%d1%81%d0%b8%d0%b2-%d1%87%d0%b8%d1%81%d0%bb%d0%be%d0%b2%d0%be%d0%b9-%d0%b1%d0%be%d0%bb%d1%8c%d1%88%d0%b8%d0%b5-%d0%bc%d0%b0%d1%81%d1%81%d0%b8%d0%b2%d1%8b-%d1%86%d0%b5%d0%bb%d1%8b%d1%85-%d1%87%d0%b8%d1%81%d0%b5%d0%bb"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">array</span> <span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="mi">0</span> <span class="o">=&gt;</span> <span class="mi">3183</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="mi">1</span> <span class="o">=&gt;</span> <span class="mi">4527</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="mi">2</span> <span class="o">=&gt;</span> <span class="mi">4084</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="mi">3</span> <span class="o">=&gt;</span> <span class="mi">4032</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="mi">4</span> <span class="o">=&gt;</span> <span class="mi">3920</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="o">...</span> </span></span><span class="line"><span class="cl"> <span class="mi">262144</span> <span class="o">=&gt;</span> <span class="mi">4455</span><span class="p">,</span> </span></span><span class="line"><span class="cl"><span class="p">)</span> </span></span></code></pre></div><table> <thead> <tr> <th></th> <th style="text-align: center">Время сериализации (msec)</th> <th style="text-align: center">Время десериализации (msec)</th> <th style="text-align: center">Размер упакованных данных (Kb)</th> </tr> </thead> <tbody> <tr> <td>MessagePack</td> <td style="text-align: center">8</td> <td style="text-align: center">30</td> <td style="text-align: center">769</td> </tr> <tr> <td>igbinary<br>compact_strings=Off</td> <td style="text-align: center">9</td> <td style="text-align: center">33</td> <td style="text-align: center">1920</td> </tr> <tr> <td>igbinary<br>compact_strings=On</td> <td style="text-align: center">9</td> <td style="text-align: center">33</td> <td style="text-align: center">1920</td> </tr> <tr> <td>JSON</td> <td style="text-align: center">15</td> <td style="text-align: center">107</td> <td style="text-align: center">1281</td> </tr> <tr> <td>SERIALIZE</td> <td style="text-align: center">86</td> <td style="text-align: center">44</td> <td style="text-align: center">3988</td> </tr> </tbody> </table> <hr> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="3-массив-числовой-большие-массивы-длинных-целых-чисел-int64" > 3. Массив числовой: большие массивы длинных целых чисел (int64) </h3> <a data-clipboard-text="https://valmat.ru/posts/archive/2013/12/serialize-php/#3-массив-числовой-большие-массивы-длинных-целых-чисел-int64" class="gblog-post__anchor clip flex align-center" aria-label="Anchor 3. Массив числовой: большие массивы длинных целых чисел (int64)" href="#3-%d0%bc%d0%b0%d1%81%d1%81%d0%b8%d0%b2-%d1%87%d0%b8%d1%81%d0%bb%d0%be%d0%b2%d0%be%d0%b9-%d0%b1%d0%be%d0%bb%d1%8c%d1%88%d0%b8%d0%b5-%d0%bc%d0%b0%d1%81%d1%81%d0%b8%d0%b2%d1%8b-%d0%b4%d0%bb%d0%b8%d0%bd%d0%bd%d1%8b%d1%85-%d1%86%d0%b5%d0%bb%d1%8b%d1%85-%d1%87%d0%b8%d1%81%d0%b5%d0%bb-int64"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">array</span> <span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="mi">0</span> <span class="o">=&gt;</span> <span class="mi">7679461759223599104</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="mi">1</span> <span class="o">=&gt;</span> <span class="mi">4898705982311625344</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="mi">2</span> <span class="o">=&gt;</span> <span class="mi">5880628818820227328</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="o">...</span> </span></span><span class="line"><span class="cl"> <span class="mi">262144</span> <span class="o">=&gt;</span> <span class="mi">6940876209816891904</span><span class="p">,</span> </span></span><span class="line"><span class="cl"><span class="p">)</span> </span></span></code></pre></div><table> <thead> <tr> <th></th> <th style="text-align: center">Время сериализации (msec)</th> <th style="text-align: center">Время десериализации (msec)</th> <th style="text-align: center">Размер упакованных данных (Kb)</th> </tr> </thead> <tbody> <tr> <td>MessagePack</td> <td style="text-align: center">10</td> <td style="text-align: center">29</td> <td style="text-align: center">2305</td> </tr> <tr> <td>igbinary<br>compact_strings=Off</td> <td style="text-align: center">11</td> <td style="text-align: center">33</td> <td style="text-align: center">3456</td> </tr> <tr> <td>igbinary<br>compact_strings=On</td> <td style="text-align: center">11</td> <td style="text-align: center">33</td> <td style="text-align: center">3456</td> </tr> <tr> <td>JSON</td> <td style="text-align: center">20</td> <td style="text-align: center">172</td> <td style="text-align: center">5121</td> </tr> <tr> <td>SERIALIZE</td> <td style="text-align: center">92</td> <td style="text-align: center">49</td> <td style="text-align: center">7828</td> </tr> </tbody> </table> <hr> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="4-массив-числовой-большие-массивы-чисел-с-плавающей-точкой-float" > 4. Массив числовой: большие массивы чисел с плавающей точкой (float) </h3> <a data-clipboard-text="https://valmat.ru/posts/archive/2013/12/serialize-php/#4-массив-числовой-большие-массивы-чисел-с-плавающей-точкой-float" class="gblog-post__anchor clip flex align-center" aria-label="Anchor 4. Массив числовой: большие массивы чисел с плавающей точкой (float)" href="#4-%d0%bc%d0%b0%d1%81%d1%81%d0%b8%d0%b2-%d1%87%d0%b8%d1%81%d0%bb%d0%be%d0%b2%d0%be%d0%b9-%d0%b1%d0%be%d0%bb%d1%8c%d1%88%d0%b8%d0%b5-%d0%bc%d0%b0%d1%81%d1%81%d0%b8%d0%b2%d1%8b-%d1%87%d0%b8%d1%81%d0%b5%d0%bb-%d1%81-%d0%bf%d0%bb%d0%b0%d0%b2%d0%b0%d1%8e%d1%89%d0%b5%d0%b9-%d1%82%d0%be%d1%87%d0%ba%d0%be%d0%b9-float"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">array</span> <span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="mi">0</span> <span class="o">=&gt;</span> <span class="mf">0.00038631346578366</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="mi">1</span> <span class="o">=&gt;</span> <span class="mf">0.00016131634134538</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="mi">2</span> <span class="o">=&gt;</span> <span class="mf">0.00043595779928503</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="mi">3</span> <span class="o">=&gt;</span> <span class="mf">0.00011754334410814</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="mi">4</span> <span class="o">=&gt;</span> <span class="mf">0.00049353469548909</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="mi">5</span> <span class="o">=&gt;</span> <span class="mf">5.2391680201184E-5</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="o">...</span> </span></span><span class="line"><span class="cl"> <span class="mi">262144</span> <span class="o">=&gt;</span> <span class="mf">0.00041876046901173</span><span class="p">,</span> </span></span><span class="line"><span class="cl"><span class="p">)</span> </span></span></code></pre></div><table> <thead> <tr> <th></th> <th style="text-align: center">Время сериализации (msec)</th> <th style="text-align: center">Время десериализации (msec)</th> <th style="text-align: center">Размер упакованных данных (Kb)</th> </tr> </thead> <tbody> <tr> <td>MessagePack</td> <td style="text-align: center">9</td> <td style="text-align: center">28</td> <td style="text-align: center">2305</td> </tr> <tr> <td>igbinary<br>compact_strings=Off</td> <td style="text-align: center">11</td> <td style="text-align: center">33</td> <td style="text-align: center">3456</td> </tr> <tr> <td>igbinary<br>compact_strings=On</td> <td style="text-align: center">11</td> <td style="text-align: center">33</td> <td style="text-align: center">3456</td> </tr> <tr> <td>JSON</td> <td style="text-align: center">75</td> <td style="text-align: center">197</td> <td style="text-align: center">5061</td> </tr> <tr> <td>SERIALIZE</td> <td style="text-align: center">264</td> <td style="text-align: center">176</td> <td style="text-align: center">8538</td> </tr> </tbody> </table> <hr> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="5-массив-строковый-большие-массивы-длинных-строк" > 5. Массив строковый: большие массивы длинных строк </h3> <a data-clipboard-text="https://valmat.ru/posts/archive/2013/12/serialize-php/#5-массив-строковый-большие-массивы-длинных-строк" class="gblog-post__anchor clip flex align-center" aria-label="Anchor 5. Массив строковый: большие массивы длинных строк" href="#5-%d0%bc%d0%b0%d1%81%d1%81%d0%b8%d0%b2-%d1%81%d1%82%d1%80%d0%be%d0%ba%d0%be%d0%b2%d1%8b%d0%b9-%d0%b1%d0%be%d0%bb%d1%8c%d1%88%d0%b8%d0%b5-%d0%bc%d0%b0%d1%81%d1%81%d0%b8%d0%b2%d1%8b-%d0%b4%d0%bb%d0%b8%d0%bd%d0%bd%d1%8b%d1%85-%d1%81%d1%82%d1%80%d0%be%d0%ba"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">array</span> <span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="mi">0</span> <span class="o">=&gt;</span> <span class="s1">&#39;f7df8cb47630b8cd7eb73d0da7a23b9c01aaaa84f718499c1c8cef6730f9fd03c8125cab&#39;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="mi">1</span> <span class="o">=&gt;</span> <span class="s1">&#39;d30f79cf7fef47bd7a5611719f936539bec0d2e93bcf6eecb2611212e088d0d91f2ade9c&#39;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="mi">2</span> <span class="o">=&gt;</span> <span class="s1">&#39;86bce22a4d2805649853ac7909c4efb4dd18f255086af6e4641abb18caafc151b9aa95c8&#39;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="mi">3</span> <span class="o">=&gt;</span> <span class="s1">&#39;63afd0edc0371ad842d7a7ecc76260be4bc3e8c0da6cb383f8f9e58f2c8af88a8c0eb65e&#39;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="mi">4</span> <span class="o">=&gt;</span> <span class="s1">&#39;13c80015875a668e8fc059517ffd124abbda63c12d95666e2649fcfc6e3af75e09f5adb9&#39;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="o">...</span> </span></span><span class="line"><span class="cl"> <span class="mi">32768</span> <span class="o">=&gt;</span> <span class="s1">&#39;0e3808238b738aafc13a2a62f36d2a49dec4e191c22abfa379f38b5b0411bc11fa9bf92f&#39;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"><span class="p">)</span> </span></span></code></pre></div><table> <thead> <tr> <th></th> <th style="text-align: center">Время сериализации (msec)</th> <th style="text-align: center">Время десериализации (msec)</th> <th style="text-align: center">Размер упакованных данных (Kb)</th> </tr> </thead> <tbody> <tr> <td>MessagePack</td> <td style="text-align: center">4</td> <td style="text-align: center">5</td> <td style="text-align: center">2401</td> </tr> <tr> <td>igbinary<br>compact_strings=Off</td> <td style="text-align: center">4</td> <td style="text-align: center">6</td> <td style="text-align: center">2464</td> </tr> <tr> <td>igbinary<br>compact_strings=On</td> <td style="text-align: center">21</td> <td style="text-align: center">6</td> <td style="text-align: center">2463</td> </tr> <tr> <td>JSON</td> <td style="text-align: center">28</td> <td style="text-align: center">16</td> <td style="text-align: center">2401</td> </tr> <tr> <td>SERIALIZE</td> <td style="text-align: center">10</td> <td style="text-align: center">7</td> <td style="text-align: center">2806</td> </tr> </tbody> </table> <hr> <p>Замечу, что приведенные в таблицах данные являются примерными и зависят от данных. Так как данные у меня заполнялись случайным образом, то цифры получались разные, но разница несущественна и в целом эти цифры отражают реальную картину.</p> Разжился SSD диском https://valmat.ru/posts/archive/2013/12/ssd/ 2013-12-02T11:55:00+00:00 2013-12-02T11:55:00+00:00 <p>Вот и я разжился SSD диском.</p> <!-- ![SSD диск](./02.12.13+-+1) --> <p>Будет на чем тестить NoSQL SSD хранилища.</p> <p>В планах потестить на нем RocksDB, LevelDB и, возможно, RethinkDB.</p> <!-- PS: а вот так он встал в мой ноут ![SSD в ноутбуке](./02.12.13+-+2) --> Пластилиновый мультик https://valmat.ru/posts/archive/2013/03/plastylynovyy-multik/ 2013-03-04T08:50:00+00:00 2013-03-04T08:50:00+00:00 <p>Сделали с дочей мультик из пластилина.</p> <p>Делается так:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">convert -delay <span class="m">20</span> -loop <span class="m">0</span> *.jpg mygif.gif </span></span></code></pre></div><p>Вот результат:</p> <p><img src="./myimage.gif" alt="Пластилиновый мультик" /></p> <p>Еще полезное:</p> <p>Сделать из кадров ролик:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">convert -delay <span class="m">20</span> -loop <span class="m">0</span> *.jpg mympg.mpg </span></span></code></pre></div><p>MOV из gif:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">convert mygif.gif mymov.mov </span></span></code></pre></div><p>Видео на YouTube:</p> <iframe width="320" height="266" src="https://www.youtube.com/embed/_oHiKSMMwAA" title="Пластелиновый человечик" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe> <p><a class="gblog-markdown__link" href="https://www.youtube.com/watch?v=_oHiKSMMwAA" >Смотреть на YouTube</a></p> Топ 7 ботов за сутки https://valmat.ru/posts/archive/2013/03/top7bots/ 2013-03-01T09:32:00+00:00 2013-03-01T09:32:00+00:00 <p>По результатам выборки из логов сервера за 1 сутки.</p> <ol> <li><strong>Googlebot</strong> — 43 229 запросов</li> <li><strong>YandexDirect</strong> — 21 260</li> <li><strong>Mediapartners-Google</strong> — 14 386</li> <li><strong>Mail.RU_Bot</strong> — 13 715</li> <li><strong>YandexBot</strong> — 13 079</li> <li><strong>AhrefsBot</strong> — 11 997</li> <li><strong>openstat ru/Bot</strong> — 2 709</li> </ol> <p><strong>Выводы:</strong><br> Гугл предсказуемо обошёл всех и вся. Честь и хвала ему.<br> Удивил Mail.RU, который обошёл Яндекс.<br> Что касается Яндекса, то очевидно, что приоритет Яндекса — их рекламная сеть. И уже потом поисковые технологии.</p> <p>Можно сравнить, какое значение Гугл и Яндекс уделяют своим поисковым и рекламным технологиям в процентном соотношении:</p> <p><em>YandexDirect</em> — <strong>62%</strong><br> <em>YandexBot</em> — <strong>38%</strong><br> <em>Mediapartners-Google</em> — <strong>25%</strong><br> <em>Googlebot</em> — <strong>75%</strong></p> <p>Далее AhrefsBot — собиратель беклинков. Бесполезная (а иногда и вредная) нагрузка на сервер. Вредная потому, что конкуренты смогут видеть то, что им видеть не положено. Его блочим в <code>robots.txt</code>.<br> Поскольку есть сомнения, что он вообще читает <code>robots.txt</code>, то для профилактики делаем примерно так:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">if</span> <span class="s">(</span><span class="nv">$http_user_agent</span> <span class="p">~</span><span class="sr">*</span> <span class="s">(Wget|ApacheBench|SISTRIX|AhrefsBot|Teleport)</span> <span class="s">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kn">return</span> <span class="mi">502</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>Ну и Bot openstat, я считаю, вообще недостоин нашего внимания.</p> <hr> <p><strong>PS. Чуть не забыл.</strong><br> Получить Top список IP-адресов можно так:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cat ваши_лог_фалы <span class="p">|</span> cut -c -15 <span class="p">|</span> sort <span class="p">|</span> uniq -c <span class="p">|</span> sort -nr <span class="p">|</span> sed -r <span class="s1">&#39;s!\s*([0-9]+)\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+).*!\1\t\2!g&#39;</span> &gt; top.log </span></span></code></pre></div><p>А выборку по отдельным ботам можно получить <code>grep</code>&lsquo;ом.</p> Google Chrome и "-" 400 0 "-" "-" https://valmat.ru/posts/archive/2013/01/chrome-400-log/ 2013-01-24T20:46:00+00:00 2013-01-24T20:46:00+00:00 <p>Еще раз к вопросу <a class="gblog-markdown__link" href="http://www.valmat.ru/2013/01/ban-bot-2.html" >откуда в логах берутся строки вида</a>:</p> <pre tabindex="0"><code>1.1.1.1 - - [19/Jan/2013:07:19:23 +0400] &#34;-&#34; 400 0 &#34;-&#34; &#34;-&#34; </code></pre><p>При медленном соединении удалось отловить эффект появления таких записей в браузере и увидеть все вживую.</p> <p>Вот тут видно, как Chrome отправил два запроса, держит соединение открытым, а потом закрывает:</p> <p><img src="./404-chrome.png" alt="chrome-400" /></p> <p>А секундой позже уже загружает то, что его просили.</p> <p><img src="./404-chrome-1.png" alt="chrome-400-1" /></p> <p>Успел сделать снимки экрана.</p> <p>Повторить эксперимент можно либо подключившись к медленному каналу, либо намеренно ограничив скорость соединения на стороне веб-сервера.</p> Баним ботов. Часть 2 https://valmat.ru/posts/archive/2013/01/ban-bot-2/ 2013-01-23T14:21:00+00:00 2013-01-23T14:21:00+00:00 <p>Небольшой анализ логов сервера. Какие странные сущности обитают в Интернете. И как с ними бороться.</p> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="открытые-подключения" > Открытые подключения </h3> <a data-clipboard-text="https://valmat.ru/posts/archive/2013/01/ban-bot-2/#открытые-подключения" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Открытые подключения" href="#%d0%be%d1%82%d0%ba%d1%80%d1%8b%d1%82%d1%8b%d0%b5-%d0%bf%d0%be%d0%b4%d0%ba%d0%bb%d1%8e%d1%87%d0%b5%d0%bd%d0%b8%d1%8f"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>В логах nginx&rsquo;а обнаружил десятки тысяч записей вида:</p> <pre tabindex="0"><code>1.1.1.1 - - [19/Jan/2013:07:19:23 +0400] &#34;-&#34; 400 0 &#34;-&#34; &#34;-&#34; 1.1.1.1 - - [19/Jan/2013:07:19:23 +0400] &#34;-&#34; 400 0 &#34;-&#34; &#34;-&#34; 1.1.1.1 - - [19/Jan/2013:07:19:23 +0400] &#34;-&#34; 400 0 &#34;-&#34; &#34;-&#34; 1.1.1.1 - - [19/Jan/2013:07:19:34 +0400] &#34;-&#34; 400 0 &#34;-&#34; &#34;-&#34; 1.1.1.1 - - [19/Jan/2013:07:19:34 +0400] &#34;-&#34; 400 0 &#34;-&#34; &#34;-&#34; 1.1.1.1 - - [19/Jan/2013:07:19:34 +0400] &#34;-&#34; 400 0 &#34;-&#34; &#34;-&#34; 1.1.1.1 - - [19/Jan/2013:07:19:34 +0400] &#34;-&#34; 400 0 &#34;-&#34; &#34;-&#34; </code></pre><p>Судя по количеству и частоте запросов, достаточно большое число таких запросов сделано именно ботами.</p> <p>Казалось бы, легко создать правило для fail2ban и забанить их всех.</p> <p>Но такие записи могут создавать и обычные пользователи. Например, если пользователь остановит загрузку или при быстром переходе со страницы на страницу (у меня получилось <a class="gblog-markdown__link" href="http://www.valmat.ru/2013/01/-400-0-.html" >отловить такой эффект в Google Chrome</a>).</p> <p>Суть таких записей такова: открытое и не закрытое соединение.</p> <p>Например, если открыть соединение telnet&rsquo;ом и оставить его, то по истечении таймаута появится именно такая запись.</p> <p><strong><code>$ telnet site.ru 80</code></strong></p> <pre tabindex="0"><code>Trying 127.0.0.1... Connected to site.ru. Escape character is &#39;^]&#39;. </code></pre><p>Или можно так:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nx">php</span> <span class="o">-</span><span class="nx">r</span> <span class="s1">&#39;for($i=0;$i&gt;500;$i++){$v=&#34;s&#34;.$i;$$v=socket_create(AF_INET,SOCK_STREAM,SOL_TCP);socket_connect($$v,&#34;localhost&#34;, 80);}&#39;</span> </span></span></code></pre></div><p>Особого вреда такие атаки нанести не могут, т.к. в силу своего асинхронного характера nginx может держать <a class="gblog-markdown__link" href="http://forum.nginx.org/read.php?21,129983,129986#msg-129986" >достаточно</a> <a class="gblog-markdown__link" href="http://nginx.org/ru/docs/ngx_core_module.html#worker_connections" >большое</a> число открытых соединений. Но специально для таких случаев (а также других недоатак) существуют такие вещи, как модули <a class="gblog-markdown__link" href="http://nginx.org/ru/docs/http/ngx_http_limit_req_module.html" >ngx_http_limit_req_module</a> и <a class="gblog-markdown__link" href="http://nginx.org/ru/docs/http/ngx_http_limit_conn_module.html" >ngx_http_limit_conn_module</a>.</p> <p>Про них написано достаточно много, простым гуглением все находится.</p> <p>Можно только добавить — не забыть вставить в robots.txt строчку вроде этой:</p> <pre tabindex="0"><code>Crawl-delay: 1 </code></pre><p>(можно дробные значения), чтобы ненароком не забанить поисковых роботов.</p> <p><code>limit_req_zone</code> должна обязательно стоять (в секции http) до подключения секций server, т.е. до</p> <pre tabindex="0"><code>include /etc/nginx/conf.d/*.conf; </code></pre><p>Еще некоторых ленивых роботов, передающих не все заголовки, можно развернуть вот таким кодом в секции server:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">if</span> <span class="s">(</span> <span class="nv">$http_user_agent</span> <span class="p">=</span> <span class="s">&#34;&#34;</span> <span class="s">)</span><span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kn">return</span> <span class="mi">444</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><div class="flex align-center gblog-post__anchorwrap"> <h3 id="try-proxy" > try proxy </h3> <a data-clipboard-text="https://valmat.ru/posts/archive/2013/01/ban-bot-2/#try-proxy" class="gblog-post__anchor clip flex align-center" aria-label="Anchor try proxy" href="#try-proxy"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Следующая разновидность ботов пытается использовать nginx в качестве открытого прокси-сервера. Точнее, пытается определить такую возможность.</p> <p>Дело в том, что если, например, telnet&rsquo;ом передать заголовок не</p> <pre tabindex="0"><code>GET /index.htm HTTP/1.1 </code></pre><p>а</p> <pre tabindex="0"><code>GET http://site.ru/index.htm </code></pre><p>то nginx не разворачивает такой запрос с кодом 400, а обрабатывает его.</p> <p>И дальше все зависит от настройки конфигов.</p> <p>В некоторых случаях, таким образом можно получить <a class="gblog-markdown__link" href="http://forum.nginx.org/read.php?21,226769,227097#msg-227097" >открытый http-прокси сервер</a>.</p> <p>В общем случае, если site.ru определён в nginx как</p> <pre tabindex="0"><code>server_name site.ru; </code></pre><p>то дальше вашего сервера запрос не уйдет.</p> <p>Вот реальный пример из log-файла:</p> <pre tabindex="0"><code>178.77.67.27 - - [20/Jan/2013:19:09:43 +0400] &#34;GET http://www.scanproxy.net:80/p-80.html HTTP/1.0&#34; 404 564 &#34;-&#34; &#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; KuKu 0.65)&#34; 82.145.35.123 - - [10/Jan/2013:08:11:20 +0400] &#34;GET http://proxyjudge2.proxyfire.net/fastenv HTTP/1.1&#34; 404 564 &#34;-&#34; &#34;Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)&#34; 80.82.215.45 - - [01/Jan/2013:08:59:03 +0400] &#34;GET http://www.scanproxy.net:80/p-80.html HTTP/1.0&#34; 404 564 &#34;-&#34; &#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; KuKu 0.65)&#34; 62.193.243.32 - - [01/Jan/2013:23:38:53 +0400] &#34;GET http://www.scanproxy.net:80/p-80.html HTTP/1.0&#34; 404 564 &#34;-&#34; &#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; KuKu 0.65)&#34; 46.32.65.23 - - [11/Dec/2012:19:20:15 +0400] &#34;GET http://www.santeh.ru/cgi-bin/textenv.pl HTTP/1.0&#34; 404 564 &#34;-&#34; &#34;Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 4.0)&#34; </code></pre><p>В основном, лечится это так: запретить использование дефолтного сервера и обработку запросов <a class="gblog-markdown__link" href="http://nginx.org/ru/docs/http/request_processing.html#how_to_prevent_undefined_server_names" >без имени сервера</a>.</p> <pre tabindex="0"><code>server { listen 80 default_server; server_name &#34;&#34;; return 444; } </code></pre><p>Далее, при передаче заголовка (без HTTP/1.1 или HTTP/1.0):</p> <pre tabindex="0"><code>GET http://site.ru/index.htm </code></pre><p>Все остальные строки запроса будут проигнорированы.</p> <p>Т.е. в запросе (вместо <code>GET /index.htm HTTP/1.1</code> написано <code>GET http://site.ru/index.htm</code>):</p> <pre tabindex="0"><code>GET http://site.ru/index.htm Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Charset:windows-1251,utf-8;q=0.7,*;q=0.3 Accept-Language:ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4 Cache-Control:max-age=0 Connection:keep-alive Host:site.ru Referer:site.ru/index.htm User-Agent:TelnetTester </code></pre><p>Будет учтена только первая строка. А значит, поможет уже знакомая нам конструкция:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">if</span> <span class="s">(</span> <span class="nv">$http_user_agent</span> <span class="p">=</span> <span class="s">&#34;&#34;</span> <span class="s">)</span><span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kn">return</span> <span class="mi">444</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>Но такая ситуация встречается не часто.</p> <p>Кроме ботов, запросы вида <code>GET http://site.ru/index.htm HTTP/1.1</code> шлет Opera. Во всяком случае, у меня в логах достаточно много строк вроде этой:</p> <pre tabindex="0"><code>188.162.15.86 - - [05/Jan/2013:09:18:41 +0400] &#34;GET http://opera10beta-turbo.opera-mini.net:80//img/spb_b_1456.jpg HTTP/1.1&#34; 404 162 &#34;http://images.yandex.ru/yandsearch?p=...&#34; &#34;Opera/9.80 (Windows NT 5.1) Presto/2.12.388 Version/12.10&#34; </code></pre><div class="flex align-center gblog-post__anchorwrap"> <h4 id="битые-заголовки" > Битые заголовки </h4> <a data-clipboard-text="https://valmat.ru/posts/archive/2013/01/ban-bot-2/#битые-заголовки" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Битые заголовки" href="#%d0%b1%d0%b8%d1%82%d1%8b%d0%b5-%d0%b7%d0%b0%d0%b3%d0%be%d0%bb%d0%be%d0%b2%d0%ba%d0%b8"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <pre tabindex="0"><code>94.41.37.135 - - [11/Jan/2013:08:51:57 +0400] &#34;ЪьЪЮ\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00ЪЧ\x00;CREATOR: gd-jpeg v1.0 (using IJG JPEG v62), quality = 75&#34; 400 166 &#34;-&#34; &#34;-&#34; 176.213.180.115 - - [07/Jan/2013:16:27:16 +0400] &#34;ЪьЪЮ\x00\x10JFIF\x00\x01\x01\x01\x00`\x00`\x00\x00Ъш\x00C\x00\x02\x01\x01\x02\x01\x01\x02\x02\x02\x02\x02\x02\x02\x02\x03\x05\x03\x03\x03\x03\x03\x06\x04\x04\x03\x05\x07\x06\x07\x07\x07\x06\x07\x07\x08\x09\x0B\x09\x08\x08&#34; 400 166 &#34;-&#34; &#34;-&#34; </code></pre><p>Такие строки создают некоторые браузеры. Как это происходит — я так и не понял. Но нечто подобное я нашел в логах на локальной машине — там, где никаких ботов быть не может. Предположительно, Google Chrome.</p> <p>Также весьма вероятно, подобные записи могут создаваться некоторыми ботами, ищущими уязвимости веб-сервера.</p> <p>Вот, например, <a class="gblog-markdown__link" href="http://disorder.ru/archives/908" >http://disorder.ru/archives/908</a> — человек описал эксплоит для старых версий Nginx, а вот это:</p> <pre tabindex="0"><code>188.138.88.171 - - [19/Jan/2013:20:05:45 +0400] &#34;GET /w00tw00t.at.ISC.SANS.DFind:) HTTP/1.1&#34; 400 166 &#34;-&#34; &#34;-&#34; 50.63.136.60 - - [19/Jan/2013:20:32:27 +0400] &#34;GET /w00tw00t.at.ISC.SANS.Win32:) HTTP/1.1&#34; 400 166 &#34;-&#34; &#34;-&#34; </code></pre><p>явно адресовано IIS.</p> <p>Такие записи просто можно игнорировать. В случае особой настойчивости помогает способ с <code>ngx_http_limit_req_module</code>, описанный в предыдущем пункте.</p> Баним ботов. Часть 1 https://valmat.ru/posts/archive/2013/01/pma-bot-ban/ 2013-01-20T11:07:00+00:00 2013-01-20T11:07:00+00:00 <p>В один прекрасный день мне надоело видеть у себя в логах такое вот безобразие:</p> <pre tabindex="0"><code>113.204.67.51 - - [19/Jan/2013:07:22:08 +0400] &#34;GET /phpmyadmin/ HTTP/1.1&#34; 404 564 &#34;-&#34; &#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)&#34; 113.204.67.51 - - [19/Jan/2013:07:22:12 +0400] &#34;GET /PMA/ HTTP/1.1&#34; 404 564 &#34;-&#34; &#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)&#34; 113.204.67.51 - - [19/Jan/2013:07:22:17 +0400] &#34;GET /pma/ HTTP/1.1&#34; 404 564 &#34;-&#34; &#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)&#34; 113.204.67.51 - - [19/Jan/2013:07:22:17 +0400] &#34;GET /admin/ HTTP/1.1&#34; 404 564 &#34;-&#34; &#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)&#34; 113.204.67.51 - - [19/Jan/2013:07:22:18 +0400] &#34;GET /dbadmin/ HTTP/1.1&#34; 404 564 &#34;-&#34; &#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)&#34; 113.204.67.51 - - [19/Jan/2013:07:22:19 +0400] &#34;GET /sql/ HTTP/1.1&#34; 404 564 &#34;-&#34; &#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)&#34; 113.204.67.51 - - [19/Jan/2013:07:22:20 +0400] &#34;GET /mysql/ HTTP/1.1&#34; 404 564 &#34;-&#34; &#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)&#34; 113.204.67.51 - - [19/Jan/2013:07:22:20 +0400] &#34;GET /myadmin/ HTTP/1.1&#34; 404 564 &#34;-&#34; &#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)&#34; 113.204.67.51 - - [19/Jan/2013:07:22:21 +0400] &#34;GET /phpmyadmin2/ HTTP/1.1&#34; 404 564 &#34;-&#34; &#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)&#34; 113.204.67.51 - - [19/Jan/2013:07:22:22 +0400] &#34;GET /phpMyAdmin2/ HTTP/1.1&#34; 404 564 &#34;-&#34; &#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)&#34; 113.204.67.51 - - [19/Jan/2013:07:22:23 +0400] &#34;GET /phpMyAdmin-2/ HTTP/1.1&#34; 404 564 &#34;-&#34; &#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)&#34; 113.204.67.51 - - [19/Jan/2013:07:22:23 +0400] &#34;GET /php-my-admin/ HTTP/1.1&#34; 404 564 &#34;-&#34; &#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)&#34; 113.204.67.51 - - [19/Jan/2013:07:22:24 +0400] &#34;GET /sqlmanager/ HTTP/1.1&#34; 404 564 &#34;-&#34; &#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)&#34; 113.204.67.51 - - [19/Jan/2013:07:22:25 +0400] &#34;GET /mysqlmanager/ HTTP/1.1&#34; 404 564 &#34;-&#34; &#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)&#34; 113.204.67.51 - - [19/Jan/2013:07:22:26 +0400] &#34;GET /p/m/a/ HTTP/1.1&#34; 404 564 &#34;-&#34; &#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)&#34; 113.204.67.51 - - [19/Jan/2013:07:22:26 +0400] &#34;GET /php-myadmin/ HTTP/1.1&#34; 404 564 &#34;-&#34; &#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)&#34; 113.204.67.51 - - [19/Jan/2013:07:22:27 +0400] &#34;GET /phpmy-admin/ HTTP/1.1&#34; 404 564 &#34;-&#34; &#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)&#34; 113.204.67.51 - - [19/Jan/2013:07:22:28 +0400] &#34;GET /webadmin/ HTTP/1.1&#34; 404 564 &#34;-&#34; &#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)&#34; 113.204.67.51 - - [19/Jan/2013:07:22:29 +0400] &#34;GET /sqlweb/ HTTP/1.1&#34; 404 564 &#34;-&#34; &#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)&#34; 113.204.67.51 - - [19/Jan/2013:07:22:30 +0400] &#34;GET /websql/ HTTP/1.1&#34; 404 564 &#34;-&#34; &#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)&#34; 113.204.67.51 - - [19/Jan/2013:07:22:31 +0400] &#34;GET /webdb/ HTTP/1.1&#34; 404 564 &#34;-&#34; &#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)&#34; 113.204.67.51 - - [19/Jan/2013:07:22:31 +0400] &#34;GET /mysqladmin/ HTTP/1.1&#34; 404 564 &#34;-&#34; &#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)&#34; 113.204.67.51 - - [19/Jan/2013:07:22:32 +0400] &#34;GET /mysql-admin/ HTTP/1.1&#34; 404 564 &#34;-&#34; &#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)&#34; </code></pre><p>и решил я всех этих мерзких ботов забанить.</p> <p>Для чего был создан fail2ban-скрипт <code>phpmyadmin.conf</code> следующего содержания:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"> <span class="c1"># Fail2Ban configuration file </span> </span></span><span class="line"><span class="cl"> <span class="c1"># </span> </span></span><span class="line"><span class="cl"> <span class="c1"># Author: Valmat </span> </span></span><span class="line"><span class="cl"> <span class="c1">#</span> </span></span><span class="line"><span class="cl"> <span class="o">[</span>Definition<span class="o">]</span> </span></span><span class="line"><span class="cl"> <span class="nv">failregex</span> <span class="o">=</span> ^&lt;host&gt; - - <span class="se">\[</span>.*<span class="se">\]</span> <span class="s2">&#34;GET /(phpmyadmin|PMA|pma|admin|dbadmin|sql|mysql|myadmin|phpmyadmin2|phpMyAdmin2|phpMyAdmin-2|php-my-admin|sqlmanager|mysqlmanager|p/m/a|php-myadmin|phpmy-admin|webadmin|sqlweb|websql|webdb|mysqladmin|mysql-admin)/ HTTP/1.1&#34;</span> <span class="m">404</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="nv">ignoreregex</span> <span class="o">=</span> </span></span></code></pre></div><p>В <code>/etc/fail2ban/jail.conf</code> нужно добавить секцию:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="o">[</span>phpmyadmin<span class="o">]</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="nv">enabled</span> <span class="o">=</span> <span class="nb">true</span> </span></span><span class="line"><span class="cl"><span class="nv">port</span> <span class="o">=</span> http,https </span></span><span class="line"><span class="cl"><span class="nv">filter</span> <span class="o">=</span> phpmyadmin </span></span><span class="line"><span class="cl"><span class="nv">logpath</span> <span class="o">=</span> /var/log/nginx/localhost.access.log </span></span><span class="line"><span class="cl"><span class="nv">bantime</span> <span class="o">=</span> <span class="m">86400</span> </span></span><span class="line"><span class="cl"><span class="nv">maxretry</span> <span class="o">=</span> <span class="m">1</span> </span></span></code></pre></div><p>За основу для построения скрипта был взят список:</p> <pre tabindex="0"><code>phpmyadmin PMA pma admin dbadmin sql mysql myadmin phpmyadmin2 phpMyAdmin2 phpMyAdmin-2 php-my-admin sqlmanager mysqlmanager p/m/a php-myadmin phpmy-admin webadmin sqlweb websql webdb mysqladmin mysql-admin 2phpmyadmin MyAdmin admin/db admin/pMA admin/phpMyAdmin admin/phpmyadmin admin/sqladmin admin/sysadmin admin/web administrator/PMA administrator/admin administrator/db administrator/phpMyAdmin administrator/phpmyadmin administrator/pma administrator/web database db mysql/admin mysql/db mysql/dbadmin mysql/mysqlmanager mysql/pMA mysql/pma mysql/sqlmanager mysql/web phpMyAdmin phpMyadmin phpmy phpmyAdmin phppma program sql/myadmin sql/php-myadmin sql/phpMyAdmin sql/phpMyAdmin2 sql/phpmanager sql/phpmy-admin sql/phpmyadmin2 sql/sqladmin sql/sqlweb sql/webadmin sql/webdb sql/websql PMA2005 pma2005 phpmanager </code></pre><p>Учитывая логику работы ботов, то что в первую очередь они простукивают каталоги первого уровня, а лишь затем уровнем выше, этот список можно сократить до такого:</p> <pre tabindex="0"><code>phpmyadmin PMA pma admin dbadmin sql mysql myadmin phpmyadmin2 phpMyAdmin2 phpMyAdmin-2 php-my-admin sqlmanager mysqlmanager p/m/a php-myadmin phpmy-admin webadmin sqlweb websql webdb mysqladmin mysql-admin 2phpmyadmin MyAdmin PMA2005 administrator database db phpMyAdmin phpMyadmin phpmanager phpmy phpmyAdmin phppma pma2005 program </code></pre><p>В результате получается приведённый выше конфиг.</p> <p>Для проверки используем команду:</p> <pre tabindex="0"><code>fail2ban-regex &#39;113.204.67.51 - - [19/Jan/2013:07:22:25 +0400] &#34;GET /mysqlmanager/ HTTP/1.1&#34; 404 564 &#34;-&#34; &#34;Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)&#34;&#39; &#39;^&lt;host&gt; - - \[.*\] &#34;GET /(phpmyadmin|PMA|pma|admin|dbadmin|sql|mysql|myadmin|phpmyadmin2|phpMyAdmin2|phpMyAdmin-2|php-my-admin|sqlmanager|mysqlmanager|p/m/a|php-myadmin|phpmy-admin|webadmin|sqlweb|websql|webdb|mysqladmin|mysql-admin|2phpmyadmin|MyAdmin|PMA2005|administrator|database|db|phpMyAdmin|phpMyadmin|phpmanager|phpmy|phpmyAdmin|phppma|pma2005|program)/ HTTP/1.1&#34; 404&#39; </code></pre> Как в PHP узнать протокол (https) https://valmat.ru/posts/archive/2013/01/php-scheme-https/ 2013-01-17T16:07:00+00:00 2013-01-17T16:07:00+00:00 <p>Оказывается, узнать, что сайт использует <strong>SSL</strong> и страница открыта по протоколу <strong>https</strong> — не настолько тривиальная задача, чтобы решить её с наскока.<br> Однако, решение оказалось достаточно простое.</p> <p>Проблема заключается в том, что для определения протокола могут быть использованы переменные:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nv">$_SERVER</span><span class="p">[</span><span class="s1">&#39;HTTPS&#39;</span><span class="p">]</span> </span></span><span class="line"><span class="cl"><span class="nv">$_SERVER</span><span class="p">[</span><span class="s1">&#39;HTTP_SCHEME&#39;</span><span class="p">]</span> </span></span><span class="line"><span class="cl"><span class="nv">$_SERVER</span><span class="p">[</span><span class="s1">&#39;HTTP_X_FORWARDED_PROTO&#39;</span><span class="p">]</span> </span></span></code></pre></div><p>И косвенно:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nv">$_SERVER</span><span class="p">[</span><span class="s1">&#39;SERVER_PORT&#39;</span><span class="p">]</span> </span></span></code></pre></div><p>Но все эти переменные, кроме номера порта, почти наверняка будут отсутствовать.<br> Определять http-схему, основываясь только на номере порта — приемлемое, но не очень гибкое решение.</p> <p>Я сделал так:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nv">$scheme</span> <span class="o">=</span> <span class="nx">isset</span><span class="p">(</span><span class="nv">$_SERVER</span><span class="p">[</span><span class="s1">&#39;HTTP_SCHEME&#39;</span><span class="p">])</span> <span class="o">?</span> <span class="nv">$_SERVER</span><span class="p">[</span><span class="s1">&#39;HTTP_SCHEME&#39;</span><span class="p">]</span> <span class="o">:</span> <span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nx">isset</span><span class="p">(</span><span class="nv">$_SERVER</span><span class="p">[</span><span class="s1">&#39;HTTPS&#39;</span><span class="p">])</span> <span class="o">&amp;&amp;</span> <span class="nv">$_SERVER</span><span class="p">[</span><span class="s1">&#39;HTTPS&#39;</span><span class="p">]</span> <span class="o">!=</span> <span class="s1">&#39;off&#39;</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="o">||</span> <span class="mi">443</span> <span class="o">==</span> <span class="nv">$_SERVER</span><span class="p">[</span><span class="s1">&#39;SERVER_PORT&#39;</span><span class="p">]</span> </span></span><span class="line"><span class="cl"> <span class="p">)</span> <span class="o">?</span> <span class="s1">&#39;https&#39;</span> <span class="o">:</span> <span class="s1">&#39;http&#39;</span> </span></span><span class="line"><span class="cl"><span class="p">);</span> </span></span></code></pre></div><p>И для надёжности, чтобы <code>$_SERVER['HTTP_SCHEME']</code> была определена, в <code>nginx.conf</code> добавил строчку:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="c1"># for SSL </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">fastcgi_param</span> <span class="s">HTTP_SCHEME</span> <span class="nv">$scheme</span><span class="p">;</span> </span></span></code></pre></div> Мозаика из фотографий с помощью convert (ImageMagick) https://valmat.ru/posts/archive/2012/12/mosaic-convert-imagemagic/ 2012-12-06T21:56:00+00:00 2012-12-06T21:56:00+00:00 <p>Из фотографий или картинок одинакового размера можно сделать мозаику. Получается интересный эффект.</p> <p>Для этого я написал два bash-скрипта.</p> <p>Первый уменьшает размеры фотографий, второй делает из миниатюр мозаику.</p> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="скрипт-для-уменьшения-фотографий" > Скрипт для уменьшения фотографий </h3> <a data-clipboard-text="https://valmat.ru/posts/archive/2012/12/mosaic-convert-imagemagic/#скрипт-для-уменьшения-фотографий" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Скрипт для уменьшения фотографий" href="#%d1%81%d0%ba%d1%80%d0%b8%d0%bf%d1%82-%d0%b4%d0%bb%d1%8f-%d1%83%d0%bc%d0%b5%d0%bd%d1%8c%d1%88%d0%b5%d0%bd%d0%b8%d1%8f-%d1%84%d0%be%d1%82%d0%be%d0%b3%d1%80%d0%b0%d1%84%d0%b8%d0%b9"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/bash </span></span></span><span class="line"><span class="cl"><span class="cp"></span> </span></span><span class="line"><span class="cl"><span class="nv">SW</span><span class="o">=</span><span class="m">150</span> </span></span><span class="line"><span class="cl"><span class="nv">SH</span><span class="o">=</span><span class="m">150</span> </span></span><span class="line"><span class="cl"><span class="nv">FROMDIR</span><span class="o">=</span><span class="s2">&#34;fromdir&#34;</span> </span></span><span class="line"><span class="cl"><span class="nv">TODIR</span><span class="o">=</span><span class="s2">&#34;todir&#34;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">for</span> name in <span class="k">$(</span>ls <span class="nv">$FROMDIR</span><span class="k">)</span><span class="p">;</span> <span class="k">do</span> </span></span><span class="line"><span class="cl"> convert -resize <span class="si">${</span><span class="nv">SW</span><span class="si">}</span>x<span class="si">${</span><span class="nv">SH</span><span class="si">}</span> -strip <span class="nv">$FROMDIR</span>/<span class="nv">$name</span> <span class="nv">$TODIR</span>/<span class="nv">$name</span> </span></span><span class="line"><span class="cl"><span class="k">done</span> </span></span></code></pre></div><p>Здесь:</p> <ul> <li><code>SW</code> — ограничение ширины миниатюры;</li> <li><code>SH</code> — ограничение высоты миниатюры;</li> <li><code>FROMDIR</code> — каталог, в котором находятся фотографии;</li> <li><code>TODIR</code> — каталог, в который будут сложены миниатюры.</li> </ul> <hr> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="скрипт-для-создания-мозаики-из-миниатюр" > Скрипт для создания мозаики из миниатюр </h3> <a data-clipboard-text="https://valmat.ru/posts/archive/2012/12/mosaic-convert-imagemagic/#скрипт-для-создания-мозаики-из-миниатюр" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Скрипт для создания мозаики из миниатюр" href="#%d1%81%d0%ba%d1%80%d0%b8%d0%bf%d1%82-%d0%b4%d0%bb%d1%8f-%d1%81%d0%be%d0%b7%d0%b4%d0%b0%d0%bd%d0%b8%d1%8f-%d0%bc%d0%be%d0%b7%d0%b0%d0%b8%d0%ba%d0%b8-%d0%b8%d0%b7-%d0%bc%d0%b8%d0%bd%d0%b8%d0%b0%d1%82%d1%8e%d1%80"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/bash </span></span></span><span class="line"><span class="cl"><span class="cp"></span> </span></span><span class="line"><span class="cl"><span class="nv">SW</span><span class="o">=</span><span class="m">150</span> </span></span><span class="line"><span class="cl"><span class="nv">SH</span><span class="o">=</span><span class="m">112</span> </span></span><span class="line"><span class="cl"><span class="nv">COLS</span><span class="o">=</span><span class="m">15</span> </span></span><span class="line"><span class="cl"><span class="nv">REZFILE</span><span class="o">=</span><span class="s2">&#34;mosaic-`date &#34;</span>+%Y-%m-%d_%H_%M_%S<span class="s2">&#34;`.jpg&#34;</span> </span></span><span class="line"><span class="cl"><span class="nv">FDIR</span><span class="o">=</span><span class="s2">&#34;small&#34;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="o">(</span> </span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;convert </span><span class="k">$(</span><span class="nv">i</span><span class="o">=</span><span class="m">0</span> <span class="o">&amp;&amp;</span> <span class="k">for</span> name in <span class="k">$(</span>ls <span class="nv">$FDIR</span> <span class="p">|</span> sort -R<span class="k">)</span><span class="p">;</span> <span class="k">do</span> <span class="nb">echo</span> -n <span class="s2">&#34; -page +</span>$<span class="s2">[(</span><span class="nv">$i</span><span class="s2">%</span><span class="nv">$COLS</span><span class="s2">)*</span><span class="nv">$SW</span><span class="s2">]+</span>$<span class="s2">[((</span><span class="nv">$i</span><span class="s2">-</span><span class="nv">$i</span><span class="s2">%</span><span class="nv">$COLS</span><span class="s2">)/</span><span class="nv">$COLS</span><span class="s2">)*</span><span class="nv">$SH</span><span class="s2">] </span><span class="nv">$FDIR</span><span class="s2">/</span><span class="nv">$name</span><span class="s2">&#34;</span><span class="p">;</span> <span class="nv">i</span><span class="o">=</span>$<span class="o">[</span><span class="nv">$i</span>+1<span class="o">]</span><span class="p">;</span> <span class="k">done)</span><span class="s2"> -mosaic </span><span class="nv">$REZFILE</span><span class="s2">&#34;</span> </span></span><span class="line"><span class="cl"><span class="o">)</span> </span></span></code></pre></div><p>Здесь:</p> <ul> <li><code>SW</code> — ширина миниатюры;</li> <li><code>SH</code> — высота миниатюры;</li> <li><code>COLS</code> — количество столбцов;</li> <li><code>REZFILE</code> — имя файла с мозаикой;</li> <li><code>FDIR</code> — каталог с миниатюрами.</li> </ul> <hr> <blockquote> <p><strong>Важно:</strong> Все миниатюры должны быть одинакового размера, иначе будут пустоты.</p> </blockquote> <p>Чтобы мозаика повторяла пропорции исходных фотографий, должно быть:</p> <p><strong>SW == SH</strong></p> <p>Число фотографий должно равняться произведению:</p> <p><strong>SW * SH</strong></p> <p>В последнем скрипте используется <code>sort -R</code> для случайного упорядочивания. Если его убрать, будет тот порядок, в котором выдает <code>ls</code>.</p> <hr> <p>Вот пример мозаики из аватарок пользователей сайта:</p> <p><img src="./mosaic-xmpl.jpg" alt="Пример мозаики" /></p> Установка Redis via unix.socket https://valmat.ru/posts/archive/2011/12/redis-via-unixsocket/ 2011-12-03T07:05:00+00:00 2011-12-03T07:05:00+00:00 <div class="flex align-center gblog-post__anchorwrap"> <h2 id="о-том-как-установить-redis-в-качестве-сервера-на-linux-и-обращаться-к-нему-через-unixsocket" > О том, как установить Redis в качестве сервера на Linux и обращаться к нему через Unix.socket </h2> <a data-clipboard-text="https://valmat.ru/posts/archive/2011/12/redis-via-unixsocket/#о-том-как-установить-redis-в-качестве-сервера-на-linux-и-обращаться-к-нему-через-unixsocket" class="gblog-post__anchor clip flex align-center" aria-label="Anchor О том, как установить Redis в качестве сервера на Linux и обращаться к нему через Unix.socket" href="#%d0%be-%d1%82%d0%be%d0%bc-%d0%ba%d0%b0%d0%ba-%d1%83%d1%81%d1%82%d0%b0%d0%bd%d0%be%d0%b2%d0%b8%d1%82%d1%8c-redis-%d0%b2-%d0%ba%d0%b0%d1%87%d0%b5%d1%81%d1%82%d0%b2%d0%b5-%d1%81%d0%b5%d1%80%d0%b2%d0%b5%d1%80%d0%b0-%d0%bd%d0%b0-linux-%d0%b8-%d0%be%d0%b1%d1%80%d0%b0%d1%89%d0%b0%d1%82%d1%8c%d1%81%d1%8f-%d0%ba-%d0%bd%d0%b5%d0%bc%d1%83-%d1%87%d0%b5%d1%80%d0%b5%d0%b7-unixsocket"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>По мотивам куцей доки: <a class="gblog-markdown__link" href="http://redis.io/topics/quickstart" >http://redis.io/topics/quickstart</a> и <a class="gblog-markdown__link" href="http://redis.io/download" >http://redis.io/download</a></p> <p>От рута делаем:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkdir /usr/src/redis </span></span><span class="line"><span class="cl"><span class="nb">cd</span> /usr/src/redis </span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">wget http://redis.googlecode.com/files/redis-2.4.4.tar.gz </span></span><span class="line"><span class="cl">tar xzf redis-2.4.4.tar.gz </span></span><span class="line"><span class="cl"><span class="nb">cd</span> redis-2.4.4 </span></span><span class="line"><span class="cl">make <span class="o">&amp;&amp;</span> make <span class="nb">test</span> </span></span></code></pre></div><p>Если тесты прошли нормально (должно быть написано что-то вроде этого: &ldquo;\o/ All tests passed without errors!&rdquo;), то двигаемся дальше.</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mv ../redis-2.4.4.tar.gz ./redis-2.4.4.tar.gz </span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cp src/redis-server /usr/local/bin/ </span></span><span class="line"><span class="cl">cp src/redis-cli /usr/local/bin/ </span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkdir /etc/redis </span></span><span class="line"><span class="cl">mkdir /var/redis </span></span></code></pre></div><p>Далее в доке предлагается сделать <code>cp utils/redis_init_script /etc/init.d/redis_6379</code>, где 6379 — номер дефолтного порта, но я планирую, что Redis будет работать у меня через unix.socket, поэтому будет так (везде далее нолик появляется именно по этой же причине):</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cp utils/redis_init_script /etc/init.d/redis_0 </span></span></code></pre></div><p>Теперь нужно подредактировать конфиг:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">nano /etc/init.d/redis_0 </span></span></code></pre></div><p>Редактированию там подлежит только номер порта (6-я строка):</p> <pre tabindex="0"><code>REDISPORT=6379 --&gt; REDISPORT=0 </code></pre><p>Если номер порта не менять, то и редактировать ничего не нужно.</p> <p>Но в моем случае, поскольку я планирую запускать редис через unix socket, то нужно еще внести несколько изменений:</p> <ul> <li>Добавляем переменную: <pre tabindex="0"><code>UNIXSOCK=/tmp/redis.sock </code></pre></li> <li>Выражение <code>$CLIEXEC -p $REDISPORT shutdown</code> в секции &ldquo;stop&rdquo; заменяем на <code>$CLIEXEC -s $UNIXSOCK shutdown</code>.</li> </ul> <p>Вот что получилось:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/sh </span></span></span><span class="line"><span class="cl"><span class="cp"></span> </span></span><span class="line"><span class="cl"><span class="nv">REDISPORT</span><span class="o">=</span><span class="m">0</span> </span></span><span class="line"><span class="cl"><span class="c1">#REDISPORT=6379</span> </span></span><span class="line"><span class="cl"><span class="nv">UNIXSOCK</span><span class="o">=</span>/tmp/redis.sock </span></span><span class="line"><span class="cl"><span class="nv">OWNER</span><span class="o">=</span>nobody </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="nv">EXEC</span><span class="o">=</span>/usr/local/bin/redis-server </span></span><span class="line"><span class="cl"><span class="nv">CLIEXEC</span><span class="o">=</span>/usr/local/bin/redis-cli </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="nv">PIDFILE</span><span class="o">=</span>/var/run/redis_<span class="si">${</span><span class="nv">REDISPORT</span><span class="si">}</span>.pid </span></span><span class="line"><span class="cl"><span class="nv">CONF</span><span class="o">=</span><span class="s2">&#34;/etc/redis/</span><span class="si">${</span><span class="nv">REDISPORT</span><span class="si">}</span><span class="s2">.conf&#34;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">case</span> <span class="s2">&#34;</span><span class="nv">$1</span><span class="s2">&#34;</span> in </span></span><span class="line"><span class="cl"> start<span class="o">)</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="o">[</span> -f <span class="nv">$PIDFILE</span> <span class="o">]</span> </span></span><span class="line"><span class="cl"> <span class="k">then</span> </span></span><span class="line"><span class="cl"> <span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$PIDFILE</span><span class="s2"> exists, process is already running or crashed&#34;</span> </span></span><span class="line"><span class="cl"> <span class="k">else</span> </span></span><span class="line"><span class="cl"> <span class="nb">echo</span> <span class="s2">&#34;Starting Redis server...&#34;</span> </span></span><span class="line"><span class="cl"> <span class="nv">$EXEC</span> <span class="nv">$CONF</span> </span></span><span class="line"><span class="cl"> <span class="k">fi</span> </span></span><span class="line"><span class="cl"> <span class="p">;;</span> </span></span><span class="line"><span class="cl"> stop<span class="o">)</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="o">[</span> ! -f <span class="nv">$PIDFILE</span> <span class="o">]</span> </span></span><span class="line"><span class="cl"> <span class="k">then</span> </span></span><span class="line"><span class="cl"> <span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$PIDFILE</span><span class="s2"> does not exist, process is not running&#34;</span> </span></span><span class="line"><span class="cl"> <span class="k">else</span> </span></span><span class="line"><span class="cl"> <span class="nv">PID</span><span class="o">=</span><span class="k">$(</span>cat <span class="nv">$PIDFILE</span><span class="k">)</span> </span></span><span class="line"><span class="cl"> <span class="nb">echo</span> <span class="s2">&#34;Stopping ...&#34;</span> </span></span><span class="line"><span class="cl"> <span class="c1">#$CLIEXEC -p $REDISPORT shutdown</span> </span></span><span class="line"><span class="cl"> <span class="nv">$CLIEXEC</span> -s <span class="nv">$UNIXSOCK</span> shutdown </span></span><span class="line"><span class="cl"> <span class="k">while</span> <span class="o">[</span> -x /proc/<span class="si">${</span><span class="nv">PID</span><span class="si">}</span> <span class="o">]</span> </span></span><span class="line"><span class="cl"> <span class="k">do</span> </span></span><span class="line"><span class="cl"> <span class="nb">echo</span> <span class="s2">&#34;Waiting for Redis to shutdown .&#34;</span> </span></span><span class="line"><span class="cl"> sleep 0.5 </span></span><span class="line"><span class="cl"> <span class="nb">echo</span> -n <span class="s2">&#34;..&#34;</span> </span></span><span class="line"><span class="cl"> sleep 0.5 </span></span><span class="line"><span class="cl"> <span class="nb">echo</span> -n <span class="s2">&#34;..&#34;</span> </span></span><span class="line"><span class="cl"> <span class="k">done</span> </span></span><span class="line"><span class="cl"> <span class="nb">echo</span> <span class="s2">&#34;Redis stopped&#34;</span> </span></span><span class="line"><span class="cl"> <span class="k">fi</span> </span></span><span class="line"><span class="cl"> <span class="p">;;</span> </span></span><span class="line"><span class="cl"> restart<span class="o">)</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="o">[</span> ! -f <span class="nv">$PIDFILE</span> <span class="o">]</span> </span></span><span class="line"><span class="cl"> <span class="k">then</span> </span></span><span class="line"><span class="cl"> <span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$PIDFILE</span><span class="s2"> does not exist, process is not running&#34;</span> </span></span><span class="line"><span class="cl"> <span class="k">else</span> </span></span><span class="line"><span class="cl"> <span class="nv">PID</span><span class="o">=</span><span class="k">$(</span>cat <span class="nv">$PIDFILE</span><span class="k">)</span> </span></span><span class="line"><span class="cl"> <span class="nb">echo</span> <span class="s2">&#34;Stopping ...&#34;</span> </span></span><span class="line"><span class="cl"> <span class="c1">#$CLIEXEC -p $REDISPORT shutdown</span> </span></span><span class="line"><span class="cl"> <span class="nv">$CLIEXEC</span> -s <span class="nv">$UNIXSOCK</span> shutdown </span></span><span class="line"><span class="cl"> <span class="k">while</span> <span class="o">[</span> -x /proc/<span class="si">${</span><span class="nv">PID</span><span class="si">}</span> <span class="o">]</span> </span></span><span class="line"><span class="cl"> <span class="k">do</span> </span></span><span class="line"><span class="cl"> <span class="nb">echo</span> <span class="s2">&#34;Waiting for Redis to shutdown ...&#34;</span> </span></span><span class="line"><span class="cl"> sleep <span class="m">1</span> </span></span><span class="line"><span class="cl"> <span class="k">done</span> </span></span><span class="line"><span class="cl"> <span class="nb">echo</span> <span class="s2">&#34;Redis stopped&#34;</span> </span></span><span class="line"><span class="cl"> <span class="k">fi</span> </span></span><span class="line"><span class="cl"> <span class="nb">echo</span> <span class="s2">&#34;Starting Redis server...&#34;</span> </span></span><span class="line"><span class="cl"> <span class="nv">$EXEC</span> <span class="nv">$CONF</span> </span></span><span class="line"><span class="cl"> <span class="p">;;</span> </span></span><span class="line"><span class="cl"> *<span class="o">)</span> </span></span><span class="line"><span class="cl"> <span class="nb">echo</span> <span class="s2">&#34;Please use start or stop as first argument&#34;</span> </span></span><span class="line"><span class="cl"> <span class="p">;;</span> </span></span><span class="line"><span class="cl"><span class="k">esac</span> </span></span></code></pre></div><p>Далее нам нужно скопировать файл конфига:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cp redis.conf /etc/redis/0.conf </span></span></code></pre></div><p>И отредактировать его:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">nano /etc/redis/0.conf </span></span></code></pre></div><p>В нем меняем следующее:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="c1">#daemonize no</span> </span></span><span class="line"><span class="cl"><span class="na">daemonize yes</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c1">#pidfile /var/run/redis.pid</span> </span></span><span class="line"><span class="cl"><span class="na">pidfile /var/run/redis_0.pid</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c1">#port 6379</span> </span></span><span class="line"><span class="cl"><span class="na">port 0</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="na">bind 127.0.0.1</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="na">unixsocket /tmp/redis.sock</span> </span></span><span class="line"><span class="cl"><span class="na">unixsocketperm 755</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c1">#loglevel verbose</span> </span></span><span class="line"><span class="cl"><span class="na">loglevel warning</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c1">#logfile stdout</span> </span></span><span class="line"><span class="cl"><span class="na">logfile /var/log/redis_0.log</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c1">#databases 16</span> </span></span><span class="line"><span class="cl"><span class="na">databases 1</span> </span></span></code></pre></div><p>В секции &ldquo;SNAPSHOTTING&rdquo; можно поменять стратегию дампов. Я сделал так:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="na">save 54000 10</span> </span></span><span class="line"><span class="cl"><span class="na">save 3600 5000</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="na">dir /var/redis/dumps/</span> </span></span><span class="line"><span class="cl"><span class="na">dbfilename dump_0.rdb</span> </span></span></code></pre></div><p>Поскольку в сеть смотреть мой редис не будет, то репликацию я в нем отключил (секция &lsquo;REPLICATION&rsquo;):</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="c1">#slave-serve-stale-data yes</span> </span></span><span class="line"><span class="cl"><span class="na">slave-serve-stale-data no</span> </span></span></code></pre></div><p>Далее, поскольку, как и сказано в конфиге, я собираюсь использовать редис не в качестве основной БД, а в качестве кеша, то стоит установить maxmemory, чтобы редис ненароком не сожрал всю память:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="c1"># 256 MB</span> </span></span><span class="line"><span class="cl"><span class="na">maxmemory 268435456</span> </span></span></code></pre></div><p>Поскольку maxmemory установлен, то нужно установить и maxmemory-policy:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="c1"># maxmemory-policy volatile-lru</span> </span></span><span class="line"><span class="cl"><span class="na">maxmemory-policy volatile-ttl</span> </span></span></code></pre></div><p>Выбрал <code>volatile-ttl</code>, потому что не знаю, как работает алгоритм LRU.</p> <p>Отключаем appendfsync:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="na">appendfsync no</span> </span></span></code></pre></div><p>Все, на этом правки конфига закончены.</p> <p>Для логов мы указывали каталог <code>/var/redis/dumps</code>. Его нужно не забыть создать:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkdir /var/redis/dumps </span></span></code></pre></div><p>Проверяем, все ли работает. Проверить можно так:</p> <p>Запускаем:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">/etc/init.d/redis_0 start </span></span></code></pre></div><p>Потом:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">redis-cli -s /tmp/redis.sock </span></span></code></pre></div><p>В консоли redis:</p> <pre tabindex="0"><code>SET key1 &#34;Test&#34; OK GET key1 &#34;Test&#34; </code></pre><p>Если все нормально, то добавляем в автозагрузку:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">update-rc.d redis_0 defaults </span></span></code></pre></div><hr> <p><strong>PS</strong><br> В логах редиса он сообщил мне следующее предупреждение:</p> <pre tabindex="0"><code>WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add &#39;vm.overcommit_memory = 1&#39; to /etc/sysctl.conf and then reboot or run the command &#39;sysctl vm.overcommit_memory=1&#39; for this to take effect. </code></pre><p>Поскольку я готов мириться с тем, что он не будет дампить себя на диск, то это предупреждение проигнорирую. А вообще решение вижу таким:</p> <p>В <code>/etc/sysctl.conf</code> ничего, естественно, не вносим, но в <code>/etc/init.d/redis_0</code><br> В секции старт, перед запуском редиса, сохраняем системное значение overcommit_memory:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">touch /tmp/overcommit_memory_bfr_redis </span></span><span class="line"><span class="cl">chmod <span class="m">0600</span> /tmp/overcommit_memory_bfr_redis </span></span><span class="line"><span class="cl">cat /proc/sys/vm/overcommit_memory &gt; /tmp/overcommit_memory_bfr_redis </span></span><span class="line"><span class="cl">sysctl vm.overcommit_memory<span class="o">=</span><span class="m">1</span> </span></span></code></pre></div><p>А в секцию стоп возвращаем системное значение:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">OCMSYS</span><span class="o">=</span><span class="k">$(</span>cat /tmp/overcommit_memory_bfr_redis<span class="k">)</span> </span></span><span class="line"><span class="cl">sysctl vm.overcommit_memory<span class="o">=</span><span class="nv">$OCMSYS</span> </span></span></code></pre></div><p>Примерно так:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/sh </span></span></span><span class="line"><span class="cl"><span class="cp"></span> </span></span><span class="line"><span class="cl"><span class="nv">REDISPORT</span><span class="o">=</span><span class="m">0</span> </span></span><span class="line"><span class="cl"><span class="c1">#REDISPORT=6379</span> </span></span><span class="line"><span class="cl"><span class="nv">UNIXSOCK</span><span class="o">=</span>/tmp/redis.sock </span></span><span class="line"><span class="cl"><span class="nv">OWNER</span><span class="o">=</span>nobody </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="nv">EXEC</span><span class="o">=</span>/usr/local/bin/redis-server </span></span><span class="line"><span class="cl"><span class="nv">CLIEXEC</span><span class="o">=</span>/usr/local/bin/redis-cli </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="nv">PIDFILE</span><span class="o">=</span>/var/run/redis_<span class="si">${</span><span class="nv">REDISPORT</span><span class="si">}</span>.pid </span></span><span class="line"><span class="cl"><span class="nv">CONF</span><span class="o">=</span><span class="s2">&#34;/etc/redis/</span><span class="si">${</span><span class="nv">REDISPORT</span><span class="si">}</span><span class="s2">.conf&#34;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c1">#fix WARNING about overcommit_memory</span> </span></span><span class="line"><span class="cl"><span class="nv">FWOBOM</span><span class="o">=</span>FALSE </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">case</span> <span class="s2">&#34;</span><span class="nv">$1</span><span class="s2">&#34;</span> in </span></span><span class="line"><span class="cl"> start<span class="o">)</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="o">[</span> -f <span class="nv">$PIDFILE</span> <span class="o">]</span> </span></span><span class="line"><span class="cl"> <span class="k">then</span> </span></span><span class="line"><span class="cl"> <span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$PIDFILE</span><span class="s2"> exists, process is already running or crashed&#34;</span> </span></span><span class="line"><span class="cl"> <span class="k">else</span> </span></span><span class="line"><span class="cl"> <span class="nb">echo</span> <span class="s2">&#34;Starting Redis server...&#34;</span> </span></span><span class="line"><span class="cl"> <span class="c1"># -- fix WARNING about overcommit_memory</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="o">[</span> <span class="s2">&#34;TRUE&#34;</span> <span class="o">=</span> <span class="nv">$FWOBOM</span> <span class="o">]</span> </span></span><span class="line"><span class="cl"> <span class="k">then</span> </span></span><span class="line"><span class="cl"> touch /tmp/overcommit_memory_bfr_redis </span></span><span class="line"><span class="cl"> chmod <span class="m">0600</span> /tmp/overcommit_memory_bfr_redis </span></span><span class="line"><span class="cl"> cat /proc/sys/vm/overcommit_memory &gt; /tmp/overcommit_memory_bfr_redis </span></span><span class="line"><span class="cl"> sysctl vm.overcommit_memory<span class="o">=</span><span class="m">1</span> </span></span><span class="line"><span class="cl"> <span class="k">fi</span> </span></span><span class="line"><span class="cl"> <span class="c1"># &lt;--</span> </span></span><span class="line"><span class="cl"> <span class="nv">$EXEC</span> <span class="nv">$CONF</span> </span></span><span class="line"><span class="cl"> <span class="k">fi</span> </span></span><span class="line"><span class="cl"> <span class="p">;;</span> </span></span><span class="line"><span class="cl"> stop<span class="o">)</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="o">[</span> ! -f <span class="nv">$PIDFILE</span> <span class="o">]</span> </span></span><span class="line"><span class="cl"> <span class="k">then</span> </span></span><span class="line"><span class="cl"> <span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$PIDFILE</span><span class="s2"> does not exist, process is not running&#34;</span> </span></span><span class="line"><span class="cl"> <span class="k">else</span> </span></span><span class="line"><span class="cl"> <span class="nv">PID</span><span class="o">=</span><span class="k">$(</span>cat <span class="nv">$PIDFILE</span><span class="k">)</span> </span></span><span class="line"><span class="cl"> <span class="nb">echo</span> <span class="s2">&#34;Stopping ...&#34;</span> </span></span><span class="line"><span class="cl"> <span class="c1">#$CLIEXEC -p $REDISPORT shutdown</span> </span></span><span class="line"><span class="cl"> <span class="nv">$CLIEXEC</span> -s <span class="nv">$UNIXSOCK</span> shutdown </span></span><span class="line"><span class="cl"> <span class="k">while</span> <span class="o">[</span> -x /proc/<span class="si">${</span><span class="nv">PID</span><span class="si">}</span> <span class="o">]</span> </span></span><span class="line"><span class="cl"> <span class="k">do</span> </span></span><span class="line"><span class="cl"> <span class="nb">echo</span> <span class="s2">&#34;Waiting for Redis to shutdown .&#34;</span> </span></span><span class="line"><span class="cl"> sleep 0.5 </span></span><span class="line"><span class="cl"> <span class="nb">echo</span> -n <span class="s2">&#34;..&#34;</span> </span></span><span class="line"><span class="cl"> sleep 0.5 </span></span><span class="line"><span class="cl"> <span class="nb">echo</span> -n <span class="s2">&#34;..&#34;</span> </span></span><span class="line"><span class="cl"> <span class="k">done</span> </span></span><span class="line"><span class="cl"> <span class="c1"># -- fix WARNING about overcommit_memory</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="o">[</span> <span class="s2">&#34;TRUE&#34;</span> <span class="o">=</span> <span class="nv">$FWOBOM</span> <span class="o">]</span> </span></span><span class="line"><span class="cl"> <span class="k">then</span> </span></span><span class="line"><span class="cl"> <span class="nv">OCMSYS</span><span class="o">=</span><span class="k">$(</span>cat /tmp/overcommit_memory_bfr_redis<span class="k">)</span> </span></span><span class="line"><span class="cl"> sysctl vm.overcommit_memory<span class="o">=</span><span class="nv">$OCMSYS</span> </span></span><span class="line"><span class="cl"> <span class="k">fi</span> </span></span><span class="line"><span class="cl"> <span class="c1"># &lt;--</span> </span></span><span class="line"><span class="cl"> <span class="nb">echo</span> <span class="s2">&#34;Redis stopped&#34;</span> </span></span><span class="line"><span class="cl"> <span class="k">fi</span> </span></span><span class="line"><span class="cl"> <span class="p">;;</span> </span></span><span class="line"><span class="cl"> restart<span class="o">)</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="o">[</span> ! -f <span class="nv">$PIDFILE</span> <span class="o">]</span> </span></span><span class="line"><span class="cl"> <span class="k">then</span> </span></span><span class="line"><span class="cl"> <span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$PIDFILE</span><span class="s2"> does not exist, process is not running&#34;</span> </span></span><span class="line"><span class="cl"> <span class="k">else</span> </span></span><span class="line"><span class="cl"> <span class="nv">PID</span><span class="o">=</span><span class="k">$(</span>cat <span class="nv">$PIDFILE</span><span class="k">)</span> </span></span><span class="line"><span class="cl"> <span class="nb">echo</span> <span class="s2">&#34;Stopping ...&#34;</span> </span></span><span class="line"><span class="cl"> <span class="c1">#$CLIEXEC -p $REDISPORT shutdown</span> </span></span><span class="line"><span class="cl"> <span class="nv">$CLIEXEC</span> -s <span class="nv">$UNIXSOCK</span> shutdown </span></span><span class="line"><span class="cl"> <span class="k">while</span> <span class="o">[</span> -x /proc/<span class="si">${</span><span class="nv">PID</span><span class="si">}</span> <span class="o">]</span> </span></span><span class="line"><span class="cl"> <span class="k">do</span> </span></span><span class="line"><span class="cl"> <span class="nb">echo</span> <span class="s2">&#34;Waiting for Redis to shutdown ...&#34;</span> </span></span><span class="line"><span class="cl"> sleep <span class="m">1</span> </span></span><span class="line"><span class="cl"> <span class="k">done</span> </span></span><span class="line"><span class="cl"> <span class="nb">echo</span> <span class="s2">&#34;Redis stopped&#34;</span> </span></span><span class="line"><span class="cl"> <span class="k">fi</span> </span></span><span class="line"><span class="cl"> <span class="nb">echo</span> <span class="s2">&#34;Starting Redis server...&#34;</span> </span></span><span class="line"><span class="cl"> <span class="c1"># -- fix WARNING about overcommit_memory</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="o">[</span> <span class="s2">&#34;TRUE&#34;</span> <span class="o">=</span> <span class="nv">$FWOBOM</span> <span class="o">]</span> </span></span><span class="line"><span class="cl"> <span class="k">then</span> </span></span><span class="line"><span class="cl"> touch /tmp/overcommit_memory_bfr_redis </span></span><span class="line"><span class="cl"> chmod <span class="m">0600</span> /tmp/overcommit_memory_bfr_redis </span></span><span class="line"><span class="cl"> cat /proc/sys/vm/overcommit_memory &gt; /tmp/overcommit_memory_bfr_redis </span></span><span class="line"><span class="cl"> sysctl vm.overcommit_memory<span class="o">=</span><span class="m">1</span> </span></span><span class="line"><span class="cl"> <span class="k">fi</span> </span></span><span class="line"><span class="cl"> <span class="c1"># &lt;--</span> </span></span><span class="line"><span class="cl"> <span class="nv">$EXEC</span> <span class="nv">$CONF</span> </span></span><span class="line"><span class="cl"> <span class="p">;;</span> </span></span><span class="line"><span class="cl"> *<span class="o">)</span> </span></span><span class="line"><span class="cl"> <span class="nb">echo</span> <span class="s2">&#34;Please use start or stop as first argument&#34;</span> </span></span><span class="line"><span class="cl"> <span class="p">;;</span> </span></span><span class="line"><span class="cl"><span class="k">esac</span> </span></span></code></pre></div><hr> <p><strong>PPS</strong></p> <ul> <li> <p>overcommit_memory влияет на выделение памяти ядром и на работу OOM Killer.<br> <code>vm.overcommit_memory=0</code> — более безопасный вариант, т.к. кто его знает, кого грохнет OOM Killer, если память кончится.</p> </li> <li> <p>Если tcp сокет устраивает, а нужно только (возможно задать порт), то в каталоге utils с исходниками есть скрипт <code>install_server.sh</code>, запуск которого сделает большую часть грязной работы, описанной выше.</p> </li> <li> <p>По поводу maxmemory-policy volatile-lru vs volatile-ttl: <a class="gblog-markdown__link" href="http://habrahabr.ru/blogs/development/136758/" >статья на хабре про LRU</a></p> </li> </ul> Библиотека для загрузки фотографий на сайт https://valmat.ru/posts/archive/2011/10/transimage-php/ 2011-10-01T11:24:00+00:00 2011-10-01T11:24:00+00:00 <p><strong>transImage</strong> — это <em>PHP библиотека для простой загрузки фотографий на сайт</em>.</p> <p>Она умеет:</p> <ul> <li>Получать изображение из файла, автоматически нормализовать его размер для экономии памяти</li> <li>Автоматически поворачивать исходное изображение по данным Exif</li> <li>Создавать копии себя с изменёнными размерами, изменять свой размер</li> <li>Наносить водяные знаки. Поддерживает любые водяные знаки, соответствующие интерфейсу <code>waterMark</code> (см. код)</li> <li>Выводить результат клиенту или сохранять его в файловую систему</li> <li>Подкладывать белый фон, если исходное изображение поддерживает прозрачность</li> <li>Быстро создавать миниатюру для предпросмотра, используя миниатюру из Exif</li> <li>Преобразовывать изображения в строку для использования их с <code>data:URI</code>. В этом случае небольшие изображения можно передавать с другими параметрами в формате JSON (при использовании Ajax)</li> </ul> <p>Для более подробной информации смотрите комментарии в коде.</p> <p>Подходит большинству сайтов, которым нужно получить от клиента фотографию, правильно преобразовать её в соответствии с Exif, создать несколько вариантов с разными размерами и, возможно, нанести водяной знак.</p> <p><strong>Не рекомендую использовать её для выдачи клиенту (браузеру) изображений больших размеров.</strong> Эта функция несёт исключительно демонстративную нагрузку.</p> <p><strong>Лицензия:</strong> BSD.</p> <p><strong>GitHub repo:</strong></p> <p><img src="./ghlogo.png" alt="GitHub Logo" /></p> <p><a class="gblog-markdown__link" href="https://github.com/valmat/transImage" >https://github.com/valmat/transImage</a></p> <p><img src="./transImage_640x480.png" alt="Пример работы transImage" /></p> Замена салонного фильтра на Fiat Albea https://valmat.ru/posts/archive/2011/09/fiat-albea-cabin-filter/ 2011-09-02T17:23:00+00:00 2011-09-02T17:23:00+00:00 <p>Для замены салонного фильтра понадобятся:</p> <ul> <li>фильтр</li> <li>крестовая отвертка</li> <li>фонарик</li> <li>тряпка под коленки</li> <li>хорошая погода</li> </ul> <p>Операция занимает 15 минут. Перед входом со стороны переднего пассажира лучше бросить тряпку, потому что, чтобы добраться до фильтра, придётся встать на колени и залезть под бардачок.</p> <p><img src="./1.jpg_new.jpg" alt="Салон Fiat Albea, вид на место установки фильтра" /></p> <p><img src="./2.jpg_new.jpg" alt="Вид под бардачком, где расположен салонный фильтр" /></p> <p>Фильтр находится под бардачком.</p> <p><img src="./3.jpg_new.jpg" alt="Расположение фильтра крупным планом" /></p> <p><img src="./4.jpg_new.jpg" alt="Крышка фильтра" /></p> <p>Крепится двумя шурупами.</p> <p><img src="./5.jpg_new.jpg" alt="Откручивание креплений" /></p> <p><img src="./6.jpg_new.jpg" alt="Снятие крышки фильтра" /></p> <p><img src="./7.jpg_new.jpg" alt="Вид на фильтр после снятия крышки" /></p> <p>Следующие две фотографии должны сподвигнуть увидевших их на срочную замену фильтра в своей машине. Я не менял фильтр два года.</p> <p><img src="./8.jpg_new.jpg" alt="Старый фильтр, вид 1" /></p> <p><img src="./9.jpg_new.jpg" alt="Старый фильтр, вид 2" /></p> <p>Я поставил себе угольный фильтр. Его код: <strong>9.7.541</strong> (260 руб. на экзисте).</p> <p>Можно не извращаться и поставить обычный — будет дешевле. Код обычного фильтра: <strong>9.7.540</strong> (150 руб. на экзисте).</p> Установка ветровиков на Fiat Albea https://valmat.ru/posts/archive/2011/09/fiat-albea/ 2011-09-02T14:58:00+00:00 2011-09-02T14:58:00+00:00 <p>Наконец-то установил ветровики на свой Фиат.</p> <p>Вот что получилось:</p> <p><img src="./02092011133.jpg" alt="Fiat Albea с ветровиками, фото 1" /></p> <p><img src="./02092011134.jpg" alt="Fiat Albea с ветровиками, фото 2" /></p> <p><img src="./02092011135.jpg" alt="Fiat Albea с ветровиками, фото 3" /></p> <p><img src="./02092011136.jpg" alt="Fiat Albea с ветровиками, фото 4" /></p> <p><img src="./02092011137.jpg" alt="Fiat Albea с ветровиками, фото 5 (вертикальное фото)" /></p> <p><img src="./02092011138.jpg" alt="Fiat Albea с ветровиками, фото 6" /></p> <p><img src="./02092011139.jpg" alt="Fiat Albea с ветровиками, фото 7" /></p> <p><img src="./02092011140.jpg" alt="Fiat Albea с ветровиками, фото 8" /></p> <p><img src="./02092011142.jpg" alt="Fiat Albea с ветровиками, фото 9" /></p> <p><img src="./02092011143.jpg" alt="Fiat Albea с ветровиками, фото 10" /></p> <p>Код дефлекторов: <strong>D07061</strong></p> Рыбацкие снасти из Китая https://valmat.ru/posts/archive/2011/04/china-fishing-tackle/ 2011-04-11T16:22:00+00:00 2011-04-11T16:22:00+00:00 <p>Сегодня наконец-то пришла моя рыбацкая посылка из Гонконга.</p> <p><img src="./0.jpg" alt="Посылка из Гонконга" /></p> <p>А именно, сделал небольшой пробный заказ на китайском сайте <a class="gblog-markdown__link" href="http://www.focalprice.com/detail_HL568X.html" >focalprice.com</a>.<br> До этого заказывал только на dealextreme.com.</p> <p>Заказывал:</p> <ol> <li><a class="gblog-markdown__link" href="http://www.focalprice.com/detail_HL568X.html" >Воблер</a></li> <li><a class="gblog-markdown__link" href="http://www.focalprice.com/detail_HL555X.html" >Набор воблеров</a></li> <li><a class="gblog-markdown__link" href="http://www.focalprice.com/detail_HL289X.html" >Набор виброхвостов</a></li> <li><a class="gblog-markdown__link" href="http://www.focalprice.com/detail_HL332G.html" >Доставалку крючка из щучьей пасти</a></li> </ol> <p>Шло около месяца, хотя я надеялся, что придет быстрее. Рыболовный сезон откроется не скоро, но тем не менее стоит учитывать. Отправили на следующий день после заказа — это, безусловно, плюс. В делекстриме бывает, что по две недели ждёшь отправки.</p> <p>Качество товара вполне ожидаемое. Примерно то же самое продаётся у нас в 90% рыбацких магазинов, но за большие деньги.</p> <p>Воблер пришёл другой расцветки. Я заказывал жёлтого цвета, пришёл серебристый с красной башкой:</p> <p><img src="./HL568X.JPG" alt="Воблер — пришёл другой расцветки" /></p> <p>Виброхвосты, я ожидал, должны были быть побольше. Но тут уж сам виноват — на сайте размеры указаны. Хотя, впрочем, я не расстроился. Маленькую рыбу тоже нужно чем-то ловить.</p> <p><img src="./6.jpg" alt="Виброхвосты" /></p> <p>Джиги, как видно, тоже маленькие — грамм по пять.</p> <p><img src="./2.jpg" alt="Джиги" /> <img src="./1.jpg" alt="Джиги, вид 2" /></p> <p><img src="./4.jpg" alt="Джиги, вид 3" /> <img src="./3.jpg" alt="Джиги, вид 4" /></p> <p><img src="./7.jpg" alt="Джиги, вид 5" /> <img src="./5.jpg" alt="Джиги, вид 6" /></p> <p>Также обратил внимание, что посылка почему-то шла через Брянск, и на Брянской таможне её вскрывали:</p> <p><img src="./10.JPG" alt="Посылка вскрыта на таможне" /> <img src="./9.jpg" alt="Маркировка после вскрытия" /></p> php Cacher https://valmat.ru/posts/archive/2010/10/php-cacher/ 2010-10-30T10:48:00+00:00 2010-10-30T10:48:00+00:00 <p>Опубликовал набор классов для кеширования:<br> <a class="gblog-markdown__link" href="http://github.com/valmat/Cacher" >http://github.com/valmat/Cacher</a><br> Опубликовано под лицензией<br> GPL v.3 (<a class="gblog-markdown__link" href="http://www.gnu.org/licenses/gpl.txt" >http://www.gnu.org/licenses/gpl.txt</a>)<br> То есть свободно для использования и изменения. Разумеется, приветствуются любые исправления и дополнения.</p> <div class="flex align-center gblog-post__anchorwrap"> <h2 id="назначение" > Назначение </h2> <a data-clipboard-text="https://valmat.ru/posts/archive/2010/10/php-cacher/#назначение" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Назначение" href="#%d0%bd%d0%b0%d0%b7%d0%bd%d0%b0%d1%87%d0%b5%d0%bd%d0%b8%d0%b5"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Мне нужен был очень простой, но в то же время мощный кеширующий модуль с понятной логикой. При этом он должен быть пригодным для работы на односерверном, но в то же время нагруженном проекте.</p> <p>Также немаловажным моментом является возможность прозрачно менять стратегию кеширования в зависимости от текущего уровня нагрузки проекта.</p> <p>То есть решение должно удовлетворять требованию изменять стратегию кеширования по мере роста нагрузки на проект и по мере изменения аппаратных возможностей (речь об ОЗУ) сервера.</p> <div class="flex align-center gblog-post__anchorwrap"> <h2 id="архитектура" > Архитектура </h2> <a data-clipboard-text="https://valmat.ru/posts/archive/2010/10/php-cacher/#архитектура" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Архитектура" href="#%d0%b0%d1%80%d1%85%d0%b8%d1%82%d0%b5%d0%ba%d1%82%d1%83%d1%80%d0%b0"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Основными логическими единицами являются:</p> <ul> <li><strong>Cacher</strong> — фронтенд к кеширующим классам.</li> <li><strong>Cacher_Backend</strong> — собственно сами кеширующие классы.</li> <li><strong>Слоты</strong> — кеширование и доступ к кешу осуществляется через слоты.</li> <li><strong>Теги</strong> — для упрощения управления кешем и, главным образом, для переуеширования.</li> <li><strong>Типы кеширования</strong> — для прозрачного изменения стратегии кеширования. То есть конкретный кеширующий бекенд подключается только через слот (или тег), которые, в свою очередь, оперируют типами. Таким образом, для изменения стратегии кеширования нужно всего лишь поменять привязку типов к бекендам.</li> </ul> <div class="flex align-center gblog-post__anchorwrap"> <h2 id="использование" > Использование </h2> <a data-clipboard-text="https://valmat.ru/posts/archive/2010/10/php-cacher/#использование" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Использование" href="#%d0%b8%d1%81%d0%bf%d0%be%d0%bb%d1%8c%d0%b7%d0%be%d0%b2%d0%b0%d0%bd%d0%b8%d0%b5"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="class-cacher" > class Cacher </h3> <a data-clipboard-text="https://valmat.ru/posts/archive/2010/10/php-cacher/#class-cacher" class="gblog-post__anchor clip flex align-center" aria-label="Anchor class Cacher" href="#class-cacher"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Требует наличия классов, унаследованных от Cacher_Backend — семейство классов, реализующих бэкэнд для класса Cacher.</p> <p>Все операции с кешем осуществляются на низшем уровне через тот или иной бекенд.</p> <p>Бэкэндом может быть файловая система, shared memory, memcache, Sqlite и другие системы кеширования.</p> <p>Для работы с классом используются слоты и теги. Слоты реализованы в виде набора дружественных функций и неявно зашиты в интерфейс текущего класса.</p> <div class="flex align-center gblog-post__anchorwrap"> <h4 id="пример-использования" > Пример использования </h4> <a data-clipboard-text="https://valmat.ru/posts/archive/2010/10/php-cacher/#пример-использования" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Пример использования" href="#%d0%bf%d1%80%d0%b8%d0%bc%d0%b5%d1%80-%d0%b8%d1%81%d0%bf%d0%be%d0%bb%d1%8c%d0%b7%d0%be%d0%b2%d0%b0%d0%bd%d0%b8%d1%8f"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nx">define</span> <span class="nx">AnyObj</span> <span class="c1">// может быть класс, массив или другой объект. </span></span></span><span class="line"><span class="cl"><span class="c1">// На основании этого объекта слот-функция вычислит ключ и, возможно, другие параметры (бэкэнд и время жизни). </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">Cacher</span><span class="o">::</span><span class="na">Slot</span><span class="p">(</span><span class="s1">&#39;AniObj&#39;</span><span class="p">,</span> <span class="nx">AniObj</span><span class="p">);</span> <span class="c1">// Инициализируем слот кеширования. Первый параметр — имя слота, второй — наш объект </span></span></span><span class="line"><span class="cl"><span class="c1"></span> </span></span><span class="line"><span class="cl"><span class="c1">// Получаем данные </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">if</span> <span class="p">(</span><span class="k">false</span> <span class="o">===</span> <span class="p">(</span><span class="nv">$CacheData</span> <span class="o">=</span> <span class="nx">Cacher</span><span class="o">::</span><span class="na">get</span><span class="p">()))</span> <span class="p">{</span> <span class="c1">// Если данные из кеша получить не удалось... </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="nv">$CacheData</span> <span class="o">=</span> <span class="nx">GetFromAnyExternal</span><span class="p">();</span> <span class="c1">// Получаем данные из внешнего хранилища </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="nx">Cacher</span><span class="o">::</span><span class="na">addTag</span><span class="p">(</span><span class="nx">Cacher</span><span class="o">::</span><span class="na">newTag</span><span class="p">(</span><span class="s1">&#39;AniTagData&#39;</span><span class="p">,</span> <span class="nx">AniTagDataObj</span><span class="p">));</span> <span class="c1">// Создаем и сразу же добавляем новый тег к слоту перед сохранением в кеш </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="nv">$tag2</span> <span class="o">=</span> <span class="nx">Cacher</span><span class="o">::</span><span class="na">newTag</span><span class="p">(</span><span class="s1">&#39;AniTagData2&#39;</span><span class="p">,</span> <span class="nx">AniTagDataObj1</span><span class="p">);</span> <span class="c1">// Создаем новый тег </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="nx">Cacher</span><span class="o">::</span><span class="na">addTag</span><span class="p">(</span><span class="nv">$tag2</span><span class="p">);</span> <span class="c1">// Добавляем новый тег к слоту перед сохранением в кеш </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="nx">Cacher</span><span class="o">::</span><span class="na">set</span><span class="p">(</span><span class="nv">$CacheData</span><span class="p">);</span> <span class="c1">// Кешируем данные </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="c1">// ... </span></span></span><span class="line"><span class="cl"><span class="c1">// Если затем нужно сбросить какой-нибудь тег, то нужно будет сделать так: </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">Cacher</span><span class="o">::</span><span class="na">newTag</span><span class="p">(</span><span class="s1">&#39;AniTagData2&#39;</span><span class="p">,</span> <span class="nx">AniTagDataObj1</span><span class="p">)</span><span class="o">-&gt;</span><span class="na">clear</span><span class="p">();</span> <span class="c1">// Очищаем кеш тега </span></span></span></code></pre></div> define vs const в PHP https://valmat.ru/posts/archive/2010/10/define-vs-const-php/ 2010-10-28T19:00:00+00:00 2010-10-28T19:00:00+00:00 <p>Как известно, при разработке крупных веб-приложений помимо архитектуры постоянно приходится задумываться также и о производительности. Этим постом я хотел бы открыть серию публикаций по тестированию PHP на производительность.</p> <p>Речь пойдет о сравнении способов хранения констант в приложении на PHP.<br> А именно сравниваются два подхода:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nx">define</span><span class="p">(</span><span class="s1">&#39;CONST1&#39;</span><span class="p">,</span> <span class="s1">&#39;val11&#39;</span><span class="p">);</span> </span></span><span class="line"><span class="cl"><span class="nx">define</span><span class="p">(</span><span class="s1">&#39;CONST2&#39;</span><span class="p">,</span> <span class="s1">&#39;val12&#39;</span><span class="p">);</span> </span></span><span class="line"><span class="cl"><span class="nx">define</span><span class="p">(</span><span class="s1">&#39;CONST2&#39;</span><span class="p">,</span> <span class="s1">&#39;val13&#39;</span><span class="p">);</span> </span></span></code></pre></div><p>и</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Consts</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">const</span> <span class="no">CONST1</span> <span class="o">=</span> <span class="s1">&#39;val1&#39;</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="k">const</span> <span class="no">CONST2</span> <span class="o">=</span> <span class="s1">&#39;val2&#39;</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="k">const</span> <span class="no">CONST3</span> <span class="o">=</span> <span class="s1">&#39;val3&#39;</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>В первом случае, вроде бы как должна использоваться специальная область памяти, и такой способ уж если и не экономит память, так точно должен быть быстрее. Второй способ в некоторых случаях существенно удобнее, так как позволяет не захламлять глобальную область видимости.</p> <p>В общем, чтобы не гадать, я провел тесты.</p> <div class="flex align-center gblog-post__anchorwrap"> <h2 id="тест-1-инициализация" > Тест 1. Инициализация </h2> <a data-clipboard-text="https://valmat.ru/posts/archive/2010/10/define-vs-const-php/#тест-1-инициализация" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Тест 1. Инициализация" href="#%d1%82%d0%b5%d1%81%d1%82-1-%d0%b8%d0%bd%d0%b8%d1%86%d0%b8%d0%b0%d0%bb%d0%b8%d0%b7%d0%b0%d1%86%d0%b8%d1%8f"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Инициализируем 100 констант при помощи <strong>define</strong>:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nx">define</span><span class="p">(</span><span class="s1">&#39;CACHER_TYPE_1&#39;</span><span class="p">,</span> <span class="s1">&#39;b60861c4492f88589429aab0c67abdd4&#39;</span><span class="p">);</span> </span></span><span class="line"><span class="cl"><span class="cm">/* ... */</span> </span></span><span class="line"><span class="cl"><span class="nx">define</span><span class="p">(</span><span class="s1">&#39;CACHER_TYPE_100&#39;</span><span class="p">,</span> <span class="s1">&#39;a66aedeafbc3f1e9fcbaa6a9e8060739&#39;</span><span class="p">);</span> </span></span></code></pre></div><ul> <li>memory_start: 114.7578125 Кб</li> <li>time: <strong>0.442981719971</strong> ms</li> <li>memory_finish: 120.8515625 Кб</li> <li>memory_diff: <strong>6.09375 Кб</strong></li> </ul> <p>Тестирование через ab:</p> <pre tabindex="0"><code>$ ab -n 1000 http://test/test/mem_class.php Requests per second: 714.52 [#/sec] (mean) Time per request: **1.400** [ms] (mean) </code></pre><p>Теперь инициализируем через константы класса:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">SlotType</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">const</span> <span class="no">TYPE_CACHER_1</span> <span class="o">=</span> <span class="s1">&#39;b60861c4492f88589429aab0c67abdd4&#39;</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="cm">/* ... */</span> </span></span><span class="line"><span class="cl"> <span class="k">const</span> <span class="no">TYPE_CACHER_100</span> <span class="o">=</span> <span class="s1">&#39;a66aedeafbc3f1e9fcbaa6a9e8060739&#39;</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><ul> <li>memory_start: 114.7578125 Кб</li> <li>time: <strong>0.0340938568115</strong> ms</li> <li>memory_finish: 114.9921875 Кб</li> <li>memory_diff: <strong>0.234375 Кб</strong></li> </ul> <p>Тестирование через ab:</p> <pre tabindex="0"><code>$ ab -n 1000 http://test/test/mem_class.php Requests per second: 818.27 [#/sec] (mean) Time per request: **1.222** [ms] (mean) </code></pre><div class="flex align-center gblog-post__anchorwrap"> <h2 id="тест-2-чтение" > Тест 2. Чтение </h2> <a data-clipboard-text="https://valmat.ru/posts/archive/2010/10/define-vs-const-php/#тест-2-чтение" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Тест 2. Чтение" href="#%d1%82%d0%b5%d1%81%d1%82-2-%d1%87%d1%82%d0%b5%d0%bd%d0%b8%d0%b5"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Считываем все константы, определённые через <strong>define</strong>:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nv">$var</span> <span class="o">=</span> <span class="nx">CACHER_TYPE_1</span> <span class="o">.</span> <span class="nx">CACHER_TYPE_2</span> <span class="o">.</span> <span class="cm">/*...*/</span> <span class="o">.</span> <span class="nx">CACHER_TYPE_100</span><span class="p">;</span> </span></span></code></pre></div><ul> <li>time: <strong>0.4</strong> ms</li> <li>memory_diff: <strong>9.3 Кб</strong></li> </ul> <p>ab -n1000:</p> <pre tabindex="0"><code>Requests per second: 488.63 [#/sec] (mean) Time per request: **2.047** [ms] (mean) </code></pre><p>Считываем через константы класса:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nv">$var</span> <span class="o">=</span> <span class="nx">SlotType</span><span class="o">::</span><span class="na">TYPE_CACHER_1</span> <span class="o">.</span> <span class="nx">SlotType</span><span class="o">::</span><span class="na">TYPE_CACHER_2</span> <span class="o">.</span> <span class="cm">/*...*/</span> <span class="o">.</span> <span class="nx">SlotType</span><span class="o">::</span><span class="na">TYPE_CACHER_100</span><span class="p">;</span> </span></span></code></pre></div><ul> <li>time: <strong>0.12</strong> ms</li> <li>memory_diff: <strong>3.5 Кб</strong></li> </ul> <p>ab -n1000:</p> <pre tabindex="0"><code>Requests per second: 609.62 [#/sec] (mean) Time per request: **1.640** [ms] (mean) </code></pre><div class="flex align-center gblog-post__anchorwrap"> <h2 id="вывод" > Вывод </h2> <a data-clipboard-text="https://valmat.ru/posts/archive/2010/10/define-vs-const-php/#вывод" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Вывод" href="#%d0%b2%d1%8b%d0%b2%d0%be%d0%b4"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Надо сказать, результат меня несколько удивил. Я ожидал, что по крайней мере скорость обработки с define будет выше. Оказывается, использование варианта</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Consts</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">const</span> <span class="no">CONST1</span> <span class="o">=</span> <span class="s1">&#39;val1&#39;</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="k">const</span> <span class="no">CONST2</span> <span class="o">=</span> <span class="s1">&#39;val2&#39;</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="k">const</span> <span class="no">CONST3</span> <span class="o">=</span> <span class="s1">&#39;val3&#39;</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>не только удобнее, но и эффективнее как по скорости исполнения, так и по расходу памяти.</p> PHP Counter https://valmat.ru/posts/archive/2010/10/php-counter/ 2010-10-28T06:02:00+00:00 2010-10-28T06:02:00+00:00 <p>Выложил на <a class="gblog-markdown__link" href="http://github.com/valmat/MC_Counter" >github.com</a> свой класс Counter.</p> <p>В основном сделал это, чтобы потестить сам GitHub.</p> <p>Адрес страницы на гитхабе: <a class="gblog-markdown__link" href="http://github.com/valmat/MC_Counter" >http://github.com/valmat/MC_Counter</a></p> <div class="flex align-center gblog-post__anchorwrap"> <h2 id="как-использовать" > Как использовать </h2> <a data-clipboard-text="https://valmat.ru/posts/archive/2010/10/php-counter/#как-использовать" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Как использовать" href="#%d0%ba%d0%b0%d0%ba-%d0%b8%d1%81%d0%bf%d0%be%d0%bb%d1%8c%d0%b7%d0%be%d0%b2%d0%b0%d1%82%d1%8c"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p><em>Класс <code>Counter</code> — это образец реализации счетчика на memcache.</em></p> <p>Можно построить другие реализации на общем интерфейсе.<br> Сохранение результатов применения значений счетчика осуществляется по заданному числу.<br> Можно реализовать сохранение по заданному интервалу времени.</p> <ul> <li>Конструктор принимает три аргумента: ключ, имя слота и идентификатор для инициализации слота.</li> <li>Для чего это сделано: инкремент счетчика должен быть очень быстрой операцией.</li> <li>Не целесообразно тратить время и системные ресурсы на создание объектов, которые не будут использованы.</li> <li>Поэтому передается только имя класса слота, который создается только в случае необходимости.</li> <li>К таким случаям относится обмен данными между локальным и постоянным хранилищем счетчика.</li> <li>Слоты необходимы, так как Counter не может знать о способе хранения данных в постоянном хранилище и путях доступа к ним.</li> <li>Для предотвращения состояния гонки необходим механизм блокировок.</li> <li>При наличии блокировки процессы, не получившие эксклюзивные права на получение данных, будут писать во временное хранилище, а процесс, установивший блокировку, по окончании своей работы инкрементирует счетчик данными из временного хранилища.</li> <li>При сбросе данных в постоянное хранилище по условию достижения кратности значения счетчика (<code>$this-&gt;Val % $this-&gt;upd_delim</code>), блокировка не требуется, так как в этом случае (при достаточно большом значении <code>$this-&gt;upd_delim</code>) в текущий момент времени только один процесс приходит к необходимости сброса данных.</li> </ul> <div class="flex align-center gblog-post__anchorwrap"> <h3 id="пример-использования" > Пример использования </h3> <a data-clipboard-text="https://valmat.ru/posts/archive/2010/10/php-counter/#пример-использования" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Пример использования" href="#%d0%bf%d1%80%d0%b8%d0%bc%d0%b5%d1%80-%d0%b8%d1%81%d0%bf%d0%be%d0%bb%d1%8c%d0%b7%d0%be%d0%b2%d0%b0%d0%bd%d0%b8%d1%8f"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nv">$cnt</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Counter</span><span class="p">(</span><span class="s1">&#39;anykey&#39;</span><span class="p">,</span> <span class="s1">&#39;AnySlot&#39;</span><span class="p">,</span> <span class="mi">15</span><span class="p">);</span> </span></span><span class="line"><span class="cl"><span class="k">echo</span> <span class="nv">$cnt</span><span class="o">-&gt;</span><span class="na">increment</span><span class="p">();</span> </span></span><span class="line"><span class="cl"><span class="k">echo</span> <span class="nv">$cnt</span><span class="o">-&gt;</span><span class="na">get</span><span class="p">();</span> </span></span><span class="line"><span class="cl"><span class="k">echo</span> <span class="nv">$cnt</span><span class="o">-&gt;</span><span class="na">set</span><span class="p">(</span><span class="mi">11</span><span class="p">);</span> </span></span></code></pre></div> Восстановление удалённых и повреждённых данных в Linux https://valmat.ru/posts/archive/2010/10/linux-restore/ 2010-10-18T18:04:00+00:00 2010-10-18T18:04:00+00:00 <p>Когда-то давно, лет десять назад, случалось мне отформатировать раздел жёсткого диска под Windows. На диске была важная информация, поэтому встала задача данные восстановить. Помню, путём продолжительного гугления были найдены несколько замечательных программ и кряков к ним. И данные были, хоть и частично, но восстановлены. Назывались эти программы, вроде бы, Easy Recovery, Recover4All и какая-то ещё.</p> <p>И вот на днях мне принесли жёсткий диск с разделом, на который была установлена новая Windows поверх старой, и вся нужная владельцу информация была благополучно удалена.</p> <p>Поскольку сейчас Windows у меня нет, была найдена чудесная линуксовая утилита под названием <strong>foremost</strong>.</p> <p>Вот, всё-таки, за что я люблю Linux — это за лаконичность и изящность решений (ну и за логичность архитектуры, конечно).</p> <p>Для восстановления данных потребовалась всего одна команда в терминале:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">foremost -t jpg -o ~/bak -i /dev/sdb1 </span></span></code></pre></div><p>Теперь по порядку, что к чему:</p> <ul> <li><code>#</code> — запускаем от root, чтобы не было проблем с чтением.</li> <li><code>-t</code> — тип восстанавливаемых файлов. Можно написать <code>-t all</code>, чтобы восстановить файлы всех типов, либо одно из значений из списка: avi, bmp, dll, doc, exe, gif, htm, jar, jpg, mbd, mov, mpg, pdf, png, ppt, rar, rif, sdw, sx, sxc, sxi, sxw, vis, wav, wmv, xls, zip.</li> <li><code>-o ~/bak</code> — куда складывать результат.</li> <li><code>-i /dev/sdb1</code> — здесь указываем раздел, который нужно сканировать. Поддерживаются разные файловые системы. Тот диск, который приносили мне, был с NTFS.</li> </ul> <p>Если запустить с опцией <code>-t all</code>, то будут созданы разные каталоги под каждый тип файлов, что само по себе очень удобно.</p> <p>Я особо не вглядывался, что он там восстановил, но при беглом обзоре можно было заключить, что в своей массе почти все файлы были восстановлены корректно. Было несколько битых фотографий, но так как раздел был не пустой, его не просто отформатировали, но и успели записать на него новые данные.</p> <p><strong>P.S.</strong> Foremost есть в репозитории Ubuntu.<br> Т.е. установить можно так:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo apt-get install foremost </span></span></code></pre></div> Принтер Canon LBP 3200 в Ubuntu https://valmat.ru/posts/archive/2010/09/canon-lbp-3200-ubuntu/ 2010-09-20T16:10:00+00:00 2010-09-20T16:10:00+00:00 <p>К великому моему сожалению, принтер Canon LBP 3200 не заработал в Ubuntu 9.10 &ldquo;из коробки&rdquo;.</p> <p>Драйверов для него в стандартной поставке нет. Просто скачать и установить <a class="gblog-markdown__link" href="http://files.canon-europe.com/files/soft31118/software/CAPTDRV180.tar.gz" >драйвер</a> тоже сразу не получилось. Поэтому я решил поискать ответ в интернете.</p> <p>Мне удалось найти две адекватные ссылки:</p> <ul> <li><a class="gblog-markdown__link" href="http://forum.ubuntu.ru/index.php?topic=87445.0" >http://forum.ubuntu.ru/index.php?topic=87445.0</a></li> <li><a class="gblog-markdown__link" href="http://help.ubuntu.ru/wiki/%D0%BF%D1%80%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D1%8B_canon_capt" >http://help.ubuntu.ru/wiki/%D0%BF%D1%80%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D1%8B_canon_capt</a></li> </ul> <p>Собственно, моя инструкция полностью написана, руководствуясь этими ссылками. К сожалению, обе они по отдельности результата не дали.</p> <p>Все, что написано ниже, у меня дало положительный результат.</p> <div class="flex align-center gblog-post__anchorwrap"> <h2 id="инструкция-для-ubuntu-910" > Инструкция для Ubuntu 9.10 </h2> <a data-clipboard-text="https://valmat.ru/posts/archive/2010/09/canon-lbp-3200-ubuntu/#инструкция-для-ubuntu-910" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Инструкция для Ubuntu 9.10" href="#%d0%b8%d0%bd%d1%81%d1%82%d1%80%d1%83%d0%ba%d1%86%d0%b8%d1%8f-%d0%b4%d0%bb%d1%8f-ubuntu-910"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Открываем терминал:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sudo su </span></span></code></pre></div><p>Удаляем:</p> <ul> <li>libcupsys2 и libstdc++5:</li> </ul> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">/usr/sbin/ccpdadmin -x LBP3200 </span></span><span class="line"><span class="cl">sudo /usr/sbin/lpadmin -x LBP320 </span></span><span class="line"><span class="cl">sudo dpkg -P cndrvcups-capt </span></span><span class="line"><span class="cl">sudo dpkg -P cndrvcups-common </span></span></code></pre></div><p>Далее скачиваем и устанавливаем <code>libcupsys2</code> и <code>libstdc++5</code>:</p> <ul> <li><a class="gblog-markdown__link" href="https://launchpad.net/ubuntu/karmic/&#43;package/libcupsys2" >libcupsys2</a></li> <li><a class="gblog-markdown__link" href="http://packages.ubuntu.com/jaunty/libstdc&#43;&#43;5" >libstdc++5</a></li> </ul> <p>Далее надо скачать драйвер принтера:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sudo su </span></span><span class="line"><span class="cl"><span class="nb">cd</span> /tmp </span></span><span class="line"><span class="cl">wget http://files.canon-europe.com/files/soft31118/software/CAPTDRV180.tar.gz </span></span><span class="line"><span class="cl">tar -xzf CAPTDRV180.tar.gz </span></span><span class="line"><span class="cl"><span class="nb">cd</span> ./CANON_UK/Driver/Debian </span></span><span class="line"><span class="cl">dpkg -i cndrvcups-common_1.80-1_i386.deb </span></span><span class="line"><span class="cl">dpkg -i cndrvcups-capt_1.80-1_i386.deb </span></span></code></pre></div><p>Далее надо отредактировать файл <code>/etc/ccpd.conf</code>:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sudo gedit /etc/ccpd.conf </span></span></code></pre></div><p>Меняем строки:</p> <pre tabindex="0"><code>#&lt;Printer LBP3200&gt; #DevicePath /dev/usb/lp0 #&lt;/Printer&gt; </code></pre><p>на</p> <pre tabindex="0"><code>&lt;Printer LBP3200&gt; DevicePath /dev/usblp0 &lt;/Printer&gt; </code></pre><p>Перегружаем сервер печати:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sudo /etc/init.d/cups restart </span></span><span class="line"><span class="cl">sudo /etc/init.d/ccpd stop <span class="o">&amp;&amp;</span> sudo /etc/init.d/ccpd start </span></span></code></pre></div><p>Регистрируем принтер в ccpd:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sudo /usr/sbin/ccpdadmin -p LBP3200 -o /dev/usblp0 </span></span><span class="line"><span class="cl">sudo /etc/init.d/ccpd stop <span class="o">&amp;&amp;</span> sudo /etc/init.d/ccpd start </span></span></code></pre></div><p>Окно статуса принтера можно открыть так:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">captstatusui -P LBP3200 </span></span></code></pre></div><hr> <div class="flex align-center gblog-post__anchorwrap"> <h2 id="update" > UPDATE </h2> <a data-clipboard-text="https://valmat.ru/posts/archive/2010/09/canon-lbp-3200-ubuntu/#update" class="gblog-post__anchor clip flex align-center" aria-label="Anchor UPDATE" href="#update"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Поставил Linux Mint 16 64bit (~ Ubuntu 13.10), и в нем моя инструкция, конечно, не подходит.</p> <p>Пришлось проходить этот квест заново. К счастью, Canon выпустил новые драйверы, что немного облегчает задачу.</p> <p>Итак, имеем названную систему.</p> <ol> <li> <p>Скачиваем и устанавливаем deb-пакеты с <a class="gblog-markdown__link" href="http://www.canon-europe.com/Support/Consumer_Products/products/printers/Laser/Laser_Shot_LBP3200.aspx" >драйверами от производителя</a>.</p> </li> <li> <p>Подключаем и включаем принтер. Смотрим, на какой usb-порт он подключился (у меня <code>/dev/usb/lp1</code>). Смотреть нужно тут: <code>/dev/usb</code></p> </li> <li> <p>Правим <code>/etc/ccpd.conf</code>:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sudo gedit /etc/ccpd.conf </span></span></code></pre></div><p>Меняем строки:</p> <pre tabindex="0"><code>#&lt;Printer LBP3200&gt; #DevicePath /dev/usb/lp0 #&lt;/Printer&gt; </code></pre><p>на</p> <pre tabindex="0"><code>&lt;Printer LBP3200&gt; DevicePath /dev/usb/lp1 &lt;/Printer&gt; </code></pre></li> <li> <p>Добавляем принтер (в архиве с драйверами есть README, можно посмотреть откуда взялись эти строки и на что их менять, если принтер отличается):</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sudo /usr/sbin/lpadmin -p LBP3200 -P /usr/share/cups/model/CNCUPSLBP3200CAPTK.ppd -v ccp://localhost:59687 -E </span></span><span class="line"><span class="cl">sudo /usr/sbin/lpadmin -p LBP3200 -m CNCUPSLBP3200CAPTK.ppd -v ccp://localhost:59787 -E </span></span><span class="line"><span class="cl">sudo /usr/sbin/ccpdadmin -p LBP3200 -o /dev/usb/lp1 </span></span></code></pre></div></li> <li> <p>Перегружаем сервер печати (см. выше)</p> </li> <li> <p>Печатаем пробную страницу.</p> </li> </ol> <p>Все!</p> <hr> <p><strong>PS</strong></p> <p>Что еще обнаружилось:</p> <ul> <li> <p>Нужно добавить <code>/etc/init.d/ccpd</code> в автозагрузку:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">update-rc.d ccpd defaults 20<span class="p">;</span> </span></span></code></pre></div><p>Причем, даже после добавления в автозагрузку, нужно перезагружать ccpd. Поэтому еще нужно в <code>/etc/rc.local</code> перед <code>exit 0</code> добавить:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">/etc/init.d/ccpd restart </span></span></code></pre></div></li> <li> <p>Кроме того выяснилось, что в зависимости от того, был ли подключен включенный принтер до загрузки системы или нет, принтеру назначаются разные файлы устройства. Если включен после загрузки ОС, то <code>/dev/usb/lp1</code>, если до — то <code>/dev/usb/lp0</code>.</p> </li> </ul> <p>Чтобы преодолеть эту неприятность, я изобрел следующий костыль:</p> <p>В <code>/etc/init.d/ccpd</code> в начало секций <code>ccpd_start()</code> и <code>ccpd_stop()</code> (только <code>ccpd_start</code> недостаточно) добавил следующий блок:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="c1">###############################</span> </span></span><span class="line"><span class="cl"><span class="c1"># Fix гуляние портов</span> </span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[</span> -e /dev/usb/lp0 <span class="o">]</span><span class="p">;</span> <span class="k">then</span> </span></span><span class="line"><span class="cl"> <span class="nb">echo</span> <span class="s2">&#34;Exist /dev/usb/lp0&#34;</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="o">[</span> ! -e /dev/usb/lp1 <span class="o">]</span><span class="p">;</span> <span class="k">then</span> </span></span><span class="line"><span class="cl"> <span class="nb">echo</span> <span class="s2">&#34;NOT exist /dev/usb/lp1&#34;</span> </span></span><span class="line"><span class="cl"> <span class="nb">echo</span> <span class="s2">&#34;ln -s /dev/usb/lp0 /dev/usb/lp1&#34;</span> </span></span><span class="line"><span class="cl"> ln -s /dev/usb/lp0 /dev/usb/lp1 </span></span><span class="line"><span class="cl"> <span class="k">fi</span> </span></span><span class="line"><span class="cl"><span class="k">fi</span> </span></span><span class="line"><span class="cl"><span class="c1">###############################</span> </span></span></code></pre></div><p>Этот блок создает символическую ссылку в случаях, когда система загружается с уже включенным принтером.</p> Установка LaTeX в Linux Ubuntu https://valmat.ru/posts/archive/2010/09/latex-linux-ubuntu/ 2010-09-08T14:30:00+00:00 2010-09-08T14:30:00+00:00 <p>Как утверждает <a class="gblog-markdown__link" href="http://ru.wikipedia.org/wiki/TeX_Live" >Википедия</a>, с 2006-го года пакет teTeX более не поддерживается, а вместо него поддерживается TeX Live. Его и ставим.</p> <p>Инструкцию по установке подглядел здесь: <a class="gblog-markdown__link" href="http://linuxandfriends.com/2009/10/06/install-latex-in-ubuntu-linux/" >http://linuxandfriends.com/2009/10/06/install-latex-in-ubuntu-linux/</a></p> <p>Ставим:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo su </span></span><span class="line"><span class="cl">apt-get install texlive texlive-full texlive-fonts-recommended latex-beamer texlive-pictures texlive-latex-extra </span></span></code></pre></div><p><code>texlive-full</code> попросил достаточно много места на диске (около 700 МБ). Место у меня было, поэтому я его всё же установил, но, при необходимости, можно не устанавливать <code>texlive-full</code>, а установить только нужные пакеты, которые входят в <code>texlive-full</code>.</p> <p>В качестве IDE были на пробу установлены LyX, gedit-latex-plugin и TeXmaker.<br> Про них пока ничего сказать не могу, т.к. ещё не юзал.</p> <p><strong>PS:</strong> это была моя первая установка LaTeX в Линуксе, до этого я устанавливал <a class="gblog-markdown__link" href="http://ru.wikipedia.org/wiki/MiKTeX" >MiKTeX</a> под виндой. Как всегда, небо и земля. Вот уж что действительно танцы с бубном, так это ставить МикТех под винду.</p> Рассказ о том, как я жесткий диск на ноуте менял https://valmat.ru/posts/archive/2010/08/laptop-hdd/ 2010-08-29T11:16:00-07:00 2010-08-29T11:16:00-07:00 <p>Был у меня, в общем-то, достаточный для работы жесткий диск объемом 250 ГБ, но не жилось мне спокойно, и захотелось мне поставить жесткий диск на 500 ГБ.</p> <p>Сказано — сделано. Винт купил и поставил. Надо переносить систему. Переустановка ОС — это не наш путь, разумеется.</p> <p>Далее будет описано, что я делал (и что не нужно было делать), а в конце — как всё-таки у меня получилось <del>через одно место</del>.</p> <p>Итак, на моем старом винте было три раздела: <code>ntfs</code>, <code>ext4</code> и <code>swap</code>.</p> <p>Новый жесткий диск я воткнул в ноутбук, а старый подключил по USB через переноску.</p> <p>Самое логичное было бы сделать:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">dd <span class="k">if</span><span class="o">=</span>/dev/sdb <span class="nv">of</span><span class="o">=</span>/dev/sda <span class="nv">bs</span><span class="o">=</span><span class="m">4096</span> <span class="nv">conv</span><span class="o">=</span>sync,noerror </span></span></code></pre></div><p>Но этот способ не сработал. Разделы создались, но файловые системы у них не определились, из-за чего пришлось бы их переформатировать.</p> <p>Собственно, того же результата можно было добиться, просто скопировав загрузчик и таблицу разделов:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">dd <span class="k">if</span><span class="o">=</span>/dev/sdb <span class="nv">of</span><span class="o">=</span>/dev/sda <span class="nv">bs</span><span class="o">=</span><span class="m">512</span> <span class="nv">count</span><span class="o">=</span><span class="m">1</span> </span></span></code></pre></div><p>После этого я по очереди удалил каждый раздел на новом жестком диске и с помощью gparted перенес их со старого жесткого диска. В результате, конечно же, загрузочный сектор и таблица разделов у меня потерлись.</p> <p>Всевозможные попытки восстановить grub, осуществляемые при помощи гугления, результата не дали.</p> <p><strong>Что в итоге я сделал:</strong></p> <ul> <li>Раздел с ntfs оставил как есть (т.е. полностью клонированным со старого жесткого диска).</li> <li>На раздел ext4, на котором у меня стоял Linux, я заново установил дистрибутив. Устанавливать нужно обязательно тот дистрибутив, который стоял до этого, иначе может не получиться.</li> </ul> <p>Таким образом у меня пересоздалась таблица разделов и загрузочный сектор (вообще таблица разделов, конечно, была).</p> <p>Затем я полностью стер всё на линуксовом разделе и скопировал при помощи утилиты <strong>rsync</strong> всё со старого линуксового раздела. Всё это я делал, загрузившись с LiveCD.</p> <p><code>rsync</code> в отличие от <code>cp</code> (даже <code>cp -a</code>) нормально копирует скрытые файлы, т.е. те, которые начинаются с &ldquo;.&rdquo;. Обе команды <code>rsync -a</code> и <code>cp -a</code> могут копировать атрибуты файлов и права доступа.</p> <p>Выглядит команда так:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">rsync -a /.../.../ /.../.../ </span></span></code></pre></div><p>Закрывающий слеш существенен, поскольку без него получится не совсем то, что нужно.</p> <p>Чтобы система начала загружаться, нужно ещё в файлах<br> <strong>/boot/grub/grub.cfg</strong> и <strong>/etc/fstab</strong><br> поменять uuid разделов на новые.</p> <p>Узнать их можно командой:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">blkid </span></span></code></pre></div><p>Ну и напоследок, поскольку у меня появилось теперь много свободного места, я создал 250-гигабайтный раздел и примонтировал его к <code>/home</code>.</p> <p>Для этого я всё тем же <code>rsync</code>-ом перенёс все файлы (сидя на LiveCD) на отведённый для этого раздел и прописал в его fstab:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># /home</span> </span></span><span class="line"><span class="cl">/dev/sda4 /home ext4 defaults,noatime,errors<span class="o">=</span>remount-ro <span class="m">0</span> <span class="m">2</span> </span></span></code></pre></div> Разборка HP ProBook 4710s: доступ к памяти https://valmat.ru/posts/archive/2010/08/hp-probook-4710s/ 2010-08-17T06:44:00+00:00 2010-08-17T06:44:00+00:00 <p>Решил я обновить свой ноутбук — увеличить память, да выяснилось, что на HP ProBook 4710s доступ к внутренностям организован не так, как у большинства других моделей, а совершенно через другое место. Поэтому выкладываю здесь инструкцию по открытию этого ноутбука.</p> <p><strong>Шаг 1:</strong></p> <p>Снимаем аккумулятор и откручиваем 5 болтов под ним:</p> <p><img src="./1.JPG" alt="Снятие аккумулятора и болтов" /></p> <p><img src="./2.JPG" alt="Болты под аккумулятором" /></p> <p><strong>Шаг 2:</strong></p> <p>Затем откручиваем 2 болта на задней части ноутбука:</p> <p><img src="./3.JPG" alt="Два болта на задней части" /></p> <p><img src="./4.JPG" alt="Вид после откручивания" /></p> <p>Складываем все шурупы в одном месте, чтобы не потерять:</p> <p><img src="./5.JPG" alt="Складываем шурупы" /></p> <p><strong>Шаг 3:</strong></p> <p>Толчком от себя снимаем заднюю часть верхней панели (ту, на которой динамики):</p> <p><img src="./6.JPG" alt="Снимаем верхнюю панель" /></p> <p><img src="./7.JPG" alt="Панель снята" /></p> <p>Эту часть можно полностью удалить, чтобы не мешала:</p> <p><img src="./7-1.JPG" alt="Удаляем панель" /></p> <p><strong>Шаг 4:</strong></p> <p>Далее у нас открывается доступ к болтам крепления клавиатуры:</p> <p><img src="./8-1.JPG" alt="Доступ к болтам клавиатуры" /></p> <p><img src="./8-2.JPG" alt="Болты клавиатуры крупнее" /></p> <p>Их нужно открутить и также толчком от себя (без лишних усилий, это всё же не советская техника — можно и сломать) отделяем клавиатуру:</p> <p><img src="./9.JPG" alt="Снимаем клавиатуру" /></p> <p>Вуаля, у нас появился доступ к оперативной памяти:</p> <p><img src="./10.JPG" alt="Доступ к памяти" /></p> <p>А дальше я не полез.</p> <p>Собирается всё, естественно, в обратном порядке.</p> tmpfs: Операции с файловой системой в виртуальной памяти https://valmat.ru/posts/archive/2010/07/tmpfs/ 2010-07-24T11:08:00+00:00 2010-07-24T11:08:00+00:00 <div class="flex align-center gblog-post__anchorwrap"> <h1 id="tmpfs-операции-с-файловой-системой-в-виртуальной-памяти" > tmpfs: Операции с файловой системой в виртуальной памяти </h1> <a data-clipboard-text="https://valmat.ru/posts/archive/2010/07/tmpfs/#tmpfs-операции-с-файловой-системой-в-виртуальной-памяти" class="gblog-post__anchor clip flex align-center" aria-label="Anchor tmpfs: Операции с файловой системой в виртуальной памяти" href="#tmpfs-%d0%be%d0%bf%d0%b5%d1%80%d0%b0%d1%86%d0%b8%d0%b8-%d1%81-%d1%84%d0%b0%d0%b9%d0%bb%d0%be%d0%b2%d0%be%d0%b9-%d1%81%d0%b8%d1%81%d1%82%d0%b5%d0%bc%d0%be%d0%b9-%d0%b2-%d0%b2%d0%b8%d1%80%d1%82%d1%83%d0%b0%d0%bb%d1%8c%d0%bd%d0%be%d0%b9-%d0%bf%d0%b0%d0%bc%d1%8f%d1%82%d0%b8"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>Для примонтирования при старте вносим в <code>/etc/fstab</code>:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">tmpfs /tmp tmpfs <span class="nv">size</span><span class="o">=</span>500M,nr_inodes<span class="o">=</span>1m,nosuid <span class="m">0</span> <span class="m">0</span> </span></span><span class="line"><span class="cl">tmpfs /var/lib/php5 tmpfs <span class="nv">size</span><span class="o">=</span>200M,nr_inodes<span class="o">=</span>1m,nosuid <span class="m">0</span> <span class="m">0</span> </span></span></code></pre></div><p>Первая строчка размещает в памяти <code>/tmp</code>, вторая — папку хранения сессий.</p> <hr> <p>Для создания папки для сессий без рестарта системы нужно выполнить следующую последовательность команд в терминале:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkdir /tmp/ses </span></span><span class="line"><span class="cl">/etc/init.d/nginx stop </span></span><span class="line"><span class="cl">mv /var/lib/php5/* /tmp/ses </span></span><span class="line"><span class="cl">mount tmpfs /var/lib/php5 -t tmpfs -o <span class="nv">size</span><span class="o">=</span>200M,nr_inodes<span class="o">=</span>1m,nosuid </span></span><span class="line"><span class="cl">mv /tmp/ses/* /var/lib/php5 </span></span><span class="line"><span class="cl">/etc/init.d/nginx start </span></span><span class="line"><span class="cl">rm -r /tmp/ses </span></span></code></pre></div><blockquote> <p>Предварительно лучше отредактировать <code>fstab</code>.</p> </blockquote> <hr> <p>Вот более сложный вариант, когда данные сессий хранятся в <code>/tmp</code>:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkdir /dev/shm/ses </span></span><span class="line"><span class="cl">/etc/init.d/nginx stop </span></span><span class="line"><span class="cl">/etc/init.d/php5-spawn stop </span></span><span class="line"><span class="cl">/etc/init.d/mysql stop </span></span><span class="line"><span class="cl">mv /tmp/* /dev/shm/ses </span></span><span class="line"><span class="cl">mount tmpfs /tmp -t tmpfs -o <span class="nv">size</span><span class="o">=</span>1g,nr_inodes<span class="o">=</span>1m,nosuid </span></span><span class="line"><span class="cl">mount tmpfs /var/lib/php5 -t tmpfs -o <span class="nv">size</span><span class="o">=</span>200M,nr_inodes<span class="o">=</span>1m,nosuid </span></span><span class="line"><span class="cl">mv /dev/shm/ses/* /tmp </span></span><span class="line"><span class="cl">/etc/init.d/mysql start </span></span><span class="line"><span class="cl">/etc/init.d/php5-spawn start </span></span><span class="line"><span class="cl">/etc/init.d/nginx start </span></span><span class="line"><span class="cl">rm -r /dev/shm/ses </span></span><span class="line"><span class="cl">du -hsx /tmp </span></span></code></pre></div> Запуск memcache через unix.socket https://valmat.ru/posts/archive/2010/07/memcache-unixsocket/ 2010-07-24T11:04:00+00:00 2010-07-24T11:04:00+00:00 <div class="flex align-center gblog-post__anchorwrap"> <h2 id="запуск-memcache-через-unixsocket" > Запуск memcache через unix.socket </h2> <a data-clipboard-text="https://valmat.ru/posts/archive/2010/07/memcache-unixsocket/#запуск-memcache-через-unixsocket" class="gblog-post__anchor clip flex align-center" aria-label="Anchor Запуск memcache через unix.socket" href="#%d0%b7%d0%b0%d0%bf%d1%83%d1%81%d0%ba-memcache-%d1%87%d0%b5%d1%80%d0%b5%d0%b7-unixsocket"> <svg class="gblog-icon gblog_link"><use xlink:href="#gblog_link"></use></svg> </a> </div> <p>В файл <code>/etc/memcached.conf</code> добавляем строчки:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1">#-s &lt;file&gt; unix socket path to listen on (disables network support)</span> </span></span><span class="line"><span class="cl">-s /tmp/memcached.socket </span></span><span class="line"><span class="cl"><span class="c1">#-a &lt;mask&gt; access mask for unix socket, in octal (default 0700)</span> </span></span><span class="line"><span class="cl">-a <span class="m">0777</span> </span></span></code></pre></div><p>Последняя нужна, чтобы пользователь, от которого работает веб-сервер (у меня <code>www-data</code>), смог прочитать сокет.</p> <p>Перезапускаем демон мемкеша:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">/etc/init.d/memcached restart </span></span></code></pre></div><p>Подключаемся к Memcache из PHP-скрипта:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nv">$memcache</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Memcache</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="nv">$memcache</span><span class="o">-&gt;</span><span class="na">connect</span><span class="p">(</span><span class="s1">&#39;unix:///tmp/memcached.socket&#39;</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span> </span></span></code></pre></div><p>Теперь memcache не должен уступать по производительности <code>tmpfs</code> или <code>/dev/shm</code>.</p> <p><strong>Правда, в этом случае перестают работать сессии в memcached.</strong><br> То есть следующая конструкция работать не будет:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nv">$session_save_path</span> <span class="o">=</span> <span class="s1">&#39;localhost:11211&#39;</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="nv">$session_save_path</span> <span class="o">=</span> <span class="s1">&#39;localhost:11211,unix:///tmp/memcached.socket:0&#39;</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="nx">ini_set</span><span class="p">(</span><span class="s1">&#39;session.save_handler&#39;</span><span class="p">,</span> <span class="s1">&#39;memcache&#39;</span><span class="p">);</span> </span></span><span class="line"><span class="cl"><span class="nx">ini_set</span><span class="p">(</span><span class="s1">&#39;session.save_path&#39;</span><span class="p">,</span> <span class="nv">$session_save_path</span><span class="p">);</span> </span></span></code></pre></div><p>Но для сессий лучше всего всё-таки использовать <a class="gblog-markdown__link" href="/posts/archive/2010/07/tmpfs/" >tmpfs</a>.</p>