西村の駄文集積所

2012-12-26

C言語 - テキストファイルを全部、一行ずつ、無駄なく(?)配列に格納する

Pythonでいうと、やりたいことはこれ。

lines = open("text").readlines()

C言語では、一行が100001バイト以上あるファイルじゃなければ、とりあえずこれでよかった。

char* *lines;
int lines_length;

void load_file(char *file_path) {
  FILE *fp;
  int i;
  long bufsize = 100000;
  char buf[bufsize];
  fp = fopen(file_path, "r");
  if (fp == NULL) {
    printf("can not open %s.\n", file_path);
    exit(1);
  }
  for(i=0; fgets(buf, bufsize, fp)!=NULL; i++);
  rewind(fp);
  lines = malloc(sizeof(char*) * i);
  for(i=0; fgets(buf, bufsize, fp)!=NULL; i++) {
    lines[i] = malloc((sizeof(char) * strlen(buf)) + 1);
    strcpy(lines[i], buf);
  }
  lines_length = i;
  fclose(fp);
}

共有オブジェクト(SO)にコンパイルすれば、このままpythonで利用できる。

import ctypes

ext = ctypes.CDLL("./ext.so")
ext.load_file("file_path")

SOのコンパイル方法

gcc バージョン 4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5)
gcc -Wall -fPIC -c ext.c
gcc -shared -o ext.so ext.o

試しに約50MB、約5万行のファイルを読んでみたところ、topでの観測では使用メモリが約52MB増えた。
この2MBは何だろう。

  • 各行へのポインタ、64bit環境なので(8 * 50000 / 1024.0 = 390KB)
  • strcpy()が返す終端(\0)のための領域、char一つ分なので(1 * 50000 / 1024.0 = 49KB)
  • buf(100000 / 1024.0 = 97KB)
  • あとは何だろう。謎。ls -hやtopで丸めて出来た誤差かも。

linesはfree()しないとプロセスが終了するまでメモリを占有し続けることに注意。
必要なければ、linesはローカルに置いてもいいと思う。
今回は、どうしてもlinesを別の関数で読みたくて、グローバルに置いて、free()せずに放置している。

「"文字列の配列" malloc」とか検索しても、ずばり!というページがなかなかヒットしなかった。

しかし長い!もっといい仕方がないかな。

2012-12-21

リビジョン間のsvn st

いつも忘れる。

svn diff --summarize -r 172:185

2012-12-11

Ubuntuでbashからzshに乗り換えたとき困ること

まだまだ途中ながら漢のzshを読みながらzshを試してる。
環境はUbuntu Server 12.04 LTS

lsコマンドのハイライト

2行の設定が必要だった。

bashのときカラーだったのがモノクロになってしまう。
これは漢のzshでも触れられているものの、うちの環境には当てはまらないようだった。

bashのときはハイライトが有効なので.bashrcを凝視して試したところ、この行が必要だった。

alias ls='ls --color=auto'

ここで少しハイライトされるようになったが、乗り換え前はzipファイルが赤くハイライトされていたのがモノクロのままだった。
bashに入ったときは設定される環境変数LS_COLORSの値が、zshに入ったときは設定されていなかったためだった。
bashに入って echo $LS_COLORS として値を取得し、.zshrcにコピペすると、乗り換え前と同じようにハイライトされるようになった。

export LS_COLORS="rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lz=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.axa=00;36:*.oga=00;36:*.spx=00;36:*.xspf=00;36:"

bashはいったいどのファイルを読んでLS_COLORSを設定しているのか?と

sudo find / -exec grep -H "LS_COLORS" {} \;

としてみたものの、時間がかかりすぎて中断。

apt-get/aptitudeの親切機能が効かなくなる

乗り換え前は、まだインストールしていないパッケージや、typoをしたときに「プログラム '???' はまだインストールされていません。 次のように入力することでイン ストールできます: sudo apt-get install ???」などとガイドしてくれていたが、zshに乗り換えた後もこれを利用するには設定が必要だった。
このガイド機能は、Package Suggestionというらしい。
http://stackoverflow.com/questions/3316568/zsh-package-suggestion

この記事の通り、
sudo apt-get install command-not-found
を実行して、.zshrcに以下の行を追加するとよくなった。

source /etc/zsh_command_not_found

TeraTerm+screen+vimでのホイールスクロール問題

結局のところ、シェルのときだけCtrlを押しながらホイールを回せば使い勝手に大差ないというところに落ち着いた。

以下、右往左往の記録。

ssh作業のお供、screen。
いつでも続きから作業できるのは便利なんだけど、デフォルトではマウスホイールの使い勝手が変わってしまうのが難点だった。

screen off: ホイールで画面をスクロール
screen on: ホイールでコマンド入力履歴を選択

