Pull to refresh

Comments 58

Поле uint32_t nTableSize избыточно. Оно хранит 29 различных значений (5 бит), а рядом есть uint16_t reserved. Можно ещё на 4 байта сократить. :)
Выравнивание структуры в памяти, ни когда не занимались? Дает ощутимый прирост производительности. Да, компилятор может сам выравнять, Про компилятор писать не нужно, у каждого он свой.
Ну а если часть в какой нибудь set засунуть :) (По моему что то такое битовое было в с/с++)
Я не говорил, что надо заменить на поле в конкретно 5 бит. Машинный байт вполне подойдёт под это редко адресуемое поле.
Можно долго спорить, но я бы оставил 4 байта.
Многозадачность, обращение в поле тормозит всю память системы и т.д. Пусть сидит в «ровном месте»
Что? reserved же часть объединения, и это поле используется исключительно для того, что бы размер структуры был всегда константен… Собственно по этой же причине размер поля flags 4 байта а не бит. И не понятно что вы собирались сократить…
reserved это неиспользуемое поле, просто overhead.
Купить книжку по C++ (можно и по C) и читать, читать, читать, читать. Про различия struct и union. До наступления полного просветления.
Вот именно, пойдите и почитайте. Поле reserve это не поле union-а, а поле структуры. Оно не используется — в него не пишутся данные и не читаются, и служит оно не для туфты, что Fesor написал, а чтобы верно адресовать 8-битные поля той же структуры на LE и BE машинах.

Впрочем, что я Си php-шникам объясняю. Купите книжку по Си и читайте.
Тяжёлый случай.
Поле reserve это не поле union-а, а поле структуры.
А ничего, что это поле другой структуры? Которая, в свою очередь, расположена в unionе?

Если вы предлагаете урезать поле flags до 16 бит и/или перенести в него nTableSize — то так и пишите. Я не знаю — используют ли разработчики больше 16 бит в поле flags или нет, но если не используют, то, действительно, можно откуcить пару байт от поля flags пару байт и перенести из в nTableSize. Но использовать reserve вместо nTableSize, увы, нельзя — о чём вам Fesor и написал. И он совершенно прав.
Вы, похоже, никогда не писали на Си. Меня эта ветка уже достала, поэтому разжёвываю последний раз.

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

В данном случае uint16_t reserve занимает два байта и «двигает» два следующих за ним поля на эти два байта. Таким образом в наименее значащих байтах структуры, при представлении структуры в виде машинного 32-битного значения, оказывается одно и то же поле, вне зависимости от порядка байтов в машинном слове.

Это нужно, в общем-то, только для одного: оптимизация доступа к памяти. На x86 архитектуре, на которой обычно запускается php-сервер, скорость доступа к 8-битным и 16-битным ячейкам памяти сильно меньше, чем к 32-битным. Кроме того, имена flags намекают на использовании этого отрезка памяти в виде флагов, а битовые операции с ними тоже быстрее работают с 32-битными значениями.

Таким образом появляется поле uint32_t flags, занимающее то же адресное пространство, что и структура с полем reserve. Наименее значащий байт этого поля занимает 8-битное поле flags. Поэтому битовые операции в пределах 8 бит над обоими полями flags будут полностью эквивалентны, и не будут модифицировать какие-либо биты в пределах наименее значащих восьми. (почему)

Именно поэтому вы можете просто взять и использовать простанство поля reserve для хранения каких-то данных. Ранее это уже было сделано, так появилось поле nApplyCount.

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

/cc Fesor — также рекомендую к прочтению.
Вы, похоже, никогда не писали на Си.
А вы, похоже, писали, но не так чтобы много.

