Pull to refresh

Comments 72

Естественно (говорю даже не запуская). Смотрите:

1. Происходит вычисление правой части, в результате создаётся новая строка в памяти, и $a присваивается эта строка (в результате чего предыдущее значение внутри $a выбрасывается). Итого: одно выделение памяти, одно освобождение, два копирования.

2. Происходит добавление к уже существующей строке, присваивания нет, происходит по сути просто увеличение буфера, и максимум одно копирование.

habrahabr.ru/blogs/php/38639/#comment_921896
я не php-программист, сейчас моими основными языками являются java и c#. к web приглядываюсь для личных нужд.

вообще, как-то странно, звучат ваши описания. во всяком случае крайне нелогично и комплиментов php не делают.

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

Если интересно, то тест в c# дал идентичные результаты для обоих вариантов.

Мне вот интересно, насколько вы уверены в том, что утверждаете?
Насчёт php — на 98%.

Насчёт Java — на 100%. В Java строки константны (их нельзя изменить вообще никак). Поэтому в Java даже оператор += создаёт новую строку в памяти.

Про C# ничего не знаю.

А вообще, я просто провёл аналогию с тем, как работает std:: string и перегрузка операторов в C++.

> комплиментов php не делают
А Java константные строки делают комплименты? Да, там нужно использовать StringBuilder, иначе будет тормозить. В PHP и C++ можно использовать .= и += без всяких лишних сущностей. Нет, это не нападение на Java (ничего против неё не имею), это доведение до абсурда вашей цитаты, чтобы показать, что она не имеет под собой веских аргументов.
в c# со строками всё также, как и в java. я не о реализации строк хотел уточнить. меня удивляет различная, как вы утверждаете, реализация идентичных по логике выражений. в с# они абсолютно одинаковые.
> меня удивляет различная, как вы утверждаете, реализация идентичных по логике выражений.
Я в PHP не специалист, но для меня описанное лежит на поверхности, так как строки в PHP можно изменять и было бы логично реализовать описанное именно так.

> в с# они абсолютно одинаковые.
Я тут уже выяснил, что строки в C# тоже константны. Поэтому реализовать эффективно оператор += в принципе невозможно. Для этого в C# есть класс System.Text.StringBuilder.
msdn.microsoft.com/en-us/library/system.string.aspx
The value of the String object is the content of the sequential collection, and that value is immutable.

То есть, фактически то же самое, что и в Java.

В C++:
std::string foo, bar;
// ...
foo = foo + bar; // (1)
foo += bar; // (2)


(1): Вычисляется foo.operator+(bar), что порождает временный объект tmp, после чего вызывается foo.operator=(tmp) и tmp уничтожается.

(2): Вычисляется foo.operator+=(bar).

(См. Страуструп, 11.3.1)
вот-вот, в с++ и первая и вторая строки эквивалентны.
в php, как получается у вас и товарища, написавшего комментируемую заметку, получается, что эти строки выполняются по-разному.

А про StringBuilder, эффективную конкатенацию строк — это всё понятно, я не спрашивал. )
В C++ они эквивалентны только в данном конкретном случае, из-за семантики класса std:: string. Можно написать свой класс, в котором так перегрузить operator+(), operator=() и operator+=(), чтобы они выполняли совершенно различные действия, и это не будет противоречить стандарту. (Но это конечно, введёт в замешательство программиста, не знакомого с перегрузкой операторов)
В C++ эти строки совсем даже не эквивалентны. Простенький тест дает такие результаты:

1. 23.988u 0.000s 0:24.79 96.7% 5+1089k 0+0io 0pf+0w
2. 0.022u 0.000s 0:00.02 100.0% 4+496k 0+0io 0pf+0w

Разницу видно? И это правильно. Поскольку, как и в PHP, второй вариант не создает временных объектов.
в общем-то струдом )
я так понимаю, разница 24 сек. и 0.02 сек.

это только мне такие вещи кажутся дико странными?

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

#include <string>
#include <iostream>

using namespace std;

int main() {
  string a = «string 1234567890123456789012345678901234567890qq»;
  string b = «string 1234567890123456789012345678901234567890aa»;
  for (int i = 0; i < 10000; i++) {
    // Раскомментировать одно из двух:
    // a = a + b;
    a += b;
  }
  cout << a.size() << endl;
}

Собиралось GCC-4.2.1. Выполнялось «time a.out». Никаких фокусов.
Кстати, обратите внимание, что пример a=a+b еще и памяти жрет вдвое больше.

