読者です 読者をやめる 読者になる 読者になる

静かなる名辞

pythonと読書

【python】numpyの型の違いによる計算速度差を見てみる

python numpy 雑記

 前回の記事で「なんとなくnp.float32が速い気がする」とか書いたので、実際に測ってみる。
 予め断っておくと、計算速度なんて環境によって違うし、どの型が速いかもCPUのアーキテクチャに依存する。numpyはバリバリにSIMD命令を使って最適化する(と、思う)ので、演算の種類とかによっても優劣は変わる。あくまでも私の環境での試験である。

つかったCPU

 4年くらい前のモバイル版i7。モバイル版なのが泣ける。物理コアが2コアしかない時点でお察しである。とはいえ、個々のコアの性能は腐ってもi7

  • クロック周波数:3GHz(たーぼ・ぶーすとなる技術によって負荷をかけると3.5GHzくらいまで引っ張ってくれる) 
  • コア数:物理コア*2(HTがあるので、論理コア*4)
  • L1キャッシュ:128KB
  • L2キャッシュ:512KB
  • L3キャッシュ:4.0MB
ソースコード
# coding: UTF-8
import warnings;warnings.filterwarnings('ignore')

import time
from random import randint

import numpy as np

def make_random(l, n):
    return [randint(1,n) for _ in range(l)]

def f(a,b,c):
    return a * b - c

def g(a,b,c):
    return a/b/c

def main():
    for vec_size in [100, 1000, 10000, 100000, 1000000]:
        af64 = np.array(make_random(vec_size, 10000), dtype=np.float64)
        bf64 = np.array(make_random(vec_size, 10000), dtype=np.float64) 
        cf64 = np.array(make_random(vec_size, 10000), dtype=np.float64)

        ai64 = np.array(make_random(vec_size, 10000), dtype=np.int64)
        bi64 = np.array(make_random(vec_size, 10000), dtype=np.int64) 
        ci64 = np.array(make_random(vec_size, 10000), dtype=np.int64)
    
        af32 = np.array(af64, dtype=np.float32)
        bf32 = np.array(bf64, dtype=np.float32)
        cf32 = np.array(cf64, dtype=np.float32)

        ai32 = np.array(ai64, dtype=np.int32)
        bi32 = np.array(bi64, dtype=np.int32)
        ci32 = np.array(ci64, dtype=np.int32)

        af16 = np.array(af64, dtype=np.float16)
        bf16 = np.array(bf64, dtype=np.float16)
        cf16 = np.array(cf64, dtype=np.float16)

        ai16 = np.array(ai64, dtype=np.int16)
        bi16 = np.array(bi64, dtype=np.int16)
        ci16 = np.array(ci64, dtype=np.int16)


        print("vec size:",vec_size)
        start1 = time.time()
        f(af64,bf64,cf64)
        end1 = time.time()
        start2 = time.time()
        g(af64,bf64,cf64)
        end2 = time.time()
        print("float64: {0:.6f} {1:.6f}".format(end1-start1, end2-start2))
        start1 = time.time()
        f(ai64,bi64,ci64)
        end1 = time.time()
        start2 = time.time()
        g(ai64,bi64,ci64)
        end2 = time.time()
        print("int64:   {0:.6f} {1:.6f}".format(end1-start1,end2-start2))
        start1 = time.time()
        f(af32,bf32,cf32)
        end1 = time.time()
        start2 = time.time()
        g(af32,bf32,cf32)
        end2 = time.time()
        print("float32: {0:.6f} {1:.6f}".format(end1-start1, end2-start2))
        start1 = time.time()
        f(ai32,bi32,ci32)
        end1 = time.time()
        start2 = time.time()
        g(ai32,bi32,ci32)
        end2 = time.time()
        print("int32:   {0:.6f} {1:.6f}".format(end1-start1,end2-start2))
        start1 = time.time()
        f(af16,bf16,cf16)
        end1 = time.time()
        start2 = time.time()
        g(af16,bf16,cf16)
        end2 = time.time()
        print("float16: {0:.6f} {1:.6f}".format(end1-start1, end2-start2))
        start1 = time.time()
        f(ai16,bi16,ci16)
        end1 = time.time()
        start2 = time.time()
        g(ai16,bi16,ci16)
        end2 = time.time()
        print("int16:   {0:.6f} {1:.6f}".format(end1-start1,end2-start2))

