Spec-Zone.ru › Perl 5.38

Memoize

СОДЕРЖАНИЕ

  • ИМЯ
  • СИНТАКСИС
  • ОПИСАНИЕ
  • ПРИМЕР
  • ДЕТАЛИ
  • НАСТРОЙКИ
    • УСТАНОВКА
    • НОРМАЛИЗАТОР
    • SCALAR_CACHE, LIST_CACHE
      • Значения списков в скалярном контексте
      • Объединённые кэши на диске
  • ДРУГИЕ ВОЗМОЖНОСТИ
    • unmemoize
    • flush_cache
  • ЗАМЕЧАНИЯ
  • ПОДДЕРЖКА КЭША ПОСТОЯННОГО ХРАНЕНИЯ
  • ПОДДЕРЖКА ИСТЕЧЕНИЯ СРОКА ДЕЙСТВИЯ
  • ОШИБКИ
  • СМОТРИТЕ ТАКЖЕ
  • БЛАГОДАРНОСТИ
  • АВТОР
  • АВТОРСКИЕ ПРАВА И ЛИЦЕНЗИЯ

ИМЯ

Memoize - Делает функции быстрее, используя пространство вместо времени

СИНТАКСИС

use Memoize;
memoize('slow_function');
slow_function(arguments);    # Is faster than it was before

Обычно этого достаточно. Однако доступно много настроек:

memoize(function, options...);

Настройки включают:

	NORMALIZER => function
	INSTALL => new_name

	SCALAR_CACHE => 'MEMORY'
        SCALAR_CACHE => ['HASH', \%cache_hash ]
	SCALAR_CACHE => 'FAULT'
	SCALAR_CACHE => 'MERGE'

	LIST_CACHE => 'MEMORY'
        LIST_CACHE => ['HASH', \%cache_hash ]
	LIST_CACHE => 'FAULT'
	LIST_CACHE => 'MERGE'

ОПИСАНИЕ

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

ПРИМЕР

Вот экстремальный пример. Рассмотрим последовательность Фибоначчи, определённую следующей функцией:

# Compute Fibonacci numbers
sub fib {
  my $n = shift;
  return $n if $n < 2;
  fib($n-1) + fib($n-2);
}

Эта функция очень медленная. Почему? Для вычисления fib(14) она сначала хочет вычислить fib(13) и fib(12), и сложить результаты. Но для вычисления fib(13) она сначала должна вычислить fib(12) и fib(11), а затем снова вычисляет fib(12), хотя ответ одинаковый. И оба раза, когда она хочет вычислить fib(12), она должна вычислить fib(11) с нуля, а затем повторить это каждый раз, когда хочет вычислить fib(13). Эта функция настолько много перевычисляет старые результаты, что её выполнение занимает очень много времени — fib(14) делает 1200 дополнительных рекурсивных вызовов к себе, чтобы вычислить и перевычислить вещи, которые она уже вычислила.

Эта функция хорошо подходит для запоминания. Если вы запоминаете функцию fib выше, она вычислит fib(14) ровно один раз, в первый раз, когда это необходимо, а затем сохранит результат в таблице. Затем, если вы запросите fib(14) снова, она вернёт результат из таблицы. Во время вычисления fib(14), вместо вычисления fib(12) дважды, она делает это один раз; во второй раз, когда ей нужно значение, она берёт его из таблицы. Она не вычисляет fib(11) четыре раза; она вычисляет его один раз, получая его из таблицы следующие три раза. Вместо того, чтобы делать 1200 рекурсивных вызовов к fib, она делает 15. Это делает функцию примерно в 150 раз быстрее.

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

	# Compute Fibonacci numbers, memoized version
	{ my @fib;
  	  sub fib {
	    my $n = shift;
	    return $fib[$n] if defined $fib[$n];
	    return $fib[$n] = $n if $n < 2;
	    $fib[$n] = fib($n-1) + fib($n-2);
	  }
        }

Или вы можете использовать этот модуль, например так:

use Memoize;
memoize('fib');

# Rest of the fib function just like the original version.