Вы здесь заложились на кучу факторов, которые из состава структуры напрямую никак не следуют. В частности на то, то что
Таким образом появляется поле uint32_t flags, занимающее то же адресное пространство, что и структура с полем reserve
что, я извиняюсь, ниоткуда не следует. Кто вам сказал, что uint32_t flags — это оптимизация и ничего больше? Кто вам сказал, что никаких других флагов, кроме тех, которые есть в 8-битном поле flags в природе не бывает? Почему не может быть так, что при некоторых условиях (скажем в зависимости от значение поля nTableSize, LOL) у вас не могут появится там дополнительные флаги? Я ничего не знаю про PHP, но в GCC, с которым я за последние лет 10 успел пообщаться изрядно так бывает сплошь и рядом: в одной ветке у вас будут использоваться uint8_t flags и nApplyCountтам, в этой ветке, поле reserved таки будет именно reserved), а в другой ветке — у вас будет 30 флагов поле uint32_t flags и никакого nApplyCount. И как вы тут будете избавляться от nTableSize в «большой» структуре?

Вот так просто бездумно перенести nTableSize нельзя, уж извините. Для этого нужно не только точно знать что там происходит в коде сейчас, но и иметь какую-то информацию о том, что там планируется делать в будущем. Как вы смогли залезть в голову к разработчикам PHP чтобы узнать об их планах — для меня, извините, загадка. Тем более странны удивляет ваша реакция на указание на вашу ошибку: вместо того, чтобы сказать что-нибудь типа «я знаю человека, который этот код разрабатывал и он мне сказал, что uint32_t flags — это оптимизация и ничего больше» (после чего весь ваш гонор имел бы смысл) вы поёте какие-то странные песни про названия полей и прочую муру.
Вы, уж пожалуйста, ни меня, ни разработчиков php за идиотов не держите. Они бы поставили какой-то комментарий, если бы были такие уловки, как вы описали. А я перед своим первым сообщением проверил исходный код. Там используется 7 бит во флагах. В паре местах придётся поправить код, но в остальном всё сооветствует здравому смыслу.
Они бы поставили какой-то комментарий, если бы были такие уловки, как вы описали.
С каких это пор стандартное использование union'ов требует отдельных комментариев? Это не уловки, это стандартный способ использования union'ов. Собственно единственно возможный в соответствии со стандартом.

Комментариев требует как раз использование нестандартных уловок, которые, строго говоря, делают вашу программу потенциально неработающей. В частности описанные вами трюки с двумя flags, вообще говоря, не работают (вернее работают на GCC в некоторых случаях, но может не работать на других компиляторах). Дело в том, что компилятор имеет право считать, что изменение 8-битового поля flags никак не влияет на 32-битовое поле flags. И наоборот. Несколько лет назад на Хабре была статья, где всё это осуждалось. А вне Хабра это обсуждалось ещё раньше.

А я перед своим первым сообщением проверил исходный код.
Вот с этого и нужно было начинать. С того, что вы проверили код, обнаружили, что в нём используется непереносимое расширение GCC (о чём, разумеется, разработчики даже не удосужились написать в комментариях к типу, что вряд ли делает им честь) и что исходя из вот всего этого можно перенести nTableMask.

А вот «наезжать» на людей, которые предполагают что unionы используются так, как они должны использоваться… не стоит.
С каких это пор стандартное использование union'ов требует отдельных комментариев? Это не уловки, это стандартный способ использования union'ов. Собственно единственно возможный в соответствии со стандартом.
Вы как-то поздно начали стандартом махать. Надо было здесь начать: с каких это пор стандартное использование функций языка Си требует отдельных комментариев?

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

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

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

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

Ваши фантазии? Это как раз и есть непереносимость. Вы уж определитесь — у вас 32-битный flags существует для оптимизаций и используется вперемешку с 8-битным или нет. И если нет — тогда зачем он вообще нужен и зачем нужен обсуждаемый union?

поскольку для корректной работы по стандарту достаточно либо не брать указатели на непосредственно его поля
Недостаточно. Это — как раз и есть расширение GCC. По стандарту — если вы пишете uint8_t, то и читать должны uint8_t, если пишете uint32_t, то и читать должны uint32_t.

В противном случае — смотри «Annex J»: The values of bytes that correspond to union members other than the one last stored into… are unspecified.

То есть записав ваш любимый uint8_t и прочитав uint32_t вы обязательно получите-таки какое-то значение и винчестер вам никто не отформатирует, но вот беда: оно может не иметь никакого отношения к тому, что в union было записано.
Я просто не понимаю вот этот ваш комментарий:
Поле uint32_t nTableSize избыточно. Оно хранит 29 различных значений (5 бит), а рядом есть uint16_t reserved.


Насколько я понимаю, 29 вариантов взялось это 2^3 — 2^32. Это поле используется так же при формировании nTableMask (nTableSize-1) которое в свою очередь используется непосредственно для хеширования. Хранение этого поля в uint32 это неплохая такая оптимизация которая существенно ускоряет вычисление хэшей в массиве.

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

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

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

Даже с новым хранением размера операция (8 << packedSize) - 1 достаточно лёгкая, а сдвиг придётся делать в любом случае.

Так что, теперь можно убрать сразу 8 байтов!
В остальном khim уже описал другие мои недопонимания в силу слишком малого опыта работы с Си.
Это не малый опыт работы с Си. Это малый опыт работы с разработчиками PHP. Потому что когда вы стреляете себе между яйцами из гранатомёта, то правила хорошего тона говорят, что этот момент стоит обговорить хотя бы в комментариях, а не предполагать, что «все так делают».

В качестве примера посмотрите на то, как в NaCl'е откомментированы функции, которые работают «на грани фола»: семейство функций SignExtendXXX — комментарий в строке 184). Потому что если вы сделаете что-нибудь странное (ну, скажем, используете int_fast32_t вместо int32_t), то можете легко свалиться в undefined behavior и потом долго ловить ошибки.
Это указано в первом абзаце
Пропустил, посыпаю голову пеплом. Сразу увидел знакомые значения из оригинальной статьи.
Можно спросить, а там про юникод в ядре ничего не слышно?
Удивился, что никто не заметил, но программа создающая массив из 100000 чисел это вот:
$startMemory = memory_get_usage();
$array[] = range(1, 100000);
echo memory_get_usage() - $startMemory, " bytes\n";
Ага, утро 31-го на работе дает о себе знать.
Понял свою ошибку, только когда допил вторую порцию кофеина.
С ней что-то не так (или смутил двумерный массив)?
Я правильно понимаю, что совершенно внутренняя вещь, такая, как конкретная реализация работы с hashtable, не появится в очередном минорном релизе, а ожидается в мажорном, причем таком, что мы увидим еще не скоро?

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

В этом смысле логику понять не всегда можно, и хочется увидеть язык не как постоянно меняющийся, а как что-то, на чем можно сделать скрипт/страничку/сайт/панель управления, и оставить их в работе в надежде, что и через несколько лет не вылезет что-то новенькое, кардинально несовместимое со старым… Для недорогих хостингов, кстати, тема более чем важная: там часто обновление php/mysql накатывают не глядя, просто потому, что некая система обновлений нашла его, а такая drop-in замена старого php на новый совершенно неожиданно может в иных скриптах создать кое в чем новое поведение (

P.S. И таки да — Юникод. Говорим про 7-ку, даже не про 6-ку. Какой там год на дворе к ее выходу будет?..
Можете привести пример где в минорном релизе ломается совместимость?

P.S. И таки да — Юникод. Говорим про 7-ку, даже не про 6-ку. Какой там год на дворе к ее выходу будет?..


Год будет предположительно 2015, подробнее тут wiki.php.net/rfc/php7timeline
6й версии не будет, об этом много написано, после 5-й будет 7-я ;)
> Можете привести пример где в минорном релизе ломается совместимость?

Пример:

В функции htmlspecialchars есть параметр $encoding. Его значение по умолчанию — ISO-8859-1 для PHP до версии 5.4.0, и UTF-8 начиная с версии 5.4.0. Если строка содержит некорректные (в данной кодировке) символы, то функция возвращает пустую строку.

Если у вас строки имеют кодировку _не_ UTF-8 (1251, например) и вы используете эту функцию с параметром $encoding по умолчанию, то при переходе на php 5.4.0 все строки станут некорректными с точки зрения htmlspecialchars, и вывод этой функции обнулится.
так в релизе написано что до 5.3 всё обратно совместимо, а после нет.
По этому часто есть хостинги 5.3, 5.4, 5.5
И пакеты в unix/linux именуются как php, php54, php55
Как можно не глядя закатать такое обновление?
Система не даст это сделать при обычном yum update

и вообще 5.4, 5.5 содержат половину функционала 6,0, которого не будет.
Суть коммента achekalin была как раз в этом — в PHP считается *нормальным* делать минорные версии несовместимыми. Хотя по правилам семантического версионирования, обновление 5.3 → 5.4 является минорным, и совместимость оно рушить не должно.
Правила правилами, маркетинг маркетингом. Взгляните на java.
А можно подробнее, что они содержат и чего не будет в итоге в 7 версии. Можно ссылочкой ответить.
И вы наверное с python ни когда не работали :)
Да, это самое ужасное несовместимое изменение, из-за которого желаю гореть в аду разработчикам php.
Масла в огонь подливает то, что на эту функцию не действует глобально установленная локаль, и единственный выход — заменять все вхождения htmlspecialchars на свою функцию, которая будет вызывать с нужной локалью. Что заставляет править не только свои проекты, но и чужие. Про закодированные вообще молчу, там жопа.
Ну не идиоты ли, есть ведь setlocale, зачем же всё ломать.
Обнимемся, коллега. Причём ведь никакой же срочной необходимости «улучшать» именно эту функцию не было. Просто у кого-то улучшалка зачесалась — и сломали совместимость на ровном месте.
И ладно бы пофиксили в следующих релизах, добавили бы действие setlocale, так нет же, сделали говнецо без глобальных настроек на ровном месте.
Вот я не могу понять, причем тут setlocale? Почему решение использовать директиву default_charset пришло только с релизом php5.6 это уже другой вопрос.

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

Ну а закодированный код… что поделать. То есть как, варианты на самом деле имеются — пересобрать PHP с нужными параметрами, окольными путями переопределить функцию на свою… а еще лучше — не обновляться.
Есть миллион проектов и сторонних библиотек, где htmlspecialchars никуда не выносилось, и вот так ломать обратную совместимость — это свинство. И мажорная или минорная версия тут не причём, в нормальном энтерпрайзе за это по башке бьют, но этим индусским разработчикам всё пофигу.
default_charset или setlocale — это неважно, главное что никаким образом нельзя сменить кодировку по умолчанию для htmlspecialchars.
Да, конечно, и именно по той причине что миллионы проектов используют htmlspecialchars не указывая явно кодировку, но при этом данных у них хранятся в UTF-8.

А вот то что никто так и не добавил никаакой информации по этому поводу в документации (кроме комментариев и багов) и вообще как-то этот вопрос опустился это уже странно. Значит не так много людей пострадало.

Ну и да, замечу что с тех пор релиз менеджеры PHP стали чуть серьезнее относиться к подобным вещам.
Если бы стали серьёзнее, то как я писал выше добавили бы учёт глобального setlocale. Ведь без него даже регистронезависимые функции не работают, и он уже правильно выставлен у многих.
Да что вы пристали со своим setLocale, сделали через default_charset и славно, пускай и только в версии 5.6. И да, у многих что локаль что кодировка по умолчанию стоит дефолтная.
Как я сказал выше — да, у многих, без этого не работают никакие строковые функции типа str_ireplace.
На php5.5 полутора-двухлетней давности сайт на laravel3 валится с ошибкой «Cannot redeclare function yield()», например.
А где Вы нашли хостинг, который сам, от балды, накатывает обновления? Я видел только 2 типа: либо как стоял 5.2, так и стоит по сей день, либо в панели управления есть возможность выбора желаемой версии и доступ к некоторым параметрам php.ini.

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

Подробнее про phpng:
habrahabr.ru/post/222219/
habrahabr.ru/company/badoo/blog/238153/
wiki.php.net/phpng
Да, действительно, поменялись многие внутренние структуры данных, просто описание всех изменений несколько обширно для одной статьи. Косвенно о новом формате zval упомянуто в статье.
совершенно внутренняя вещь, такая, как конкретная реализация работы с hashtable

А как же бинарная совместимость (собственноручно написанных) нативных расширений? Подобные проблемы вылезут в виде segmentation fault черте где, а не в виде сообщения интерпретатора о синтаксической ошибке, так что не бекпортировать такие вещи вполне разумное решение.
Ну то есть остальные внутренние несовместимости побоку с 5.5 на 5.6? Т.е. эта оптимизация заслуживает bump мажорной версии, а многое другое — нет?

По мне так не ломали бы в пределах минорных версиях совместимость, а делали бы только баги и проблемы безопасности, а уж революции бы творили в мажоных.
Что значит не ломайте обратную совместимость?
PHP ломает её в крайне малом количестве случаев. Плюс любые версии в пределах одной обратную совместимость не ломают.

И не стоит рассматривать изменение второй цифры в версии как минорное обновление. У PHP это мажорные релизы. Минорные обновления у PHP это изменение изменение третьей цифры в версии. Изменение первой цифры версии при следующем релизе, обусловлено тем что в нём будет очень много важных изменений.
Если упростить, то первая цифра это маркетинговая штука. То есть просто что бы обозначить поколение языка. Скажем после выхода PHP7 говорить что мол «Я пишу на версии PHP5» уже не так круто. Вот и все. А уже вторая цифра обозначает номер релиза. В пределах же минорных релизов обратная совместимость сохраняется почти всегда.
Для справки на примере из статьи (всё для 64-бит):
PHP 5.6 — 13.97 MiB
PHP 7.0 — 4.00 MiB
HHVM 3.5.0-dev — 2.00 MiB
Доклад Стогова на DevConf 2014 — www.youtube.com/watch?v=RnmyT7SLbVU — стоит учесть, что предполагаемая оптимизация не будет работать с динамическими паттернами типа «Фабрика». Но оно того стоит.
Изучаю как устроен PHP и вот для наглядности приведу пример, как увеличивается в 2 раза «Корзина».

static HashTable *sort(HashTable *ht)
{
   HashTable *result;

   zval *value;

   php_printf("size hashtable: %d used %d ", ht->nTableSize, ht->nNumUsed);
   php_printf("count: %d ", ht->nNumOfElements);
....
}


php > ms_sort([]);
size hashtable: 8 used 0 count: 0
php > ms_sort([0]);
size hashtable: 8 used 1 count: 1 item: 0.000000
php > ms_sort([0,1]);
size hashtable: 8 used 2 count: 2 item: 0.000000 item: 1.000000
php > ms_sort([0,1,2]);
size hashtable: 8 used 3 count: 3 item: 0.000000 item: 1.000000 item: 2.000000
php > ms_sort([0,1,2,3]);
size hashtable: 8 used 4 count: 4 item: 0.000000 item: 1.000000 item: 2.000000 item: 3.000000
php > ms_sort([0,1,2,3,4]);
size hashtable: 8 used 5 count: 5 item: 0.000000 item: 1.000000 item: 2.000000 item: 3.000000 item: 4.000000
php > ms_sort([0,1,2,3,4,5]);
size hashtable: 8 used 6 count: 6 item: 0.000000 item: 1.000000 item: 2.000000 item: 3.000000 item: 4.000000 item: 5.000000
php > ms_sort([0,1,2,3,4,5,6]);
size hashtable: 8 used 7 count: 7 item: 0.000000 item: 1.000000 item: 2.000000 item: 3.000000 item: 4.000000 item: 5.000000 item: 6.000000
php > ms_sort([0,1,2,3,4,5,6,7]);
size hashtable: 8 used 8 count: 8 item: 0.000000 item: 1.000000 item: 2.000000 item: 3.000000 item: 4.000000 item: 5.000000 item: 6.000000 item: 7.000000
php > ms_sort([0,1,2,3,4,5,6,7,8]);
size hashtable: 16 used 9 count: 9 item: 0.000000 item: 1.000000 item: 2.000000 item: 3.000000 item: 4.000000 item: 5.000000 item: 6.000000 item: 7.000000 item: 8.000000



Sign up to leave a comment.

Articles