0

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

 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?

4

2 回答 2

7

查看您的 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。

要做的最重要的事情是将其移出函数,因此它仅在加载模块时初始化一次。

在这个简单的改变之后花费的时间:1m20.714s。

但既然我是在提出建议,你可以写得更清楚:

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。(你真的想要一个空字符串来表示 A 的 MW 吗?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 分)。这种变化会非常轻微地改变输出。再一次,**2 应该被乘法代替。

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

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

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

我愿意在部分 C 端口上试一试。这段代码有多稳定,你希望它有多快?:)

于 2013-05-14T23:13:09.583 回答
0

将其放入数据库将使其更易于维护、扩展、扩展等......使用数据库还可以为您节省大量 RAM——它仅在 RAM 中获取和存储所需的结果,而不是存储所有值.

至于速度,这取决于。使用文本文件,您需要很长时间才能将所有值读入 RAM,但是一旦加载,检索值非常快,比查询数据库还要快。

所以这取决于你的程序是如何编写的以及它的用途。您是否一次读取所有值然后运行 ​​1000 次查询?TXT 文件方式可能更快。您是否每次进行查询时都读取所有值(以确保您拥有最新的值集) - 那么数据库会更快。你每天 1 次查询吗?使用数据库。ETC......

于 2013-05-13T16:23:37.147 回答