Если вы посмотрите на реализацию операторов += и + в STL, то увидите, что первый просто вызывает метод append у строки a. А оператор += сначала создает новую строку — копию a (вы ведь могли написать c=a+b, поэтому изменять саму строку a нельзя), добавляет к этой копии b, используя все тот же append и возвращает результат. В этот момент у нас есть и оригинал строки a, и сумма a+b — отсюда разница по памяти. После этого результат передается в оператор = строки а, в которую и копируется, а исходное ее значение выбрасывается. Надеюсь, теперь вам понятно, почему эти варианты так отличаются?
Вот именно из-за памяти мне и пришлось разбираться с этой проблемой, т.к. PHP мне заявил, что ему мало выделенных 128Мб, при том, что данных там от силы на 2Мб было.
! Понятно, но непривычно. )
Я в С++ не разбираюсь, но есть предположение, которое раскрывает причины разного поведения операторов, что перегрузка операторов + и += различна. В первом случае не известна переменная в которую происходит сохранение результата, а во втором случае известна.
Что-то вроде:

static string operator +(string a, string b) {… }

void operator +=(string a) {… } //сохранение результата в текущий экземпляр класса

Спасибо за разбор.
Могу сказать за c#, я смотрел дизассемблированный код, там в обоих случаях выполняется вызов одной и той же процедуры конкатенации. Ну и в документации прописано, что + и += одно и то же и перегружаются одним методом.
А это оправдывает разницу по скорости в сотни раз (в моем случае получилось примерно в 300 раз)?
Да, оправдывает. Программист должен знать язык, на котором пишет. Он должен знать как работают основные конструкции, знать где что подсмотреть в документации, и он должен прочитать бОльшую часть документации (особенно если она написана доступным языком, как в PHP). Иначе или это человек очень-очень начинающий, или человек занимающийся программированием как хобби.
Эм… Причем тут программист, я не об этом.

Разве является две-три лишних операции с памятью причиной ТАКОГО падения скорости?
Да, выделение/освобождение памяти + копирование большого куска памяти — дорогие операции. Особенно учитывая количество косвенных слоёв, которые вносит интерпретатор.
Я не понимаю, что за привычка минусовать людей, не оставив при этом ни единого комментария! Можно поставить минус в подтвереждение согласия с большинство, если большинство и так уже выразило в комментариях свое недовольствою. Но втихоря на пустом месте — хамство!

P.S. Порадовался, блин, что могу хоть в личный блог что-то писать… недолго радовался.
Не интересна вам тема? Ну и не читайте! Заминусуйте топик на крайняк. Ну нафига карму понижать, да так, чтобы она в минус ушла??

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

Когда топик из личного блога был перемещен в блог PHP, надеюсь, не нужно в самом верху большими буквами писать, что речь о PHP?
это абсолютно бесполезная информация — поэтому и минусуют.
зачем в таком случае минусовать автора, а не бесполезную информацию?
Видимо для того, чтобы автор на некоторое время не смог писать ещё бесполезной информации.
топик посвящен РНР програмистам. другие пусть не лезут.
всё это интересно. но вот я например даже представить не могу, зачем мне нужно было бы 10 000 раз добавлять строку к строке :)
например, если 10000 клиентов одновременно захотят по одному разу добавить.
Это будет в разный процессах, какие проблемы? :) А если у вас 10000 запросов в секунду, то оптимизировать прийдётся уж никак не конкатенацию строк ;)
Например, сформировать INSERT, вставляющий 300 строк, в каждой по 30 значений. Получается два вложенных цикла, общее число итераций — 9000.

Делать отдельные вставки — неоптимально, выполняется гораздо дольше. Конечно, в идеале было бы писать сначала все в файл, а потом загрузить этот файл в базу с использованием LOAD DATA INFILE, но на хостинге нет соответствующих прав.
какие-то пример совсем уж теоретические.
я принимал участие в создании минимум двух крупных медиа порталов.
но всё равно Ваши примеры — чистая теория.
Я, к сожалению, другого способа решить задачу не нашел (может плохо искал). Пример реальный из жизни, теоретическими изысканиями так просто не занимаюсь и искусственно такие ситуации придумывать бы не стал )
мысль Вашу понял. просто сам с таким не сталкивался. доведётся — буду иметь Ваш опыт в виду. так что всё равно спасибо за инфу :)
Пример станет не теоретическим, если вы его сформулируете не в терминах «сгенерить 300 инсертов», а напишите, зачем это понадобилось. Особенно если учесть, что это, по вашим словам(но на хостинге нет соответствующих прав), происходит на shared хостинге.
Как только появится обоснование необходимости оптимизации, которое будет понятно большинству — будут и плюсы :)
Имеется выгрузка из другой базы данных в текстовом файле с разделителем. В файле примерно 10000 записей и 58 полей в каждой записи. Каждую запись необходимо обработать:
1. Исправить корявое форматирование (часть данных — обычный текст, который набивался операторами вручную).
2. Добавить некоторые данные в справочник с проверкой, что такой записи там еще нет и получить Foreign Key.
3. Выделить из одной записи другие — например, разделить контакты на телефон, e-mail и ФИО. Не спрашивайте меня, почему нельзя было это предусмотреть изначально в той базе, из которой делают выгрузку — что есть, то есть.
4. И т.д.