Это делает запоминание лёгким.

Вот ещё один простой пример: я написал простой трассировщик лучей; программа смотрит в определённом направлении, определяет, на что смотрит, и затем преобразует color значение (обычно строку, например red) этого объекта в значение RGB пикселя так:

for ($direction = 0; $direction < 300; $direction++) {
  # Figure out which object is in direction $direction
  $color = $object->{color};
  ($r, $g, $b) = @{&ColorToRGB($color)};
  ...
}

Поскольку в картинке относительно мало объектов, существует всего несколько цветов, которые неоднократно выглядят одинаково. Запоминание ColorToRGB ускорило программу на несколько процентов.

ДЕТАЛИ

Этот модуль экспортирует ровно одну функцию, memoize. Остальные функции в этом пакете — не ваши дела.

Вы должны сказать

memoize(function)

где function — имя функции, которую вы хотите запомнить, или ссылка на неё. memoize возвращает ссылку на новую, запоминающую функцию, или undef при возникновении ошибки, не требующей аварийного завершения работы. На данный момент таких ошибок нет, но они могут появиться в будущем.

Если function было именем функции, то memoize скрывает старую версию и устанавливает новую запоминающую версию под старым именем, так что &function(...) фактически вызывает запоминающую версию.

НАСТРОЙКИ

Существуют некоторые необязательные настройки, которые вы можете передать memoize, чтобы изменить её поведение. Для передачи настроек вызовите memoize так:

	memoize(function, NORMALIZER => function,
			  INSTALL => newname,
                          SCALAR_CACHE => option,
	                  LIST_CACHE => option
			 );

Каждая из этих настроек необязательна; вы можете включить некоторые, все или ни одну из них.

УСТАНОВКА

Если вы передадите имя функции INSTALL, memoize установит новую, запоминающую версию функции под заданным именем. Например,

memoize('fib', INSTALL => 'fastfib')

устанавливает запоминающую версию fib как fastfib; без опции INSTALL она бы заменила старую fib запоминающей версией.

Чтобы предотвратить установку запоминающей версии memoize, используйте INSTALL => undef.

НОРМАЛИЗАТОР

Предположим, ваша функция выглядит так:

# Typical call: f('aha!', A => 11, B => 12);
sub f {
  my $a = shift;
  my %hash = @_;
  $hash{B} ||= 2;  # B defaults to 2
  $hash{C} ||= 7;  # C defaults to 7

  # Do something with $a, %hash
}

Теперь следующие вызовы вашей функции полностью эквивалентны:

f(OUCH);
f(OUCH, B => 2);
f(OUCH, C => 7);
f(OUCH, B => 2, C => 7);
f(OUCH, C => 7, B => 2);
(etc.)

Однако, если вы не сообщите Memoize, что эти вызовы эквивалентны, она не узнает об этом и будет вычислять значения для этих вызовов вашей функции отдельно и хранить их отдельно.

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

sub normalize_f {
  my $a = shift;
  my %hash = @_;
  $hash{B} ||= 2;
  $hash{C} ||= 7;

  join(',', $a, map ($_ => $hash{$_}) sort keys %hash);
}

Каждый из списков аргументов выше выходит из функции normalize_f в точно таком же виде:

OUCH,B,2,C,7

Вы сказали бы Memoize использовать этот нормализатор так:

memoize('f', NORMALIZER => 'normalize_f');

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

По умолчанию нормализатор просто конкатенирует аргументы с символом 28 между ними. (В ASCII это называется FS или control-\.) Это всегда работает правильно для функций с одним строковым аргументом, а также когда аргументы никогда не содержат символ 28. Однако это может спутать некоторые списки аргументов:

normalizer("a\034", "b")
normalizer("a", "\034b")
normalizer("a\034\034b")

например.

Поскольку ключи хеша — это строки, нормализатор по умолчанию не будет различать undef и пустую строку. Он также не будет работать, когда аргументы функции — ссылки. Например, рассмотрим функцию g, которая получает два аргумента: число и ссылку на массив чисел:

g(13, [1,2,3,4,5,6,7]);

