Perl在哈希表中查找数据的速度更快

时间:2013-05-13 10:12:15

标签: performance perl

我使用这样的代码来查找计算的数据值:

 sub get_data {
 $x =0 if($_[1] eq "A"); #get column number by name
 $data{'A'}= [2.00000, 0.15000, -0.00143, 33.51030, 0.77, 1, 0, 12];
 return $data{$_[0]}[$x];
 }

数据在Perl文件中存储如下。我计划不超过100列。然后为了得到价值我打电话:

    get_data(column, row);

现在我意识到在表格中查找数据的速度非常慢。我怎么能更快地做到这一点? SQL?

2 个答案:

答案 0 :(得分:6)

查看你的github代码,你遇到的主要问题是你的 每次调用函数时都会初始化数组的大哈希值。

您当前的代码:

my @atom;
# {'name'}= radius, depth, solvation_parameter, volume, covalent_radius, hydrophobic, H_acceptor, MW
$atom{'C'}= [2.00000, 0.15000, -0.00143, 33.51030, 0.77, 1, 0, 12];
$atom{'A'}= [2.00000, 0.15000, -0.00052, 33.51030, 0.77, 0, 0, ''];
$atom{'N'}= [1.75000, 0.16000, -0.00162, 22.44930, 0.75, 0, 1, 14];
$atom{'O'}= [1.60000, 0.20000, -0.00251, 17.15730, 0.73, 0, 1, 16];
...

慢速上网本上的测试用例所花费的时间我正在输入:6m24.400s。

最重要的是将其移出功能,所以它就是这样 加载模块时只初始化一次。

这个简单的改变所花费的时间:1分20.714秒。

但是既然我正在提出建议,你可以更清晰地写出来:

my %atom = (
  C => [ 2.00000, 0.15000, -0.00143, 33.51030, 0.77, 1, 0, 12 ],
  A => [ 2.00000, 0.15000, -0.00052, 33.51030, 0.77, 0, 0, '' ],
  ...
);

请注意,%atom在两种情况下都是哈希值,因此您的代码不会对您执行任何操作 想象:它声明了一个词法范围的数组@atom,它是未使用的,然后继续填充一个不相关的全局变量%atom。 (你真的想要一个空的字符串用于MW的A吗?无论如何A是什么样的原子?)

其次,您的名称到数组索引映射也很慢。目前的代码:

#take correct value from data table
$x = 0 if($_[1] eq "radius");
$x = 1 if($_[1] eq "depth");
$x = 2 if($_[1] eq "solvation_parameter");
$x = 3 if($_[1] eq "volume");
$x = 4 if($_[1] eq "covalent_radius");
$x = 5 if($_[1] eq "hydrophobic");
$x = 6 if($_[1] eq "H_acceptor");
$x = 7 if($_[1] eq "MW");

作为哈希(再次,在函数外部初始化)这样做要好得多:

my %index = (
  radius              => 0,
  depth               => 1,
  solvation_parameter => 2,
  volume              => 3,
  covalent_radius     => 4,
  hydrophobic         => 5,
  H_acceptor          => 6,
  MW                  => 7
);

如果你愿意,你可能会变得时髦:

my %index = map { [qw[radius depth solvation_parameter volume
                      covalent_radius hydrophobic H_acceptor MW
                     ]]->[$_] => $_ } 0..7;

无论哪种方式,函数内部的代码都是:

$x = $index{$_[1]};

现在时间:1分13.449秒。

另一种方法是将字段数定义为常量。 常量按惯例大写:

use constant RADIUS=>0, DEPTH=>1, ...;

然后函数中的代码是

$x = $_[1];

然后你需要使用常量而不是字符串来调用函数:

get_atom_parameter('C', RADIUS);

我没试过这个。

但稍微退一步看看你是如何使用这个功能的:

while($ligand_atom[$x]{'atom_type'}[0]) {
 print STDERR $ligand_atom[$x]{'atom_type'}[0];
 $y=0;
 while($protein_atom[$y]) {
 $d[$x][$y] = sqrt(distance_sqared($ligand_atom[$x],$protein_atom[$y]))
 - get_atom_parameter::get_atom_parameter($ligand_atom[$x]{'atom_type'}[0], 'radius');
 - get_atom_parameter::get_atom_parameter($protein_atom[$y]{'atom_type'}[0], 'radius');
 $y++;
 }
$x++;
print STDERR ".";
}

每次通过循环,您都会拨打get_atom_parameter两次 检索半径。 但对于内环,一个原子始终是恒定的。所以提升电话 从内循环到get_atom_parameter,你几乎减半了 通话次数:

while($ligand_atom[$x]{'atom_type'}[0]) {                                       
 print STDERR $ligand_atom[$x]{'atom_type'}[0];                                 
 $y=0;                                                                          
 my $lig_radius = get_atom_parameter::get_atom_parameter($ligand_atom[$x]{'atom_type'}[0], 'radius');                                                               

 while($protein_atom[$y]) {                                                     
 $d[$x][$y] = sqrt(distance_sqared($ligand_atom[$x],$protein_atom[$y]))         
 - $lig_radius
 - get_atom_parameter::get_atom_parameter($protein_atom[$y]{'atom_type'}[0], 'radius');
 $y++;
 }
$x++;
print STDERR ".";
}

但还有更多。在你的测试案例中,配体有35个原子和 蛋白质4128原子。这意味着您的初始代码 4128 * 35 * 2 = 288960次拨打get_atom_parameter,现在是。{ 只有4128 * 35 + 35 = 144515次调用,只需制作一些数组即可 这个半径只有4128 + 35 = 4163个电话:

my $protein_size = $#protein_atom;
my $ligand_size;
{                                                                               
  my $x=0;                                                                      
  $x++ while($ligand_atom[$x]{'atom_type'}[0]);                                 
  $ligand_size = $x-1;                                                          
}
#print STDERR "protein_size = $protein_size, ligand_size = $ligand_size\n";
my @protein_radius;
for my $y (0..$protein_size) {
  $protein_radius[$y] = get_atom_parameter::get_atom_parameter($protein_atom[$y]{'atom_type'}[0], 'radius');
}                                                                               

my @lig_radius;
for my $x (0..$ligand_size) {
  $lig_radius[$x] = get_atom_parameter::get_atom_parameter($ligand_atom[$x]{'atom_type'}[0], 'radius');
}                                                                               

for my $x (0..$ligand_size) {
 print STDERR $ligand_atom[$x]{'atom_type'}[0];
 my $lig_radius = $lig_radius[$x];
 for my $y (0..$protein_size) {
 $d[$x][$y] = sqrt(distance_sqared($ligand_atom[$x],$protein_atom[$y]))
 - $lig_radius
 - $protein_radius[$y]
 }
print STDERR ".";
}

最后,呼叫distance_sqared [原文如此]:

#distance between atoms
sub distance_sqared {
my $dxs = ($_[0]{'x'}-$_[1]{'x'})**2;
my $dys = ($_[0]{'y'}-$_[1]{'y'})**2;
my $dzs = ($_[0]{'z'}-$_[1]{'z'})**2;
return $dxs+$dys+$dzs;
}

此功能可以有效地替换为使用的以下内容 乘法而不是**。

sub distance_sqared {                                                       
my $dxs = ($_[0]{'x'}-$_[1]{'x'});                                          
my $dys = ($_[0]{'y'}-$_[1]{'y'});                                          
my $dzs = ($_[0]{'z'}-$_[1]{'z'});                                          
return $dxs*$dxs+$dys*$dys+$dzs*$dzs;                                       
}                                                                           

所有这些修改后的时间:0m53.639s。

有关**的更多信息:您声明的其他地方

use constant e_math => 2.71828;                                                 

并使用它:

$Gauss1 += e_math ** (-(($d[$x][$y]*2)**2));

内置函数exp()为您计算(事实上,**通常是 实现为x**y = exp(log(x)*y),所以每次你这样做都是 执行不必要的对数,其结果略微减少 因为你的常数只能精确到6 d.p.而不是1。这种变化会改变 输出很轻微。同样,** 2应该用乘法代替。

无论如何,这个答案可能已经足够长了,并计算d[] 不再是瓶颈了。

总结:从循环和函数中提升常量值!计算 同样的事情一点也不好玩。

使用任何类型的数据库都不会对你的表现有所帮助 丝毫。可能对你有帮助的一件事是Inline::C。 Perl是 并非真正为这种密集计算而构建,而Inline :: C 允许您轻松地将性能关键位移动到C中 在Perl中保留现有的I / O.

我愿意在部分C端口拍摄。多么稳定 是这个代码,你想要多快? :)

答案 1 :(得分:0)

将它放在数据库中会使维护,扩展,扩展等变得更容易....使用数据库也可以节省大量的RAM - 它只在RAM中存储和存储所需的结果而不是存储所有值。

关于速度,它取决于。使用文本文件需要很长时间才能将所有值读入RAM,但是一旦加载,检索值就会超快,比查询数据库更快。

所以这取决于你的程序是如何编写的以及它的用途。您是否读取了所有值ONCE然后运行1000个查询? TXT文件的方式可能更快。每次进行查询时都会读取所有值(以确保设置了最新的值) - 然后DB会更快。你每天1个查询吗?使用DB。等......