Pull to refresh

Comments 56

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


Называть copy-on-write передачей по ссылке — это упрощение, граничащее с невежеством. Вас за него закидают тухлыми камнями, и поделом.
Это два совершенно разных механизма, и то, что вы их путаете, говорит о том что вы не поняли оба. Ну как можно утверждать, что массивы передаются по ссылке, если исходный массив не меняется после его изменения внутри функции?
И про скаляры вы тоже попали пальцем в небо (особенно про "не занимают много места", ха-ха), хотя могли бы элементарно проверить, заточив свой пример под передачу строк вместо массивов. И убедиться, что в отношении потребления памяти скаляры ведут себя совершенно идентично массивам — не занимают места, пока не поменялись, и удваивают потребление памяти при изменении. В чем, собственно, и заключается суть copy-on-write.


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

Кроме путаницы со ссылками.

А что я там напутал?

Называть copy-on-write передачей по ссылке — это упрощение, граничащее с невежеством.

Я нигде не называл copy-on-write передачей по ссылке.

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

В этом и есть суть copy-on-write. Как только вы решите изменить массив переданный в функцию PHP создаст копию исходного массива. Поэтому исходный и останется без изменений. Что собственно и подтверждается приведённым выше экспериментом. Про это написано тут.

И про скаляры вы тоже попали пальцем в небо (особенно про "не занимают много места", ха-ха), хотя могли бы элементарно проверить, заточив свой пример под передачу строк вместо массивов. И убедиться, что в отношении потребления памяти скаляры ведут себя совершенно идентично массивам — не занимают места, пока не поменялись, и удваивают потребление памяти при изменении. В чем, собственно, и заключается суть copy-on-write.

Я опять же не писал, что скаляры ведут себя как-то отдельно и что их копии не создаются. Создаются, просто они маленькие по памяти. Но про строки вы верно заметили, это я поправлю в статье. Internally, PHP strings are byte arrays. As a result, accessing or modifying a string using array brackets is not multi-byte safe, and should only be done with strings that are in a single-byte encoding such as ISO-8859-1.

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


Я нигде не называл copy-on-write передачей по ссылке.

Ну как не называли-то? Вот же, прямым текстом, первая фраза в разделе "Увеличиваем потребление памяти вдвое":


Массивы в PHP передаются по ссылке.

Данное утверждение является на 100% неверным. Массивы не передаются по ссылке. Вы называете передачей по ссылке передачу по значению с использованием copy-on-write. То есть, путаете эти два механизма.


Я опять же не писал, что скаляры ведут себя как-то отдельно

А вы точно автор этой статьи? В ней написано, что ведут:


Если умолчать про массивы, то все остальные типы данных в PHP — это скаляры (чтобы быть точным см. is_scalar). Скаляры не занимают много памяти, поэтому их можно быстро скопировать и передать в функцию копию хранимого значения.

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


Я опять же не писал ..., что что их копии не создаются.

Правильно. Вы писали, что создаются. Что, опять же, является 100% неверным. При передаче копия скаляра в памяти не создаётся. А создаётся при записи.


Плюс чуть раньше вы повторяете то же самое:


то, что объекты (и массивы, об этом — далее) в PHP всегда передаются по ссылке, а всё остальное по значению я знал.

То есть опять вы заявляете, что массивы передаются по ссылке, а все остальное (фактически, остаются только скаляры) — по значению.


На всякий случай повторю: Данное утверждение является дважды неверным.
Массивы передаются не по ссылке
Передача "всего остального" (т.е., по контексту статьи — скаляров) не отличается от передачи массивов. И те и другие передаются по значению, с использованием copy-on-write.

Данное утверждение является на 100% неверным. Массивы не передаются по ссылке. Вы называете передачей по ссылке передачу по значению с использованием copy-on-write. То есть, путаете эти два механизма.

Действительно, в документации, кажется, нигде явно не написано, что массивы передаются по ссылке. На stackoverflow.com есть разбор этого вопроса: Are arrays in PHP copied as value or as reference to new variables, and when passed to functions? И там говорится, что по ссылке. Там же есть ссылка на другой документ (один я уже приводил: «Reference-counting and copy-on-write»): PHP internals: When does foreach copy? И там есть вот такие строки (вырвано из контекста, нужно ознакомиться с разделом целиком):

