Особенности PHP 7+: строки, массивы и Copy on write
Рассказываем об особенностях языка программирования PHP от 7 версии. Рассматриваем строки, массивы, Interned variables и Copy on write.
Все популярные языки программирования похожи, они основываются на общих фундаментальных концепциях. Были разработаны и развиваются с учетом лучших практик. Несмотря на сходства, каждый язык программирования также имеет свои уникальные особенности, в этой статье поговорим о некоторых особенностях языка PHP (7+).
Принцип работы PHP
Стандартный путь выполнения PHP скрипта следующий: поступает запрос на выполнение PHP скрипта, создается процесс выполнения, где PHP код компилируется в байт-код. Далее байт-код оптимизируется и исполняется виртуальной машиной (ZEND). После выполнения, процесс уничтожается со всей сопутствующей информацией, и при повторном вызове процесс полностью создается заново. Поэтому существует такое выражение: “PHP рождён, чтобы умирать”.
Ситуацию меняют OPcache и JIT. OPcache (Opcode Cache) – это расширение для PHP, которое выполняет кэширование скомпилированного байт-кода PHP. OPcache сохраняет этот скомпилированный байт-код в памяти, чтобы избежать повторной компиляции при каждом запросе. JIT (Just-In-Time) генерирует и кэширует машинный код из оптимизированного байт-кода, который без изменений может вызывать интерпретатор. JIT работает только с теми частями кода, которые активно используются во время выполнения.
Copy on write
Механизм Copy on write используется для оптимизации использования памяти. В PHP, когда передается значение в функцию или одна переменная присваивается другой (и во многих других случаях), то будет создана отдельная копия значения:
$a = 1; $b = $a; $a++; // Увеличится только $a function addZero($n) < $n[]=0; >$arr = [1,2,3,4,5,6,7,8,9,10]; addZero($arr); // Массив $arr не будет изменен вне функции addZero var_dump(memory_get_usage()); // Потребление памяти int(405808)
Очевидно, что если передать большой массив в функцию, то выделение ресурсов на его копирование не сулит ничем хорошим. Чтобы избежать лишнего копирования, PHP использует copy on write: копирование значения происходит только при его модификации:
$a = 1; $b = $a; $a++; // Увеличится только $a function addZero($n) < //$n[]=0; Убрали модификацию массива >$arr = [1,2,3,4,5,6,7,8,9,10]; addZero($arr); var_dump(memory_get_usage()); // Потребление памяти стало меньше int(405280)
Стоит заметить, что копирование это не совсем копирование в привычном понимании. Это означает, что если вы “копируете” значение, вы на самом деле повторно используете старое значение и увеличиваете его счетчик ссылок (refcount). Только после того, как вы выполните какую-либо модификацию, будет сделана реальная копия (дубликат).
Строки
Строка (тип string) – это набор символов, где символ – это то же самое, что и байт. Это значит, что PHP поддерживает ровно 256 различных символов, а также то, что в PHP нет встроенной поддержки многобайтовых кодировок. Для работы с ними есть расширение mbstring, которое предоставляет функции для работы с многобайтовыми строками:
$str = 'Привет'; // Многобайтовая строка var_dump(strlen($str)); // int(12) var_dump(mb_strlen($str)); // int(6)
Кавычки
Строки в разных кавычках обрабатываются по разному. В двойных кавычках PHP будет искать и обрабатывать переменные, следовательно, будет тратить больше ресурсов на обработку. Это также касается и функций вывода строк: echo и print будут оптимальнее чем printf.
Interned variables
В PHP хранение переменных осуществляется в variable containers, это значит, что можно повторно использовать переменные, память при это не выделяется.
На примере строк: Interned strings относится к процессу оптимизации памяти, при котором один экземпляр строки хранится в памяти и используется повторно, вместо создания дубликатов этой строки.
Когда вы объявляете строковую переменную в PHP, например:
$str1 = "Hello"; $str2 = "Hello";
Обычно каждая строка будет иметь свой собственный экземпляр в памяти. Однако, PHP оптимизирует использование памяти и делает так, чтобы обе переменные ссылались на один и тот же экземпляр строки “Hello”. Но, если строки создаются динамически или изменяются во время выполнения программы, они не будут интернированы.
Массивы
Массивы в PHP отличаются от классических массивов в других языках. В PHP массивы это упорядоченные словари, но в зависимости от ключей, интерпретатор выбирает наиболее оптимальное внутреннее представление массива. Выделить можно 3 типа массивов: неизменяемые, packed array и обычные массивы.
Неизменяемый массив заполнен неизменяемыми элементами, чьи значения не требуют вычислений и известны во время компиляции.
Packed array получается в случае, когда ключи массива это целые числа в порядке возрастания. Он занимает меньше памяти (чем обычный массив) и прост для итерации.
$arr = []; for($i=0;$i <10000;$i++) < $arr []= $i; >$arr [] = 1; var_dump(memory_get_usage()); // int(917272)
Обычный массив (добавим ключ с типом string):
$arr = []; for($i=0;$i <10000;$i++) < $arr []= $i; >$arr['some_str_key'] = 'some_str_val'; var_dump(memory_get_usage()); // int(1044456)
Потребление памяти заметно стало больше.
Обход массивов
Обход также будет отличатся для packed и обычных массивов.
При обходе packed array, функции использующие callback (map, filter и другие) и foreach будут менее эффективны по памяти и производительности чем циклы for и while:
$arr = []; for($i=0;$i <10000;$i++) <$arr []= $i;>foreach($arr as $k => &$v) < $v += 1; >unset($v); var_dump(memory_get_usage(false)); // int(1237592)
$arr = []; for($i=0;$i <10000;$i++) <$arr []= $i;>$arrLength = count($arr); for($i=0;$i <$arrLength;$i++) < $arr [$i]= $arr[$i] + 1; >var_dump(memory_get_usage(false)); // int(917960)
При обходе массивов со смешанными ключами, все циклы плюс минус одинаковы по производительности.
Заключение
Мы рассмотрели несколько особенностей PHP. Язык постоянно развивается, оптимизируется и становится лучше. Если было полезно, ставьте классы. Всем добра!
Копирование при записи — Copy-on-write
Копирование при записи (COW ), иногда называемый неявным совместным использованием или затенением, представляет собой метод управления ресурсами, используемый в компьютерном программировании для эффективной реализации «дубликата» или «копии». «работа с изменяемыми ресурсами. Если ресурс дублируется, но не изменяется, нет необходимости создавать новый ресурс; ресурс может использоваться совместно копией и оригиналом. Модификации все равно должны создавать копию, отсюда и техника: операция копирования откладывается до первой записи. Совместное использование ресурсов таким образом позволяет значительно снизить потребление ресурсов немодифицированными копиями, добавляя при этом небольшие накладные расходы на операции по изменению ресурсов.
- 1 В управлении виртуальной памятью
- 2 В программном обеспечении
- 2.1 Примеры
В управлении виртуальной памятью
Копирование при записи находит свое основное применение при совместном использовании виртуальной памяти операционной системы процессов, в реализации системного вызова fork . Обычно процесс не модифицирует память и немедленно выполняет новый процесс, полностью заменяя адресное пространство. Таким образом, было бы расточительно копировать всю память процесса во время вилки, и вместо этого используется техника копирования при записи.
Копирование при записи может быть эффективно реализовано с использованием таблицы страниц, помечая определенные страницы памяти как доступные только для чтения и ведя подсчет количества ссылок на страницу. Когда данные записываются на эти страницы, ядро ядро перехватывает попытку записи и выделяет новую физическую страницу, инициализированную данными копирования при записи, хотя выделение может быть пропущено, если есть только одна ссылка. Затем ядро обновляет таблицу страниц новой (доступной для записи) страницей, уменьшает количество ссылок и выполняет запись. Новое распределение гарантирует, что изменение памяти одного процесса не будет видно в памяти другого.
Метод копирования при записи может быть расширен для поддержки эффективного выделения памяти за счет заполнения страницы физической памяти нулями. Когда память выделена, все возвращенные страницы ссылаются на страницу нулей и все помечаются как копирование при записи. Таким образом, физическая память не выделяется для процесса до тех пор, пока данные не будут записаны, что позволяет процессам резервировать больше виртуальной памяти, чем физическая память, и использовать память редко, рискуя исчерпать виртуальное адресное пространство. Комбинированный алгоритм аналогичен подкачке по запросу.
Страницы копирования при записи также используются в ядре Linux в функции слияния одной страницы ядра.
Загрузка библиотек для приложения также является использованием техники копирования при записи. Динамический компоновщик отображает библиотеки как частные, как показано ниже. Любое действие записи в библиотеки вызовет COW в управлении виртуальной памятью.
openat (AT_FDCWD, "/lib64/libc.so.6", O_RDONLY | O_CLOEXEC) = 3 mmap (NULL, 3906144, PROT_READ | PROT_EXEC, MAP_PRIVATE | MAP_DENYWRITE, 3, 0) mmapced (0x07T0) PROT_WRITE, MAP_PRIVATE | MAP_FIXED | MAP_DENYWRITE, 3, 0x1b0000)
В программном обеспечении
COW также используется в библиотеке, приложении и системе код.
В многопоточных системах COW может быть реализован без использования традиционной блокировки и вместо этого используется сравнение и замена для увеличения или уменьшения внутренний счетчик ссылок. Поскольку исходный ресурс никогда не будет изменен, его можно безопасно скопировать несколькими потоками (после увеличения счетчика ссылок) без необходимости дорогостоящей блокировки, такой как мьютексы. Если счетчик ссылок становится равным 0, то по определению только 1 поток держал ссылку, поэтому ресурс можно безопасно освободить от памяти, опять же без использования дорогостоящих механизмов блокировки. Таким образом, преимущество отсутствия копирования ресурса (и, как следствие, повышение производительности по сравнению с традиционным глубоким копированием) будет справедливо как в однопоточных, так и в многопоточных системах.
Примеры
Класс string, предоставляемый стандартной библиотекой C ++, был специально разработан, чтобы позволить реализации копирования при записи в исходном C + +98 стандарта, но не в новом стандарте C ++ 11:
std :: string x ("Hello"); std :: string y = x; // x и y используют один и тот же буфер y + = ", World!"; // теперь y использует другой буфер // x все еще использует тот же самый старый буферВ языке программирования PHP все типы, кроме ссылок, реализованы как копирование при записи. Например, строки и массивы передаются по ссылке, но при изменении они дублируются, если имеют ненулевое количество ссылок. Это позволяет им действовать как типы значений без проблем с производительностью, связанных с копированием при назначении или их неизменяемостью.
В структуре Qt многие типы копируются при записи («неявно разделяются «в терминах Qt). Qt использует атомарные операции сравнения и замены для увеличения или уменьшения внутреннего счетчика ссылок. Поскольку копии дешевы, типы Qt часто могут безопасно использоваться несколькими потоками без необходимости использования механизмов блокировки, таких как мьютексы. Таким образом, преимущества COW действительны как в однопоточных, так и в многопоточных системах.
В компьютерном хранилище
COW также может использоваться в качестве основного механизма для снимков, таких как те, которые предоставляются управлением логическими томами, файловыми системами, такими как Btrfs и ZFS, и серверами баз данных, такими как Microsoft SQL Server. Обычно в моментальных снимках хранятся только измененные данные и они хранятся рядом с основным массивом, поэтому они представляют собой лишь слабую форму инкрементной резервной копии и не могут заменить полную резервную копию. Некоторые системы также используют метод COW, чтобы избежать нечетких резервных копий, которые в противном случае возникают, когда какой-либо файл в наборе файлов, для которого выполняется резервное копирование, изменяется во время этого резервного копирования.
При реализации моментальных снимков существует два метода:
- Перенаправление при записи или ROW: исходное хранилище никогда не изменяется. Когда делается запрос на запись, он перенаправляется от исходных данных в новую область хранения.
- Копирование при записи или COW: при запросе записи данные копируются в новое хранилище. область, а затем изменяются исходные данные.
Несмотря на их названия, копирование при записи обычно относится к первому способу. COW выполняет две записи данных по сравнению с записью ROW; его сложно реализовать эффективно, и поэтому он используется нечасто.
Техника копирования при записи может использоваться для имитации хранения чтения-записи на носителях, которые требуют выравнивания износа или физически пишут один раз прочитано много.
qcow2 (QEMU copy on write) формат образа диска использует технику копирования при записи для уменьшения размера образа диска.
Некоторые Live CD (и Live USB ) используют методы копирования при записи, чтобы создать впечатление возможности добавлять и удалять файлы в любом каталоге без собственно внесение каких-либо изменений в компакт-диск (или флешку).
В программном обеспечении высокой надежности
Phantom OS использует COW на всех уровнях, а не только в базе данных или файловой системе. В любой момент компьютер, на котором работает эта система, может выйти из строя, а затем, когда он снова запустится, программное обеспечение и операционная система возобновят работу. Может быть потеряно только небольшое количество работы.
Основной подход заключается в том, что все данные программы хранятся в виртуальной памяти. По некоторому расписанию сводка всех данных программного обеспечения записывается в виртуальную память, образуя журнал, который отслеживает текущее значение и расположение каждого значения.
Когда компьютер выходит из строя, последняя копия журнала и другие данные остаются в безопасности на диске. Когда работа возобновляется, программное обеспечение операционной системы считывает журнал для восстановления согласованных копий всех программ и данных.
Этот подход использует копирование при записи на всех уровнях всего программного обеспечения, включая прикладное программное обеспечение. Это требует поддержки в рамках языка программирования приложений. На практике Phantom OS разрешает только языки, генерирующие байтовые коды Java.
См. Также
- Allocate-on-flush
- Btrfs
- Demand paging
- Dirty COW — уязвимость компьютерной безопасности для ядра операционной системы Linux
- Шаблон Flyweight
- Управление памятью
- Отображение памяти
- Постоянная структура данных
- ReFS
- Снимок (компьютерное хранилище)
Вопросы с собеседований: что такое Copy-On-Write
Это стратегия управления памятью, при которой копирование данных выполняется только в том случае, если один из объектов пытается изменить данные.

