linux上的怪異現象-網路程式比本機程式有更高的效能

此文完整連結 http://note.tc.edu.tw/264.html

這是民國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";

arrow
arrow

    Johnson峰 發表在 痞客邦 留言(0) 人氣()