2012-03-16 41 views
8

我想在內存中創建和操作大數組(4字節)的整數。我的意思是大約數億人。陣列中的每個細胞將充當染色體上位置的計數器。我所需要的只是讓它適應內存,並且具有快速(O(1))訪問元素的能力。我正在計算的東西不是稀疏特徵,所以我不能使用稀疏數組。perl中類似C的數組

我不能用普通的perl列表來做到這一點,因爲perl(至少在我的機器上)每個元素使用64個字節,所以我使用的大多數生物的基因組太大了。我試圖通過SQLite和哈希捆綁將數據存儲在磁盤上,儘管它們工作的速度非常慢,尤其是在普通驅動器上。 (當我在4驅動器的RAID 0上運行時,它工作得很好)。

我想我可以使用PDL數組,B/C PDL像C一樣存儲它的數組,每個元素只使用4個字節。然而,我發現,更新的速度相比,Perl的名單是極其緩慢:

use PDL; 
use Benchmark qw/cmpthese/; 

my $N = 1_000_000; 
my @perl = (0 .. $N - 1); 
my $pdl = zeroes $N; 

cmpthese(-1,{ 
    perl => sub{ 
     $perl[int(rand($N))]++; 
    }, 
    pdl => sub{ 
     # note that I'm not even incrementing here just setting to 1 
     $pdl->set(int(rand($N)), 1); 
    } 
}); 

返回:

  Rate pdl perl 
pdl 481208/s -- -87% 
perl 3640889/s 657% --  

沒有人知道如何提高PDL集()的表現,或知道不同的模塊那可以做到這一點?

+1

有你看着生物信息學perl模塊? – AlfredoVR 2012-03-16 01:46:11

回答

8

我不知道你會得到什麼樣的性能,但我建議使用vec函數(記錄爲here)將字符串拆分爲位字段。我已經嘗試過並發現我的Perl將容忍長達500_000_000個字符的字符串。這對應於125,000,000個32位值。

my $data = "\0" x 500_000_000; 
vec($data, 0, 32)++;   # Increment data[0] 
vec($data, 100_000_000, 32)++; # Increment data[100_000_000] 

如果這還不夠,那麼Perl的構建中可能會有一些東西來控制極限。另外,如果你認爲你可以得到較小的領域 - 比如16位計數 - vec將接受2的任何權力場寬度可達32

編輯:相信串大小限制是關係到最大2GB在32位Windows進程上的私人工作集。如果你正在運行Linux或者擁有64位perl,你可能比我更幸運。


我已經添加到您的基準測試程序是這樣

my $vec = "\0" x ($N * 4); 

cmpthese(-3,{ 
    perl => sub{ 
     $perl[int(rand($N))]++; 
    }, 
    pdl => sub{ 
     # note that I'm not even incrementing here just setting to 1 
     $pdl->set(int(rand($N)), 1); 
    }, 
    vec => sub { 
     vec($vec, int(rand($N)), 32)++; 
    }, 
}); 

給這些結果

  Rate pdl vec perl 
pdl 472429/s -- -76% -85% 
vec 1993101/s 322% -- -37% 
perl 3157570/s 568% 58% -- 

所以使用vec是三分之二的本地陣列的速度。推測這是可以接受的。

2

當操作可以進行線程化時,PDL獲勝,顯然它沒有針對隨機訪問和分配進行優化。也許有更多PDL知識的人可以提供幫助。

+0

這不是隨機訪問,而是初始化PDL數據結構的啓動時間。根據所涉及的計算,PDL需要處理100-1000個元素,然後再用本機perl操作破壞性能。通過對隱式循環的支持,一行PDL代碼可以替換普通perl中的多個嵌套標量或列表循環。 – chm 2012-04-26 00:32:38

+0

你會是最好的一個知道,感謝評論。當然,現在有更好的答案發布到這個問題比我的。 :-) – 2012-04-26 03:29:49

2

Packed::Array CPAN可能會有所幫助。

Packed :: Array提供了一個打包的有符號整數數組類。使用Packed :: Array構建的數組只能存放與您的平臺本機整數相匹配的有符號整數,但只佔用盡可能多的內存來存放這些整數。因此,對於32位系統而言,每個數組條目只需要大約20個字節,而只需要4個。

+2

是的我已經看過這個模塊,以及'Tie :: Array :: Pack','Tie :: Array :: Packed','Tie :: VecArray'和'Tie :: Array :: PackedC '。不幸的是,Perl'tie' API非常慢,最快('Tie :: Array :: Packed')仍然比原生數組慢10倍。 – Borodin 2012-03-16 14:35:55

7

你想要的PDL命令是indadd。 (感謝PDK Pumpking的Chris Marshall爲我指出這一點elsewhere。)

PDL是爲我所謂的「矢量化」操作而設計的。與C操作相比,Perl操作非常緩慢,因此您希望將PDL方法調用的數量降至最低,並且每次調用都要做很多工作。例如,此基準測試可讓您指定一次執行的更新數(作爲命令行參數)。 Perl的側面有環,但PDL側僅執行五年左右的函數調用:

use PDL; 
use Benchmark qw/cmpthese/; 

my $updates_per_round = shift || 1; 

my $N = 1_000_000; 
my @perl = (0 .. $N - 1); 
my $pdl = zeroes $N; 