ホイールでスクロールしてほしいんだよな…と調べると、teraterm.iniの項目で設定できるらしい。

TranslateWheelToCursor=off(デフォルトはon)

ところが今度はvimやmanのホイールスクロールが画面外にはみ出るようになった。よろしくない。

TranslateWheelToCursorはアプリケーションカーソルモード(vimやlessやmanなど)のとき上下カーソルキーを送信するか、スクロールバーを動かすか選ぶ項目で、offだとホイールは、スクロールバーをドラッグするかのように動かすだけになるので、しょうがないようす。
http://ttssh2.sourceforge.jp/manual/ja/setup/teraterm-misc.html

対症療法的に、teraterm+vimでスクロール云々の話をゴソゴソ調べると、vimのスクロールがはみ出さなくなる設定があった。

set mouse=a
set ttymouse=xterm2

スクロールがはみ出さなくなる上に、マウスのクリックでカーソルが移動する!
しかし相変わらずmanのスクロールはよろしくなくてTranslateWheelToCursorはonに戻すことに。

するとmanのスクロールは元の調子に戻った。
vimも上記の設定を無しにしてみても、スクロールは問題ない。
使い勝手が変わるのはシェルだけのようだった。

なんとかならんかといじり倒して、たまたま分かったのが冒頭に書いた方法で、なんだそんなことでいいのかと思ったけど、右往左往してるうちにteraterm内で動くvimのカーソルをクリックで動かせることが分かって、それはよかった。

むしろscreenは、アプリケーションを閉じたときの表示が気に入らない。
例えばscreenなしだと、ls; vim -> ctrl+z ですぐ lsの結果を見られるけど、screenありだとvimの画面の跡が残って見られない。地味に不便。
これを解消する設定はいくら探しても見つからない。
そういうことはscreenのウィンドウ切り替えでやれってことかな。
慣れれば気にならなくなるのかもしれない。

2012-11-26

Python - tempfile.mkstemp()でハマった

tempfileモジュールのmkstemp()はos.open()で開きっぱなしのファイルディスクリプタを返してくるので、閉じないといけない。

import os
import tempfile
fd, path = tempfile.mkstemp()
os.close(fd)

ときどき1022回以上mkstempを呼ぶスクリプトで、かなり離れた行に別のopenがあり、そこで「IOError: [Errno 24] Too many open files」と言われるもんだから、かなりハマった。
閉じてますがなにか?とひたすらコードを見直したあげく、lsof -p <pid>で分かった。

ドキュメントをよく読むと

mkstemp() は開かれたファイルを扱うための OS レベルの値とファイルの絶対パス名が順番に並んだタプルを返します。

とあるものの、ええ?閉じてくださいよ、と思わなくもない。
自分でopenしたら気をつけますが。

2012-11-15

Ubuntu/Pythonの非同期シグナルセーフ関数のお話

最初に書いてしまうと、
ubuntuのsleep()は非同期シグナルセーフ関数で、実行中にシグナルを受信すると、ただちにシグナルハンドラへ処理が移り、シグナルハンドラが完了したあとで、指定秒数が経過していなくても、再開することなくただちに完了する。
ubuntuのread()とwrite()は非同期シグナル関数で、実行中にシグナルを受信すると、実行が完了してからシグナルハンドラへ処理が移り、シグナルハンドラが完了したあとは、read/writeの完了後から処理が再開する。つまりI/Oはシグナルを受信しなかったときと同じように実行される。
みたいだった。
非同期シグナルセーフ関数は他にもたくさんあるけど、今回はこれだけ。

pythonのsignalモジュールとmultiprocessingモジュールを試してるとき、どうも引っかかった。

プロセスがtime.sleep(100)を実行して、time.sleepの次の行が実行されるまでの間にkill -sでシグナルを送ると、time.sleepへの指定秒数にかかわらず、ただちにシグナルハンドラへ処理が移り、シグナルハンドラの完了後はtime.sleepの次の行からただちに処理が再開された。

はて、read/writeの実行中にシグナルを送ると、どうなってしまうのやら?

リファレンスをよく見るとこんな説明が。
http://www.python.jp/doc/2.6/library/signal.html#signal-example

シグナルが I/O 操作中に到着すると、シグナルハンドラが処理を返した後に I/O 操作が例外を送出する可能性があります。これは背後にある Unix システムが割り込みシステムコールにどういう意味付けをしているかに依存します。

なんですって?

使用中のUnixシステムはUbuntu Server 12.04LTSだったので、いろいろ検索してると、これが出てきた。
http://manpages.ubuntu.com/manpages/jaunty/man7/signal.7.html
「Async-signal-safe functions」というリストに、read, write, sleep,ほか多数の関数が載っていた。
※このページは aptitude install manpages-ja; man 7 signalで日本語で読める

