программирование

Скорость PHP7 на интерпретаторе Brainfuck

Производительность PHP7 (161.98КиБ)

У меня есть старинная забава — брать свой оптимизирующий интерпретатор языка «Брейнфак» и запускать программу, вычисляющую число «Пи» под разными версиями ПХП, чтобы посмотреть их производительность. Тест довольно синтетический, но посмотреть всё равно интересно.

Сравнил в данном случае версии 5.5.14  и сегодняшнюю сборку 7.0, разница всё равно заметна, хоть и не так впечатляюща, как в случае с «Вордпрессом» — там разница в два раза.

Время у меня на скриншоте общее — то есть трансляция программы на «Брейнфаке» в ПХП и запуск получившегося через eval.

Оставить комментарий

PHP7: строки с Unicode-вхождениями

\u{…} (11.43КиБ)

ПХП тут сделал маленький шаг к унификации синтаксиса с Руби, Джаваскриптом и HHVM — ввёл возможность указывать Юникодные символы через последовательность на скриншоте. Сегодня я выписал текущую версию из репозитория (она, кстати, не компилируется без исправлений в коде) и убедился, что всё работает.

6 комментариев

Ошибка оракула

В одном из проектов при исправлении ошибки всплыло неожиданное. Кусок кода, который работал ошибочно, родился где-то в двухтысячных и по всей видимости в те времена работал правильно. В нём проверялось — если тип переменной «целое», то переменная передавалась в «Оракл» с флагом SQLT_INT, иначе — с SQLT_CHR.

Всё работало хорошо, пока ПХП был 32-битным. Проверка is_int в этом языке возвращала «истину» только для чисел в диапазоне -2147483648…2147483647, всё что шире, трактовалось как float. В 64-битном интерпретаторе целыми считаются числа из гораздо более широкого диапазона, соответственно, в каком-то коде такое значение попало в драйвер «Оракла», промаркированное как SQLT_INT.

Беда в том, что драйвер (в этом месте), похоже, остался в пределах 32 бит, код:

$var = null;
$st = oci_parse($con, "SELECT :int FROM DUAL");
oci_bind_by_name($st, 'int', $var, -1, SQLT_INT);

foreach ([100500, PHP_INT_MAX, 2147483648] as $var) {
    oci_execute($st);
    echo oci_fetch_array($st, OCI_NUM)[0], "\n";
}

Выведет: 100500, -1, -2147483648. Решение я пока знаю только одно: не использовать для целых, которые не помещаются в 32 бита указание типа SQLT_INT.

Проблема в файле oci8_statement.c (это текущий код драйвера из PHP 5.7):

