2009年9月30日水曜日

String concatenation in Python

文字列連結の効率の話
addのほうが早かったよなと思って、色々試してみた。

def str_mul():
    return name * count

def str_join():
    lst = []
    append = lst.append
    for i in xrange(count):
        append(name)
    return ''.join(lst)

def str_map():
    lst = map(lambda x:name, xrange(count))
    return ''.join(lst)

def list_comp():
    return ''.join(name for i in xrange(count))

def str_add():
    s = ''
    for i in xrange(count):
        s += name
    return s

import cStringIO
def cstring_io():
    io = cStringIO.StringIO()
    write = io.write
    for i in xrange(count):
        write(name)
    return io.getvalue()

import StringIO
def string_io():
    io = StringIO.StringIO()
    write = io.write
    for i in xrange(count):
        write(name)
    return io.getvalue()

from array import array
def str_array():
    a = array('c')
    add = a.fromstring
    for i in xrange(count):
        add(name)
    return a.tostring()

from mmap import mmap
def str_mmap():
    m = mmap(-1, count * len(name))
    write = m.write
    for i in xrange(count):
        write(name)
    m.seek(0)
    return m.read(count * len(name))

def main():
    global count, name
    func_list = (str_mul, str_join, str_add, string_io, cstring_io,
                 str_map, list_comp, str_array, str_mmap)

    # test
    count = 2
    name = 'hello'
    assert all((name * count).__eq__(f()) for f in func_list)

    import timeit
    for c in (10, 1000):
        for b in (1, 1000):
            for f in func_list:
                count = c
                name = 'hello' * b
                print '%10s x %4s, "hello"*%4s: %9.4fms'%(
                    f.func_name, c, b, timeit.timeit(f, number=100)/100*1000)

if __name__ == '__main__':
    main()


   str_mul x   10, "hello"*   1:    0.0006ms
  str_join x   10, "hello"*   1:    0.0042ms
   str_add x   10, "hello"*   1:    0.0033ms
 string_io x   10, "hello"*   1:    0.0312ms
cstring_io x   10, "hello"*   1:    0.0072ms
   str_map x   10, "hello"*   1:    0.0059ms
 list_comp x   10, "hello"*   1:    0.0077ms
 str_array x   10, "hello"*   1:    0.0081ms
  str_mmap x   10, "hello"*   1:    0.0180ms
   str_mul x   10, "hello"*1000:    0.0108ms
  str_join x   10, "hello"*1000:    0.0131ms
   str_add x   10, "hello"*1000:    0.0126ms
 string_io x   10, "hello"*1000:    0.0411ms
cstring_io x   10, "hello"*1000:    0.0364ms
   str_map x   10, "hello"*1000:    0.0146ms
 list_comp x   10, "hello"*1000:    0.0167ms
 str_array x   10, "hello"*1000:    0.0377ms
  str_mmap x   10, "hello"*1000:    0.1105ms
   str_mul x 1000, "hello"*   1:    0.0017ms
  str_join x 1000, "hello"*   1:    0.2352ms
   str_add x 1000, "hello"*   1:    0.2235ms
 string_io x 1000, "hello"*   1:    2.4272ms
cstring_io x 1000, "hello"*   1:    0.5111ms
   str_map x 1000, "hello"*   1:    0.3591ms
 list_comp x 1000, "hello"*   1:    0.2296ms
 str_array x 1000, "hello"*   1:    0.5338ms
  str_mmap x 1000, "hello"*   1:    0.4778ms
   str_mul x 1000, "hello"*1000:    8.5971ms
  str_join x 1000, "hello"*1000:    8.3908ms
   str_add x 1000, "hello"*1000:   29.4590ms
 string_io x 1000, "hello"*1000:   10.6351ms
cstring_io x 1000, "hello"*1000:   28.7235ms
   str_map x 1000, "hello"*1000:    8.8407ms
 list_comp x 1000, "hello"*1000:    8.2300ms
 str_array x 1000, "hello"*1000:   38.9228ms
  str_mmap x 1000, "hello"*1000:   18.6053ms

addは50kB程度までは、文字列長や回数にかかわらず早い。
'str' * nをjoinが逆転するのは見てびっくり。
StringIOは1MBを超えたあたりで突然加速、理由不明。
cStringIOを余裕で追い抜き、100MBあたりではjoinに並ぶ。
ただし、1MBを超えるようなものはPythonで扱うべきではないかも。
for, map, list内包は勝ったり負けたり。
disってみたところ、早そうなのはfor-join。
psycoも使ってみたけど、速度はあまりかわらず。

