【问题标题】:Perl creates objects very slowlyPerl 创建对象非常缓慢
【发布时间】:2015-05-31 23:48:28
【问题描述】:

我有一个 perl 脚本,它从数据库中读取约 50,000 行并将它们存储在一个哈希数组中。标准 DBI 代码。与其直接处理哈希,我更喜欢将数据放入可以非常干净地传递给其他代码模块的对象中。我正在阅读的表格中有 15 列以上。我的代码基本上是这样的:

my $db = DBI->connect(); # Just pretend you see a proper DBI connect here
my $resultSet = $db->selectall_arrayref($sql);
$db->disconnect();

# Here's where the problem starts.
my %objects;
for my $row (@{$resultSet}) {
    my ($col1, $col2, ..., $col15) = @{$row};
    my %inputHash;
    $inputHash{col1} = $col1 if $col1;
    ...
    $inputHash{col15} = $col1 if $col15;
    my $obj = Model::Object->new(%inputHash);
    $objects{$col1} = $obj;
}
return values %objects;

它将内容收集到哈希中以消除选择中的重复。问题从“问题出在这里”的注释下方的循环中开始。我在循环中放了一条消息,为每创建 100 个对象记录一行。前 100 个对象是在 5 秒内创建的。接下来的 100 个用时 16 秒。达到 300 需要 30 多秒。它最多可包含 9000 个对象,并且需要 12 分钟以上才能创建 100 个对象。我不认为 50,000 个对象大到足以造成这类问题。

正在创建的 Model::Object 是一个具有每个属性的 getter 和 setter 的类。它有一个新方法和一个序列化方法(本质上是一个 toString),就是这样。没有逻辑。

我正在一台 Windows 笔记本电脑上运行 ActiveState Perl 5.16,该笔记本电脑具有 8 GB 的 RAM、一个 i7 处理器(3 年前)和一个具有合理空间的 SSD 驱动器。我已经在具有相同版本 Perl 的 Linux 机器上看到了这一点,所以我认为这不是硬件问题。我需要继续使用 AS Perl 的 5.16。任何有关如何提高性能的建议将不胜感激。谢谢。

【问题讨论】:

  • 您是否在不创建 $obj(甚至 %inputHash)的情况下检查了性能?性能问题似乎与散列有关。
  • 如果性能有问题,那么您应该将您的my $obj = Model::Object->new(%inputHash); 行转换为my $obj = \%inputHash,仅用于测试目的。再次进行基准测试。如果更改几乎没有区别,那么Model::Object 不是您的问题。如果它有很大的不同,那么它很可能是。
  • 如果您在开始陷入困境之前对可能的最佳结果有所了解,则更容易进行性能调整。据我们所知,真正的问题可能是您使用 ... 传递的代码。
  • @DavidO:虽然我大体上同意你的观点,不应该遗漏任何代码,但在这种情况下,周围环境强烈建议它只有 13 个分配给 $inputHash。此外,与“shotgun profiling”相比,分析是找到慢行的更好方法。
  • Model::Object里面的结构是什么?你不能简单地说$objects{$input_hash{col1}} = bless \%input_hash, 'Model::Object'而不是调用构造函数吗?并且您总是想要与col1last 值对应的数据吗?如果 first 可以,那么当它只会被丢弃时,您可以节省大量构建 %input_hash 的工作。您还可以向后迭代 $result_set 以获取列表中的最后一个值

标签: performance perl object


【解决方案1】:

首先:分析你的程序!你已经把它缩小到一个子,用Devel::NYTProf(例如)你可以把它缩小到是罪魁祸首的那一行。

以下是我的一些一般性考虑:

只是看了一眼,一些可能的减慢因素立即浮现在脑海,但您无法确定是否分析您的程序

Mayhe 散列分配时间太长。随着%objects 哈希的增长,perl 将稳定地分配更多内存。 您可以预先设置 $objects 哈希的大小。这个功能是documented here。由于这是一个内存分配问题,如果您profile使用的数据集太小,您将不会意识到这一点。