switch (bind->array.type) {
    case SQLT_NUM:
    case SQLT_INT:
    case SQLT_LNG:

        ...
        ZVAL_LONG(entry, ((ub4 *)(bind->array.elements))[i]);
        ...

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

2 комментария

Как работает Bashfuck

Думаю, по горячим следам надо описать как работают мои Bashfuck и Bashfuck-2. Пока ещё сам детально помню.

Поскольку буквы и цифры использовать запрещено, все имена переменных состоят исключительно из подчёркиваний — это единственный вариант в «Баше» в таком случае. Чтобы что-то напечатать, сначала надо получить какие-то буквы.

К счастью, переменная с именем, состоящим из одного подчёркивания содержит полный путь к интерпретатору, которым был запущен наш скрипт. Поскольку путь может отличаться, а название интерпретатора — нет, путь надо удалить, оставив только слово «bash». Это делается в обоих случая в первой строке:

# Удаляем из переменной $_ всё до последнего слеша
__=${_##*/}

Теперь у нас есть четыре буквы «b», «a», «s» и «h». Из имеющегося надо как-то получить недостающее. Тут нам помогает следующий факт: можно запускать что угодно, по имени, которое содержится в переменной.

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

# bash тут — строка, которую мы уже получили
$ base64<<<bash
YmFzaAo=
$ base64<<<bash | base64
WW1GemFBbz0K

Например, в полученных строках некоторые буквы нам вполне интересны: «Hello world». Но как получить слово base64? С цифрами проще: имея строки разной длины и арифметические операции можно получить нужное:

# Переменная __ содержит слово bash
__=bash
# Очень просто получить цифру четыре: надо измерить длину строки:
$ echo ${#__}
4
# Любым похожим путём получаем двойку, прибавляем к четвёртке и у нас есть 6
$ echo $((${#__} + 2))
6

Поскольку типы переменных «Баш» не особо-то различает, склеить всё остальное труда не представляет. Но нужно ещё откуда-то получить букву «e». Тут дорожки этих двух скриптов расходятся. Первый работает так: составляя из имеющихся букв команду «hash», запускает её и получает следующую строку:

$ hash
hash: hash table empty

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

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

# пробуем вывести на печать
$ echo <(:)
/dev/fd/63

Нам это значение нужно в переменной и с присвоением этот трюк тоже сработет.

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

# переменная теперь содержит что-то вроде /dev/fd/xx
___=<(:)
# выбираем третий символ (отсчёт с нуля):
echo ${___:2:1}

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

# переменная, где нужная мне буква в середине слова
v=sed
# так можно срезать последнюю букву
echo ${v%?}
# а так —  первую
echo ${v#?}

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

v='heLLo WORld'
# всё к нижнему (будет hello world)
echo ${v,,}
# только первую —  к верхнему (будет HeLLo WORld)
echo ${v^}

Собранную строку осталось вывести на экран. Для этого в том же коде я попутно собираю из получающихся букв ещё и строку «cat» (что на одну букву меньше «echo», кода получается меньше) и с помощью этой команды получаю нужный результат:

c=cat
v='Hello world'
# Выведет Hello world
$c<<<$v

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

2 комментария

Bashfuck-2

Попробовал несколько иной способ сбора строки «Hello world» в Bashfuck. Получилось, работает. В предыдущей версии один из читателей жаловался, что у него не запустилось, я тут подумал, что возможно версия «Баша» не та — требуется четвёртая, разумеется.

Вот другой вариант сбора строки:

__=${_##*/} ___=${#__} ______=${__::$(($___-${#___}))} ____=$((${#___}+${#___}))
_____=<(:) ___=$______${_____:$____:${#___}}$(($___+$____))$___
__=${__:(-${#____})}${_____:$____:${#____}} ____=`$___<<<$___|$___`
__=$__${____:$((${#______}+${#__})):$((${#______}^${#__}))} __=$__${__#??}
_______=${____:(-${#__})}
______=`$___<<<$__` __=$__${______#??????} __="${__%?} ${____::$((${#______}/${#__}))}"${__#????}
______=${#___} ____=`$___<<<$____|$___` __=${__%?}${____:$((${#___}+${#___})):$______}
__=${__%????}${_____:${#______}:${#______}} __=${__,,}
_______=${_______::${#______}}${___:${#______}:${#______}}$($___<<<$______|$___)
_______=${_______::$((${#____}/${#__}))};${_______,,}<<<${__^}

Надо будет выбрать время, написать как оно работает.

1 комментарий

Bashfuck

«…fuck» — это не ругательство, а развлечение, навеянное знаменитым языком программирования «Brainfuck». Практического смысла никакого, правила просты — написать программу на каком-либо языке программирования, не используя буквы и цифры. Уровень повышенной сложности — использовать для программы не все доступные закорюки, а сильно ограниченное количество, например, только пять любых на выбор.

JSFuck у меня в блоге был, PHPFuck — тоже, был даже совмещённый в одной программе JSFuck и Brainfuck, почему бы не попробовать сделать то же на «Баше»?

__=${_##*/} ___=${#__}
__=($__ $(${__:$(($___-${#___}))}${__:${#___}:$___}) $__)
____=${__::$((${#__}-${#___}))}${__[$___]::${#___}}${#__[@]}${#__}
_____=$($____<<<${__[@]}) ______=${_____:${#__[${#__}]}:${#___}}
______=${______,}${__[${#_}]:${#___}:${#___}}${__[$(($___-${#___}))]:${#_}:${#___}}
_______=$($____<<<$_____)
____=${__[${#______}]:${#______}:${#___}} ____=$____$____
__=${__[${#_}]:${#______}}${__[$___]:${#_}:${#___}}$____${_____:$((${#__[@]}+${#__[$___]})):${#___}}
__=$__' '${_______::${#___}}${__:$___}${_____:$((${#_____}>>${#___})):${#___}}
__=$__${____:${#___}}${_______:$(($___+${#______})):${#___}} __=${__,,}
$______<<<${__^}

Сказано — сделано, собственно. Программа на «Баше», что выше, выводит фразу «Hello world», её исходники можно свободно скачать с «ГитХаба».

9 комментариев

99 бутылок: Scratch и HQ9+

54. HQ9+ — язык, у которого только четыре оператора. «H» выводит «Hello world», «Q» печатает исходный код программы, 9 выводит «песню о пиве», а «плюс» «увеличивает аккумулятор». Язык, понятное дело, придуман в шутку и у неё есть продолжения — «объектный» «HQ9++», Тюринг-полный «HQ9+B» и так далее.

Данная программа на этом языке выглядит вот так:

9


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

Я, для интереса, написал на «Скретче» свои «99 бутылок». Программа на скриншоте. В среде редактирования мы управляем лисёнком и всё программирование крутится вокруг него, куча блоков позволяют что-то с ним делать — двигать, вращать, заставляют его что-то говорить и так далее. «Думать» выводит свой аргумент в  «облачке» его мыслей.

Скретч (84.30КиБ)

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

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

Есть ещё какие-то «Другие блоки», но с ними я не разбирался. Кажется, через них можно как-то расширять язык.

15 комментариев

Знание PHP

В рамках разговора о знании языка (ПХП, в данном случае), родился у меня такой пример:

var_dump(b"1" == 0b1);

Что тут будет — true или false и почему?

7 комментариев

«Глаголы» в PCRE

В известной библиотеке «PCRE», реализующей Перл-подобные регулярные выражения, есть такая штука как «глаголы» (в оригинале — VERB). Это ключевые слова, указывающиеся в скобках и начинающиеся со звёздочки. Когда-то я рассказывал об одном из них — «глаголе» UCP, который позволяет полноценно использовать ЮТФ-8 в этой библиотеке.

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

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

«Глагол» ACCEPT даёт понять библиотеке, что дальше можно не искать — найденное нас устроило. Вот смотрите:

A((?:A|B(*ACCEPT)|C)D)

Если подставить в эту регулярку «ACD», то ничего необычного не произойдёт: совпадение выглядит ожидаемым способом:  A((?:A|B(*ACCEPT)|C)D), выглядит так, как будто строка для проверки всегда обязана заканчиваться на «D», по если передать сюда «AB», то совпадение тоже будет: A((?:A|B(*ACCEPT)|C)D) — «глагол» ACCEPT говорит о том, что дальше можно не продолжать, остаток нас не интересует.

«Глагол» FAIL позволяет достичь ровно противоположного и, в частности, сформировать отрицание, которое другими средствами добиться тяжелее и представляет сложность для новичков в регулярках.

A(?:BCD(*FAIL)|.*?)E

Если передать сюда строки «ABCE», «AEEE», «AwhateverE» совпадение произойдёт по следующим правилам шаблона: A(?:BCD(*FAIL)|.*?)E, но если передать «ABCDE», то совпадения не будет, вступит в силу «глагол» FAIL: A(?:BCD(*FAIL)|.*?)E.

Эти и другие «глаголы» (кроме MARK) отлично работают в ПХП, потому что фукнции «preg_*» как раз используют библиотеку PCRE у себя внутри.

4 комментария

Defer в PHP

В языке «Гоу» есть превосходная конструкция «defer», которая запускает переданное ей выражение, когда заканчивает выполняться текущий блок. Вот как это может выглядеть:

profile, e := os.OpenFile(oProfile, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0777)
if e == nil {
    defer profile.Close()
    profile.WriteString("0:   0  0 0 0 ;\n0:   1  8 0 2 ;\n0:   9 63 0 2 ;\n0:   1 63 2 1 ;\n0:   1 63 1 0;")
}

Это маленький пример из одной моей реальной программы. Как только управление покинет текущую функцию, вызовется метод Close у profile и файл автоматически закроется. Пример маленький и всех удобств не раскрывает, но надеюсь они, как на ладони: если у вас много условий при которох управление может покинуть блок, то вам не нужно уничтожать все свои ресурсы в каждой такой ситуации, достаточно их один раз описать через «defer» и всё сработает само.

Одна из причин любить «Гоу».

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

with open("/etc/passwd", "r") as f:
    print(f.read(10))

Объекты, у которых реализованы оба этих метода, могут быть вызываны из специальной конструкции «with», при входе в которую будет вызван метод «__enter__», при выходе — «__exit__», это нагляднее, чем в «Гоу» и тут невозможно забыть освободить какой-то ресурс, но недостаток в том, что надо реализовать эти методы, для какой-то внешней библиотеки нельзя каким-то образом автоматически вызвать тамошний эквивалент «free», придётся всё обернуть в класс.

В ПХП так сделать нельзя, но можно на шаг приблизиться к этому, если использовать деструктор специального объекта, но это и близко не то же самое, что в вышерассмотренных случаях. В «Пайтоне» не зря выделили специальный метод, срабатывающий на выходе, а не обошлись деструкторами, которые (в отличие от «Гоу») в этом языке есть: «__exit__» вызовется гарантированно в момент выхода, тогда как деструктор — когда придётся.

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

В общем, хорошо, что в ПХП нет такого встроенного класса, его бы сразу начали активно использовать, что привело бы к куче сложноуловимых «багов». Вот это средоточие зла:

class Defer
{
    private $funcs;

    public function __construct(callable $func)
    {
        $this->funcs = [$func];
    }

    public function __destruct()
    {
        foreach ($this->funcs as $func) {
            call_user_func($func);
        }
    }

    public function __invoke(callable $func)
    {
        array_unshift($this->funcs, $func);
    }
}

Очень простой класс с тремя методами. Как же его можно использовать? (никак! не надо! нет!). Пример ниже, он, конечно, мегасинтетический, но что вы хотите от «совы» в семь утра, у меня ещё и мозг-то не работает.

function doSomePHP()
{
    for ($i = 0; $i<10; $i++) {
        $fp = fopen("/tmp/$i.tmp", "wb");

        $zone = new Defer(function() use ($fp) {
            fclose($fp);
        });

        // кстати, обратите внимание, а вот так можно сделать в Hack — это диалект PHP, здорово же:
        // $zone = new Defer(() ==> fclose($fp));

        // тут какой-то код и куча проверок, причём мы можем выйти в любой момент, а fclose вызовется

        fwrite($fp, "My number is $i");
    }
}

Что тут происходит? У нас тут, как видно, цикл, в котором открываются на запись 10 файлов, в каждый из них выводится некая строка, а посередине торчит неиспользумая переменная с моим новым объектом. В объект передаётся функция, которая будет вызываться (посмотрите на код объекта) в деструкторе. Как только переменная zone перетрётся (следующим её значением, у нас же цикл или при выходе из функции), объект будет помечен к удалению и когда-нибудь удалится.

Важно заметить две вещи: переменная, содержащая объект, должна затираться в нужное нам время и деструктор вызовется обязательно, но когда — мы не знаем. Первое — недостаток, второе — фатальный недостаток. Оба неустранимые.

Для чего нужен третий метод в класс Defer — «__invoke__»? В языке он появился для поддержки анонимных функций и вызовется, если попытаться вызвать объект, как функцию.

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

Напрягитесь, сейчас будет немного тяжело.

fn select<'r, T>(shape: &'r Shape, threshold: f64,
                 a: &'r T, b: &'r T) -> &'r T {
    if compute_area(shape) > threshold {a} else {b}
}

Выше код на «Расте», взятый из руководства по языку. Тут «’r» — это именованный срок жизни. Сроки жизни всех переменных, помеченных одним именем должны пересекаться. Очень грубо говоря, это значит, что язык не должен уничтожать одну переменную с тем же именем жизни раньше других. Пример выше вызовет ошибку компиляции — видно, что возвращаемое функцией значение пытается пережить остальные переменные с тем же именем — они-то уничтожатся при выходе из функции.

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

Вернёмся к ПХП. Тут, конечно, всё и близко не так и цели другие, но немного похоже. Упомянутый мною метод был введён для постоения цепочек уничтожения. Например нам надо освободить ресурсы в определённом порядке (например, нужно прибрать за собой временные файлы и только потом убрать блокировку с lock-файла):

function doAnything()
{
    $fp = fopen('/var/lock/do.lock', 'r');
    flock($fp, LOCK_EX);

    $zone = new Defer(function() use ($fp) { fclose($fp); });

    // делаем что-то полезное от чего остался мусор

    $zone(function() use ($tmpfiles) { array_map('unlink', $tmpfiles); });

    // делаем что-то ещё
}

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

В общем, я считаю, что языку ПХП пошла бы на пользу языковая конструкция, близкая к defer «Гоу», поскольку она очень гармонично решает проблемы процедурного программирования — эффективной работы с процедурным АПИ внешних ресурсов (а в ПХП оно почти всё такое).

9 комментариев

PHP: хинтование возвращаемого типа

Наконец-то в скором времени (вероятно в седьмой версии) в ПХП появится указание типа возвращаемого значения. Голосование по предложению добавить в язык эту возможность завершилось с огромным перевесом: 49 «за» и 3 — «против».

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

function foo(): array {
    return [];
}

Такой синтаксис встречается во многих языках и понятен сразу. Указать можно будет (стандартно для ПХП): имя класса, «array» или «callable». Надеюсь, когда-нибудь можно будет указать и скалярные типы. Пока нет возможности задать тип так, чтобы вместо него можно было вернуть null (иногда это очень удобно), но есть некоторая вероятность, что и эта возможность в языке появится.

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

10 комментариев

Русские имена на «Маке» после разархивации ZIP

Я вчера скачал несколько десятков зип-архивов, внутри которых тысячи файлов с русскими именами. Скачивал через браузер «Сафари» и этот поганец, как у него водится, услужливо мне их распаковал после скачивания. У меня время от времени вылетает из головы эта особенность, в общем, я получил кучу папок и файлов, которые выглядят вот так:

Кракозябры (17.71КиБ)

Эта давнишняя проблема «Мака», проистекающая из родовой травмы зип-архивов — этот архиватор родился ещё во времена ДОСа и для совместимости сохраняет русские имена ещё в той, ДОСовской кодировке. Правда, лет 15 назад я ковырял формат и увидел, что в каких-то метаданных лежит оригинальное имя, но должно быть «Мак» про это поле ничего не знает.

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

Способ употребления такой: складываете всё испорченное в какую-либо папку и запускаете внутри fixzip. В процессе «ремонта» он будет показывать на экране восстановленные имена. Два раза запускать нельзя — имена снова будут испорчены.

38 комментариев

Как скачать документ из АИС ЦГАСО-2

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

С просьбой что-то с этой проблемой сделать, мне написало около десятка человек, сегодня у меня наконец дошли руки посмотреть что изменилось. Как и в прошлый раз надо открыть окно консоли браузера и запустить скрипт (я проверял его в «Хроме»). Плохие новости в том, что скрипт будет работать относительно неспешно — 400 страниц обрабатывается примерно за 5—6 минут.

В конце работы в консоли будет длинная строка, начинающаяся с «dxo.itemsValue» и заканчивающаяся на «];». Её, как и в прошлый раз, надо будет скопировать в файл и передать в параметрах программе.

!function () {
    var id = '[id$=seadragonContainer] div div';
    var bt = '[id$=ForwardBtn]';
    var pages = {};
    var o = document.querySelector(id);

    !function _observe(undefined) {
        var ob = new MutationObserver(function() {
            var imgs = $(id+' img[src*=Pages]:first');
            if (imgs && imgs[0]) {
                var page = imgs[0].src.match(/id=([^&]+)/)[1];
                if (pages[page] == undefined) {
                    pages[page] = true;

                    ob.disconnect();

                    var next = $(bt);
                    if (next && next[0]) {
                        _observe();
                        next[0].click();
                    } else {
                        console.clear();
                        console.log("dxo.itemsValue=['" + Object.keys(pages).join("','")+"'];");
                    }
                }
            }
        });

        ob.observe(o, { childList: true, attributes: true, subtree: true });
    }();

    o.setAttribute('start', true);
}();

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

Внимание: скрипт должным образом я ещё не протестировал (просто не успел), так что на корректную работу пока не рассчитывайте!

45 комментариев

Кодировка JSON

Я всегда полагал, что кодировка данных в формате JSON («джейсон») должна быть ЮТФ-8. Оказывается в стандарте указано только, что она должна быть Юникодом. Более того, в стандарте указаны способы определения разных типов кодировок.

Так как первые два символа данных в формате «джейсон» всегда будут в первой половине таблицы ASCII (в самом деле, как не крути, первый символ — «{» или «[», а далее кавычка, буква «f» («false»), «n» («null»), «t» («true») или цифра), то легко угадать в какой именно кодировке встретился Юникод:

00 00 00 xx  UTF-32BE
           00 xx 00 xx  UTF-16BE
           xx 00 00 00  UTF-32LE
           xx 00 xx 00  UTF-16LE
           xx xx xx xx  UTF-8

Как видите каких-либо маркеров для ЮТФ-8 не выделено, то есть во всех остальных случаях считается, что кодировка ЮТФ-8. Я посмотрел встроенные парсеры ПХП и Пайтона, они ни с какими другими кодировками Юникода, помимо ЮТФ-8, по всей видимости, не работают, то есть нарушают стандарт.

Оставить комментарий

Комментарии в формате JSON

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

{
  "api_host" : "The hostname of your API server. You may also specify the port.",
  "api_host" : "hodorhodor.com",

  "retry_interval" : "The interval in seconds between retrying failed API calls",
  "retry_interval" : 10,

  "auth_token" : "The authentication token. It is available in your developer dashboard under 'Settings'",
  "auth_token" : "5ad0eb93697215bc0d48a7b69aa6fb8b",

  "favorite_numbers": "An array containing my all-time favorite numbers",
  "favorite_numbers": [19, 13, 53]
}

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

38 комментариев

99 бутылок: AppleScript, Applesoft BASIC

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

52. AppleScript — встроенный в операционную систему «МакОС» язык программирования, используемый для мелкой автоматизации. По программе этой не очень очевидно, но язык максимально приближен к английскому, причём многие вещи можно формировать разнообразными способами — так же, как бы это сделал носитель английского языка.

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

(* Песня про пиво. Автор — Евгений Степанищев *)
repeat with beer from 99 to 1 by -1
     set b to bottles(beer)
     log b & " on the wall, " & b & "."
     log "Take one down and pass it around, " & bottles(beer - 1) & " of beer on the wall."
     log
end repeat

log "No more bottles of beer on the wall, no more bottles of beer."
log "Go to the store and buy some more, 99 bottles of beer on the wall."

on bottles(beer)
     if beer = 0 then
 	        return "no bottles"
     else if beer > 1 then
 	        return (beer as string) & " bottles"
     else
 	        return "1 bottle"
     end if
end bottle


51. Applesoft BASIC — Бейсик разработанный компанией Майкрософт, использовавшийся в компьютерах фирмы Эпл, начиная с 1977 года. Ничего особо примечательного, вполне полнофункциональный язык, хоть и не без недостатков тех ранних «бейсиков» — вызов функций по номеру строки, параметров нет, локальных переменных нет, в общем, все прелести.

Я специально использовал конструкцию «ON…GOTO», хоть в ней и не было необходимости — хотел показать аналог конструкции switch. SGN тут — встроенная функция нахождения знака выражения.

000 REM Песня про пиво, автор — Евгений Степанищев
001 HOME: TEXT
010 FOR beer = 99 TO 1 STEP -1
020 b = beer: GOSUB 100
030 ? bottles$; " on the wall, "; bottles$; "."
035 b = beer - 1: GOSUB 100
040 ? "Take one down and pass it around, "; bottles$; " of beer on the wall."
050 ?
060 NEXT
070 ? "No more bottles of beer on the wall, no more bottles of beer."
080 ? "Go to the store and buy some more, 99 bottles of beer on the wall."
090 END
099 REM
100 ON SGN(b-1)+2 GOTO 120, 130, 110
110 LET bottles$ = STR$(b) + " bottles": RETURN
120 LET bottles$ = "no bottles": RETURN
130 LET bottles$ = "1 bottle": RETURN

Оставить комментарий

Конструкция «??»

В ПХП 7 появилась, наконец, конструкция, которая должна была быть в языке с первых версий:

echo $_GET['var'] ?? 'default'; // PHP 7
echo isset($_GET['var']) ? $_GET['var'] : 'default'; // PHP 5.6 и ниже

Очень похоже на тернарный оператор без средней части, появившийся в версии 5.3, но с одним отличием: проверяется не ложь или истина, а существование.

Теперь в ПХП можно писать намного более эмоционально:

echo X-D ? :-D ??!! O_o; // это работает в PHP 7, я даже могу сказать что это значит.

Может кому-то пригодится, я собирал у себя на «Маке» текущую версию ПХП 7 при помощи следующих команд:

git clone https://github.com/php/php-src.git
cd php-src

brew install re2c
brew info bison27
PATH=/usr/local/opt/bison27/bin/:$PATH

./buildconf
./configure --prefix=/opt/php-5.7 --disable-all
make
make install

4 комментария

JSOND

JSOND — это ещё один «дешёвый» способ увеличить производительность вашего проекта на ПХП, наряду с igbinary. Исходный код есть на PECL, для всех систем существуют пакеты. Насколько я знаю, есть планы по замене стандартного расширения JSON в ПХП7 на JSOND.

Для интереса погонял стандартный тест пакета на одной из наших машин (мы там вчера перешли на ПХП 5.5 наконец-то, стоит последняя версия), вот результаты (меньше — лучше):

STR: {"i": 23, "array": [1, null, false, true, ["aha", "baba", 23, {"test": 23}]]}
JSON: time for 100000 iterations: 1.096384
JSOND: time for 100000 iterations: 0.353658
STR: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
JSON: time for 100000 iterations: 0.700904
JSOND: time for 100000 iterations: 0.202519
STR: {"a": 23.2234232}
JSON: time for 100000 iterations: 0.398129
JSOND: time for 100000 iterations: 0.083977
STR: сериализуется очень длинная строка
JSON: time for 100000 iterations: 0.392990
JSOND: time for 100000 iterations: 0.341663
STR: сериализуется переменная $_SERVER
JSON: time for 100000 iterations: 4.983888
JSOND: time for 100000 iterations: 2.321528

Что приятно, АПИ полностью совместимо, так что проблем нет никаких — в вызовах надо просто добавить одну букву. Есть так же несложный способ самостоятельной замены стандартного JSON на этот модуль в интерпретаторе.

13 комментариев

Процедурное настоящее PHP

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

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

Например, есть такая вещь как «постоянные соединения» (persistent connections) — это специальные режим, в котором на уровне процесса PHP FastCGI соединения с чем-либо не закрываются и выдаются в следующий запуск кода уже открытыми. Чудесная вещь, ведь операция соединения, например, с «Ораклом» очень дорогая.

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

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

А пока этого не произошло, блокировки на базе могут привести к коллапсу.

В общем-то, есть хороший выход — у нас же есть сервис-объект для работы с базой? Вставим в деструктор принудительный откат транзакции (rollback) и все дела! Если транзакция была не завершена в силу фатальной ошибки, она откатится и всё! Ведь деструктор должен вызвааться на уничтожение объекта!

Как показывает практика, должен, но не обязан. Ниже простой код:

class ImportantToClose {
    public function __destruct()
    {
        // закрываем что-то важное
        echo "Вызвался деструктор\n";
    }
}

$itc = new ImportantToClose;

register_shutdown_function(function()  {
    echo "Вызвалась shutdown function\n";
});

set_time_limit(1);

for(;;);

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

PHP Fatal error: Maximum execution time of 1 second exceeded in fatal.php on line 18

Fatal error: Maximum execution time of 1 second exceeded in fatal.php on line 18 Вызвалась shutdown function

Можно ещё попробовать записать что-нибудь в файл, результат будет тот же — деструктор не вызовется, тогда как-то старая добрая процедурная shutdown function — да. Поэтому, если у вас есть нечто важное, что необходимо аккуратно закрывать, не делайте это в деструкторе, делайте в этой магической функции. В нашем случае, можно реализовать в каждом сервисе метод close и в этой функции вызвать этот метод для кажого открытого сервиса.

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

10 комментариев

ENUM

Не понимаю, почему в «Оракле» (это СУБД) нет такой полезной вещи как тип ENUM. Вещь совершенно необходимая, как по мне. Вот посмотрите, что понятнее:

CREATE TABLE event_queue (
     …
     status ENUM('done', 'new', 'failed', 'running'),
     …
);

или

CREATE TABLE event_queue (
     …
     status NUMBER(1), -- тут у нас 4 значения будет
     …
);

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

Кроме удобства программиста, есть очевидная польза и для самой СУБД. Например, возьмём запрос к таблице выше, где мы определили поле status как число. Мы знаем, что статусов у нас всего четыре, это знаем мы, но это не знает «Оракл». Теперь взглянем на два совершенно эквивалентных с точки зрения программиста запроса (ведь он знает, что значений четыре) и посмотрим планы их выполнения:

SELECT *
FROM event_queue
WHERE status IN (1,2)
AND target='search' AND attempts < 3

 

План №1 (17.65КиБ)

 

SELECT *
FROM event_queue
WHERE status NOT IN (3,4)
AND target='search' AND attempts < 3

 

План №2 (17.77КиБ)

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

Вообще, есть несколько способов имитировать тип ENUM: добавить ограничение (check), ввести новый тип. Но первое никак не сказывается на плане и требует перехода к строковому типу, а со вторым я ещё не эксперементировал.

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

21 комментарий

← 1 26 27 28 29 30 67 →