2011-12-28 4 views
11

Я имею дело с веб-приложением, которое использует самодельную систему шаблонов, которая позволяет встроить Perl-код в HTML. Эти операторы выполняются парсером шаблонов во время выполнения с использованием eval EXPR.Более быстрая альтернатива eval?

Это очень гибкий, но эти заявления разбросаны повсюду и получают выполненный лот. eval EXPR (в отличие от eval BLOCK) требует, чтобы Perl каждый раз запускал интерпретатор, и мое профилирование показывает, что они являются достаточно значительным источником замедления.

Многие встроенные операторы Perl очень просты. Например, шаблон может иметь следующую строку:

<p>Welcome, <!--E: $user->query('name') -->. 

Или:

<p>Ticket number <!--E: $user->generate_ticket_number() --> has been generated. 

То есть, они просто вызывая методы объекта. Однако есть и более сложные.

Я надеюсь оптимизировать это, и до сих пор есть две идеи, обе из которых ужасные. Первый заключается в том, чтобы переписать все шаблоны для замены простых вызовов с помощью токенов, таких как USER:NAME и USER:GENERATETICKETNUMBER, которые анализатор мог затем выполнить поиск и вызвать соответствующий метод объекта. Но вместо того, чтобы иметь дело с шаблонами, которые смешивают HTML и Perl, у меня бы были шаблоны, которые смешивают HTML, Perl и токены.

Вторая идея - попытаться разобрать встроенный Perl, выяснить, что хочет сделать оператор, и, если это достаточно просто, вызвать соответствующий метод объекта с помощью символической ссылки. Это, безусловно, безумие.

Есть ли какое-то логическое решение, которое я пропускаю?

+1

+1 для «Это, очевидно, безумие». –

ответ

9

Try принимая подход, аналогичный тому, который mod_perl использует для компиляции CGI,:

  1. Преобразование шаблона в код Perl. Например, ваш первый пример может преобразовать в что-то вроде:

    print "<p>Welcome, "; 
    print $user->query('name'); 
    print ".\n"; 
    
  2. Оберните sub { ... } вокруг этого кода, наряду с некоторым кодом распаковывать аргументы (например, для таких вещей, как $user в образце).

  3. eval этот код. Обратите внимание, что он возвращает coderef.

  4. Позвоните, что coderef несколько раз. :)

+5

Связанный подход заключается в том, чтобы записать сгенерированный код в файл '.pm', а затем загрузить этот модуль. Таким образом, шаблон нужно будет только пересмотреть, когда он изменится. Какой способ лучше зависит от того, как реализовано приложение. – cjm

+0

Вау, это блестяще! Это позволяет мне исправить проблему, не переписывая шаблоны gazillion. Я попробую, спасибо! – parsim

+0