safeと聞いて、ちょっと期待。

さて、非同期シグナルセーフ関数とは…
http://d.hatena.ne.jp/yupo5656/20040712/p2 によると

最後に、念のために「非同期シグナルセーフ」とは何であるか説明しておきます。非同期シグナルセーフな関数とは、「関数内の任意の場所でシグナルに割り込まれても、次回の関数呼び出しに問題がない関数」の事です。その関数に静的に紐づけられたデータを更新するような関数(例: malloc)は、大抵の場合、非同期シグナルセーフにできません。ただし、静的なデータを持っていても、そのデータの処理中のシグナル受信を禁止(マスク)している場合は、特例的に非同期シグナルセーフ関数たりえます。

なるほど。
Pythonのsignalモジュールは一時的なシグナル受信の禁止をサポートしていないようなので*1後半は置いておいて、「関数内の任意の場所でシグナルに割り込まれても、次回の関数呼び出しに問題がないこと」が非同期シグナルセーフなのね。

期待していたのは、非同期シグナルセーフな関数は「関数内の任意の場所でシグナルに割り込まれても、関数はシグナルに割り込まれなかったときと同様に動作する」ということなんだけど、sleepはとてもそう見えなかったし、readやwriteについては、試してみるしかないみたいだった。

次のように試した。

500MBのテキストファイルを用意

>>> open("bigtext.txt", "w").write("a" * 500 * 1024 ** 2)

次のモジュール test.py を用意

import signal
import time

sig_term_received = False

def sig_term_handler(signum, frame):
  print "sig term handler"
  global sig_term_received
  sig_term_received = True

def f(second):
  signal.signal(signal.SIGTERM, sig_term_handler) #5
  global sig_term_received
  while True:
    if sig_term_received:
      print "sig term received"
      break
    print "f = open r 1"; f = open("bigtext.txt")
    print "f.read()";   text = f.read() #1
    print len(text)
    print "f.close()";  f.close()

    print "f = open w"; f = open("bigtext.txt", "w")
    print "f.write()";  f.write(text) #2
    print "f.close()";  f.close()

    print "f = open r 2"; f = open("bigtext.txt")
    print "f.read()";   text = f.read()
    print len(text)                       #3
    print "f.close()";  f.close()

    print "time.sleep 1"; time.sleep(second) #4
    print "time.sleep 2"; time.sleep(second)
  print "while loop end"

f()を実行

>>> import test; from multiprocessing import Process
>>> p = Process(target=test.f, args=(5,))
>>> p.start()
>>> p.pid

タイミングを見計らってシグナルを送る

$ kill -s TERM <pid>

#1の実行中にシグナルを送っても、シグナルを送らないときと同じlen(text)が得られた。
#2の実行中にシグナルを送っても、#3で得られるlen(text)はシグナルを送らないときと同じだった。
#4の実行中にシグナルを送ると、ただちに"sig term handler"がプリントされ、ただちに"time.sleep 2"がプリントされた。
#5のシグナルをsignal.SIGUSR1に変更して、USR1シグナルを送っても、同じ結果が得られた。

式の評価中にシグナルを受信したらどうなるんだろう?という心配はあるけど、リファレンスには

Python のシグナルハンドラは Python のユーザが望む限り非同期で呼び出されますが、呼び出されるのは Python インタプリタの “原子的な (atomic)” 命令実行単位の間です。したがって、 (巨大なサイズのテキストに対する正規表現の一致検索のような) 純粋に C 言語のレベルで実現されている時間のかかる処理中に到着したシグナルは、不定期間遅延する可能性があります。

とあって、Pythonの式がatomicならたぶんシグナルは式と式の間に割り込むので、大丈夫だと思っておく。
Pythonだから、きっとそうしてくれるだろう。

非同期シグナルセーフ関数が実行中にシグナルを受信したとき、シグナルを受信しなかったときと同じように動作するかどうかは、どこに書いてあるんだろう?
open()やclose()やfcntl()はどうなんだろう?

追記:
非同期シグナルセーフ関数の作動中にシグナルを受信したとき何が起こるかは、man 7 signalに全部書いてあった。
apaitude install manpages-jaで日本語になる。
式の評価中、文字列なんかを整形中にシグナルを受信したらどうなるかは、まだ謎。I/Oが安全でも、そこが分からない。

man 7 signal
シグナルハンドラによるシステムコールライブラリ関数への割り込み
システムコールライブラリが停止 (block) している間にシグナルハンドラが
起動されると、以下のどちらかとなる。

* シグナルが返った後、呼び出しは自動的に再スタートされる。

* 呼び出しはエラー EINTR で失敗する。