結論:
addでもjoinでも扱いやすいほうを使おう。
速度はPythonを使う上では気にしない。

2009年9月14日月曜日

Interpolation surprise — And now for something completely Pythonic...

Interpolation surprise — And now for something completely Pythonic...

おそろしや

class Surprise(object):
def __str__(self):
return "[str]"
def __unicode__(self):
return u"[unicode]"

surprise = Surprise()

print "%s %s %s" % (surprise, u"foo", surprise)

出力:
[str] foo [unicode]

つまり、途中でUnicodeが来ると、それ以降は__unicode__が呼ばれるようになるわけです。


ちなみにPython2.3なら
出力:
[str] foo [str]
となります。

2009年9月3日木曜日

setuptools

SolacePlurkからリリースされました。

Stack Overflowのクローンです。
あいかわらず仕事が早いしソースが読みやすい。

しかし今回、最も驚いたのが、setup.pyの使い方。
こんな便利なものだったんですね。
なお、正しい手順はREADMEInstallationを見てください。

私の手順は以下のとおり。

$ hg clone http://bitbucket.org/plurk/solace
$ mkvirtualenv solace
(solace)$ cd solace
(solace)$ python setup.py develop
(solace)$ echo SECRET_KEY = \'`mkpasswd -l 40`\' > config.py
(solace)$ SOLACE_SETTINGS_FILE=config.py python setup.py reset
(solace)$ SOLACE_SETTINGS_FILE=config.py python setup.py runserver

はやい!本当に早い!
setup.pyってこんなに便利だったんだ。
拡張コマンドを追加できるなんて知らなかったよ!

さて、setup.pyを読んでいきます。
まずはここ。

try:
from solace import scripts
except ImportError:
pass
else:
extra['cmdclass'] = {
'runserver': scripts.RunserverCommand,
'initdb': scripts.InitDatabaseCommand,
'reset': scripts.ResetDatabase,
'make_testdata': scripts.MakeTestData,
'compile_catalog': scripts.CompileCatalogEx
}


コマンドの実装はこんな感じ

from distutils.cmd import Command
class InitDatabaseCommand(Command):
description = 'initializes the database'
user_options = [
('drop-first', 'D',
'drops existing tables first')
]
boolean_options = ['drop-first']

def initialize_options(self):
self.drop_first = False

def finalize_options(self):
pass

def run(self):
from solace import database
if self.drop_first:
database.drop_tables()
print 'dropped existing tables'
database.init()
print 'created database tables'

わかりやすい!

もう一つ勉強になったのがtests_require

setup(
name='Plurk_Solace',
version='0.1',
url='http://opensource.plurk.com/solace/',
license='BSD',
author='Plurk Inc.',
author_email='opensource@plurk.com',
description='Multilangual User Support Platform',
long_description=__doc__,
packages=['solace', 'solace.views', 'solace.i18n', 'solace.utils'],
zip_safe=False,
platforms='any',
test_suite='solace.tests.suite',
install_requires=[
'Werkzeug>=0.5.1',
'Jinja2',
'Babel',
'SQLAlchemy>=0.5',
'creoleparser',
'simplejson',
'webdepcompress'
],
tests_require=[
'lxml',
'html5lib'
], **extra
)

ここで、テストのときだけ必要なライブラリを書くことができる。
python setup.py test
とすると依存ライブラリをとりにいきます。
すばらしい!


setuptoolsをもっと勉強します。
deploy+setup Scriptとして、今後活用していくことを誓います。

2009年8月6日木曜日

eclipse WTP encoding

eclipse


WTP encoding

最近、Javaの仕事が増えたせいでEclipseをよく使います。
WTPのencoding設定で困ってほかのプラグインに移ってしまう人が多いので、
まとめておきます。

その1. JSPがISO-8859-1になってしまって困っちゃう
Window => Preferences => General => Content Types
より
Text => JSP を選んで"Default encoding"にUTF-8を指定し、Updateを押す。


その2. HTMLがShift_JISに決め打ちされて困っちゃう。
eclipse.iniに
-Duser.language=en
と書いておく。

mercurial