if __name__ == '__main__':
    main()

 そのまま回すとオーバーフローしまくるので警告は消している。関数fとgは適当に考えた計算処理。本来はもっと色々なものを試すべきだが、面倒くさいのでこれだけ。
 実行すると乱数生成に時間がかかるので十数秒待たされる。ベクトルの計算自体は一瞬で終わる。

実行結果
vec size: 100
float64: 0.000051 0.000012
int64:   0.001442 0.000989
float32: 0.000040 0.000009
int32:   0.001109 0.000763
float16: 0.000125 0.000019
int16:   0.001328 0.001525
vec size: 1000
float64: 0.000123 0.000015
int64:   0.000014 0.000091
float32: 0.000009 0.000007
int32:   0.000008 0.000017
float16: 0.000129 0.000047
int16:   0.000007 0.000016
vec size: 10000
float64: 0.000682 0.000051
int64:   0.000033 0.000406
float32: 0.000017 0.000015
int32:   0.000015 0.000101
float16: 0.000933 0.000438
int16:   0.000015 0.000103
vec size: 100000
float64: 0.000842 0.000844
int64:   0.000672 0.003524
float32: 0.001567 0.000242
int32:   0.000189 0.003431
float16: 0.009531 0.004141
int16:   0.000073 0.002247
vec size: 1000000
float64: 0.034912 0.004574
int64:   0.004239 0.010060
float32: 0.002116 0.002203
int32:   0.002295 0.010271
float16: 0.089270 0.038791
int16:   0.001104 0.010050

 まず全体を見て気づくのは、ほとんどのケースでfloat64に比べてfloat32が速いこと。ちゃんと調べてないが、恐らくCPUアーキテクチャの問題なのだろう。また、float16にメリットはまったくない(半精度の浮動小数点演算を高速に実行したいというニーズはあまりないのだろう)。int16は掛け算と引き算しかない計算では無双しているが、割り算になると極端に遅いことがわかる。というか、intの割り算は全般に遅い。
 結論としては、やはり32bit浮動小数点数が速い。あくまでも私の環境ではと最初に断ったが、実際にはx86系のアーキテクチャなら似たような結果になる可能性が高い訳で、速度にシビアな(かつ精度を要求されない)状況ではこれを使っておけばよさそう。

【python】pythonでメモリ不足になったときにすること

python

 pythonはLLですが、なぜかメモリを何十GBも消費するような(一般的なPCのリソースからすれば)大規模なデータ分析に広く使われています。このようなデータ分析では、往々にしてメモリ不足が生じ、それなりに配慮してプログラムを書かないとそもそもプログラムが走らないといった事態が発生しがちです。
 そういうときにやるべきことをつらつらと書いていきます。なお、下の方に行くほど邪悪度()が増していきます。

メモリを増設する・システムのswap領域を増やす

 一番安直な解決策です。無理矢理チューニングするより手っ取り早いことも多いのではないでしょうか。また、ガチガチにチューニングしても駄目だったとき、最後に選べる選択肢もこれです。
 swapする場合、HDDだと相当厳しいものがあるので、SSDが必要です。どうしてもHDDしかない場合、リムーバブルディスクでやるという手もあります(起動中に抜けちゃったら一貫の終わりですが)。

