Perl DBI - 用于循环查杀性能?

Mr.*_*ama 0 perl performance 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 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;
}
Run Code Online (Sandbox Code Playgroud)

我有一个包含5列和1000万行的测试表.总卸载时间为90秒(输出重定向到,/dev/null因此磁盘写入不会干扰基准测试).

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

这是踢球者:删除实际的替换步骤($col =~ s/(?=[\x5C\x00-\x1F])/\\/g;)只能节省大约12秒的处理时间.这意味着loop(for $col (@$row) { ; })的do-nothing 会产生18秒的开销,而不是替换本身.(这是通过完全删除循环来验证的.)

摘要:

  • 清理循环大约占总执行时间的1/3,对于我的测试数据大约需要30秒.根据源数据中的列数,按比例增加时间.
  • 清理循环($col =~ s/...//g;)的替换部分需要12秒才能获得我的测试数据.
  • 剩下的18秒是for循环结构本身.

题:

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

笔记:

  • 清理本身只是在任何特殊字符之前加上反斜杠.

  • 消毒是必需的,必须在join发生之前应用于每个色谱柱.这是一个技术限制,因为它$COLUMN_DELIM_STR可能包含特殊字符,我们需要它们被转义.此外,$COLUMN_DELIM_STR脚本的运行长度和值可能不同.

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

  • 如果有更好的方法来清理列数据,请随时提出建议.我愿意接受其他想法.

Sch*_*ern 5

如果您只想将表转储为分隔文件,请让数据库执行此操作. MySQL有SELECT INTO其他数据库有类似的功能.这避免了将所有数据复制到程序中,改变它并再次吐出的开销.


另一种选择是在SELECT中进行转义.在Oracle中,您可以使用REGEXP_REPLACE.这应该这样做(我可能有反斜杠的细节错误).

REGEXP_REPLACE(column, '([^[:print:]])', '\\\\1')
Run Code Online (Sandbox Code Playgroud)

现在的问题是每列都要这样做.你不知道有多少列具有或他们的名字,但你可以找到很轻松地SELECT * FROM table LIMIT 1$sth->fetchrow_hashref或更直接地$dbh->column_info.现在,您可以构造具有正确行数的SELECT,并将REGEXP_REPLACE应用于每个行.这可能会更快.你甚至可以在SELECT中进行连接.

您甚至可以编写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) {} }'
Run Code Online (Sandbox Code Playgroud)

简单地迭代5000万次(for (1..50_000_000))需要三分之一的时间.所以也许有一种方法可以对内循环进行微观优化.我会饶了你,它在无效的上下文中显示出一个地图,没有阻止明显更快.

map s{(?=[\x5C\x00-\x1F])}{\\}g, @$rows;
Run Code Online (Sandbox Code Playgroud)

为什么?使用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 
Run Code Online (Sandbox Code Playgroud)

这是地图.

    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 
Run Code Online (Sandbox Code Playgroud)

基本上,for循环必须经历为每次迭代设置新词法上下文的额外工作.地图没有,但你不能使用块.有趣的是,s/1/2/ for @$rows编译几乎相同for (@$rows) { s/1/2/ }.

  • @ Mr.Llama如果您的服务器的CPU比我的笔记本电脑慢三倍,您可能需要投资服务器硬件.与300美元的CPU升级相比,您可以节省更多时间/金钱进行优化. (2认同)