Это именно то, что делает [Template Toolkit] (http://template-toolkit.org). –

2

Возможно, вы захотите посмотреть на кишки Text::MicroTemplate. Реально, вы можете захотеть использовать Текст :: MicroTemplate, поскольку он, скорее всего, подходит для ваших нужд. Он создает подпрограмму, которая связывает строки по мере необходимости, как и предложил duskwuff. Вот результат build_mt('hello, <?= $_[0] ?>') в re.pl:

$CODE1 = sub { 
     package Devel::REPL::Plugin::Packages::DefaultScratchpad; 
     use warnings; 
     use strict 'refs'; 
     local $SIG{'__WARN__'} = sub { 
     print STDERR $_mt->_error(shift(), 4, $_from); 
     } 
     ; 
     Text::MicroTemplate::encoded_string(sub { 
     my $_mt = ''; 
     local $_MTREF = \$_mt; 
     my $_from = ''; 
     $_mt .= 'hello, '; 
     $_from = $_[0]; 
     $_mt .= ref $_from eq 'Text::MicroTemplate::EncodedString' ? $$_from : do { 
      $_from =~ s/([&><"'])/$Text::MicroTemplate::_escape_table{$1};/eg; 
      $_from 
     }; 
     return $_mt; 
     } 
     ->(@_)); 
    }; 
+0

Спасибо за помощь! Я надеюсь избежать этого, не заменяя существующую систему шаблонов, поскольку это будет довольно огромная работа. Но если это будет слишком сложно, я посмотрю на MicroTemplate. – parsim

3

Вы можете взглянуть на Mojolicious. У этого есть templating engine, которые позволяют синтаксис близко к тому, что вы используете.Вы можете переключиться на его использование или посмотреть на его источник (щелкните источник слева от предыдущей ссылки), чтобы узнать, можете ли вы нарисовать некоторые идеи.

FYI синтаксис шаблонного движка Mojolcious позволяет следующие формы перемешаны с HTML соответствующим

<% Perl code %> 
<%= Perl expression, replaced with result %> 
<%== Perl expression, replaced with XML escaped result %> 
<%# Comment, useful for debugging %> 
<%% Replaced with "<%", useful for generating templates %> 
% Perl code line, treated as "<% line =%>" 
%= Perl expression line, treated as "<%= line %>" 
%== Perl expression line, treated as "<%== line %>" 
%# Comment line, treated as "<%# line =%>" 
%% Replaced with "%", useful for generating templates 
+0

Спасибо! Когда я отправился в Джош, я надеюсь избежать замены всей системы шаблонов, просто потому, что это 10-летняя программа, и ее трудно модифицировать. Но я слышал хорошие вещи об Mojolicious. – parsim

+0

Я полностью понимаю! Возможно, его код может помочь вам с несколькими идеями. –

+1

@JoelBerger везде, где этот вопрос получит ответ «Идиот использования CPAN». +1 за то, что я отличный друг. – Hawken

0

Вы не должны использовать «Eval» для вызова методов в шаблоне. Извините, что звук суровый, но точка отдельного представления - удалить код обработки из слоя представления. Системы шаблонов, описанные выше вместе с Template Toolkit, просто передаются в объект/хэш, поэтому вы можете получить к нему доступ.

почему бы не передать $ пользователю как hashref как:

$user = { 
     'name' => 'John', 
     'id' => '3454' 
     }; 

это позволит вам получить доступ к «имя» с:

$user->{'name'}; 

В противном случае, вполне вероятно, что у вас есть, что вы что-то вроде:

  1. template calls $ user-> query();
  2. метод требует БД, чтобы получить значение
  3. метод возвращает значение

Это тааак гораздо дороже, чтобы сделать запрос к базе данных, чем передать ссылку хэш/объекта в шаблоне. Вы можете проверить некоторые инструменты профилирования кода, такие как Devel :: NYTProf, чтобы узнать, какая часть выполнения кода действительно замедляет вас. Я скептически отношусь к тому, что eval настолько увядает вашу программу настолько, что вам нужно оптимизировать eval. Похоже, что код внутри eval - это то, что замедляет вас.

+0

Я действительно запускал NYTProf, и, похоже, сам индикатор является значительным источником замедления. Он потребляет всего 5-10% от общего времени сервера, но это также самая большая точка. Невозможно заменить вызовы $ user-> query ($ value) $ user -> {$ value}, так как подпрограмма & query часто действительно делает что-то помимо простого поиска значения. Кроме того, это всего лишь один звонок среди многих. – parsim

+0

parsim, почему вы вызываете подпрограммы для работы на уровне представления? Не следует ли все это заботиться, прежде чем выводить результат? Может быть, вам стоит подумать о создании API другого типа? Если вы звоните из-за взаимодействия с пользователем, вероятно, время для некоторых вызовов на стороне сервера через AJAX. –

+0

Ответ веб-приложения не имеет архитектуры MVC. (Я упоминал, что ему почти 10 лет?) Шаблоны эффективно управляют приложением. Было бы огромной работой, чтобы изменить это, поэтому я застрял с ним. – parsim