问题 比eval更快的替代方案?


我正在处理一个使用本土模板系统的Web应用程序,该系统允许将Perl代码嵌入到HTML中。这些语句由模板解析器在运行时使用执行 eval EXPR

这是非常灵活的,但这些陈述遍布各处,并被执行了 批量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,弄清楚语句想要做什么,如果它足够简单,通过符号引用调用适当的对象方法。这显然是疯了。

我有一些合理的解决方案吗?


3329
2017-12-28 05:27


起源

为“这显然是疯了”+1。 - mu is too short


答案:


尝试采取类似于那种方法 mod_perl 用于编译CGI:

  1. 将模板转换为Perl代码。例如,您的第一个示例可能会转换为:

    print "<p>Welcome, ";
    print $user->query('name');
    print ".\n";
    
  2. 包裹一个 sub { ... } 围绕该代码,以及一些解包参数的代码(例如,类似于 $user 在样本中)。

  3. eval 那段代码。请注意,它返回coderef。

  4. 重复调用该coderef。 :)


9
2017-12-28 05:51



一种相关的方法是将生成的代码写入a .pm 文件,然后加载该模块。这样,模板只需要在更改时重新解析。哪种方式更好取决于应用程序实现方式的详细信息。 - cjm
哇,太棒了!它让我可以解决问题而无需重写大量的模板。我会尝试一下,谢谢! - parsim
这正是如此 模板工具包 确实。 - Brad Gilbert


你可以看看 Mojolicious。它有一个 模板引擎 它允许语法接近你正在使用的语法。您可以切换到使用它或查看其来源(单击上一个链接左侧的源)以查看是否可以绘制一些想法。

仅供参考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

3
2017-12-28 14:07



谢谢!当我发布给Josh时,我希望避免更换整个模板系统,因为它是一个已有10年历史的软件而且很难修改。但我听说过关于Mojolicious的好话。 - parsim
我完全明白了!或许它的代码可能会帮助你提出一些想法。 - Joel Berger
@JoelBerger在其他任何地方这个问题都会得到“白痴使用CPAN”的回应。我的朋友真棒+1。 - Hawken


你可能想看看它的内脏 文字::微模板。实际上,你可能想要 使用 Text :: 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;
       }
       ->(@_));
     };

2
2017-12-28 08:12



谢谢您的帮助!我希望摆脱这一点,而不必更换当前的模板系统,因为这将是一项相当大的工作。但如果它变得太麻烦,我会看看MicroTemplate。 - parsim


您不应该使用'eval'来调用模板中的方法。很抱歉听起来很苛刻,但分离视图的目的是从视图层中删除处理代码。上面描述的模板系统和Template Toolkit只传入一个对象/哈希,因此您可以访问它。

为什么不将$ user作为hashref传递:

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

这将允许您通过以下方式访问“名称”:

$user->{'name'};

否则,你可能会做以下事情:

  1. 模板调用$ user-> query();
  2. 方法调用DB来获取值
  3. 方法返回值

制作数据库查询比将哈希/对象引用传递给模板要昂贵得多。您可能需要查看一些代码分析工具,如Devel :: NYTProf,以查看代码执行的哪些部分确实会让您失望。我怀疑eval是否会使你的程序陷入困境,以至于你需要优化eval。听起来像eval中的代码正在减慢你的速度。


0
2017-12-28 20:03



我确实运行了NYTProf,它似乎表明eval本身是减速的重要来源。它只占服务器总时间的5-10%,但它也是最大的一点。用$ user - > {$ value}替换$ user-> query($ value)调用是不可行的,因为&query sub通常确实做了一些事情而不仅仅是查找值。此外,这只是众多人中的一个电话。 - parsim
parsim,为什么要调用子程序在视图层工作?在输出结果之前,是否应该完成所有工作?也许您需要考虑构建不同类型的API?如果您是因为用户交互而打电话,那么可能是通过AJAX进行一些服务器端调用的时候了。 - Dave Koston
答案是Web应用程序没有MVC架构。 (我提到它已经差不多10年了吗?)模板有效地推动了应用程序。改变这一点将是一项巨大的工作,所以我坚持下去。 - parsim