在 linux 伺服器上的怪異現象--網路程式比本機程式有更高的效能
這是民國97年10月16日左右寫的程式,今天把他整理一下。先丟出一個問題:
Q. 在同一台機器上。假設一個單執行緒的 A 程式,例如產生200萬組的亂數,用本機在Console下執行要花 Ta秒。而同樣的功能寫一個B,差別在於B 會開啟一個TCP的埠,接受程式C傳入的參數,進行運算,運算完畢再將結果丟回給C,而C不負責運算,只負責接線到B,將參數告訴B,再接收B算完的結果,同樣也算200萬組的亂數,用本機在Console下執行要花 Tb秒,請問 Ta大還是 Tb大?
思考.
Ta = 程式A執行產生及分類200萬組變數的時間;
Tb = 程式C和B建立連線時間 + 程式B 產生及分類200萬組變數執行的時間 + 程式B 將結果回傳給C 的時間 。
由於產生及分類200萬組變數的程式碼都相同,也在同一台機器上執行,所以合理的推論
程式A執行產生及分類200萬組變數的時間 =近似於= 程式B 產生及分類200萬組變數執行的時間,而Tb多了TCP連線及傳參、傳結果的時間,所以Tb理論上應微大於 Ta,所以 Tb > Ta
實驗結果,出人意料, Ta > Tb 本機執行的A程式花費時間,竟然比同台機器上的網路程式B來得多,而且多很多。
說明
1. 程式 A 和 B 的功能都是產生一個常態分配的亂數,有關細節可以參考這篇;現在都設定為產生200萬組,標準差為1,然後以1為間距分類,例如6代表5.5<=x<6.5的亂數; 以下是分類的結果:
$ ./a.pl <==
Program Start....
1 61
2 4493
3 119461
4 1212828
5 4833075
6 7658486
7 4835924
8 1211702
9 119407
10 4504
11 58
12 1
Elapsed time=00:01:34.584939
測試第二次,時間也落在00:01:34左右,差距不大,經實測10多次,差距非常小。
1 52
2 4453
3 120136
4 1211275
5 4835101
6 7656908
7 4836712
8 1210831
9 119857
10 4610
11 64
12 1
Elapsed time=00:01:34.379488
平均時間 1分34秒 = 94秒
2. 程式B為一個RPC 的Server,負責接收來自 RPC 的 Client (程式C)傳進來的參數,例如組數200萬筆,均值6,標準差為1,然後以1為間距分類等參數。運算完畢再將結果回傳給程式C
1 78
2 4548
3 118873
4 1210720
5 4830204
6 7660240
7 4836865
8 1213987
9 119893
10 4513
11 79
Elapsed time=00:01:24.393868
測試第二次,時間也落在00:01:24左右,差距不大,經實測10多次,差距非常小。
1 65
2 4506
3 119423
4 1210867
5 4836787
6 7658475
7 4833891
8 1211412
9 119771
10 4731
11 71
12 1
Elapsed time=00:01:24.345658
平均時間 1分24秒 = 84秒
3. 程式C只是一個 RPC 的 Client ,只負責傳送參數給 RPC Server 及接收計算完的結果,本身不負責計算。
4. 三個程式都是單一執行緒,計算時間檢視 CPU的Loading,只有一顆在>90%,其他都低於3%,而C程式執行時,另一顆CPU會有一瞬間(少於1s)達5%~7%。
5. 本機程式 B花費時間是 A 的 84/94 = 89%,表示效能約高 11%。無法解釋這個現象,只能把結果寫出來,而程式碼附於後。
[附錄一] 測試機器
以下兩台為測試過的機器,兩台的結果Tb < Ta是相同的
1. CentOs4.3 Linux 2.6.9-34.ELsmp Xeon3.0 x2
2. Ubuntu Linux 8.04 2.6.11 HP IA-64 位元主機
[附錄二] 程式碼,這裡就不解釋程式碼了,請自行參看
A 程式
其中的副函式:
#create Normal Distribution random value
sub dev{
my %h=();
local $meanvalue = shift;
local $randnum = shift;
local $sd = shift;
for(1..$randnum){
do{
$v1 = (2 * rand 1) - 1;
$v2 = (2 * rand 1) - 1;
$r = $v1 **2 + $v2 **2;
}while($r >= 1);
$fac = sqrt(-2 * log($r)/ log(exp(1)) / $r);
$gauss = $v2 * $fac;
$vr= $gauss * $sd + $meanvalue;
$sddif = $sd*$dif;
$hid= int( ( $vr + $sddif /2 ) / $sddif ) * $sddif;
$h{$hid}++;
} # end for
return %h;
} # end dev |
B 程式
B 程式本身是 RPC的 Server,所以我去改他的 RPC 套件 RPC::Simple:
#!/usr/bin/perl -w
package main ;
use RPC::Simple::Server;
use RPC::Simple::Factory;
my $arg = shift ;
my $verbose = 1 ; # you may change this value to see RPC traffic
my $pid = &spawn( undef ,$verbose) ; # spawn server
sleep(3600); # 執行後放在記憶體中一個小時,這行是我花了很多時間才想到應該要加的。 |
這個 B 程式會產生 RPC Server,他預設會去包括起一個 library 檔 MyRemote.pm
lib/perl5/site_perl/5.8.8/RPC/Simple/AnyLocal.pm:#@_ [MyLocal=HASH(0x82fb0fc) RPC::Simple::Factory=HASH(0x812fb44) MyRemote.pm]
lib/perl5/site_perl/5.8.8/RPC/Simple.pm: $self->createRemote($remote,'MyRemote.pm') ;
此檔為 Server端的函式檔:
package MyRemote ;
#use strict;
use vars qw(@ISA @RPC_SUB);
use base RPC::Simple::AnyRemote;
@ISA=('RPC::Simple::AnyRemote') ;
@RPC_SUB = qw(answer);
# 接收參數並呼叫函式 dev 進行運算
sub NormalDistribution
{
my $self=shift ;
my %args = @_;
my $callback = $args{callback} || undef;
my $randnum = $args{randnum} || 10000;
my $meanvalue = $args{meanvalue} || 0;
my $sd = $args{sd} || 1;
my $dif = $args{dif} || 1;
print "接收到 RPC Clients 的 Procedure Call,進行運算…\n參數:$randnum $meanvalue $sd $dif";
#進行運算…呼叫函式 dev
my %res = &dev($meanvalue, $randnum, $sd );
foreach $key (sort {$a<=>$b} keys %res) {
print "$key \t\t$res{$key}\n";
}
if (defined $callback)
{
#整個hash 回傳
$self->$callback(%res);
}
}
|
#計算常態分佈標準差並分組的函式這裡和程式A的 dev 函式完全相同,不再列出
#create Normal Distribution random value
sub dev{
...程式A的 dev 函式完全相同,不再列出
}
C 程式
C 程式本身是 RPC的 Client
#!/usr/bin/perl -w
package MyLocal ;
use base RPC::Simple::AnyLocal;
use vars qw($VERSION @ISA @RPC_SUB $tempObj);
@ISA = qw(RPC::Simple::AnyLocal);
@RPC_SUB = qw(remoteAsk NormalDistribution);
sub new
{
#print @_; #MyLocal , RPC::Simple::Factory=HASH(0x82fbb44)
#print "\n";
my $type = shift ; # $type=MyLocal 從最左取出一個值
my $self = {} ; # $self=HASH(0x87fdd58) 雙大括號是什麼意義
my $remote = shift ; #$remote=RPC::Simple::Factory=HASH(0x8632b44)
#To create an object, bless a referent 把 $self (HASH) 轉換成 $type=MyLocal 物件
bless $self,$type ;
$self->createRemote($remote,'MyRemote') ;
return $self ;
}
#回呼函式
sub answer
{
my $self = shift ;
my %h2 = @_ ;
print "RPC Server 計算後,傳回結果...\n" ;
foreach $key (sort {$a<=>$b} keys %h2) {
print "$key \t\t$h2{$key}\n";
}
}
1;
package main ;
use RPC::Simple::Factory ;
use Time::Elapse;
use IO::Socket ;
use IO::Select ;
print "\nProgram Start....\n";
Time::Elapse->lapse(my $now);
my $verbose = 1 ; # you may change this value to see RPC traffic
my $factory = new RPC::Simple::Factory(verbose_ref => \$verbose, remote_host=> '163.17.38.83') ;
# print $factory."\n";
my $local = new MyLocal($factory) ;
# $local->remoteAsk(callback => 'answer');
# 叫用RPC Server 的函式 NormalDistribution,注意本地端並不存在此函式
$local->NormalDistribution(callback => 'answer', randnum=> 20000000, meanvalue=>6, sd=>1, dif=>1);
#等待回傳的結果
#sleep(3);
my $selector = IO::Select->new();
$selector->add($factory->getSocket());
# sleep(0.01);
my ($toRead, undef, undef) = IO::Select->select($selector, undef, $selector, 10);
foreach my $fh (@$toRead)
{
if($fh == $factory->getSocket())
{
$factory->readSock();
}
}
print "Elapsed time=". $now. "\n";
|
END
|