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 を埋めます。(また、本当に 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
、半径を取得するために 2 回呼び出します。しかし、内側のループでは、1 つのアトムはずっと一定です。したがって、呼び出しを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
[sic] への呼び出し:
#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;
}
これらすべての変更後の時間: 0 分 53.639 秒。
**についての詳細: 宣言する他の場所
use constant e_math => 2.71828;
次のように使用します。
$Gauss1 += e_math ** (-(($d[$x][$y]*2)**2));
組み込み関数exp()
がこれを計算します (実際、 ** は一般的に として実装されているx**y = exp(log(x)*y)
ため、これを行うたびに不要な対数を実行していることになりますが、その結果は 1 よりわずかに小さいだけです。 6 dp)。この変更により、出力がわずかに変更されます。また、**2 は掛け算に置き換える必要があります。
とにかく、この答えはおそらく今のところ十分長く、の計算d[]
はもはやボトルネックではありません。
まとめ: ループや関数から定数値を巻き上げよう! 同じことを繰り返し計算するのはまったく面白くありません。
これにどのような種類のデータベースを使用しても、パフォーマンスは少しも向上しません。あなたを助けるかもしれないことの1つはInline::C
. Perl は、実際にはこの種の集中的な計算用に構築されているわけではなく、Inline::C を使用すると、既存の I/O を Perl に保持しながら、パフォーマンスが重要なビットを C に簡単に移動できます。
部分的な C ポートで撮影したいと思います。このコードはどの程度安定しており、どれくらい速くしたいですか? :)