The reason is that the array zval is now shared between multiple variables: The $array variable outside the function and the $array variable inside it.

Т.е. подразумевается, что массив передался по ссылке и теперь две переменные указывают на один и тот же zval.

Я склонен считать, что массивы в PHP передаются по ссылке всегда.

Ага, вот теперь мы уже сильно ближе к пониманию.
То есть вы действительно путаете передачу по ссылке с copy-on-write.
И, следуя вашему ходу мысли, придется заключить что строки-таки тоже передаются по ссылке. Верно?


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


Кроме этого, можете попытаться объяснить, в чем смысл передавать массив в foreach по ссылке… если — как вы утверждаете — он и так передается по ссылке! ;)


По источникам: доверять какому-то васе пупкину, который решил выступить на стаковерфлое, всегда следует с осторожностью. Из всех приведенных вами источников следует читать только то, что пишет Никита про 7 версию.

Баста! Я придумал как доказать это:

<?php

function passByReference(&$array, int $baseMemory) {

  printf('memory in %s: %s%s', __FUNCTION__, memory_get_usage() - $baseMemory, PHP_EOL);
}

function passByValue($array, int $baseMemory) {

  printf('memory in %s: %s%s', __FUNCTION__, memory_get_usage() - $baseMemory, PHP_EOL);
}

$baseMemory = memory_get_usage();
$array = range(0, 99);

printf('base memory: %s%s', memory_get_usage() - $baseMemory, PHP_EOL);
passByReference($array, $baseMemory);
printf('memory between: %s%s', memory_get_usage() - $baseMemory, PHP_EOL);
passByValue($array, $baseMemory);
printf('memory after: %s%s', memory_get_usage() - $baseMemory, PHP_EOL);

Вывод:

base memory: 2616
memory in passByReference: 2680
memory between: 2680
memory in passByValue: 2680
memory after: 2680
  1. Разницу в 2680 - 2616 = 64 опустим как несущественную.

  2. Внутри passByReference как видно объём не увеличился вдвое. Почему? Потому что массив передаётся по ссылке.

  3. Внутри passByValue как видно объём не увеличился вдвое. Почему? По той же самой причине: массив передаётся по ссылке.

@FanatPHP, как вы объясните наблюдаемые данные?

Отличный анекдот, кстати )

Только это не объясняет почему в обоих случаях потребление памяти осталось одинаковым. Ведь очевидно, что копия сделана не была.

Почему не объясняет? Как раз отлично объясняет. Вы точно так же из объективных данных эксперимента делаете неверные выводы.


  1. Таракан не убежал. Следовательно, уши у него в ногах
  2. Копия сделана не была. Следовательно, массив был передан по ссылке.

Ход мысли ну совершенно же идентичный.


Ваша проблема в том, что вы смотрите на ситуацию исключительно с точки зрения потребления памяти. И ни на что больше не обращаете внимание.


Знаете, я как-то подарил племянникам лазерные шахматы, Khet. Отличная игра, кстати. И часто проигрывал. Потому что мысленно выстраивая путь лазера, смотрел только на фигуры противника, и забывал про свои.


У вас здесь то же самое. Вы пытаетесь объяснить поведение РНР, и по-своему, в своем узком контексте правы. Но вы забываете, что передача по ссылке — это совершенно конкретный механизм языка, который здесь НЕ используется. А используется какой-то другой. Который хотя и имеет сходную природу, но всё равно совершенно отдельный. Именно поэтому ваши заявления выглядят так дико. Вам необходимо использовать другую терминологию. Передача по ссылке уже занята.


Попробуйте выйти из своего контекста, и взглянуть на свои утверждения с чистого листа. Вот представьте, что кто-то вас останавливает в коридоре и говорит, что в РНР массивы и строки передаются по ссылке. Представили? Вот именно это состояние охудивления и испытывают все, кто читает ваш опус.

Потому что массив передаётся по ссылке.

Строки тоже "передаются по ссылке". Вы можете это проверить тем же самым кодом.