multiprocessingを使っているなら使うのをやめる、あるいはプロセス数を減らす

 二番目の選択肢です。プロセス並列は基本的に必要なデータがすべてコピーされるので、メモリ消費の面でかなりしんどいものがあります。プログラムが走っている最中にタスクマネージャ等で子プロセスのメモリ消費を見て、500MB以上消費しているなら検討するべき選択肢です。当然処理速度は低下します。
 どうしてもmultiprocessingを使いたい場合、できるだけ小さい粒度で回して、子プロセスにあまり多くのデータが行かないように気をつけます。また、子には必要なデータだけを渡すようにします。
 なお、multiprocessingには共有メモリがありますが、pythonのデータ型を直接プロセス間で共有することはできません。また、なんとかmanagerなる強そうな機能もありますが、内部的にはpickle漬けにしてパイプで送る普通のmultiprocessingでしかないので、使ってもメモリ消費低減機能はありません。

要らないデータはGCに回収させる

 基本中の基本ですが、よく使うので例を挙げて説明します。

with open("data", "r") as file:
    data1 = pickle.load(file)
data2 = some_processing1(data1)
data3 = some_processing2(data2)

 こういう処理を書くと、data1,data2,data3がそれぞれメモリ領域を消費することになります。普通、これらすべてが最終的に必要ということはないはずなので、使い終わったデータは解放してやった方がメモリを節約できます。
 一つの考え方としては、pythonGCは基本的に参照カウントなので、ぜんぶ同じ名前に束縛しちゃえば良いという方法があります。

with open("data", "r") as file:
    data = pickle.load(file)
data = some_processing1(data)
data = some_processing2(data)

 この方法だとsome_processingが返ってdataに値が束縛される度、古いdataはメモリ上から消えていきます(逆に言えば、代入が終わってからGCが走るまでの期間では、瞬間的に古いdataと新しいdataが両方メモリ上にあることになります。これについては後述)。ただ、実際問題としては、別の名前がついてた方がプログラムの可読性は上がります。なので、del文を使います。

with open("data", "r") as file:
    data1 = pickle.load(file)
data2 = some_processing1(data1)
del data1
data3 = some_processing2(data2)
del data2

 del文は受け取った名前の参照を消します。注意すべきなのは、del文は名前の指す実体を消す訳ではなく、あくまでも参照を消すに過ぎないということです。del文で実体を消せるのは、del文によってオブジェクトへの参照が0になるときだけです。

リストは積極的にnumpy配列にする

 私は癖で「配列の形をlistで作ってからnumpy配列に変換する」という処理を良く書くのですが、その度にその配列のメモリ消費が半分近く減少するという経験をしています。
 これはある意味当たり前のことで、そもそもリスト構造はポインタの塊なので、データの中身を記録するのと同じくらいのメモリ領域をアドレスデータを持つことに費やしています。numpy配列はリストと比べればだいぶ効率的なデータ構造をしているので、これを使ってメモリ消費が小さくなるのは当然です。
 なお、あまり使われているのを見かけませんが、numpy配列は数値型の他にも多くのデータを格納できます。たとえば、str型を入れられる他、objectなるデータ型も持っています。str型のnumpy配列は大規模データならそれなりに有用ですが、object型は実質的にpythonのデータへのポインタでしかなく、numpyの便利な機能(shapeとかtransposeとか)が使える以外のメリットはないと心得るべきです。

32bitにする

 データ処理では良くfloatのnumpy配列を使うと思います。巨大なnumpy配列がメモリを食っている場合、これを32bitにするとメモリ消費は単純計算で半分になります。

import numpy as np
some_data = np.array(some_data, dtype=np.float32)

 また、これをやると心なしか(ほとんど気のせいレベルですが)実行速度が速くなるような気がします。64bitのFPUが使えるCPUでもそうなのは、キャッシュに入れられるデータ量が増えるからとか?
 当たり前ですが、32bitで必要な精度が得られるかはまったく別の問題です。とはいえ、pythonで良くやる機械学習では32bitで足りる場面も多いのではないでしょうか。

配列処理は破壊的代入で行う

 たとえば、こういう処理があったとします。

large_data = [some_processing(x) for x in large_data]

 一見すると良さそうですが、リスト内包表記が返ってlarge_dataに新しい値が代入されてから、GCが走るまでの間、メモリ上には旧いlarge_dataと新しいlarge_dataが併存することになります。データの大きさ次第では、メモリが溢れてしまいます。
 こういうときは破壊的代入で書くしかありません。

