php shared memory class

Демоны, shared memory и семафоры

Итак, продолжим тему системного программирования на php.

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

Работа с shared memory. Основы.

Давайте попробуем написать простой пример сохранения данных.

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

Работа с семафорами

Итак, в общих чертах уже должно быть понятно как работать с shared memory. И осталось выяснить пару ньюансов, например: “А что сделать если два процесса одновременно захотят записывать в один блок памяти?” или “Как сохранять бинарные файлы произвольного размера?”.

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

В коде это ещё выглядит ещё понятнее:

Теперь можно с помощью консольной утилиты md5summ сравнить оба файла, полученный и исходный. Или же, можно запустить полученный файл в графическом редакторе или в чём он у вас должен запускаться?

На этом знакомство с shared memory и семафорами я считаю законченным. А на домашнее задание хочу вас попросить написать код, который в демоне будет использовать семафоры и общую память.

Источник

Разделяемая (shared) память

Содержание

User Contributed Notes 8 notes

Since there is no mention of the (lack of) need for locking here, I took a look into the shmop.c extensions code. So correct me if I’m wrong, but the shmop.c extension uses memcpy() to copy strings to and from shared memory without any form of locking, and as far as I know, memcpy() is not atomic.

If that’s true as I suspect, then these ‘easy to use’ functions are not so ‘easy to use’ any more and have to be wrapped in locks (e.g. semaphores, flocks, whatever).

I have written a script to highlight the superiority of shared memory storage.
Although it doesn’t use the shmop function, the underlying concept is similar.
‘/shm_dir/’ is a tmpfs directory, which is based on shared memory, that I have mounted on the server.

Below is the result on an Intel Pentium VI 2.8 server:

IO test on 1000 files
IO Result of Regular Directory : 0.079015016555786
IO Result of Shared Memory Directory : 0.047761917114258

IO test on 10000 files
IO Result of Regular Directory : 3.7090260982513
IO Result of Shared Memory Directory : 0.46256303787231

IO test on 40000 files
IO Result of Regular Directory : 117.35703110695 seconds
IO Result of Shared Memory Directory : 2.6221358776093 seconds

The difference is not very apparent nor convincing at 100 files.
But when we step it up a level to 10000 and 40000 files, it becomes pretty obvious that Shared Memory is a better contender.

// Your regular directory. Make sure it is write enabled
$setting [ ‘regular_dir’ ] = ‘/home/user/regular_directory/’ ;

// Your shared memory directory.
$setting [ ‘shm_dir’ ] = ‘/shm_dir/’ ;

// Number of files to read and write
$setting [ ‘files’ ] = 40000 ;

$endtime = time ()+ microtime ();

I wrote a php memcache back in 2003 as a sort of proof of concept
it is use on a few machines for doing heavy page load caching.
it works very well.
Following are some of the core functions I made

Windows does support shared memory through memory mapped file. Check the following functions for details:

What you need to realise is that sysvshm is extremly php oriented in it’s ability, it’s quite a kludge interfacing other NON PHP utilities with it. For example have you tried using sysvshm to read an shm segment NOT created by php? It’s not possible, because sysvshm uses a proprietry format, in essense it can ONLY be used within PHP unless of course you take time to figure out this format.
So basically, the purpose of shmop is to provide a symple interface to shared memory that can be used with OTHER NON php shm creators.

Hope this clears it up.

The shmop implementation as described in this help page is acttually merely a ramdisk / tmpfs that exists only within php, and even only on the linux servers. Or am I missing something?

On windows, the very same functionality can easily be achieved by creating such disk.

This would greatly improve performance of many PHP applications, and could save a lot of burden on server memory. Especially if those variables could be classes containing functions.

You could implement that it is up to the programmer to guard atomicity.

Such superglobal would be feasible on windows servers as well.

Источник

Работа с памятью (и всё же она есть)

php shared memory class. Смотреть фото php shared memory class. Смотреть картинку php shared memory class. Картинка про php shared memory class. Фото php shared memory classСуществует распространенное мнение, что «рядовому» PHP разработчику практически не нужно заботиться об управлении памятью, однако «заботиться» и «знать» всё же немного разные понятия. Попытаюсь осветить некоторые аспекты управлению памятью при работе с переменными и массивами, а также интересные «подводные камни» внутренней оптимизации PHP. Как вы сможете убедиться, оптимизация это хорошо, но если не знать как именно она «оптимизирует», то можно столкнуться с «неочевидными граблями», которые могут вас заставить изрядно понервничать.

