【问题标题】:Perl - Data comparison taking huge timePerl - 需要大量时间的数据比较
【发布时间】:2013-04-12 00:32:14
【问题描述】:
open(INFILE1,"INPUT.txt");

my $modfile = 'Data.txt';
open MODIFIED,'>',$modfile or die "Could not open $modfile : $!";   

for (;;) {
    my $line1 = <INFILE1>;
    last if not defined $line1;

    my $line2 = <INFILE1>;
    last if not defined $line2;

    my ($tablename1, $colname1,$sql1) = split(/\t/, $line1);
    my ($tablename2, $colname2,$sql2) = split(/\t/, $line2);

    if ($tablename1 eq $tablename2)
    {
        my $sth1 = $dbh->prepare($sql1);
        $sth1->execute;
        my $hash_ref1 = $sth1->fetchall_hashref('KEY');

        my $sth2 = $dbh->prepare($sql2);
        $sth2->execute;
        my $hash_ref2 = $sth2->fetchall_hashref('KEY');

        my @fieldname = split(/,/, $colname1);
        my $colcnt=0;
        my $rowcnt=0;
        foreach $key1 ( keys(%{$hash_ref1}) ) 
        {

            foreach (@fieldname)
            { 
                $colname =$_;

                my $strvalue1='';
                @val1 = $hash_ref1->{$key1}->{$colname};

                if (defined @val1)
                {
                    my @filtered = grep /@val1/, @metadata;
                    my $strvalue1 = substr(@filtered[0],index(@filtered[0],'||') + 2);
                }

                my $strvalue2='';
                @val2 = $hash_ref2->{$key1}->{$colname};
                if (defined @val2)
                {
                    my @filtered = grep /@val2/, @metadata2;
                    my $strvalue2 = substr(@filtered[0],index(@filtered[0],'||') + 2);
                }                   

                if ($strvalue1 ne $strvalue2 )
                { 
                    $colcnt = $colcnt + 1;
                    print MODIFIED "$tablename1\t$colname\t$strvalue1\t$strvalue2\n";
                }
            }
        }
        if ($colcnt>0) 
        {   
            print "modified count is $colcnt\n";
        }   

        %$hash_ref1 = ();
        %$hash_ref2 = ();

    }

程序是读取输入文件,其中每一行包含三个由制表符分隔的字符串。第一个是表名,第二个是所有列名,中间有逗号,第三个包含要运行的 sql。由于此实用程序正在比较数据,因此每个表名都有两行。每个数据库一个。因此需要从每个相应的数据库中挑选数据,然后逐列比较。

SQL 在结果集中作为 ID 返回,如果值来自 db,则需要通过从数组中读取将其转换为字符串(该数组包含 100K 条记录,键和值由 || 分隔)
现在我为一组表运行了这个,每个表中包含 18K 条记录。每个 sql 中从 db 中选择了 8 列。因此,对于 18K 中的每条记录,然后对于该记录中的每个字段(即 8),此脚本都需要大量时间。

我的问题是,是否有人可以查看是否可以对其进行改进以减少花费的时间。 文件内容示例

INPUT.TXT      
TABLENAME   COL1,COL2   select COL1,COL2 from TABLENAME where ......    
TABLENAMEB  COL1,COL2   select COL1,COL2 from TABLENAMEB where ......    

元数据数组包含类似这样的东西(每个数据库有两个)

111||Code 1    
222||Code 2    

请推荐

【问题讨论】:

  • 您是否通过分析器(如 Devel::NYTProf 或 Devel::DProf)运行它并消除了 SQL 执行延迟的可能性?
  • 通常prepare 在循环中是一件坏事。你确定你不能prepare循环外的所有查询,而只是execute他们稍后使用不同的参数?
  • 查询是否总是返回相同的列集?你实际上在比较什么?代码很乱,但你能说出你在找什么吗?例如。 我想要 TABLENAME 中但不在 TABLENAMEB 中的所有行的列表 或类似的东西?
  • 要求 a) 两个 diff dbs 的表集的逐列比较 b) 由其他程序创建的 sql 集,因此生成的 sql 存储在文件中。 c)两个数据库中每个表的布局都是相同的。 d) 列值始终是每个 db 的 id,但不能比较,因为两个 db 中的 id 不同,因此使用数组。关于循环内的准备,这是唯一的方法,因为每个表都有要比较的 diff 列。所以需要循环。另外,我会尝试使用分析器。但是我在测试的时候放了这么多打印语句,可以确认sqls并没有花费太多时间。
  • 真的有两个不同的数据库吗?或者,同一个数据库中只有两个表?

标签: perl


【解决方案1】:

您的代码确实看起来有点不寻常,并且可以通过使用子例程而不是仅使用循环和条件来获得清晰。以下是一些其他建议。


摘录

for (;;) {
  my $line1 = <INFILE1>;    
  last if not defined $line1;    
  my $line2 = <INFILE1>;    
  last if not defined $line2;  
  ...;
}