Нормализатор по умолчанию преобразует это во что-то вроде "13\034ARRAY(0x436c1f)". Это было бы неплохо, за исключением того, что последующий массив чисел может храниться в другом месте, даже если он содержит те же данные. Если это происходит, Memoize будет думать, что аргументы разные, даже если они эквивалентны. В этом случае подходит такой нормализатор:

sub normalize { join ' ', $_[0], @{$_[1]} }

Для примера выше это даёт ключ «13 1 2 3 4 5 6 7».

Ещё одно применение нормализаторов — когда функция зависит от данных, отличных от тех, что в её аргументах. Предположим, у вас есть функция, которая возвращает значение, зависящее от текущего часа дня:

	sub on_duty {
          my ($problem_type) = @_;
	  my $hour = (localtime)[2];
          open my $fh, "$DIR/$problem_type" or die...;
          my $line;
          while ($hour-- > 0)
            $line = <$fh>;
          } 
	  return $line;
	}

В 10:23 эта функция генерирует 10-ю строку файла данных; в 15:45 она генерирует 15-ю строку вместо этого. По умолчанию Memoize будет видеть только аргумент $problem_type. Чтобы исправить это, включите текущий час в нормализатор:

sub normalize { join ' ', (localtime)[2], @_ }

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

SCALAR_CACHE, LIST_CACHE

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

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

Аргумент для LIST_CACHE или SCALAR_CACHE должен быть одной из четырёх следующих строк:

	MEMORY
	FAULT
	MERGE
        HASH

или же это должна быть ссылка на массив, первым элементом которого является одна из этих четырёх строк, например [HASH, arguments...].

MEMORY

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

HASH

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

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

Типичный пример:

use DB_File;
tie my %cache => 'DB_File', $filename, O_RDWR|O_CREAT, 0666;
memoize 'function', SCALAR_CACHE => [HASH => \%cache];

Это приводит к хранению кэша в DB_File базе данных, имя которой находится в $filename. Кэш будет сохранён после завершения программы. При следующем запуске программы он найдёт кэш, уже заполненный из предыдущего запуска программы. Или вы можете принудительно заполнить кэш, создав пакетную программу, которая работает в фоновом режиме и заполняет файл кэша. Тогда, когда вы запустите свою реальную программу, мемоизированная функция будет быстрой, так как все её результаты будут предварительно вычислены.

Ещё одна причина использовать HASH - предоставить вашу собственную переменную хэша. Вы можете затем просмотреть или изменить содержимое хэша, чтобы получить более точный контроль над управлением кэшем.

TIE

Этот вариант больше не поддерживается. Он всё ещё документирован только для помощи в отладке старых программ, которые его используют. Старые программы следует перевести на использование варианта HASH вместо него.

memoize ... ['TIE', PACKAGE, ARGS...]

просто сокращение для

        require PACKAGE;
	{ tie my %cache, PACKAGE, ARGS...;
          memoize ... [HASH => \%cache];
        }
FAULT

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

`foo' function called in forbidden list context at line ...
`foo' function called in forbidden scalar context at line ...
MERGE

MERGE обычно означает, что мемоизированная функция не различает списочный и скалярный контекст, и что значения возвращаемые в обоих контекстах должны храниться вместе. LIST_CACHE => MERGE и SCALAR_CACHE => MERGE означают то же самое.

Рассмотрим эту функцию:

	sub complicated {
          # ... time-consuming calculation of $result
          return $result;
        }

Функция complicated вернёт то же числовое значение $result независимо от того, вызывается ли она в списочном или скалярном контексте.

Обычно следующий код приведёт к двум вызовам функции complicated, даже если complicated мемоизирована:

$x = complicated(142);
($y) = complicated(142);
$z = complicated(142);

Первый вызов кэширует результат, скажем, 37, в скалярном кэше; второй кэширует список (37) в списочном кэше. Третий вызов не вызывает реальную функцию complicated; он получает значение 37 из скалярного кэша.