# somewhere outside of the loop
keys(%objects) = $number_of_rows * 1.2;
# the hash should be a little bigger than the objects to be stored in it

其次,可能是对象创建时间过长。看看Model::Object。我不知道里面有什么,所以我无法对此发表评论。但您当然应该考虑传递%inputHash 作为参考。使用Model::Object->new(%inputHash);,您将键和值放在堆栈上,然后检索它,在最坏的情况下为my %options = @_;。这样一来,您就可以重新计算每个键的哈希值。

也许你可以想出一个方法来彻底摆脱小$inputHash。我很快只能想出一些方法,这将基于definednes,但你正在检查真实性(你确定这是对的,顺便说一句?"0" 是错误的,例如)。

但同样,最重要的是:分析您的程序。也许采用较小的数据集,但您将无法清楚地看到内存分配问题。但是通过profiling,您会准确地看到,此时您的程序花费的时间最多。

The perldoc has something to say about speeding up your program。它也有一章关于profiling

【讨论】:

  • ...在最坏的情况下为my %options = @_;。通过这一举措,您可以重新计算每个键的哈希非常好的点
  • 感谢大家的建议,尤其是关于 Devel::NYTPRof!我一直在玩它,并发现了一些有趣的结果。我们在所有类中都使用了 Params::Validate,它被调用的次数非常多,占用的时间也最多。所以我正在调查我的逻辑是做什么的。我确实有一个问题,NYTProf 为一个模块返回了一条消息,上面写着“影响此处看到的整个应用程序的正则表达式性能的变量”。我试图弄清楚这意味着什么,但谷歌并不是很有帮助。有什么想法吗?
  • 我找到了关于影响正则表达式性能的变量的问题的答案。谢谢!
  • 仅供将来参考:您可能在某处使用$`、$& 或$',这些对所有正则表达式匹配的性能都会产生不良影响。最好使用p 标志及其对应的变量。
【解决方案2】:

正如您所读到的,在进一步优化之前,您必须使用分析器来确定代码中的瓶颈所在。但是,正如我在评论中所描述的,可以以不同的方式重写您的循环,这样就不会不必要地创建和丢弃未使用的哈希

您还应该看到通过引用传递哈希而不是作为键和值的简单列表传递的改进

这是对您的代码的修改,应该会给您一些想法

use constant COLUMN_NAMES => [ qw/
  col1  col2  col3  col4  col5
  col6  col7  col8  col9  col10
  col11 col12 col13 col14 col15 
/ ];

sub object_results {

    my $dbh = DBI->connect($dsn, $user, $pass);
    my $result_set = $dbh->selectall_arrayref($sql);
    $dbh->disconnect;

    my %objects;
    for ( my $i = $#$result_set; $i >= 0; --$i ) {
        my $row = $result_set->[$i];
        next if exists $objects{$row->[0]};

        my %input_hash;
        for my $i ( 0 .. $#$row ) {
          my $v = $row->[$i];
          next unless defined $v;
          $input_hash{COLUMN_NAMES->[$i]} = $v;
        }

        $objects{$input_hash{col1}} = Model::Object->new(\%input_hash);
    }

    values %objects;
}

【讨论】:

  • 小提示:您也可以use constant COLUMN_NAMES => qw/…/ 并使用$input_hash{(COLNAMES)[$i]} = $v; 访问它。刮掉几毫秒并降低复杂性。但我很少使用常量,也许有一些我不知道的用法。
  • 感谢您的额外提示。在实际代码中,列名是常量。此代码是真实代码的简化和浓缩版本,以使其脱离上下文可读。在现实生活中,我永远不会使用“col1”作为标识符。
  • @limeri:我猜到了。这就是我添加COLUMN_NAMES 常量而不是自动生成列名的原因。我的代码使用COLUMN_NAMES->[$i] 而不是'col'.($i+1)。请记住,您也不应该根据数据库列名来命名变量
猜你喜欢
  • 1970-01-01
  • 2012-02-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-24
  • 1970-01-01
相关资源
最近更新 更多