查看您的 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 端口上试一试。这段代码有多稳定,你希望它有多快?:)