Очевидно, второй вызов complicated - пустая трата времени, а хранение его возвращаемого значения - пустая трата места. Указание LIST_CACHE => MERGE заставит memoize использовать один и тот же кэш для значений возвращаемых в скалярном и списочном контекстах, так что второй вызов будет использовать скалярный кэш, который был заполнен первым вызовом. complicated вызывается только один раз, а последующие вызовы возвращают 37 из кэша независимо от контекста вызова.

Списочные значения в скалярном контексте

Рассмотрим эту функцию:

sub iota { return reverse (1..$_[0]) }

Эта функция обычно возвращает список. Предположим, вы мемоизируете её и объединяете кэши:

memoize 'iota', SCALAR_CACHE => 'MERGE';

@i7 = iota(7);
$i7 = iota(7);

Здесь первый вызов кэширует список (1,2,3,4,5,6,7). Второй вызов не имеет смысла. Memoize не может угадать поведение функции iota в скалярном контексте без её фактического вызова в этом контексте. Обычно Memoize вызывала бы функцию iota в скалярном контексте и кэшировала бы результат, но параметр SCALAR_CACHE => 'MERGE' запрещает это, но использовать значение кэша списочного контекста вместо него. Но она не может вернуть список из семи элементов в скалярном контексте. В этом случае $i7 получит первый элемент кэшированного значения списка, а именно 7.

Объединённые кэши на диске

Ещё одно применение параметра MERGE - когда вы хотите хранить оба типа возвращаемых значений в одном файле на диске; это избавляет вас от необходимости работы с двумя файлами на диске вместо одного. Вы можете использовать функцию нормализации, чтобы сохранить два набора возвращаемых значений раздельно. Например:

        local $MLDBM::UseDB = 'DB_File';
        tie my %cache => 'MLDBM', $filename, ...;

	memoize 'myfunc',
	  NORMALIZER => 'n',
	  SCALAR_CACHE => [HASH => \%cache],
	  LIST_CACHE => 'MERGE',
	;

	sub n {
	  my $context = wantarray() ? 'L' : 'S';
	  # ... now compute the hash key from the arguments ...
	  $hashkey = "$context:$hashkey";
	}

Эта функция нормализации будет хранить значения возвращаемых в скалярном контексте в файле на диске под ключами, начинающимися с S:, а значения, возвращаемые в списочном контексте, под ключами, начинающимися с L:.

ДРУГИЕ ВОЗМОЖНОСТИ

unmemoize

Есть функция unmemoize, которую вы можете импортировать, если хотите. Зачем вам это нужно? Вот пример: Предположим, что ваш кэш привязан к файлу DBM, и вы хотите убедиться, что кэш записывается на диск, если кто-то прерывает выполнение программы. Если программа завершается нормально, это произойдёт в любом случае, но если кто-то нажмёт Ctrl+C или что-то подобное, программа завершится немедленно, не синхронизируя базу данных. Так что вы можете вместо этого сделать

$SIG{INT} = sub { unmemoize 'function' };

unmemoize принимает ссылку на или имя ранее мемоизированной функции и отменяет всё, что она сделала, чтобы предоставить мемоизированную версию в первую очередь, включая приведение имени к ссылке на немемоизированную версию, если это применимо. Она возвращает ссылку на немемоизированную версию функции.

Если вы попросите её размемоизировать функцию, которая никогда не была мемоизирована, произойдёт ошибка.

flush_cache

flush_cache(function) очистит кэши, удалив все кэшированные данные. Аргументом может быть имя функции или ссылка на функцию. Для более тонкого контроля над тем, когда данные удаляются или истекают, см. документацию по Memoize::Expire, включённую в этот пакет.

Обратите внимание, что если кэш - это связанный хэш, flush_cache попытается вызвать метод CLEAR в хэше. Если метода CLEAR нет, это приведёт к ошибке во время выполнения.

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

ОСОБЕННОСТИ