for i in range(len(large_data)):
    large_data[i] = some_processing(large_data[i])

 これだとメモリは溢れません。
 pythonではfor文を回して配列のインデックスを叩くような処理は、とても重いことで有名です。とはいえ、他に良い手がないことも多いので、実際にはこういう方法は割と良く使うかと思います。これで速度が厳しいときはcythonで書くとだいぶマシになります。

メモリ上でデータを圧縮する

 「必要なデータなんだけどすごく大きくて、ただしそんなに頻繁にアクセスする訳ではない」というときは、メモリ上でデータを圧縮する方法があります。これについては以前記事にしました。
 当然これには圧縮・解凍分のオーバーヘッドがありますが、中間ファイルを吐くよりはだいぶマシになるかと思います。どこまで実用性があるかは微妙ですが、他にどうしようもないときに検討すべき選択肢ではあります。

終わりに

 pythonは泥臭いリスト・配列処理やメモリ管理を隠蔽してくれる良い言語ですが、データフローまで面倒を見てくれる訳ではないので、人間がちゃんとデータフローを作ってやらないとハマります。そして、pythonは泥臭い部分を隠蔽してしまっているので、いざというときチューニングが効かないシチュエーションも相応にあります。極端な話、「C/C++ならこのデータフローで大丈夫だけど、pythonだと無駄が多くて無理」という状況もあり得ます。なので、pythonで実用できる程度に効率的なプログラムを書くのは意外と難しい側面があります。やむを得ずバッドノウハウを駆使してどうにか動かす、というシチュエーションも多いのではないでしょうか。
 そういう難しい面はありますが、pythonはやっぱり楽です。なので、どうにか使っていけば良いと思います。

【python】pythonでn-gramの特徴量を作る

python ngram 機械学習

 ○○ってパッケージでできるよ! という意見もあると思いますが、ちょっと挙動を変えたくなる度にパッケージのhelp読んだり、微妙に柔軟性のないパッケージに苦しむ(たとえば文末の句点と次の文の最初の文字は繋げないで欲しいのにできない、とか)くらいなら、最初から自分で書いた方が速いです。好きなだけ編集できます。
 とりあえず、文字列ないし形態素のリストなどをn-gramに切り分ける関数を作ってみます。

def ngram_split(string, n, splitter="-*-"):
    """
    string:iterableなら何でも n:n-gramのn splitter:処理対象と混ざらなければ何でも良い
    """
    lst = []
    for i in range(len(string[:-n+1])):
        lst.append(splitter.join(string[i:i+n]))
    return lst

 結果はこんな感じ。

>>> for x in ngram_split("吾輩は猫である。", 3):
...     print(x)
... 
吾-*-輩-*-は
輩-*-は-*-猫
は-*-猫-*-で
猫-*-で-*-あ
で-*-あ-*-る
あ-*-る-*-。
>>> for x in ngram_split(["吾輩","は","猫","で","ある","。"], 3):
...     print(x)
... 
吾輩-*-は-*-猫
は-*-猫-*-で
猫-*-で-*-ある
で-*-ある-*-。

 悪くないですが、返り値はdictの方が便利そうです。

from collections import defaultdict
def itr_dict(itr):
    d = defaultdict(int)
    for x in itr:
        d[x] += 1
    return d

 ↑こういうのを作っておいて、

>>> itr_dict(ngram_split(["吾輩","は","猫","で","ある","。"], 3))
defaultdict(<class 'int'>, {'は-*-猫-*-で': 1, '吾輩-*-は-*-猫': 1, '猫-*-で-*-ある': 1, 'で-*-ある-*-。': 1})

 こんな感じで使えば良いのではないでしょうか。


 さて、以下のようなデータを考えます。