过于复杂:不是每个人都知道 C-ish for(;;) 成语。你有很多代码重复。你不是说loop while I can read two lines吗?

while (defined(my $line1 = <INFILE1>) and defined(my $line2 = <INFILE1>)) {
  ...;
}

是的,这条线更长,但我认为它更像是自我记录。


而不是做

if ($tablename1 eq $tablename2) { the rest of the loop }

你可以说

next if $tablename1 eq $tablename2;
the rest of the loop;

并保存一定程度的意图。更好的意图等于更好的可读性使得编写好的代码更容易。更好的代码可能性能更好。


你在foreach $key1 (keys ...) 做什么——告诉我你没有use strict! (提示一下:my 的词法变量的性能比全局变量略好)

此外,出于同样的原因,在 for 循环中执行 $colname = $_ 是一件愚蠢的事情。

for my $key1 (keys ...) {
  ...;
  for my $colname (@fieldname) { ... }
}

my $strvalue1='';
@val1 = $hash_ref1->{$key1}->{$colname};

if (defined @val1)
{
  my @filtered = grep /@val1/, @metadata;
  my $strvalue1 = substr(@filtered[0],index(@filtered[0],'||') + 2);
}

我认为这不会像认为的那样。

$hash_ref1 中检索单个元素,然后将该元素分配给一个数组(多个值的集合)。

然后你在这个数组上调用了defined。数组不能未定义,并且您正在做的事情已被弃用。在集合上调用defined 函数会返回有关内存管理的信息,但不指示①该数组是否为空或②该数组中的第一个元素是否已定义。

将数组内插到正则表达式中不太可能有用:数组的元素与 $" 的值连接,通常是一个空格,并将生成的字符串视为正则表达式。如果存在元字符,这将造成严重破坏。

当您只需要列表的第一个值时,您可以强制列表上下文,但分配给单个标量,如

my ($filtered) = produce_a_list;

这可以让您摆脱不需要的奇怪下标,这只会减慢您的速度。

然后你分配给一个你刚刚声明$strvalue1变量。这会遮蔽外部$strvalue1。它们不是同一个变量。所以在if 分支之后,$strvalue1 中仍然有空字符串。

我会这样写代码

my $val1 = $hash_ref1->{$key1}{$colname};

my $strvalue1 = defined $val1
  ? do {
    my ($filtered) = grep /\Q$val1/, @metadata;
    substr $filtered, 2 + index $filtered, '||'
  } : '';

但是,如果您将 @metadata 预先拆分成对并使用正确的字段测试是否相等,这会更便宜。这将消除一些仍然潜伏在该代码中的错误。


$x = $x + 1 通常写成$x++


在迭代结束时清空 hashrefs 是不必要的:hashrefs 在循环的下一次迭代中被分配给一个新值。此外,对于这些简单的任务,无需协助 Perls 垃圾收集。


关于元数据:10 万条记录很多,所以要么将其放入数据库本身,要么至少是一个哈希。特别是对于如此多的记录,使用哈希比遍历所有条目并使用慢正则表达式要快得多 ...啊啊!

  1. 从文件创建哈希,在程序开始时一次

    my %metadata;
    while (<METADATA>) {
       chomp;
       my ($key, $value) = split /\|\|/;
       $metadata{$key} = $value; # assumes each key only has one value
    }
    
  2. 只需查找循环内的键

    my $strvalue1 = defined $val1 ? $metadata{$val1} // '' : ''
    

那应该快得多。

(哦,请考虑为变量使用更好的名称。$strvalue1 没有告诉我任何信息,除了它是一个字符串值(d'oh)。$val1 更糟糕。)

【讨论】:

    【解决方案2】:

    这不是一个真正的答案,但它也不太适合评论,所以在您提供更多信息之前,这里有一些观察。

    在你的内部for 循环中,有:

    @val1 = $hash_ref1->{$key1}->{$colname};
    

    你是说@val1 = @{ $hash_ref1-&gt;{$key1}-&gt;{$colname} };吗?

    稍后,您检查if (defined @val1)?你真正想检查什么?正如perldoc -f defined 指出的那样:

    在聚合(散列和数组)上使用“定义”是 已弃用。它用于报告该聚合的内存是否 曾经被分配过。这种行为将来可能会消失 Perl 的版本。您应该改为使用简单的大小测试:

    在您的情况下,if (defined @val1) 将始终为真。

    那么,你有my @filtered = grep /@val1/, @metadata;@metadata是从哪里来的?您实际上打算检查什么?

    那么你有my $strvalue1 = substr(@filtered[0],index(@filtered[0],'||') + 2);

    里面发生了一些有趣的事情。

    你需要用语言表达你真正想要做的事情。

    我强烈怀疑您可以运行一个 SQL 查询来满足您的需求,但我们首先需要知道您想要什么。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-09-23
      • 2022-06-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-07
      • 2016-12-21
      相关资源
      最近更新 更多