воскресенье, 21 марта 2010 г.

Экономия памяти: прием по ссылке

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

Допустим у нас есть огромный блок данных - $data;
и функция для записи данных в файл $file - set_file_content;
sub set_file_content {
    my ($file, $data) = @_;
    ....
   print FILE $data;
}

Вариант 1
set_file_content("$file", "$data") - самый нерациональный вариант вызова. Параметры заключены в двойные кавычки и Perl создаст в памяти еще одну копию строки для передачи в функцию.

Вариант 2
set_file_content($file, $data) - хороший вариант вызова, но тут уже проблема возникает в самой функции set_file_content - она внутри когда принимает параметры, копирует их в локальные переменные.

Вариант 3
Следовательно нужно избежать копирования данных внутри функции set_file_content  и многие решат передавать и принимать данные по ссылке
используя токой вот вызов: set_file_content($file, \$data)
и такую реализацию функции:

sub set_file_content {
    my ($file, $data_ref) = @_;
    ....
   print FILE $$data_ref;
}


По использованию памяти - это отличный вариант, но есть другие недостатки:
1. Первый параметр передается обычным способом, второй по ссылке
2. Если код уже написан, то необходимо изменить везде код вызова функции
3. Самый существенный для меня недостаток - когда я передаю параметр по ссылке, я всегда задумываюсь, а не проделает ли функция каких либо операций с $data внутри и можно будет ли использовать переменную $data после вызова функции.

Вариант 4 (Сабж)
Так вот собственно перейдем к сабжу: прием по ссылке
Функцию set_file_content реализовываем следующим образом:

sub set_file_content {
    my ($file, $data_ref) = ($_[0],  \$_[1]);
    ....
   print FILE $$data_ref;
}

Вызов мы используем обычный - set_file_content($file, $data);

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

Почему это работает?
В перле есть понятие ссылки, а есть понятие алиаса(второго имени). Так вот, в массиве @_ содержатся алиасы на переданные параметры и модифицируя элементы @_, мы модифицируем внешние переменные.
Алиасы также используются в map, grep, foreach
Например:
foreach my $var (@array) {
    $var = 1;
}
мы присвоим  "1" каждому элементу массива @array

ЗЫ:
Когда использовать прием по ссылке, а когда передачу по ссылке? я придерживаюсь следующего правила:
some_function(\$data); - функция изменяет $data внутри
some_function($data);  - функция не изменяет $data внутри

4 коммент.:

Анонимный комментирует...

>я всегда задумываюсь, а не проделает ли функция каких либо операций с $data внутри

Но ведь и в 4-ом варианте функция может проделать какие-либо операции с $data.

P.S. Не могу авторизоваться по OpenID LJ. "Не удалось проверить учетные данные OpenID." и все тут.

koorchik комментирует...

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

То есть, когда я вижу get_file_content($file, $data), то я считаю, что get_file_content точно внутри ничего не сделает лишь потому, что это не есть нормально - я ж не передавал ссылку на переменную... и если функция все-таки что-то делает внутри с $data - это плохой стиль программирования

ЗЫ: По поводу OpenID LJ - у меня все заработало.
Делал так:
1. Зашел в свой аккаунт в LJ
2. В этом блоге выбрал использовать LiveJournal и указал имя пользователя LJ
3. При попытке опубликовать сообщение, меня перебросило на LJ на "Подтверждение идентификации" для http://www.blogger.com.
4. После подтверждения могу использовать аккаунт LJ для комментирования

Игорь комментирует...

В 4-ом варианте функция также может проделать какие-либо операции с $data

koorchik комментирует...

2Игорь: да, все верно

Отправить комментарий

Не забудьте добавить себя в постоянные читатели и включить уведомления о новых комментариях, либо воспользуйтесь RSS каналом ;)