С большим интересом читаю Ваши комментарии. Но я уже сам запутался. Можно как-то доступнее? :)

П.С. Я уже на старте проверять код на массивы и форичи.

Ну вот я как раз написал только что последний. Хотя писал уже об этом и раньше. Автор путает сам язык и его внутреннюю реализацию. По ссылке передаются только объекты. Массивы передаются по значению. При этом не происходит тупого копирования, а память часто экономится за счет механизма copy-on-write. Внутри себя этот механизм использует механизм ссылок. Но говорить при из-за этого, что массивы передаются по ссылке — неграмотно.


Я уже на старте проверять код на массивы и форичи.

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

Большое спасибо за подробное разъяснение.

Может у меня получится прояснить разницу.

Вот есть функция. У неё есть аргументы. Есть код, который вызывает это функцию и передаёт какие-то переменные.

Вопрос - а что по факту передаётся в функцию?

Ответ - по факту это всегда некий адрес в памяти. Массив, int, объект, и т.д. - это всё просто некий адрес в памяти, а дальше уже компилятор разруливает как вызвать метод объекта по этому адресу памяти или как трактовать это как элемент массива.

На заре программирования думали-думали и решили сделать два способа передачи параметров (забавный факт - в компуктерах PDP-11 было аж 42 способа адресовать аргумент) :

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

2) По значению - когда функция получает совершенной другой адрес переменной при вызове. В данном случае компилятор выполняет полное копирование переменной.

После это стали думать ещё. А вдруг аргумент функции не изменяется, а мы передаём его по значению. Если это огромный массив - то мы зря делаем полную копию массива.

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

Итого: передача аргумента (в более широком смысле - адресация переменных) и стратегия cope-on-write разные вещи, но существуют в одном контексте. Поэтому их часто употребляют как взаимозаменямо. Но их гораздо больше!

Написано же "Но.." Не вижу, что ТС что-то путает .

Увеличиваем потребление памяти вдвое Массивы в PHP передаются по ссылке. Но если вы что-то попытаетесь записать в него, то будет создана копия массива со всеми вытекающими по памяти и процессору. Это и есть реализация механизма copy-on-write.

будет создана копия массива

Это не передача по ссылке.

Для понимания разницы обратите внимание, как работает передача объекта в функцию - объекты передаются по ссылке и разница с массивами огромна.

Да понятно, что если "будет создана копия массива", то это не по ссылке.
Если в функции массив только читается - то по ссылке. А если в функции в массив что-то пишется - то создается копия. Так наверное?

Если в функции массив только читается - то по ссылке.

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

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

https://learn.microsoft.com/ru-ru/dotnet/visual-basic/programming-guide/language-features/procedures/differences-between-passing-an-argument-by-value-and-by-reference

передача по значению
При использовании этого механизма передачи Visual Basic копирует значение базового программного элемента в локальную переменную в процедуре. Код процедуры не имеет доступа к базовому элементу в вызывающем коде.
Передача по ссылке
При использовании этого механизма передачи Visual Basic предоставляет процедуре прямую ссылку на базовый программный элемент в вызывающем коде.

Разумеется это вопрос терминологии. И есть общепринятая. ТС путает именно использование общепринятой терминологии.

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

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

В php доступ не только на чтение (это означало бы ошибку при попытке изменить переменную). Доступ только на чтение у констант. Сравните разницу.

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

Верно, этот третий термин ввели. Copy-on-write.

Copy-on-write - это не третий и никакой вообще способ передачи. Это механизм позволяющий оттянуть или вовсе избежать копирования параметров функции. Он опционален. И без COW можно реализовать передачу по ссылке и по значению.

Что касается терминологии, то возможно, следовало упомянуть в статье, что имеется ввиду по ссылкой (я подправлю потом текст). В «Reference Counting Basics» сказано:

Since PHP allows user-land references, as created by the & operator, a zval container also has an internal reference counting mechanism to optimize memory usage.

Я имею ввиду именно те самые внутренние ссылки - internal reference - которые можно явно задать при помощи амперсанда или те, которые PHP создаст сам, где посчитает нужным. В данном случае он создаёт внутреннюю ссылку (не внутрь куда-то, а другую, не user-land) на массив при передаче его в функцию по значению.