Общие сведения

Небольшой ликбез

Переменная в PHP как бы состоит из двух частей: «имени«, которое хранится в hash_table symbol_table, и «значения«, которое хранится в zval контейнере.
Такой механизм позволяет создавать несколько переменных ссылающихся на одно значение, что в отдельных случаях позволяет оптимизировать потребление памяти. О том, как это выглядит на практике будет написано далее.

Наиболее частыми элементами кода, без которых сложно себе представить более менее функциональный скрипт, являются следующие моменты:
— создание, присвоение и удаление переменных (чисел, строк и т.п.),
— создание массивов и их обход (в качестве примера будет использована функция foreach),
— передача и возврат значений для функций/методов.

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

Первый пример работы с памятью

И простой первый пример теста потребления памяти для строки:

include ( ‘func.php’ ) ;
echo «String memory usage test.\n\n» ;
$base_memory_usage = memory_get_usage ( ) ;
$base_memory_usage = memory_get_usage ( ) ;

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

Результат кода вполне очевиден:

Start
Bytes diff: 0
String value setted
Bytes diff: 15448
String value unsetted
Bytes diff: 0

Тот же самый пример, но вместо unset($a) используем $a=null;:

Start
Bytes diff: 0
String value setted
Bytes diff: 15448
String value set to null
Bytes diff: 76

Как видите, переменная не была полностью уничтожена. Под нее остается выделенным еще 76 байт.
Достаточно прилично, если учесть, что ровно столько же выделяется и под переменные типа boolean, integer, float. Речь идет не об объеме памяти, выделяемой под значение переменной, а о полном потреблении памяти для хранения сведений о присвоенной переменной (zval контейнер со значением и само имя переменной).
Так что если вы хотите освободить память при помощи присвоения, то не является принципиальным присвоение именно null значения. Выражение $a=10000; даст тот же результат для расхода памяти.

В документации PHP сказано, что приведение к null уничтожит переменную и ее значение, однако, по данному скрипту видно что это не так, что собственно является багом (документации).

Зачем использовать присвоение null, если можно unset()?
Присвоение — это присвоение, (спасибо КО), то есть изменяется значение переменной, соответственно, если новое значение требует меньше памяти, то она высвобождается сразу, однако это требует вычислительных ресурсов (пусть и сравнительно немного).
unset() в свою очередь освобождает память, выделенную под имя переменной и ее значение.
Отдельно стоит упомянуть момент, что unset() и присвоение null совершенно по разному работают со ссылками на переменные. Unset() уничтожит только ссылку, в то время как присвоение null изменит значение, на которое ссылаются имена переменных, соответственно все переменные станут ссылаться на значение null.

Примечание:
Встречается заблуждение, что unset() является функцией, однако, это не верно. unset() — это языковая конструкция (как например if), о чем прямо сказано в документации, соответственно ее нельзя использовать для обращения через значение переменной:

Немного дополнительной информации для праздных размышлений (при изменении примера выше):
$a = array();
выделит 164 байта, unset($a) всё вернет.

class A < >
$a = new A();
выделит 184 байта, unset($a) всё вернет.

$a = new stdClass();
выделит 272 байта, но после unset($a) «утекут» 88 байт (куда именно и почему они утекли, мне пока не удалось выяснить).

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

Массивы

Массивы в PHP «съедают» достаточно памяти, и именно в них как правило хранят значительные объемы данных при обработке, поэтому следует очень аккуратно относиться к работе с ними. Однако, работа с массивами в PHP имеет свои «прелести оптимизации» и об одном из таких моментов, связанных с потреблением памяти, стоит упомянуть.

? php
include ( ‘func.php’ ) ;
echo «Array memory usage example.» ;
$base_memory_usage = memory_get_usage ( ) ;
$base_memory_usage = memory_get_usage ( ) ;

Посмотрите на вывод:

Array memory usage example.Base usage.
Bytes diff: 0
Array is set.
Bytes diff: 61940
In FOREACH cycle.
Bytes diff: 77632
In FOREACH cycle.
Bytes diff: 93032
In FOREACH cycle.
Bytes diff: 108432
In FOREACH cycle.
Bytes diff: 123832
Usage right after FOREACH.
Bytes diff: 61940
Array unset.
Bytes diff: 0

Получается, что в последней итерации цикла foreach в данном случае потребление массивом памяти возросло в два раза, хотя по самому коду это не очевидно. Но сразу после цикла, потребление памяти вернулось к прежнему значению. Чудеса да и только.
Причиной тому является оптимизация использования массива в цикле. На время работы цикла, при попытке изменить исходный массив, неявно создается копия структуры массива (но не копия значений), которая и становится доступной по завершению цикла, а исходная структура уничтожается. Таким образом, в вышеприведенном примере, если вы присваиваете новые значения исходному массиву, то они не будут заменены сразу, а для них будет выделена отдельная память, которая будет возвращена по выходу из цикла.
Этот момент очень легко пропустить, что может привести к значительному потреблению памяти на время работы цикла с большими массивами данных, например при выборке из БД.

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

Коварный пример №2
Чуть-чуть изменим код.

Сам код цикла мы никак не меняли, единственное что мы изменили, это увеличили счетчик ссылок на исходный массив, но это в корне поменяло работу цикла:

Bytes diff: 0
Array is set.
Bytes diff: 61940
In FOREACH cycle.
Bytes diff: 61988
In FOREACH cycle.
Bytes diff: 61988
In FOREACH cycle.
Bytes diff: 61988
In FOREACH cycle.
Bytes diff: 61988
Usage right after FOREACH.
Bytes diff: 61940
Array unset.
Bytes diff: 0

Bytes diff: 0
Array is set.
Bytes diff: 61940
In FOREACH cycle.
Bytes diff: 61940
In FOREACH cycle.
Bytes diff: 61940
In FOREACH cycle.
Bytes diff: 61940
In FOREACH cycle.
Bytes diff: 61940
Usage right after FOREACH.
Bytes diff: 61940
Array unset.
Bytes diff: 0

Передача по ссылке или передача через копирование

Рассмотрим случай, «что делать» если требуется передать в метод или функцию (или вернуть из них), какое-либо очень большое значение. Первым очевидным решением обычно рассматривают использование передачи/возвращения по ссылке.
Однако в документации по PHP сказано: Не используйте возврат по ссылке для увеличения производительности. Ядро PHP само занимается оптимизацией.
Попытаемся разобраться в том, что же это за «оптимизация».

Для начала самый простой пример (пока без передачи аргументов):

Start
Bytes diff: 0
String value setted
Bytes diff: 15496
String value unsetted
Bytes diff: 0

В результате получим вывод:

Bytes diff: 0
String value setted
Bytes diff: 30896
String value unsetted
Bytes diff: 0

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

Данный пример даст выход:

Bytes diff: 0
String value setted
Bytes diff: 46704
String value unsetted
Bytes diff: 0

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

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

echo «Array memory usage example.» ;
$base_memory_usage = memory_get_usage ( ) ;
$base_memory_usage = memory_get_usage ( ) ;

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

Исключительно в познавательных целях, стоит обратить внимание на эти два значения:

Примечание:
В PHP5 (в отличие от PHP4), все объекты по-умолчанию передаются по ссылке, хотя по факту, это неполноценная ссылка. См. эту статью.

Краткие выводы

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

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

PS: Можно было бы разбить это на несколько статей, но не вижу в этом смысла, так как подобную информацию лучше всё же хранить «вместе». Полагаю тем, кому данная информация несет практический смысл, так будет удобнее. Тестировалось на PHP 5.3.2 (Ubuntu 32bit), так что ваши значения по выделенным байтам могут отличаться.

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

Источник

Использование разделяемой памяти в PHP

Перевод: Крушняков Кирилл
13.05.2004