Мемоизация не является панацеей:

  • Не мемоизируйте функцию, поведение которой зависит от состояния программы, помимо её собственных аргументов, таких как глобальные переменные, время суток или ввод из файла. Эти функции не дадут правильных результатов при мемоизации. Вот простой пример:

    sub f {
      time;
    }

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

  • Не мемоизируйте функцию с побочными эффектами.

    	sub f {
    	  my ($a, $b) = @_;
              my $s = $a + $b;
    	  print "$a + $b = $s.\n";
    	}

    Эта функция принимает два аргумента, складывает их и печатает сумму. Её возвращаемое значение - количество напечатанных символов, но вас, вероятно, это не интересовало. Но Memoize этого не понимает. Если вы мемоизируете эту функцию, вы получите ожидаемый результат в первый раз, когда попросите её вычислить сумму 2 и 3, но последующие вызовы вернут 1 (возвращаемое значение функции print) без фактического вывода чего-либо.

  • Не мемоизируйте функцию, возвращающую структуру данных, которая модифицируется вызывающим её кодом.

    Рассмотрим эти функции: getusers возвращает список пользователей каким-то образом, а затем main удаляет первого пользователя из списка и выводит остальных:

    sub main {
      my $userlist = getusers();
      shift @$userlist;
      foreach $u (@$userlist) {
        print "User $u\n";
      }
    }
    
    sub getusers {
      my @users;
      # Do something to get a list of users;
      \@users;  # Return reference to list.
    }

    Если вы мемоизируете getusers здесь, она будет работать правильно ровно один раз. Ссылка на список пользователей будет храниться в таблице мемо. main удалит первый элемент из связанного списка. При следующем вызове main, Memoize не будет вызывать getusers; она просто вернёт ту же ссылку на тот же список, который она получила в последний раз. Но на этот раз список уже лишился своего начала; main ошибочно удалит ещё один элемент из него. Список будет становиться всё короче с каждым вызовом main.

    Аналогично, это:

    $u1 = getusers();    
    $u2 = getusers();    
    pop @$u1;

    также изменит $u2, как и $u1, потому что обе переменные - ссылки на один и тот же массив. Если бы getusers не была мемоизирована, $u1 и $u2 ссылались бы на разные массивы.

  • Не мемоизируйте очень простую функцию.

    Недавно кто-то сказал мне, что модуль Memoize замедлил его программу вместо того, чтобы ускорить. Оказалось, что он мемоизировал следующую функцию:

    sub square {
      $_[0] * $_[0];
    }

    Я указал, что Memoize использует хэш, и что поиск числа в хэше обязательно займёт гораздо больше времени, чем однократное умножение. Действительно, нет способа ускорить функцию square.

    Мемоизация не является магией.

ПОДДЕРЖКА ПОСТОЯННОГО КЭША

Вы можете связать таблицы кэша с любым типом связанного хэша, который вы хотите, если он поддерживает TIEHASH, FETCH, STORE, и EXISTS. Например,

tie my %cache => 'GDBM_File', $filename, O_RDWR|O_CREAT, 0666;
memoize 'function', SCALAR_CACHE => [HASH => \%cache];

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

SDBM_File не предоставляет метод EXISTS, поэтому в этот пакет включён вспомогательный модуль Memoize::SDBM_File, который его предоставляет. Используйте его вместо простого SDBM_File, чтобы хранить вашу таблицу кэша на диске в базе данных SDBM_File:

tie my %cache => 'Memoize::SDBM_File', $filename, O_RDWR|O_CREAT, 0666;
memoize 'function', SCALAR_CACHE => [HASH => \%cache];

NDBM_File имеет ту же проблему и то же решение. (Используйте Memoize::NDBM_File instead of plain NDBM_File.)

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

        tie my %cache => 'Memoize::Storable', $filename;
	memoize 'function', SCALAR_CACHE => [HASH => \%cache];

        tie my %cache => 'Memoize::Storable', $filename, 'nstore';
	memoize 'function', SCALAR_CACHE => [HASH => \%cache];

Включите параметр nstore, чтобы база данных Storable записывалась в сетевом порядке. (См. Storable для получения дополнительной информации об этом.)

Функция flush_cache() выведет ошибку во время выполнения, если связанный пакет не предоставляет метод CLEAR.