Он опционален.

Нет, не опционален. Вы не можете включить в php классическую передачу по значению.

И без COW можно реализовать передачу по ссылке и по значению.

Конечно можно. Но в php не реализована классическая передача по значению, только cow.

Я имею ввиду именно те самые внутренние ссылки

Для массивов? А для объектов вы тоже имеете ввиду именно внутренние ссылки? Они ведь передаются не так, как массивы, а вы назвали их одинаковым термином.

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

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

Вот ваше расписание на неделю:

Вы не ответили на вопрос как объяснить

Вы, кажется, этот вопрос задавали не мне? Но ответ простой - copy-on-write копирует не сразу, а только при попытке изменения. Поэтому и не увеличивается. Конечно при чтении php пользуется тем же самым блоком памяти. Пользуется тем же адресом, если угодно.

Потому что ответ очевиден

Да, очевиден, php не копирует область памяти без необходимости.

вы не хотите признать эту очевидность

Как видите, признал. Теперь у вас пропал повод для бессмысленных переходов на личности?

не вызывая копирования, как это ожидается

Кем ожидается? Вами? При cow копирование ожидается только при изменении, не раньше. Вы это знаете, я это знаю. Так к чему этот странный оборот "ожидается"?

На мой вопрос, пожалуйста, тоже ответьте. Нехорошо указывать оппоненту на неотвеченный вопрос, а самому при этом вопрос проигнорировать.

Я прошу прощения, я вас с @FanatPHPперепутал. А ожидается как минимум мной потому что в документации сказано, что by default, variables are always assigned by value. А тонкие материи там освещены так себе. Поэтому и ожидается... дефолтное поведение.

Может у меня получится объяснить )

function passByReference(&$array) {
	$array[0] = 100;
}

function passByValue($array) {
	$array[0] = 100;
}

$array = range(0, 5);
passByReference($array);
var_export($array);

$array = range(0, 5);
passByValue($array);
var_export($array);
array (
  0 => 100,
  1 => 1,
  2 => 2,
  3 => 3,
  4 => 4,
  5 => 5,
)
array (
  0 => 0,
  1 => 1,
  2 => 2,
  3 => 3,
  4 => 4,
  5 => 5,
)
Copy-on-write — это не третий и никакой вообще способ передачи.

Правильно!!!
И вот в случае с вашими тараканами происходит именно ЭТО! А никакая не передача по ссылке. Которая происходит по значению.


Вы упорно путаете сам язык и его внутреннюю реализацию. Массивы передаются по значению. Запомните уже это наконец! Но память при этом экономится. За счет cow. Это будет корректное описание ситуации. А не у таракана уши в ногах "массивы передаются по ссылке".


Вы пытаетесь коряво сформулировать причину экономии памяти, но вместо "с использованием механизма Copy-on-write, который там где-то внутри себя использует механизм ссылок" постоянно повторяете эту чушь, "массивы передаются по ссылке!".


Если уж на то пошло, вы говорите не о массивах, а о структурах zval.


те самые внутренние ссылки — internal reference — которые можно явно задать при помощи амперсанда или те, которые PHP создаст сам

Вы опять все перепутали. Ссылки, которые можно задать при помощи амперсанда — это механизм языка. Который реализуется с помощью internal reference. У вас логика на уровне "Марк Цукерберг и Вася Пупкин оба произошли от обезьяны. Поэтому Марк Цукерберг и Вася Пупкин — это один и тот же человек!".


Причем эти самые "internal reference" в при реализации передачи по ссылке и при реализации cow — тоже разные, насколько я могу судить. А вы валите всё в кучу.

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

Ну в общем-то не конкретного языка, я погорячился.
https://ru.wikipedia.org/wiki/Копирование_при_записи

Механизм копирования при записи (англ. Copy-on-write, COW) используется для оптимизации многих процессов, происходящих в операционной системе, таких как, например, работа с оперативной памятью или файлами на диске...

Вы, как и автор, путаете сам язык с его внутренними оптимизациями.