data_lst = ["吾輩は猫である",
            "国境の長いトンネルを抜けると雪国であった",
            "恥の多い生涯を送って来ました",
            "一人の下人が、羅生門の下で雨やみを待っていた",
            "幼時から父は、私によく、金閣のことを語った"]

 これを文字2-gramの特徴量にしてみます。すでにn-gramは作れるようになっているので簡単です。

from sklearn.feature_extraction import DictVectorizer

bgram_dict_lst = [itr_dict(ngram_split(x, 3)) for x in data_lst]
dict_vectorizer = DictVectorizer()
a = dict_vectorizer.fit_transform(bgram_dict_lst).toarray()

 DictVectorizerって何? という声が聞こえてきそうなので、sklearnのドキュメントを貼ります。
sklearn.feature_extraction.DictVectorizer — scikit-learn 0.18.1 documentation
 結果を先に見せると、こういうものが生成されています。要するに、dictから特徴量まで一気に作ってくれます。

>>> a
array([[ 0.,  0.,  0.,  0.,  1.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,
         0.,  0.,  0.,  0.,  0.,  1.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,
         0.,  0.,  1.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,
         0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  1.,  0.,  0.,  0.,
         0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  1.,  0.,  0.,  0.,
         0.,  0.,  1.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  1.,  0.,  0.,  1.,  0.,  0.,  0.,  0.,  1.,  0.,
         0.,  1.,  0.,  0.,  0.,  1.,  0.,  0.,  1.,  0.,  0.,  0.,  0.,
         1.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  1.,  0.,  1.,  0.,  0.,
         1.,  1.,  1.,  1.,  0.,  0.,  0.,  0.,  0.,  0.,  1.,  1.,  1.,
         0.,  0.,  0.,  0.,  1.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,
         0.,  0.,  0.,  0.,  0.,  1.,  0.,  0.,  0.,  1.],
       [ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  1.,  0.,  0.,  0.,  0.,  0.,
         1.,  0.,  1.,  0.,  1.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  1.,
         0.,  0.,  0.,  1.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  1.,
         0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,
         1.,  0.,  0.,  1.,  0.,  0.,  1.,  1.,  0.,  0.,  1.,  0.,  0.,
         0.,  0.,  0.,  1.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  1.,  0.,  0.,  0.,  1.,  0.,  0.,  0.,  1.,  0.,  0.,  0.,
         0.,  0.,  1.,  1.,  0.,  0.,  1.,  0.,  0.,  0.,  0.,  2.,  0.,
         0.,  0.,  0.,  0.,  1.,  1.,  0.,  0.,  0.,  1.,  0.,  0.,  0.,
         0.,  0.,  0.,  0.,  1.,  1.,  1.,  1.,  1.,  0.,  0.,  0.,  0.,
         0.,  0.,  1.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  1.,  0.,
         1.,  0.,  0.,  0.,  0.,  0.,  1.,  0.,  1.,  0.],
       [ 1.,  0.,  1.,  0.,  0.,  0.,  0.,  0.,  1.,  0.,  1.,  0.,  1.,
         0.,  1.,  0.,  0.,  0.,  0.,  0.,  1.,  0.,  1.,  1.,  0.,  0.,
         0.,  1.,  0.,  0.,  0.,  0.,  1.,  1.,  0.,  0.,  0.,  1.,  0.,
         0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,
         0.,  1.,  0.,  0.,  0.,  1.,  0.,  0.,  1.,  0.,  0.,  0.,  1.,
         0.,  1.,  0.,  0.,  1.,  0.,  0.,  1.,  0.,  0.]])

 DictVectorizerに似たようなものを自分で作ることも、当然できます。が、基本的にこの手のデータ処理で細かく弄り回したいと思うのは前処理の部分ですから、こういう「dictを行列に変換する」みたいな単純な処理はパッケージに投げてしまった方が世の中の幸せの総量は増える気がします。逆に言えば、前処理が必要ならDictVectorizerに投げる前(あるいは投げた後)に済ませておく必要があります。


 ところで、この特徴量はこれでアリですが、たぶんそれぞれのベクトルをテキスト長か何かで割って相対頻度に変換してやると、機械学習的に良い感じになると思います。

