Performance Perl DBI-用于消除循环的性能?

Performance Perl DBI-用于消除循环的性能?,performance,perl,loops,dbi,Performance,Perl,Loops,Dbi,我正在编写一个perl脚本,它使用DBI将数据从数据库表卸载到特定格式。我有点事要做,但演出。。。缺乏 以下是代码的性能关键部分: while (my $row = $query->fetchrow_arrayref()) { # Sanitize the columns to make sure certain characters are escaped with a backslash. # The escaping is required as some binar

我正在编写一个perl脚本,它使用DBI将数据从数据库表卸载到特定格式。我有点事要做,但演出。。。缺乏

以下是代码的性能关键部分:

while (my $row = $query->fetchrow_arrayref()) {
    # Sanitize the columns to make sure certain characters are escaped with a backslash.
    # The escaping is required as some binary data may be included in some columns.
    # This must occur *before* the join() as $COLUMN_DELIM_STR may contain one of the special characters.
    for $col (@$row) { $col =~ s/(?=[\x5C\x00-\x1F])/\\/g; }

    # Output the sanitized row
    print join($COLUMN_DELIM_STR, @$row) . $RECORD_DELIM_STR;
}
我有一个5列1000万行的测试表。总卸载时间为90秒(输出重定向到
/dev/null
,因此磁盘写入不会干扰基准测试)

在尝试删除代码块以了解它们对性能的影响之后,我意识到清理循环占用了大量的处理时间,大约30秒(大约是总执行时间的1/3)。Settings
DBI_PROFILE=4
显示获取本身大约需要45秒

关键在于:删除实际的替换步骤(
$col=~s/(?=[\x5C\x00-\x1F])/\\/g;
)只节省大约12秒的处理时间。这意味着对$col(@$row){;}不执行循环(
为$col(@$row){;}
)会产生18秒的开销,比替换本身还要多。(通过完全移除回路验证了这一点。)

摘要:

  • 消毒循环大约占总执行时间的1/3,对于我的测试数据大约30秒。根据源数据中的列数,按比例需要更多的时间
  • 消毒循环的替换部分(
    $col=~s/..//g;
    )需要12秒来获取测试数据
  • 剩下的18秒是for循环结构本身
问题:

如何提高消毒步骤的性能?
奖励:为什么for循环开销很高

注意事项:

  • 消毒本身只是在任何特殊字符前加上反斜杠

  • 需要进行消毒,并且必须在
    连接之前对每一列进行消毒。这是一个技术限制,因为
    $COLUMN\u DELIM\u STR
    可能包含特殊字符,我们需要它们不能转义。此外,
    $COLUMN\u DELIM\u STR
    的长度和值在脚本运行期间可能会有所不同

  • 可以预先确定列数,但不能确定列名或数据类型。脚本事先不知道哪些列可能包含或不包含需要转义的特殊字符

  • 如果有更好的方法来清理列数据,请随时提出建议。我对其他想法持开放态度

    • 对我来说

      • 测试线束加上替换每个元素需要3.57µs(对于七个字符串,其中一个字符需要转义)
      • 测试线束加上回路每个元件需要0.960µs+0.141µs

      • 因此,在5个元素上循环将变成1.66µs

      这些数字在实践中可能会有所不同,但这个比率比你所说的更符合我的期望。执行基于正则表达式的替换代价相当高,但增加计数器则不然,因此循环应该比替换便宜得多


      输出:

      • 对于
        (1000个元素):7065.42/s
      • (0个元素):1041030.65/s
      • s//
        :284348.25/s
        • 对我来说

          • 测试线束加上替换每个元素需要3.57µs(对于七个字符串,其中一个字符需要转义)
          • 测试线束加上回路每个元件需要0.960µs+0.141µs

          • 因此,在5个元素上循环将变成1.66µs

          这些数字在实践中可能会有所不同,但这个比率比你所说的更符合我的期望。执行基于正则表达式的替换代价相当高,但增加计数器则不然,因此循环应该比替换便宜得多


          输出:

          • 对于
            (1000个元素):7065.42/s
          • (0个元素):1041030.65/s
          • s//
            :284348.25/s

          如果您只想将一个表作为分隔文件转储,就让数据库来完成。其他数据库也有类似的功能。这避免了将所有数据复制到程序中、对其进行修改并再次输出的开销


          另一个选项是在SELECT中进行转义。在Oracle中,您可以使用。这应该可以(我可能把反斜杠的细节搞错了)

          现在的问题是对每一列都这样做。您不知道有多少列或它们的名称,但您可以使用
          SELECT*fromtable LIMIT 1
          $sth->fetchrow\u hashref
          或更直接地使用
          $dbh->column\u info
          来查找。现在,您可以构造一个具有正确行数的SELECT,并将REGEXP_REPLACE应用于每个行。这可能更快。您甚至可以在选择中进行连接

          您甚至可以编写一个PL/SQL函数来完成这一切。这可能是最有效的。这里有一个可以用来做转义的


          至于为什么空循环很慢,您运行了5000万次,尽管18秒似乎很高。我的2011 Macbook Pro可以在大约6秒钟内运行它,让我们验证一下空循环是问题所在。这个代码需要多长时间

          time perl -wle 'my $rows = [1..5]; for my $row (1..10_000_000) { for $col (@$rows) {} }'
          
          简单地迭代5000万次(
          对于(1..50\u 000\u 000)
          )需要三分之一的时间。所以也许有一种方法可以对内部循环进行微观优化。恕我直言,事实证明,在无块的void上下文中,映射速度要快得多

          map s{(?=[\x5C\x00-\x1F])}{\\}g, @$rows;
          
          为什么??用B::Terse转储字节码告诉我们Perl在映射中做的工作更少。下面是内部for循环的作用:

              UNOP (0x1234567890ab) null 
                  LOGOP (0x1234567890ab) and 
                      OP (0x1234567890ab) iter 
                      LISTOP (0x1234567890ab) lineseq 
                          COP (0x1234567890ab) nextstate 
                          BINOP (0x1234567890ab) leaveloop 
                              LOOP (0x1234567890ab) enteriter 
                                  OP (0x1234567890ab) null [3] 
                                  UNOP (0x1234567890ab) null [147] 
                                      OP (0x1234567890ab) pushmark 
                                      UNOP (0x1234567890ab) rv2av [7] 
                                          OP (0x1234567890ab) padsv [1] 
                                  PADOP (0x1234567890ab) gv  GV (0x1234567890ab) *_ 
                              UNOP (0x1234567890ab) null 
                                  LOGOP (0x1234567890ab) and 
                                      OP (0x1234567890ab) iter 
                                      LISTOP (0x1234567890ab) lineseq 
                                          COP (0x1234567890ab) nextstate 
                                          PMOP (0x1234567890ab) subst 
                                              SVOP (0x1234567890ab) const [12] PV (0x1234567890ab) "2" 
                                          OP (0x1234567890ab) unstack 
                          OP (0x1234567890ab) unstack 
          
          这是地图

              UNOP (0x1234567890ab) null 
                  LOGOP (0x1234567890ab) and 
                      OP (0x1234567890ab) iter 
                      LISTOP (0x1234567890ab) lineseq 
                          COP (0x1234567890ab) nextstate 
                          LOGOP (0x1234567890ab) mapwhile [8] 
                              LISTOP (0x1234567890ab) mapstart 
                                  OP (0x1234567890ab) pushmark 
                                  UNOP (0x1234567890ab) null 
                                      PMOP (0x1234567890ab) subst 
                                          SVOP (0x1234567890ab) const [12] PV (0x1234567890ab) "2" 
                                  UNOP (0x1234567890ab) rv2av [7] 
                                      OP (0x1234567890ab) padsv [1] 
                          OP (0x1234567890ab) unstack 
          
          基本上,for循环必须进行额外的工作,为每次迭代设置新的词汇上下文。地图没有,但你不能
              UNOP (0x1234567890ab) null 
                  LOGOP (0x1234567890ab) and 
                      OP (0x1234567890ab) iter 
                      LISTOP (0x1234567890ab) lineseq 
                          COP (0x1234567890ab) nextstate 
                          BINOP (0x1234567890ab) leaveloop 
                              LOOP (0x1234567890ab) enteriter 
                                  OP (0x1234567890ab) null [3] 
                                  UNOP (0x1234567890ab) null [147] 
                                      OP (0x1234567890ab) pushmark 
                                      UNOP (0x1234567890ab) rv2av [7] 
                                          OP (0x1234567890ab) padsv [1] 
                                  PADOP (0x1234567890ab) gv  GV (0x1234567890ab) *_ 
                              UNOP (0x1234567890ab) null 
                                  LOGOP (0x1234567890ab) and 
                                      OP (0x1234567890ab) iter 
                                      LISTOP (0x1234567890ab) lineseq 
                                          COP (0x1234567890ab) nextstate 
                                          PMOP (0x1234567890ab) subst 
                                              SVOP (0x1234567890ab) const [12] PV (0x1234567890ab) "2" 
                                          OP (0x1234567890ab) unstack 
                          OP (0x1234567890ab) unstack 
          
              UNOP (0x1234567890ab) null 
                  LOGOP (0x1234567890ab) and 
                      OP (0x1234567890ab) iter 
                      LISTOP (0x1234567890ab) lineseq 
                          COP (0x1234567890ab) nextstate 
                          LOGOP (0x1234567890ab) mapwhile [8] 
                              LISTOP (0x1234567890ab) mapstart 
                                  OP (0x1234567890ab) pushmark 
                                  UNOP (0x1234567890ab) null 
                                      PMOP (0x1234567890ab) subst 
                                          SVOP (0x1234567890ab) const [12] PV (0x1234567890ab) "2" 
                                  UNOP (0x1234567890ab) rv2av [7] 
                                      OP (0x1234567890ab) padsv [1] 
                          OP (0x1234567890ab) unstack