Смотрите, передача по ссылке — это не просто "термин". А конкретный механизм языка, совершенно определённый, не допускающий интерпретаций и толкований. Который описывает поведение переменной с точки зрения программиста.


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


Учитывая, что передача по ссылке — это конкретный механизм, мы не можем употреблять этот термин по своему усмотрению. И называть передачу по значению передачей по ссылке будет просто неграмотно. Как и любые попытки выдумать свою собственную терминологию, "если только читается — то по ссылке, а если пишется — то по значению".


Массивы передаются по значению, а не по ссылке. Точка.


А вот внутренняя реализация copy-on-write — это уже другой вопрос. Здесь можно порассуждать, но чётко понимая, что говорим мы совсем о другом.


Но даже и там не поставишь знак равенства. Например, в документации к пятой версии однозначно сказано, что


If one zval can be used in multiple places, PHP needs some way to find out when the zval is no longer used by anyone, in order to destroy (and free) it. PHP accomplishes this simply by keeping track of how often the zval is referenced. Note that “referenced” here has nothing to do with PHP references (as in &) and just means that something (a variable, function, etc) makes use of the zval.

В доке по 7 версии этого предложения нет, но зато в разделе, посвящённом передаче по ссылке, написано, что она реализуется с помощью отдельной структуры zend_reference. То есть, насколько я понимаю, это тоже ссылки, но не те, которые использует внутренний механизм Zend по управлению памятью.

https://ru.wikipedia.org/wiki/Стратегия_вычисления#вызов_по_имени

Да там можно закопаться с головой.

Их (массивы) точно лучше передавать по ссылке, как PHP передаёт объекты

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

Используйте for

Вот так, без каких либо условий, всегда? Так себе совет. Используйте for в тех случаях, когда вы знаете для какой конкретно цели он вам нужен. Например, если вам надо итерируясь по огромному массиву менять его содержимое, так чтобы они сохранилось по завершению цикла... Что также достаточно редкий кейс.

Точно лучше?

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

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

Можно и просто по номеру индекса менять в for'е и всё тоже сохранится. У foreach многовато побочных эффектов, я о них упомянул. Должно было облегчить жизнь, а вышло наоборот. Он только для "посмотреть", но не потрогать.

Передача по ссылки очень частая причина багов.Datetime (вместо DatetimeImmutable или DatetimeInterface) первый в списке источников багов, передал в функцию время, а она тебе его поменяла. Или более неявно, ты передал dto, а тебе через геттер в dto поменяли состояние dto.

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

Функция count() работает за O(1), вычислений никаких там не будет, только оверхед на вызов функции

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

Да, есть такое. Массивы в PHP хранят свой размер, а не вычисляют его при каждом вызове.

Самая мякотка начинается когда у вас массив объектов :-)

А что там такого особенного или неочевидного?

Ну, например, мутабельность.
Если внутрь какой-нибудь функции передан по значению (на самом деле не важно) массив, например, строк, то пыха создаст копию этого массива и внутри этой функции эти строки массива можно менять как угодно - значение строк в массиве снаружи не изменится. (Что ожидаемо)
А вот если передан массив объектов, то после изменения этих объектов изнутри функции, снаружи они тоже будут изменены (что не очевидно)

<?php

class Obj {
	private string $prop = 'Initial value';
	
	public function setProp(string $value)
	{
		$this->prop = $value;
	}
	
	public function getProp(): string
	{
		return $this->prop;
	}
}

function changeProp(array $arrayOfObjects)
{
	$arrayOfObjects[0]->setProp('Changed value');
}

$object = new Obj();
$arrayOfObjects = [$object];

changeProp($arrayOfObjects);

echo $object->getProp(); // Changed value

Ну и про память. Поскольку ссылки на объекты в массиве это не сами объект, то, памяти копии тоже занимается не x2 или x3

То что вы описываете это базовое поведение объектов в PHP, что переменная содержит ссылку на объект и если ты не делаешь clone то ты работаешь с исходным объектом.

После таких любителей "оптимизировать" внутреннюю кухню языка порой вешаешься на проекте... Давайте будем честными, на небольших объемах данных в 9 из 10 случаев всем будет насрать на сколько там память увеличилась. А если вы пытаетесь изменить что-то в массиве размером со "слона", то вы явно делаете что-то не так. Я даже не могу представить такую ситуацию)