import numpy as np

len_array = np.array([[len(x)]*a.shape[1] for x in data_lst])
#↑もうちょっと綺麗な方法があるなら教えて欲しい

std_a = a/len_array

 ここまで来れば、そのまま機械学習アルゴリズムに突っ込んでもそこそこなんとかなるかと思います。性能を求めるなら、更に特徴選択等を入れるべきでしょう。

【python】pythonのsetは値比較なのか?

python

 ふと思った。「pythonのsetって値比較してるの?」

>>> a = ("hoge",) #同値かつ同一でないimmutableの作り方がタプル以外思いつかなかった
>>> b = ("hoge",)
>>> a is b
False
>>> id(a)
140412431555272
>>> id(b)
140412430884312
>>> set([a,b])
{('hoge',)}

 してるらしい。このsetの中身はどっちのオブジェクトなんだろう。

>>> id(list(set([a,b]))[0])
140412431555272

 先に書いた方? 数を3つにして試してみる。

>>> a = ("hoge",)
>>> b = ("hoge",)
>>> c = ("hoge",)
>>> id(a)
140412431554880
>>> id(b)
140412431555272
>>> id(c)
140412430884312
>>> id(list(set([a,b,c]))[0])
140412431554880
>>> id(list(set([b,c,a]))[0])
140412431555272
>>> id(list(set([c,a,b]))[0])
140412430884312

 規則性があるような気もするけど、言語仕様で決まってる訳ではないので、これを利用してプログラムを書こうとか考えない方が良いだろう。まあ、それ以前に使い道もないけど。

【python】pythonでもprintf/scanfしたい!

ctypes python

※注意! この記事はネタ記事です。この記事に書いてある方法でpythonからprintf/scanfを使うことはできますが、実用性は保証しません。


 C言語でプログラミングの基礎を勉強した人がpythonにやってきて初めに抱く不満は「文字列処理面倒くさ」ではないでしょうか。pythonにはpythonの文字列フォーマットがありますし、正規表現も標準で使えるので慣れれば快適な環境という考え方もありますが、Cの書記指定に郷愁を抱く方もいるでしょう(今時そんな奴いるのか? というツッコミはなしで)。
 幸い、pythonには標準でctypesというモジュールがあり、これを使えばDLLを読み出せます。なので、いつもCで使っていたprintf/scanfをpythonから呼び出してやることにしましょう。

 とりあえず練習としてputsを使ってみます。

>>> from ctypes import *
>>> libc = CDLL("libc.so.6")
>>> libc.puts("hello world".encode()) #よくわからないけどエンコードしないと駄目でした
hello world
12

 死ぬほど簡単ですね。hello worldの後に表示されている12はいつもの関数返り値がREPLに出てるだけです。hello worldはDLLが直接標準出力に書いてます(るはず)。「libc.so.6ってなに?」って人は諦めてpythonの文字列フォーマットを覚えましょう。
 ところで、日本語は大丈夫なんでしょうか。

>>> libc.puts("ほげほげ".encode())
ほげほげ
13

 とりあえず動いてるんだと思いますが、この辺は環境によってシビアな場合もあるので、なんとも言えません。とりあえず私のlinuxは大丈夫みたいです(たぶん。特定文字だけ化けるみたいな嫌過ぎるアレがなければ)。

 いけそうなので、printfしてみましょう。

>>> libc.printf("%s%d\n".encode(),"ほげほげ".encode(), 1234)
ほげほげ1234
17

 死ぬほど簡単ですね。これなら特に悩むこともなく使えそうです。ところで、小数を与えたら?

>>> libc.printf("%s%f\n".encode(),"ほげほげ".encode(), 12.34)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ctypes.ArgumentError: argument 3: <class 'TypeError'>: Don't know how to convert parameter 3

 こうなりました。実はctypesでは基本的に、pythonの型を「Cの型をラップしたオブジェクト」に変換してからCの関数に渡す必要があります。ただし「文字列と整数(とNone)は頑張って自動的キャストする」という仕様になっているので、これまでエラーが出てこなかっただけです。
 こういう場合は、大人しく型を書きましょう。簡単です。