cmpthese(-1,{ 
    perl => sub{ 
     $perl[int(rand($N))]++ for (1..$updates_per_round); 
    }, 
    pdl => sub{ 
     my $to_update = long(random($updates_per_round) * $N); 
     indadd(1,$to_update,$pdl); 
    } 
}); 

當我運行此爲1的說法,我使用set,這是什麼時候得到比表現甚至更糟的是我預計:

$ perl script.pl 1 
      Rate pdl perl 
pdl 21354/s -- -98% 
perl 1061925/s 4873% -- 

這是一個很多的理由彌補!但保持在那裏。如果我們做的每一輪100次迭代,我們得到了改進:

$ perl script.pl 100 
     Rate pdl perl 
pdl 16906/s -- -18% 
perl 20577/s 22% -- 

而且每回合萬次更新,PDL優於Perl的由四個因素:

$ perl script.pl 10000 
     Rate perl pdl 
perl 221/s -- -75% 
pdl 881/s 298% -- 

PDL繼續執行大約快4倍比普通的Perl更大的值。

請注意,對於更復雜的操作,PDL的性能會降低。這是因爲PDL將分配和拆除用於中間操作的大而臨時的工作空間。在這種情況下,您可能需要考慮使用Inline::Pdlpp。但是,這不是初學者的工具,所以不要跳到那裏,直到你確定它真的是你最好的東西。

另一種方法對所有這一切就是用Inline::C像這樣:

use PDL; 
use Benchmark qw/cmpthese/; 

my $updates_per_round = shift || 1; 

my $N = 1_000_000; 
my @perl = (0 .. $N - 1); 
my $pdl = zeroes $N; 
my $inline = pack "d*", @perl; 
my $max_PDL_per_round = 5_000; 

use Inline 'C'; 

cmpthese(-1,{ 
    perl => sub{ 
     $perl[int(rand($N))]++ for (1..$updates_per_round); 
    }, 
    pdl => sub{ 
     my $to_update = long(random($updates_per_round) * $N); 
     indadd(1,$to_update,$pdl); 
    }, 
    inline => sub{ 
     do_inline($inline, $updates_per_round, $N); 
    }, 
}); 


__END__ 

__C__ 

void do_inline(char * packed_data, int N_updates, int N_data) { 
    double * actual_data = (double *) packed_data; 
    int i; 
    for (i = 0; i < N_updates; i++) { 
     int index = rand() % N_data; 
     actual_data[index]++; 
    } 
} 

對於我來說,內聯函數始終跳動都Perl和PDL。對於$updates_per_round的較大值,比如說1,000,我得到的Inline::C的版本大約比純Perl快5倍,比PDL快1.2倍和2倍。即使$updates_per_round僅僅是1,Perl在Perl中輕鬆跳過PDL,Inline代碼比Perl代碼快2.5倍。

如果這是全部您需要完成,我推薦使用Inline::C。但是,如果您需要對數據執行很多操作,則最好堅持使用PDL的強大功能,靈活性和性能。請參閱下文,瞭解如何將vec()與PDL數據一起使用。

4

PDL::set()PDL::get()比其他任何東西都更有助於學習。它們構成訪問PDL變量的最佳方法。使用一些內置的批量訪問例程會更好。 PDL構造函數本身接受Perl列表:

$pdl = pdl(@list) 

並且速度相當快。您還可以使用PDL::rcols從ASCII文件直接加載數據,或者使用多個IO例程中的一個從二進制文件加載數據。如果你有數據爲機訂購打包的字符串,你甚至可以訪問到PDL內存直接:

$pdl = PDL->new_from_specification(long,$elements); 
$dr = $pdl->get_dataref; 
$$dr = get_my_packed_string(); 
$pdl->upd_data; 

另外請注意,你可以在「有你的蛋糕和熊掌兼得」通過使用PDL對象握住你的整數數組,PDL計算(如indadd)對數據進行大規模的操作,也可直接使用vec()的PDL數據作爲一個字符串,你可以通過get_dataref方法獲得:

vec($$dr,int(rand($N)),32); 

如果您在小端系統上,您將需要bswap4數據:

$pdl->bswap4; 
$dr = $pdl->get_dataref; 
vec($$dr,int(rand($N)),32)++; 
$pdl->upd_data; 
$pdl->bswap4; 

Et,voila!

2

因爲使用整數,這應該是確定使用 與染色體試試這個

use PDL; 
use Benchmark qw/cmpthese/; 

my $N = 1_000_000; 
my @perl; 
@perl = (0 .. $N - 1); 
my $pdl; 
$pdl = (zeroes($N)); 

cmpthese(-1,{ 
perl => sub{ 
    $perl[int(rand($N))]++; 
}, 
pdl2 => sub{ 
    # note that I'm not even incrementing here just setting to 1 
    $pdl->set(int(rand($N)), 1); 
    $pdl2 = pack "w*", $pdl; 
} 
}); 

和出把我從這個是......

  Rate pdl2 perl 
pdl2 46993/s -- -97% 
perl 1641607/s 3393% -- 

這表明大性能差異 從我第一次嘗試這個代碼出來 加入我的2美分我得到

  Rate pdl perl 
pdl 201972/s -- -86% 
perl 1472123/s 629% -- 
0

我上面的回答可能一文不值...... 這可能幫助壽..

use PDL; 
$x = sequence(45000,45000); 

現在不會工作,除非你有RAM的16 GB和使用

$PDL::BIGPDL=1;