Ключевая фраза "в 9 из 10 случаев" )
Если у вас 1К онлайн юзеров, то оптимизация на 1Мб может дать экономию 1Гб ОП на одном скрипте. Но вы правы - таких проектов немного. И про слона тоже плюсую )

Меня всегда радуют такие оптимизаторы-теоретики.
1к онлайн юзеров не делают по запросу в секунду. При том что нормальный скрипт выполняется сильно меньше секунды. А уж сам цикл — и того меньше. То есть, даже с вашей арифметикой, экономия получится сильно так пожиже, раз в сто.


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

Всегда рад порадовать других теоретиков, которые в концептуальном посте радуются возможности придраться к конкретике, которой там и не планировалось, а потом с отцовской снисходительностью поясняют юнцам "за ошибки". Специально для вас поясню: числа взяты условные, для демонстрации принципа, а не конкретного выигрыша. Ну и последнее предложение моего поста означало, что я разделяю недоумение попыткой "изменить что-то в массиве размером со "слона", и самим наличием такого массива, внезапно. Так что второй абзац пояснил мне то, с чем я изначально не спорил, поэтому, видимо, был написан только для вашей радости.
Надеюсь порадовал вторично ;)

function doSmth(array &$array, int $memory) {

   printf('memory: %s%s', memory_get_usage() - $memory, PHP_EOL);
   $array[0] = 0;
   printf('memory: %s%s', memory_get_usage() - $memory, PHP_EOL);
 
   foreach ($array as $i => &$value) {
     $array[$i] ++;
     printf('memory: %s, i: %s%s', memory_get_usage() - $memory, $i, PHP_EOL);
     break;
   }
 
   printf('memory: %s%s', memory_get_usage() - $memory, PHP_EOL);
 }
 
 $memory = memory_get_usage();
 $array = range(0, 99);
 doSmth($array, $memory);
 printf('memory: %s%s', memory_get_usage() - $memory, PHP_EOL);

memory: 8280
memory: 8312
memory: 8344, i: 0
memory: 8344
memory: 8344

Если добавить две ссылки, тогда встает все в норму, но все таки полезно знать.

У меня вот такой PHP:

PHP 8.2.3 (cli) (built: Feb 15 2023 00:30:25) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.2.3, Copyright (c) Zend Technologies
    with Xdebug v3.2.1, Copyright (c) 2002-2023, by Derick Rethans
    with Zend OPcache v8.2.3, Copyright (c), by Zend Technologies

И вот такой вывод:

memory: 2648
memory: 2680
memory: 2712, i: 0
memory: 2712
memory: 2712

Т.е. PHP сразу в 3 раза меньше памяти потребляет, интересно. Покажите ваш php -v. Если уберёте break из вашего кода, то увидите рост.

Не надо просить версию, можно и самому посмотреть
Тем более что вопрос тут не в абсолютных цифрах, а в относительных.

Только не две, а одну. Вторая у вас лишняя, и — как правильно вам ответил автор — приводит к увеличению потребления памяти в вашем коде при полном проходе цикла. А вот если убрать ссылку у $value, то потребление памяти не растёт.


Причем про первую ссылку в статье и так написано:


нужно поставить амперсанд перед параметром $array, вот так: function doSmth(array &$array, int $memory)

Так что не очень понятно, что вы хотели сказать своим комментарием.

Не понимаю как автор предлагает заменить for each на for для ассоциативных массивов

Наверное как-то так:


$idx = array_keys($array);
for ($i = 0; $i < count($idx); $i++) {
    $key = $idx[$i];
    $value = $array[$key];

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

А что с вариантом итерации через reset/key/next?


   reset($array) ;
   while (($i = key($array)) !== false) {
      $array[$i]++;
      // ..
      next($array);
   }

выглядит, как ситуация доступная для статического анализа, нет ли инструментов для его проведения?

Если получать значение в foreach по ссылке и писать $value++;, то никакого увеличения памяти не будет внутри foreach
`foreach ($array as &$value)`

Sign up to leave a comment.

Articles