>>> libc.printf("%s%f\n".encode(),"ほげほげ".encode(), c_double(12.34))
ほげほげ12.340000
22

 使える型の一覧とかは公式ドキュメント見てください。
16.16. ctypes — Pythonのための外部関数ライブラリ — Python 3.5.2 ドキュメント

 printfはこれで何とか使えそうですが、次はscanfを使わないといけませんね。scanfは周知の通り、標準入力から読み込んだ文字列を書式に従って解釈し、結果をポインタに格納します。なんかポインタとかやばそうです。そもそも使えるんでしょうか。
 結論から言うと、使えます。つーか、ドキュメントに使えるって書いてあります
 どれどれ

>>> i = c_int()
>>> libc.scanf("%d".encode(), byref(i))
0
>>> i
c_int(0)

 これを実行すると、入力待ちにならないで速攻終わってくれます。そして値が入っていてほしかったiの中身は、初期化された状態のままの0です。駄目じゃん。駄目な理由はわからないようななんとなくわかるような雰囲気ですが、とりあえずちゃんと調べて対処するのは面倒くさいのでパス。動いてないのは標準入力だと思うので、sscanfを使ってみます(ドキュメントにもそっちが書いてあるし)。

>>> i = c_int()
>>> libc.sscanf("1234".encode(), "%d".encode(), byref(i))
1
>>> i
c_int(1234)

 これは期待通りですね。ちなみに、c_intからpythonの型の値を取り出すには、

>>> i.value
1234

 で良いようです。
 これができるなら後は簡単で、

>>> s = input()
1234
>>> libc.sscanf(s.encode(), "%d".encode(), byref(i))
1
>>> i.value
1234

 と動かすことができ、scanfと同じことができます。やったねたえちゃん、書式指定が使えるよ!

まとめ

 こんなの使うより正規表現とか覚えるべき。あとはsscanf風(というかpythonのformatの逆みたいな感じ)に文字列を切り出せるparseってパッケージもあるらしいです。pipで入ります。
pypi.python.org

おすすめのテキストエディタ

雑記

 テキストエディタと言っても、vimとかemacs秀丸だのサクラだの、Sublime TextだのAtomだのの話をしたいのではない。そりゃ、真面目にソースコードだの文章だのを編集するなら大掛かりなエディタだって必要かもしれないけど、普段ちょっとしたテキストファイルを開きたいとき、そんなでかいものは要らない(vimは小さいけど)。だけどwindowsのメモ帳とか、geditあたりはいまいち納得がいかない。そんなとき用のエディタ。

GreenPad - kMonos.NET

 GreenPad、とても使いやすいです。

【python】multiprocessingはアホみたいにメモリ食うよって話

python

 タイトルで落ちてるんだけど、それなりに大きい(それでも数GBとかそんなもん)データをmultiprocessingで処理しようとしたら、メモリが溢れて大変だった。
 multiprocessingはプロセス間でメモリを共有しない。ということは、必然的にそうなるしかないのだが、処理するデータは一々プロセスにコピーされる。3GBのデータを4つのプロセスで並列処理しようとしたら、あっという間に12GB+親プロセスの3GBで15GB埋まる。それどころか、プロセス間のデータ転送アルゴリズムがpickleなせいで、瞬間的に転送されたデータ+そのpickleが存在することになり、そしてpickleはお世辞にも省容量とは言い難いデータ構造をしている。つまりpickleの分もメモリは消費されると見ておいた方が良い。
 こういうことは理解して正しく使えば問題にならないんだけど(つまりメモリ消費が大きくなりそうなときはmultiprocessingはやめとけば良い。大して時間がかからないなら諦めて1コアで実行するか、可能ならcython等に逃げる)、「ヒャッハー! お手軽並列処理だぜ!」な気分で使ってた自分は割と酷い目に合った。反省。