2013年5月15日水曜日

[Code reading] time command

[Motivation]
FORTRANで記述されコンパイラで自動並列化した数値解析のプログラムの実行時間を下記環境でtimeコマンドを使って測定したら下記の様な結果になった。


-実行環境-
OS: Cent OS
CPU: Intel Xeon Processor L5640 x 2

-並列数-
OMP_NUM_THREAD=12

-time コマンド結果-

real 84m23.521s
user 1010m2.767s
sys 0m7.199s


[reasoning]
user時間は、各コアのuser時間の合計となるためスレッド並列化して実行したコマンドでは、real時間よりも長くなることがある?

[research]


適当なミラーサイトからソースを取ってくる。
後、linuxカーネル(2.6.34)のソースも取ってくる。

まずは、タイムコマンドのmain関数
-time.c: main
632 main (argc, argv)
633      int argc;
634      char **argv;
635 {
636   const char **command_line;
637   RESUSE res;

640   run_command (command_line, &res);
641   summarize (outfp, output_format, command_line, &res);

run_commandという関数を見てみる
-time.c: run_command
597 static void
598 run_command (cmd, resp)
599      char *const *cmd;
600      RESUSE *resp;
601 {

605   resuse_start (resp);

614       execvp (cmd[0], cmd);

623   if (resuse_end (pid, resp) == 0)

timeコマンドで指定したコマンドをexecvp関数で実行している.
execvp関数の前後で呼んでいるresuseで始まる関数を次に見てみる

-resuse.c: resuse_start
 46 void
 47 resuse_start (resp) //resuseは、resource use startの略と思われる。
 48      RESUSE *resp;
 49 {
 50 #if HAVE_WAIT3
 51   gettimeofday (&resp->start, (struct timezone *) 0);
 52 #else
 53   long value;
 54   struct tms tms;
 55
 56   value = times (&tms); // man によると返り値は過去のある時点からのtick
 57   resp->start.tv_sec = value / HZ; // HZ は、CPUクロックか? 確証は得られてない
 58   resp->start.tv_usec = value % HZ * (1000000 / HZ);
 59 #endif
 60 }
現在のクロックカウント(と言っていいのか?)をHZで割って時刻をresp->start.tvに保持する

-resuse.c: resuse_end
117   resp->elapsed.tv_sec -= resp->start.tv_sec; // real time (sec)?
124   resp->elapsed.tv_usec -= resp->start.tv_usec; // real time (micro sec)?
コマンド実行後に省略したが同様にCPU時刻を取得して実行前の時刻を引いている。=> 正味実行にかかった時間(real time)を算出。


struct tmsとは何か?
struct tms -> linux/times.h
 6 struct tms {
  7         __kernel_clock_t tms_utime; // user time
  8         __kernel_clock_t tms_stime; // system time
  9         __kernel_clock_t tms_cutime; // child process user time
 10         __kernel_clock_t tms_cstime; // child process system time
 11 };

__kernel_clock_t -> linux/types.h
 79 typedef __kernel_clock_t        clock_t;


-time.c : summarize
320 static void
321 summarize (fp, fmt, command, resp)
322      FILE *fp;
323      const char *fmt;
324      const char **command;
325      RESUSE *resp;
326 {


-kernel/sys.c 912 void do_sys_times(struct tms *tms)
 921         tms->tms_utime = cputime_to_clock_t(tgutime);
 922         tms->tms_stime = cputime_to_clock_t(tgstime);
 923         tms->tms_cutime = cputime_to_clock_t(cutime);
 924         tms->tms_cstime = cputime_to_clock_t(cstime);


thread_group_timesとは
kernel/sched.c

3398 void thread_group_times(struct task_struct *p, cputime_t *ut, cputime_t *st)
3399 {
3400         struct task_cputime cputime;
3401        
3402         thread_group_cputime(p, &cputime);
3403
3404         *ut = cputime.utime;
3405         *st = cputime.stime;
3406 }


kernel/posix_cpu_timers.c

234 void thread_group_cputime(struct task_struct *tsk, struct task_cputime *times)
 235 {
 236         struct sighand_struct *sighand;
 237         struct signal_struct *sig;
 238         struct task_struct *t;
 239
 240         *times = INIT_CPUTIME;
 241
 242         rcu_read_lock();
 243         sighand = rcu_dereference(tsk->sighand);
 244         if (!sighand)
 245                 goto out;
 246
 247         sig = tsk->signal;
 248
 249         t = tsk;
 250         do {
 251                 times->utime = cputime_add(times->utime, t->utime);
 252                 times->stime = cputime_add(times->stime, t->stime);
 253                 times->sum_exec_runtime += t->se.sum_exec_runtime;
 254
 255                 t = next_thread(t);
 256         } while (t != tsk);
 257
 258         times->utime = cputime_add(times->utime, sig->utime);
 259         times->stime = cputime_add(times->stime, sig->stime);
 260         times->sum_exec_runtime += sig->sum_sched_runtime;
 261 out:
 262         rcu_read_unlock();
 263 }


include/asm-generic/cputime.h
12 #define cputime_add(__a, __b)           ((__a) +  (__b))

next_thread(t);とは
include/linux/sched.h

2177 static inline struct task_struct *next_thread(const struct task_struct *p)
2178 {              
2179         return list_entry_rcu(p->thread_group.next,
2180                               struct task_struct, thread_group);
2181 }

thread_group.nextとは、
include/linux/sched.hのtask_structのメンバー
1285         struct list_head thread_group;

list_headとは、
include/linux/list.h

 41 struct list_head {
 42         struct list_head *next, *prev;          
 43 };


list_entry_rcuとは?
include/linux/rculist.h

210 #define list_entry_rcu(ptr, type, member) \
211         container_of(rcu_dereference_raw(ptr), type, member)




spin_lock_irq のirqとは?
include/linux/spinlock.h

307 static inline void spin_lock_irq(spinlock_t *lock)
308 {      
309         raw_spin_lock_irq(&lock->rlock);
310 }
include/linux/spinlock.h
220 #define raw_spin_lock_irq(lock)         _raw_spin_lock_irq(lock)
include/linux/spinlock-api_up.h
59 #define _raw_spin_lock_irq(lock)                __LOCK_IRQ(lock)


spin_local_irqに渡しているcurrent_sighand_siglockとは?


[conclusion]
utimeとstimeは、とりあえず全てのスレッド分足してそうだというとこまでは行ったがかなり消化不足気味

[気になるキーワード]
Hyper Threading
SMT (Simultaneous Multi Threading) 同時マルチスレッディング
SMP (Symmetric Multi Processing) 対象型マルチプロセシング

[Visual Studio C++] PEバイナリのエクスポートされたシンボル

PEバイナリのエクスポートされたシンボル名を確認する

漢は黙ってバイナリエディタといきたいとこだけど
今回は、MSさんのツール(dumpbin)を使います。

このdumpbinは、Visual Studioを使うと [Install Direcotry]\VC\bin にインストールされます。

ここは、PATHが通ってないし、dumpbinが使うライブラリがあるPATHがサーチパスに含まれてないため、dumpbin.exeと同一ディレクトリにあるバッチファイル vcvars32.bat を実行する必要があります。


そして、

dumpbin /EXPORT input.dll

とすることでシンボル名を確認することができる。

[c言語] Windows と Linux の共有ライブラリ

分けあってWindows向けの共有ライブラリ(dll) と Linux向けの共有ライブラリ(so) を
一つのC言語のソースで用意する必要が生じだ。

後にも先にもこれ一回限りな気がするけど一応メモ
検証環境は、

Windows 7 64bit版 C Compiler: gcc ライブラリを使う言語とコンパイラ Delphi XE2
Linux (RHEL) C Compiler: gcc ライブラリを使う言語とコンパイラ Fortran 90  pgfortran



まずは、ヘッダファイルとソース

add.h

#ifndef ADD_H
#define ADD_H

int add_(int *a, int *b)

int devide_(float *a, float *b, float *c)
#endif


add.c
#include<stdio.h>


int add_(int *a, int *b){
    int c;

  c = *a + *b;
  return c;
}

int devide_(float *a, float *b, float *c){

    *c = *a / *b;

    return 0;
}

ポイントは、引数がすべて参照渡しになっている点です。 Fortranが参照渡しがデフォルトなためです。 また、関数名がアンダースコアで終わっているのは、また後で。。。

お次は、コンパイル
まずは、Windowsで

gcc -shared -m32 -o add.dll add.c

-m32 は、32bi tPEバイナリを出力するためのオプションでdllを使う、アプリケーション(exe)が32bitであるため。

次に、Linuxで
gcc -fPIC -shared -o libadd.so add.c

-fPICは、位置独立コード(Position Independent Code)というメモリのどのアドレにもロード可能にするためのオプションです。


そして、使う側。
まずは、Delphi

unit use;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm6 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

  TAdd = function(a : PInteger; b : PInteger) : integer ; stdcall;
  TDevide = function(a : PSingle; b : PSingle; c : PSingle) : integer ; stdcall;

var
  Form6: TForm6;

implementation

{$R *.dfm}

procedure TForm6.Button1Click(Sender: TObject);
var
 ret : integer;
 a,b : integer;
 c,d,e   : single;
 Handle : THandle;
 Add    : TAdd;
 Devide : TDevide;
begin
 a := 4;
 b := 2;
 c := 4.0;
 d := 2.0;
 Handle := LoadLibrary('add.dll');
 if Handle <> 0 then
 begin
   @Add := GetProcAddress(Handle, 'add_');
   if @Add <> nil then
   begin
     ret := add(@a, @b);
     ShowMessage(IntToStr(ret));
   end;
   @Devide := GetProcAddress(Handle, 'devide_');
   if @Devide <> nil then
   begin
     ret := devide(@c, @d, @e);
     ShowMessage(FloatToStr(e));
   end;
   FreeLibrary(Handle);
 end;
end;

end.

関数をtypedefして、GetProcAddress にて関数のあるアドレスを取得する。

次は、Linux上のFortran
interface節を記述したインクルードファイルを作成

addf.h
      interface
        integer function add(a,b)
          integer a,b
        end function add

        integer function devide(a,b,c)
          real  a,b,c
        end function devide

      end interface

次にFortranのソース本体
       program test
       include 'addf.h'
       integer c,d
       real e,f,g
       c=4
       d=2
       e=4.0 
       f=2.0
       d = add(c,d)
       i = devide(e,f,g)
       write(*,*) d,g,i
       end program test

そしてコンパイル
pgfortran -I./include -L./lib -ladd src/use.f

-l で使用するライブラリを指定します。
また、Fortranの場合、関数名は、アンダースコアがついたシンボルを探すのでCの関数名には、アンダースコアをつける必要があります。


2013年5月2日木曜日

[CentOS] コマンドラインでWindowsなホストの共有ディレクトリをマウント

Windows独自プロトコルであるSMB (Server Message Block)を拡張仕様公開した、CIFS を実装してGPL v3 にて公開しているのがSamba ということらしいのですが

今回は、mountコマンドにてWindowsなホストで共有でしているディレクトリをCentOS release 6.4 にてマウントしてみます。

まず、必要パッケージのインストール

yum -y install samba-client
yum -y install cifs-utils


必要に応じてマウントポイントの作成

mkdir -p /mnt/winshare

そして、マウント

mount -t cifs //192.168.100.2/Users/hoge /mnt/winshare -o username=hoge

実行するとWindows上の共有しているディレクトリへのアクセス権のあるWindowsドメインのユーザーアカウントのパスワードを聞かれるので入力する。

192.168.200.2は、WindowsホストのIPアドレス