Работает это сейчас так. Выбираются из файла первые n=300 строк. Для каждой строки в цикле (58 итераций — по числу полей) происходит сначала обработка данных, затем формирование SQL запроса, где и происходит пресловутая конкатенация. И все это повторяется 300 раз. Затем в отдельной таблице фиксируется, на какой строке завершилась обработка. Через веб-интерфейс с использованием AJAX проверяется, что вернул скрипт обработки — если все нормально, то обновляется progressBar и обрабатываются следующие 300 строк, если возникла ошибка — выводим сообщение об ошибке и останавливаем работу.

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

вот что ваш скрипт показывает:
0.001384973526001
0.001276969909668

увеличил строку до 120 символов и количество повторов до 50000 получил такое:
0.011452913284302
0.010983943939209

второй конечно быстрее по уже объясненным причинам, но судя по тесту утверждать
>>первый вариант выполнятеся в сотни раз дольше
не приходится
Напишите, пожалуйста, версию PHP. У меня получилось
Сорри, проглючило при отправке…
У меня получилось:
2.2300808429718
0.0039939880371094
В комментариях в другой ветке один товарищ написал, что это все-таки баг и дал ссылку:
bugs.php.net/bug.php? id=44069
Исправлен баг как раз в версии 5.2.6. Хотя у меня на хостинге тоже 5.2.6, но «проблема» наблюдается.

habrahabr.ru/blogs/php/38639/#comment_923162
вернул phpinfo
5.2.6-pl6 в этой версии проблем нет.
5.2.6RC4-pl0, а вот в этой есть.
оба сервера на gentoo
Почитайте внимательно описание. Там говорится о чрезмерном использовании памяти, которая в результате не освобождается.
угу, в той ветке я не писал что именно сабж — баг
баг с памятью вполне может влиять на скорость, но не напрямую
Да, я уже после публикации обратил на это внимание.
кажется уже разбирали про echo и print. Я одно могу сказать — 2ой вариант глазу приятнее
UFO landed and left these words here
Скорее не «нигде», а «редко где». Ситуации разные бывают.
Тем не менее, лучше делать так как лучше, а не так как получится — не правда ли?
Никого обидеть не хочу, но зачем же писать статьи о том, что фактически есть в официальной документации, не первый раз такое встречаю. Про скорость работы разных вариантов конкатенации не поручусь но точно знаю что замечания по скорости работы ряда функций там есть. А если начинающие разработчики будут чаще самостоятельно напрягать свою соображалку и думать какая конструкция работает быстрее, это пойдет только на пользу и им самим и будущим продуктам. С одной стороны хорошо конечно, лишний раз нагуглить сможет кто-то и т.д. но с другой, мне кажется есть такая вероятность, что официальную документацию, которую и так к сожалению перед началом изучения читают далеко не все, станут читать еще того меньше.
Ну во-первых, про «статью» это вы громко сказали. Так, небольшая заметка.
А во-вторых, ссылка на баг в конце статьи говорит сама за себя. А если вы еще почитаете обсуждение, то заметите, что многих неновичков тоже удивило подобное поведение.
нужно запросы уметь оптимизировать, а не знать как сэкономить 0.0000000017sec на конкатенации
microtime ([ bool $get_as_float ] ) — не пробовали true в скобочках написать? :)
Я сейчас пишу очень сложную огромную социальную сеть. Страница в среднем собирается за 36.3725 секунд. Мне сказали, что чтобы не было так долго нужно оптимизировать скрипты. О том что echo выполняется быстрее print я уже выяснил, везде заменил, и что надо использовать одинарные кавычки вместо двойных тоже знаю.

Меня очень интересуют сравнительные скорости выполнения в огромных масштабах конструкций вроде:

$a = «это $a$b»;
$a = «это $a». $b;
$a = «это {$a}{$b}»;
$a = 'это '. $a. $b;

Можете устроить тесты?

Вот осталось с конкатенацией разобраться и будет все летать. Ух, заживем!

Товарищи, заканчивайте херней маяться, пожалуйста.
зато какая постановка акцента на проблему в контексте топика:
>> Я на своем горьком опыте убедился
— 4orever, горький опыт, это когда выяснилось, что ваши конкатенации при планируемой нагрузке ложат сервер на лопатки. А выход из решения был в замене x=x.y на x.=y.
4orever, Вы сами-то в такой сюжет верите?

Не придирайтесь к формулировкам, никаких акцентов там не было.
1. Пользуйтесь шаблонизатором.
2. Сделайте кеширование (хотя бы на уровне блоков или отдельных шаблонов/подшаблонов).
3. Внимательно посмотрите запросы к БД (не поленитесь, посмотрите план выполнения запросов).
недавно провёл такие тесты:

$str = '';
$var1 = 1;
$var2 = 2;
$var3 = 3;
$var4 = 4;
$var5 = 5;
$var6 = 6;
$var7 = 7;
$var8 = 8;
$var9 = 9;
$var10 = 10;

в 2х вариантах

вариант 1:
for ($i=0; $i<1000000; ++$i) {
$str = "
Var 1 = $var1;
Var 2 = $var2;
Var 3 = $var3;
Var 4 = $var4;
Var 5 = $var5;
Var 6 = $var6;
Var 7 = $var7;
Var 8 = $var8;
Var 9 = $var9;
Var 10 = $var10;
";
}

и вариант2:
for ($i=0; $i<1000000; ++$i) {
$str = '
Var 1 = '.$var1.';
Var 2 = '.$var2.';
Var 3 = '.$var3.';
Var 4 = '.$var4.';
Var 5 = '.$var5.';
Var 6 = '.$var6.';
Var 7 = '.$var7.';
Var 8 = '.$var8.';
Var 9 = '.$var9.';
Var 10 = '.$var10.';
';
}

Разница поразила. Почему решил потестить — старая «аксиома» о том, что двойные кавычки медленнее(развеял для себя давно), удобство использования двойных кавычек, особенно при формировани SQL-запросов. Просто хотел нашему второму программеру показать наглядно.
Тестил на 2 компах, на рабочем — Phenom ll x3 3.4 (PHP Version 5.2.4) и на сервере с Ксеоном 4х ядерным (PHP Version 5.2.9), и там и там почти одинаковые результаты, которые показывают, что двойные кавычки быстрее. Особенно это заметно, когда длину строки в 10 раз увиличить… разница 260%.

наткнулся на обсуждение на Хабре, решил поделиться… Мож кому пригодится.
Исправьте…

было:
$end = getmicrotime(1);
$time = $end — $start;.

стало:
$end = microtime(1);
$time = $end — $start;.

съело все тэги (((

Ещё изменяйте тэги циклов, что даёт прирост ~50%

---БЫЛО:

for( $ i=0;$ i
съело все тэги (((

Ещё изменяйте тэги циклов, что даёт прирост ~50%

---БЫЛО:
[code]
for( $ i=0;$ i < 10000;$ i++) {
$ a .= $ b;
}
[/code]

---СТАЛО:

[code]
for( $ i=0;$ i
20000 циклов:

$string .= "fdsfdfsfsdf\ndsfsdfsfsdfsdfdsafasf\nfdsfdasfdf";
TIME: 0.0020899772644043
Peak memory usage:1.5 MB

$string = $string . "fdsfdfsfsdf\ndsfsdfsfsdfsdfdsafasf\nfdsfdasfdf";
TIME: 7.0406210422516
Peak memory usage:4 MB

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

define('CYCLES', 20000);
define('STRNG', "fdsfdfsfsdf\ndsfsdfsfsdfsdfdsafasf\nfdsfdasfdf");

////////// ************************************************************************

$string = STRNG;
$t1 = microtime(true);
for ($i=0; $i<CYCLES; $i++){
$string .= STRNG;
}
echo 'TIME: ' , microtime(true) - $t1 , "\n";
echo 'Peak memory usage: ' , memory_get_peak_usage(true) , ' Bytes' , "\n";
echo 'STRLEN: ' , strlen($string) , "\n";

////////// ************************************************************************

$string = STRNG;
$i = CYCLES;
$t1 = microtime(true);
while($i) {
$string .= STRNG;
$i--;
}
echo 'TIME: ' , microtime(true) - $t1 , "\n";
echo 'Peak memory usage: ' , memory_get_peak_usage(true) , ' Bytes' , "\n";
echo 'STRLEN: ' , strlen($string) , "\n";

////////// ************************************************************************

$string = STRNG;
$i = CYCLES;
$t1 = microtime(true);
while($i) {
$string = $string . STRNG;
$i--;
}
echo 'TIME: ' , microtime(true) - $t1 , "\n";
echo 'Peak memory usage: ' , memory_get_peak_usage(true) , ' Bytes' , "\n";
echo 'STRLEN: ' , strlen($string) , "\n";


Вот что по времени и потреблению памяти:

TIME: 0.0057101249694824
Peak memory usage: 1572864 Bytes
STRLEN: 880044
TIME: 0.0034489631652832
Peak memory usage: 1572864 Bytes
STRLEN: 880044
TIME: 2.270350933075
Peak memory usage: 2621440 Bytes
STRLEN: 880044


Разница по времени действительно очень большая, да и памяти есть действительно больше. Кроме того, похоже, что цикл с for работает чуууууть-чуть дольше цикла с while.
Only those users with full accounts are able to leave comments. Log in, please.