http://www.vectrace.com/mercurialeclipse/
ためしにインストールしてみたmercurialプラグインは比較で文字化けしてしまい、
使い物になりません。

http://bitbucket.org/mercurialeclipse/main/
bitbucketが使われているのならば、対応は私がしようと思って、最新版をとってみたら
あれれ?使える。
というわけで、しばらくmercurial用クライアントとして、Eclipseも使ってみようと思います。

しかし、他のクライアントを使ってみるとわかるのですが、
Subversiveは本当によくできている。
Syncronize with Repositoryが本当に使いやすい。
あれをmercurialに使えたらどれほど幸せか。
まあ、ほしい機能はIssuesに全て登録してあるので、生ぬるく見守ります。

merge

分散型にした一番のメリットはマージ。
多人数で開発するときは、最初はpush --forceを使ってもらって、headを作りまくってもらう。
あとはこちらで全部マージできる。これは安心だ。
でも、この方式ならdarcsのほうがいいのかなー。

まだマージのBest Practiceを見つけられない。
試行錯誤の連続だけど、ツールとしてはextdiff=WinMergeが今のところ楽かな。

2009年6月19日金曜日

Java detect Encoding for Japanese

javaにはエンコード自動認識が標準ではなかったので、探し回った。
* jchardet
* juniversalchardet
などが見つかったけど、とりあえず、標準で逃げる方法。


private final List<CharsetDecoder> decoders;
{
// 色々試した結果、この順序が必須
String[] names = new String[] { "ISO-2022-JP", "EUC-JP", "UTF-8",
"windows-31j" };
decoders = new LinkedList<CharsetDecoder>();
for (String name : names) {
decoders.add(Charset.forName(name).newDecoder());
}
}
public Charset detectEncoding(byte[] bytes) throws Exception {
for (CharsetDecoder d : decoders) {
try {
d.decode(ByteBuffer.wrap(bytes));
} catch (CharacterCodingException e) {
continue;
}
return d.charset();
}
throw new IllegalArgumentException("デコードできませんでした。");
}

public void testDetectEncoding() throws Exception {
String[] samples = new String[] { "平", "カ", "1", "ひ", "b", };
for (String s : samples) {
assertEquals("windows-31j", detectEncoding(s.getBytes("sjis")).toString());
assertEquals("UTF-8", detectEncoding(s.getBytes("utf-8")).toString());
assertEquals("EUC-JP", detectEncoding(s.getBytes("euc_jp")).toString());
assertEquals("ISO-2022-JP", detectEncoding(s.getBytes("jis")).toString());
}
}

2009年5月25日月曜日

timeit on java

SpringのAutoWireの有無による速度差と、CGLIBとProxyの速度差を調べたくなった。
AutoWireの有無による速度差はなし。
CGLIBとProxyも十分無視してよい速度差。

上の目的のためにtimeitみたいに、適当な回数分だけループして時間を計測してくれるツールを作った。
一定の時間を越えるまで回数を増やしてループし続けます。
最初は500msにしたら、テストスイートが終わらなくなったので、50ms。
比較用に交互に実行する機能があるといいかも。

public abstract class Timeit {
protected String name;

public Timeit(String name) {
this.name = name;
}

public void timeit() {
int before = 0;
int count = 1;
long sum = 0;
prepare();
while (true) {
int loop = count - before;
long start = System.nanoTime();
for (int i = 0; i < loop; ++i) {
this.invoke();
}
long end = System.nanoTime();
long current = end - start;
sum += current;
if (current > 50 * 1000 * 1000) {
break;
}
before = count;
count = count * 2;
}
long time = sum / count;
System.out.print(name + ": ");
if (time < 1000) {
System.out.printf("%dns\n", time);
} else if (time < 1000 * 1000) {
System.out.printf("%.3fμs %dtimes\n", time / 1000.0, count);
} else if (time < 1000 * 1000 * 1000) {
System.out.printf("%.3fms %dtimes\n", time / 1000.0 / 1000, count);
} else {
System.out.printf("%.3fs %dtimes\n", time / 1000.0 / 1000 / 1000,
count);
}
}

public void prepare() {

}

public abstract void invoke();

public static class TimeItTest extends TestCase {
public void testSimple() throws Exception {
new Timeit("simple") {
public void invoke() {
try {
Thread.sleep(2);
} catch (InterruptedException e) {
}
}
}.timeit();
}
}
}