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秒的开销,而不是替换本身.(这是通过完全删除循环来验证的.)
摘要:
$col =~ s/...//g;)的替换部分需要12秒才能获得我的测试数据.题:
如何提高消毒步骤的性能?
额外:为什么for循环开销很高?
笔记:
清理本身只是在任何特殊字符之前加上反斜杠.
消毒是必需的,必须在join发生之前应用于每个色谱柱.这是一个技术限制,因为它$COLUMN_DELIM_STR可能包含特殊字符,我们需要它们不被转义.此外,$COLUMN_DELIM_STR脚本的运行长度和值可能不同.
可以预先确定列数,但不能确定列名称或数据类型.该脚本不知道哪些列可能包含或不包含需要转义的特殊字符.
如果有更好的方法来清理列数据,请随时提出建议.我愿意接受其他想法.
如果您只想将表转储为分隔文件,请让数据库执行此操作. 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/ }.
| 归档时间: |
|
| 查看次数: |
120 次 |
| 最近记录: |