ПОДДЕРЖКА ИСТЕЧЕНИЯ СРОКА ДЕЙСТВИЯ

См. Memoize::Expire, который является модулем дополнения, добавляющим функциональность истечения срока действия к Memoize. Если вам не нравятся типы политик, которые реализует Memoize::Expire, легко написать свой собственный модуль дополнения, чтобы реализовать любую желаемую вами политику. Memoize поставляется с несколькими примерами. Управляющий истечением срока действия, реализующий политику LRU, доступен на CPAN как Memoize::ExpireLRU.

END_OF_DOCUMENT_MARKER

ОШИБКИ

Набор тестов значительно улучшен, но всегда требует доработки.

Существует проблема с работой goto &f в многопоточном Perl, возможно, из-за лексического области видимости @_. Это ошибка Perl, и до её решения кешированные функции будут видеть немного отличающийся caller() и будут работать немного медленнее в многопоточном Perl, чем в однопоточном.

В некоторых версиях DB_File нельзя сохранить данные под ключом длиной 0. Это означает, что если у вас есть функция f, которую вы кешируете, и кеш хранится в базе данных DB_File, то значение f() (f вызвана без аргументов) не будет кешировано. Если это серьезная проблема, вы можете указать функцию нормализации, которая добавляет "x" к каждому ключу.

СМОТРИТЕ ТАКЖЕ

На странице https://perl.plover.com/MiniMemoize/ есть статья о кешировании и внутреннем устройстве Memoize, опубликованная в журнале The Perl Journal, выпуск #13.

Книга Марка-Джейсона Доминиуса Higher-Order Perl (2005, ISBN 1558607013, издательство Morgan Kaufmann) подробно рассматривает кеширование (и многие другие темы). Она доступна онлайн бесплатно. Для получения дополнительной информации посетите https://hop.perl.plover.com/.

БЛАГОДАРНОСТИ

Большое спасибо Флориану Рагвитцу за помощь в администрировании и упаковке, Джону Тромпу за сообщения об ошибках, Джонатану Рою за сообщения об ошибках и предложения, Майклу Шверну за другие сообщения об ошибках и исправления, Майку Кариасо за помощь в определении правильного подхода к истечению срока действия, Джошуа Герту, Джошуа Чамасу, Джонатану Рою (ещё раз), Марку Д. Андерсону и Эндрю Джонсону за дополнительные предложения об истечении срока действия, Бренту Пауэрсу за модуль Memoize::ExpireLRU, Ариэлю Скольникову за замечательные сообщения о функции Фибоначчи, Диону Альмаеру за стимулирующие предложения по функции нормализации по умолчанию, Вальту Манковски и Курту Старсинику за помощь в исследовании проблем в многопоточном Perl, Алексу Дудкевичу за сообщение об ошибке в прототипированных функциях и проверку моего исправления, Тони Бассу за много полезных предложений, Джонатану Рою (ещё раз) за нахождение применения unmemoize(), Филиппу Вердре за просветительские обсуждения Hook::PrePostCall, Нату Торкинтону за советы, которые я проигнорировал, Крису Нандору за советы по переносимости, Рэндольфу Шварцу за предложение функции 'flush_cache' и Йенде Кринички за то, что он светит в этом мире.

Особая благодарность Яркко Хьетниеми, автору пакета 5.8.0, за включение этого модуля в ядро и за его терпеливое и полезное руководство во время процесса интеграции.

АВТОР

Марк Джейсон Доминиус

АВТОРСКИЕ ПРАВА И ЛИЦЕНЗИЯ

Данное программное обеспечение защищено авторским правом (c) 2012 года Марка Джейсона Доминиуса.

Это свободное программное обеспечение; вы можете его перераспределять и/или изменять на тех же условиях, что и сама система языка программирования Perl 5.

© 1993–2023 Larry Wall and others
Licensed under the GNU General Public License version 1 or later, or the Artistic License.
The Perl logo is a trademark of the Perl Foundation.
https://perldoc.perl.org/5.38.0/Memoize

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API