これらの二つの挙動のうちどちらが起こるかは、インターフェイスにより依存
し、 シグナルハンドラが SA_RESTART フラグ (sigaction(2) 参照) を使って設
定されていたかにも依存する。 詳細は UNIX システムによって異なる。 Linux
における詳細を以下で説明する。

以下のインターフェイスのいずれかの呼び出しが停止している間に シグナルハン
ドラにより割り込まれた場合、 SA_RESTART フラグが使用されていれば、シグナ
ルハンドラが返った後に その呼び出しは自動的に再スタートされることになる。
それ以外の場合は、その呼び出しはエラー EINTR で失敗することになる。

* read(2), readv(2), write(2), writev(2), ioctl(2) の「遅い
(slow)」デバイスに対する呼び出し。 ここでいう「遅い」デバイス
は、I/O 呼び出しが無期限に停止 (block) する 可能性のあるデバイス
ことで、例としては端末、パイプ、ソケットがある (この定義では、ディ
スクは遅いデバイスではない)。 遅いデバイスに対する I/O 呼び出しが、
シグナルハンドラにより割り込まれた時点までに何らかのデータを すでに
転送していれば、呼び出しは成功ステータス (通常は、転送されたバイト
数) を返すことだろう。

* 停止 (block) する可能性のある open(2) (例えば、FIFO のオープン時;
fifo(7) 参照)。

* wait(2), wait3(2), wait4(2), waitid(2), waitpid(2).

* ソケットインターフェイス: accept(2), connect(2), recv(2),
recvfrom(2), recvmsg(2), send(2), sendto(2), sendmsg(2). 但し、ソ
ケットにタイムアウトが設定されていない場合 (下記参照)。

* ファイルロック用インターフェイス: flock(2), fcntl(2) F_SETLKW.

* POSIX メッセージキューインターフェイス: mq_receive(3),
mq_timedreceive(3), mq_send(3), mq_timedsend(3).

* futex(2) FUTEX_WAIT (Linux 2.6.22 以降; それ以前は常に EINTR で失敗
していた)。

* POSIX セマフォインターフェイス: sem_wait(3), sem_timedwait(3)
(Linux 2.6.22 以降; それ以前は常に EINTR で失敗していた)。

以下のインターフェイスは、 SA_RESTART を使っているどうかに関わらず、シグ
ナルハンドラにより割り込まれた後、 再スタートすることは決してない。 これ
らは、シグナルハンドラにより割り込まれると、常にエラー EINTR で失敗する。

* setsockopt(2) を使ってタイムアウトが設定されているソケットインター
フェース: accept(2), recv(2), recvfrom(2), recvmsg(2) で受信タイム
アウト (SO_RCVTIMEO) が設定されている場合と、 connect(2), send(2),
sendto(2), sendmsg(2) で送信タイムアウト (SO_SNDTIMEO) が設定されて
いる場合。

* シグナル待ちに使われるインターフェイス: pause(2), sigsuspend(2),
sigtimedwait(2), sigwaitinfo(2).

* ファイルディスクリプタ多重インターフェイス: epoll_wait(2),
epoll_pwait(2), poll(2), ppoll(2), select(2), pselect(2).

* System V IPC インターフェイス: msgrcv(2), msgsnd(2), semop(2),
semtimedop(2).

* スリープ用のインターフェイス: clock_nanosleep(2), nanosleep(2),
usleep(3).

* inotify(7) ファイルディスクリプタからの read(2).

* io_getevents(2).

sleep(3) 関数も、ハンドラにより割り込まれた場合、決して再スタートされるこ
とはない。 しかし、成功となり、残っている停止時間を返す。

どちらも非同期シグナルセーフ関数なのにsleepとread/writeとが違う動きに見えたのは、そういうわけだったんだ。

PythonではSA_RESTARTはsignal.siginterrupt(signalnum, flag)で設定するそうな。
http://stackoverflow.com/questions/5844364/linux-blocking-signals-to-python-init

リファレンスによるとsignal.signal(signalnum, handler)は暗黙のうちにsiginterrupt(signalnum, True)を呼び出す(SA_RESTARTをOFFにする)そうだけど、それだと今度は上記の実験でSA_RESTARTが暗黙のうちにOFFになっていたにもかかわらず、まるでSA_RESTARTがONになっていたかのような動作になっていたことの説明がつかない。
リファレンスの間違いじゃないかと、わざわざsignterrupt(signalnum, True/False)を両方試しても、動作は同じように見える。謎。

もはやおまじないの域だけど、ひとまずリファレンスに従って、SA_RESTARTは明示的に指定するだけして、祈ることにした。

*1http://www.python.jp/doc/2.6/library/signal.html: クリティカルセクションから一時的にシグナルを”ブロック”することはできません。この機能をサポートしない Unix 系システムも存在するためです。

Connection: close