Оптимизация Copy-On-Write (COW) представляет собой стратегию управления памятью, при которой копирование данных выполняется только в том случае, если один из объектов пытается изменить данные. Эта стратегия применяется в различных областях, таких как операционные системы, языки программирования и базы данных.
Принцип Copy-On-Write заключается в следующем:
- Исходное копирование: Когда создается копия данных, например, массива или строки, на самом деле происходит только копирование указателя на данные, но не их фактическое копирование. Оба объекта (оригинал и копия) ссылаются на одни и те же данные.
- Копирование при изменении: Если один из объектов (оригинал или копия) пытается изменить данные, тогда система выполняет фактическое копирование данных, и каждый объект теперь имеет свою собственную копию данных. Это гарантирует, что изменения в одном объекте не затронут другой.
Преимущества оптимизации Copy-On-Write
Вот некоторые преимущества такого подхода:
- Экономия памяти: Изначально, когда создается копия, не требуется фактическое копирование данных, что позволяет экономить память.
- Эффективность при передаче параметров: Передача аргументов в функции по значению может быть более эффективной, так как фактическое копирование данных не выполняется до их изменения.
- Более эффективное управление ресурсами: Это особенно полезно в ситуациях, где копирование данных редко приводит к их изменению.
Примеры использования COW включают копирование строк в некоторых языках программирования, управление процессами в операционных системах, копирование файлов и т. д.
Пример copy on write в Swift
В Swift, как в языке программирования, стратегия Copy-On-Write применяется в различных типах данных, таких как строки ( String ) и массивы ( Array ). Давайте рассмотрим пример использования COW на строках в Swift:
// Создаем оригинальную строку var originalAppString = "Hello, World!" // Создаем копию строки, но фактического копирования данных не происходит var copyString = originalAppString // Изменяем копию строки copyString.append("Welcome!") // Печать оригинальной и копии строки print("Original String: \(originalString)") print("Copy String: \(copyString)")В этом примере, когда мы создаем копию строки copyString , она на самом деле ссылается на те же данные, что и originalString . Фактическое копирование данных произойдет только в тот момент, когда мы изменяем копию строки ( copyString.append(«Welcome!») ). Это гарантирует, что оригинальная строка остается неизменной.
Эта стратегия обеспечивает эффективное использование памяти, поскольку фактическое копирование данных выполняется только при необходимости.
Как управлять Copy-On-Write
В языках программирования, которые поддерживают Copy-On-Write, как Swift, управление этой стратегией происходит автоматически внутри языка. Обычно программисту не нужно явно управлять COW; система сама следит за этим процессом. Однако, есть несколько рекомендаций и сценариев, которые могут быть полезными:
1. Применение констант
Если у вас есть данные, которые не предполагают изменений, вы можете объявить их как константы ( let в Swift). Это поможет языку оптимизировать работу с памятью, поскольку он будет знать, что данные не будут изменяться.
2. Осторожное использование неявных копий
Избегайте создания неявных копий объектов, если это необходимо. В Swift, например, при передаче аргументов функции, они передаются по значению, но фактическое копирование данных происходит только при изменении. Это помогает уменьшить ненужные копии данных.
func someFunction(value: String) < // Неявная копия происходит только при изменении `value` var localCopy = value localCopy.append(" Some additional text") print(localCopy) >3. Избегайте избыточных копий
Будьте внимательны при работе с большими объемами данных. Если вам необходимо многократно изменять данные, возможно, имеет смысл использовать другие структуры данных, такие как NSMutableString или NSMutableArray (в Objective-C), которые разработаны с учетом изменений.
В целом, управление COW встроено в язык и работает автоматически. При правильном использовании констант, передачи параметров по значению и избегании избыточных копий, вы можете создать эффективные программы, использующие COW.
Недостатки Copy-On-Write
Хотя эта стратегия предоставляет ряд преимуществ, таких как экономия памяти и эффективность при копировании данных, она также имеет некоторые недостатки:
- Дополнительные затраты на копирование: Когда происходит фактическое копирование данных, это может вызвать некоторые дополнительные затраты по времени и ресурсам процессора. Это особенно касается ситуаций, когда данные копируются несколько раз, прежде чем происходит их изменение.
- Неэффективность в многозадачных сценариях: В многозадачных средах COW может создать проблемы, поскольку разные потоки или процессы могут иметь свои собственные копии данных. Это может потребовать больше памяти и вызвать проблемы с согласованностью данных.
- Потенциальные проблемы с производительностью: В определенных случаях Copy-On-Write может вызвать нежелательные задержки из-за дополнительных проверок и копирования данных. Это особенно важно в приложениях, где производительность играет критическую роль.
- Сложности с многозадачностью в реальном времени: В системах реального времени, где важно предсказуемое поведение, стратегия COW может создавать дополнительные трудности из-за неопределенности времени копирования данных.
- Сложности с некоторыми типами данных: Некоторые типы данных могут быть менее подходящими для COW. Например, если данные имеют сложные зависимости или структуры, то COW может привести к неожиданным результатам или нежелательным сложностям в управлении состоянием.
Несмотря на эти недостатки, COW обычно оправдывает свое использование благодаря экономии памяти и эффективности в тех случаях, когда данные редко изменяются по сравнению с их использованием. Правильное использование и понимание контекста помогают минимизировать потенциальные проблемы, связанные с COW.
Ссылки
Если вы нашли опечатку — выделите ее и нажмите Ctrl + Enter! Для связи с нами вы можете использовать info@apptractor.ru.
Механизм Copy-on-Write
В этой статье рассмотрим механизм Copy-on-Write и когда его можно использовать. Ранее мы уже рассматривали Reference Type и Value Type. Так вот, Value Type имеют семантику копирования — это когда вы присваиваете или передаете в качестве параметра функции экземпляр типа Value Type, то данные этого экземпляра будут скопированы. В итоге получится два одинаковых экземпляра, но с разными адресами в памяти.
Но не все так просто. С точки зрения производительности, если при передаче параметра в функцию типа Value Type этот параметр всегда бы копировался, то такая операция была бы очень дорогой. Пытаясь оптимизировать затраты на такую операцию в Swift механизм Copy-on-Write применяется только к коллекциям и только при мутации такого объекта. (и только если на такой объект имеется более одной ссылки, иначе объект можно было бы просто изменить по ссылке и никакое копирование бы не требовалось). Что это значит? — что при присвоении переменной значений или при передаче коллекции в функцию необязательно произойдет копирование этой коллекции.
Давайте посмотрим простой пример:
// 1 func print(address obj: UnsafeRawPointer ) < print(String(format: "%p", Int(bitPattern: obj))) >// 2 var array1: [Int] = [0, 1, 2, 3] var array2 = array1 // 3 print(address: array1) //0x600000078de0 print(address: array2) //0x600000078de0 // 4 array2.append(4) // 5 print(address: array1) //0x600000078de0 print(address: array2) //0x6000000aa100Что происходит в блоке кода выше:
- Функция print просто печатает адрес в памяти параметра obj.
- Происходит инициализация двух массивов array1 и array2.
- Распечатаем адреса этих массивов в памяти. Как мы видим — они одинаковые. Swift просто оптимизирует процесс копирования. Зачем создавать новый объект в памяти, если объекты одинаковые?
- Изменим второй массив. В этом месте сработает механизм Copy-on-Write.
- Еще раз выводим адреса наших массивов. Как мы видим — они разные!
Также, вы можете настраивать механизм Copy-on-Write на своих собственных типах. Подробнее с реализацией можно ознакомиться в этой статье: Copy on Write in Swift
- Механизм Copy-on-Write работает только с коллекциями.
- Копирование происходит при мутации коллекции.
- При копировании создается новый адрес в памяти.
- Данный механизм можно реализовать для собственных типов.
- Copy on Write in Swift
- Copy-on-Write In Swift
- Разница при копировании массива и структуры
Выразить благодарность или найти уникальный материал вы можете в boosty.
Подписывайтесь на мой Telegram-канал iOS Interview Channel, чтобы не пропустить новый материал.