Концепция IPC слагается из нескольких компонентов. Термин подразумевает различные механизмы обмена данными между процессами, стартовавшими в одной системе. IPC позволяет избежать создания огромного приложения с большим набором функций всех различных назначений и заменить его на использование отдельных, малых приложений, способных обмениваться данными между собой. Традиционный подход Unix заключается в том, чтобы позволить многопроцессорным системам запускать приложения в отдельных процессах (threads) для сокращения времени, требуемого на выполнение специфических задач.

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

Идентификаторы IPC

Каждый объект IPC (очередь ли сообщений, семафор, или сегмент разделяемой памяти) обладает уникальным идентификатором (Id), который позволяет ядру ОС идентифицировать этот объект. К примеру, для того, чтобы сослаться на определенный сегмент разделяемой памяти, вам всего лишь необходимо знать уникальный идентификатор, назначенный этому сегменту.

Ключи IPC

Как создается идентификатор IPC? Для этого необходим ключ. Первым шагом при создании среды взаимодействия между приложениями является координирование использования ключей. Представьте себе это так: для того, чтобы позвонить кому-либо, вы должны знать его телефонный номер. Телефонная компания должна знать, как переслать ваш звонок абоненту. И только, когда он отвечает, происходит соединение.

В случае применения System V IPC «телефон» соединяет объекты одного типа. Телефонной компанией или способом маршрутизации является «ключ» IPC.

В примере, приведенном выше, директория /tmp/myapp комбинируется с литеральным идентификатором a для генерации ключа. Другим распространенным примером является использование текущей директории:

Следующие системные вызовы IPC используют значение возвращаемого ключа для создания или изменения доступа к объектам IPC.

Команда ipcs отображает состояние всех объектов System V IPC.

По умолчанию показываются все три категории объектов. Рассмотрим пример простейшего вывода команды ipcs :

Команда ipcs является мощным механизмом для мониторинга памяти ядра объектов IPC.

ipcrm удаляет объекты IPC из ядра. Однако, поскольку объекты IPC могут быть удалены с помощью системных вызовов из прикладной программы, необходимость удалять их вручную возникает редко. Команда очень простая:

Семафоры.

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

Этот механизм также обеспечивает функции для работы с разделяемой памятью System V. Разделяемая память обеспечивает доступ к глобальным переменным для различных процессов. Демоны httpd и даже другие программы (на Perl, C и других языках) могут получить доступ к этим данным с целью глобального обмена данными. Помните, однако, что разделяемая память не защищена от одновременного доступа.

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

Table 1. Переменные разделяемой памяти Unix

SHMMAXМаксимальное количество разделяемой памяти, обычно 131072 байт
SHMMINМинимальное количество разделяемой памяти, обычно 1 байт
SHMMNIМаксимальное количество сегментов разделяемой памяти в системе, обычно 100
SHMSEGМаксимальное количество сегментов разделяемой памяти для одного процесса, обычно 6

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

Использование разделяемой памяти и семафоров

Изучая что-то новое, каждый разработчик хочет начать использовать эту технологию на практике, хотя бы написав простую программу «Hello, world!». Это нормально, поэтому приведем минимальные сведения, необходимые для того, чтобы вы могли создать этот простой тестовый пример.

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

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

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

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

int sem_get (int key [, int max_acquire [, int perm]])

Эта функция возвращает id семафора. Он будет положительным в случае успеха и равен FALSE в случае ошибки. Используйте id для доступа к семафору V с помощью ключа key.

Вторичный вызов функции sem_get() для того же ключа вернет другой id, но они оба будут указывать на один и тот же семафор.

bool sem_acquire(int sem_identifier)

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

int shm_attach(int key [, int memsize [, int perm]])

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

mixed shm_get_var(int id, int variable_key)

int shm_put_var(int shm_identifier, int variable_key, mixed variable)

bool sem_release(int sem_identifier)

Эта функция освобождает семафор, если он занят текущим процессом. В противном случае выдает предупреждение (warning). Возвращает TRUE в случае успеха и FALSE в случае ошибки.

int shm_remove(int shm_identifier)

Эта функция удаляет сегмент разделяемой памяти и все содержащиеся в нем данные.

bool sem_remove(int sem_identifier)

Образец кода, использующего разделяемую память

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

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

Для более подробной информации об этих или других функциях обращайтесь к официальной документации PHP.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *