challenge 分散関数呼び出し

分散関数呼び出しを実装してください.

呼び出される関数は,定価を整数で,割引率(%)を整数で受け取り,
文字列で「販売価格 ○円(定価○円から○%引き)」を返すものとします.
また,数字は3桁のカンマ区切りにするものとします.

たとえば,pricestring(2000, 20) なら
"販売価格 1,600円 (定価2,000円から20%引き)"
を返します.

関数の呼び出し元と,呼び出される側は,物理的に異なる
サーバに配置できることを条件とします.
呼び出し方法は問いませんが,呼び出し方法に名前がある場合,
それをタグに加えてください.
(XML-RPC,SOAP,CORBA,RMI,など)

また,作成した関数を直列に1万回呼び出して,
実行にかかった時間を測定してください.
測定時は別サーバでなくても構いません.
(なるべく別サーバが望ましいです)

測定環境として,
・サーバとクライアントのCPU・メモリ
・同一サーバ内での実行か別サーバでの実行か
・別サーバの場合,通信経路.(100Mbps Ethernet等)
・言語のバージョン
・ミドルウェアを使用している場合,その名前とバージョン
も併記してください.

1つの言語で複数の分散関数呼び出しの実装方法がある場合,
複数の回答を歓迎します.

出題の意図は,様々な分散呼び出し方法の実装例と,
レスポンス速度の確認にあります.
このお題は沢渡 みかげさんの投稿です。 まったく手を加えないでいい完成度の投稿で本当に助かります。 ありがとうございました。

Posted feedbacks - Python

とりあえずPython使ってCGIサーバを立てて見ました。
http://blueeye.bne.jp:7777/cgi-bin/discount.py?2000&33

↑のアドレスにアクセスすると、次のように帰ってきます。
> 販売価格 1340円(定価2000円から33%引き)

以下の記事を参考にしてHTTPサーバを走らせて、その上で下にあるコードを走らせています。
http://coreblog.org/ats/python-de-cgi

今日一日くらい、鯖を立てておきますので遊びたい方はご自由に。
スレッドプールもないようなHTTP鯖なので、それなりに遅いと思いますが、速度測定はそのうちやります。
(ネットゲームのサーバが動いているので、正確な速度測定は難しいと思います)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# -*- coding: utf-8 -*-
#-------------------------以下サーバー側コード
import sys

if(len(sys.argv) == 2):
    (price,discount) = sys.argv[1].split("&")
    price = int(price)
    discount = int(discount)

    print "Content-Type: text/plain"
    print ""
    print "販売価格 %d円(定価%d円から%d%%引き)" % (int(price * (100.0-discount)/100.0 ), price, discount)
else:
    print "Content-Type: text/plain"
    print ""
    print "引数エラー"


# -*- coding: utf-8 -*-
#------------------------以下呼び出し側コード
import urllib
def pricestring(price, discount):
    return urllib.urlopen("http://blueeye.bne.jp:7777/cgi-bin/discount.py?%d&%d" % (price, discount)).read()

print unicode(pricestring(2000,20), "utf8")

何なんだこの遅さは。
直列で100回実験したら18秒強。
10個のスレッドで、並列に10回ずつ取りに行ったら6.5秒。

多分コンソール周りのPrintに時間食っているのかなぁ。
コンソールを表示しなければ、早くなるのか?
まぁ、裏でネットゲームの鯖が動いているところで、実験ってのが無茶な話ですが。

鯖のPCのスペックはこんな感じ。
Pen4 2.8GHz HT
RAM 512MB
OS XP
so-net ADSL12M線
ネットゲームのサーバプログラムがCPUパワーの80%を常時消費

測定したクライアントPC
sinetに接続されている某大学のPC
Pen4 3.2GHz HT
RAM 2GB
OS XP
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# -*- coding: utf-8 -*-
import urllib
import time
import threading

def pricestring(price, discount):
    return urllib.urlopen("http://blueeye.bne.jp:7777/cgi-bin/discount.py?%d&%d" % (price, discount)).read()


t1 = time.time()

for x in xrange(100):
    print unicode(pricestring(x * 1000, 20),"utf8")

t2 = time.time()

class TEST_THREAD(threading.Thread):
    def __init__(self, price, discount):
        threading.Thread.__init__(self)
        self.price = price
        self.discount = discount
    def run(self):
        for x in xrange(10):
            print unicode(pricestring(self.price, self.discount + x),"utf8")

threadList = []
for x in xrange(10):
    threadList.append(TEST_THREAD(x * 1000 , 20))
    threadList[-1].start()

for t in threadList:
    t.join()
       
t3 = time.time()

print "series time",t2 - t1
print "pararells time",t3 - t2

pythonの標準ライブラリに含まれているXML-RPCを
使ってみました。

とはいっても、参考にしたIBMのサイトに乗っているサンプル
そのもののような気がしないでもない。

実は数値のコンマ区切りの方が大変でした。

測定環境
 同一マシン上で実行
 Athlon XP 1700+ 512MB
 python 2.5

実行時間
 画面出力を行う    64秒
 画面出力を行わない  112秒
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#
# server.py
#

# -*- coding: utf-8 -*-

import SimpleXMLRPCServer

class PliceString:
  def plicestring(self, p, d):
    def f(i):
      s = str(i)
      n = len(s)
      return ','.join([s[i<0 and -n or i:i+3] for i in range(n-(n+2)/3*3, n, 3)])
    return '販売価格 %s円 (定価%s円から%d%%引き)' % (f(int(p*(1-d*0.01))), f(p), d)

ps = PliceString()
sv = SimpleXMLRPCServer.SimpleXMLRPCServer(('localhost', 8888))
sv.register_instance(ps)
sv.serve_forever()

#
# client.py
#

# -*- coding: utf-8 -*-

import xmlrpclib
import time

sv = xmlrpclib.ServerProxy('http://localhost:8888/')
t0 = time.time()
for i in range(10000):
  sv.plicestring(2000, 20)
#  print sv.plicestring(2000, 20)
print time.time() - t0

Twisted のPerspectiveBroker使ってみました。
----
# 1
Client:
 OS: OpenBSD
 CPU: SUNW,UltraSPARC-IIi (rev 1.2) @ 299.768 MHz
 Python: 2.5 (r25:51908, Mar 11 2007, 13:48:09) \n[GCC 3.3.5 (propolice)]
 Twisted: 2.5.0

Server:
 OS: FreeBSD release-6.2
 CPU: Intel Celeron (634.78-MHz 686-class CPU)
 RAM: 254MB
 Python 2.4.4 (GCC3.4.6 FreeBSD 20060305)
 Twisted: 2.5.0

経路は100Base-TX

 89.23s user 8.78s system 86% cpu 1:53.92 total

----
# 2 上記 Client環境、同一ホスト内
 94.81s user 23.25s system 53% cpu 3:42.37 total
----
# 3 同一ホスト内
 OS: CentOS
 CPU: Intel(R) Xeon(R) 2.66GHz
 RAM: 2GB
 Python: '2.5.1 (r251:54863, Jul 28 2007, 19:05:07) \n[GCC 3.4.6 20060404 (Red Hat 3.4.6-8)]
 Twisted: 2.5.0

 2.95s user 0.25s system 52% cpu 6.133 total
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# ※ 文字コード等は、各環境に合わせて下さい。

## commify.py 
#  参考: http://pleac.sourceforge.net/pleac_python/numbers.html
# Putting Commas in Numbers

def simple_glue(*funcs):
    "glue functions: simple_glue(f, g, h) -> lambda arg: h(g(f(arg)))"
    return lambda arg: reduce(lambda n,m:m(n), funcs, arg)

def digits_div_by(num):
    return lambda xs: reduce(lambda (n,m),i: (n+[xs[m:i]],i),
                             reversed(xrange(len(xs),0,-num)),
                             ([],0))[0]

commify = simple_glue(str, digits_div_by(3), ",".join)


##############################
# サーバー

# -*- coding: euc-jp -*-
from twisted.spread import pb
from twisted.internet import reactor
from commify import commify

PORT = 10800
FORMAT = u"販売価格%(price)s円 (定価 %(original_price)s円から%(discount)d%引き)"

class PriceStringService(pb.Root):
    def remote_pricestring(self, original_price, discount):
        price = commify(int(original_price * ((100.0 - discount) / 100.0)))
        original_price = commify(original_price)
        return FORMAT % vars()

if __name__ == '__main__':
    reactor.listenTCP(PORT, pb.PBServerFactory(PriceStringService()))
    reactor.run()


##############################
# クライアント
# ホスト・アドレス、ポート番号を指定して実行。

# -*- coding: euc-jp -*-
import sys
import itertools
from twisted.spread.pb import PBClientFactory
from twisted.internet import reactor

if __name__ == '__main__':
    host = sys.argv[1]
    port = int(sys.argv[2])

    def failure(_):
        reactor.stop()

    def connected(perspective):
        counter = itertools.count()

        def callloop():
            d = perspective.callRemote("pricestring", 2000, 20)
            d.addCallbacks(success, failure)

        def success(result):
            print result.encode("euc-jp")
            if counter.next() < 10000:
                callloop()
            else:
                reactor.stop()

        callloop()

    factory = PBClientFactory()
    reactor.connectTCP(host, port, factory)
    factory.getRootObject().addCallbacks(connected, failure)
    reactor.run()

OS: Windows XP sp2
CPU: Intel Celeron 1.4GHz
RAM: 480MB
Python: 2.5
PyRO: 3.7

同一サーバー内。実行時間 20秒
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#### Server
# -*- coding: utf-8 -*-

from Pyro.core import ObjBase, Daemon, initServer

def calc_sales_price(price, discount):
    return int(price * (1-discount*0.01))

def commify(amount, num=3, sep=","):
    def _commify(s):
        a,b = s[:-num],s[-num:]
        return commify(a)+sep+b if a else b
    return _commify(str(amount))


class PriceString(ObjBase):
    def pricestring(self, price, discount):
        FORMAT = u"販売価格%(sales_price)s円 (定価 %(price)s円から%(discount)d%引き)"
        sales_price = commify(calc_sales_price(price, discount))
        price = commify(price)
        return FORMAT % vars()

if __name__ == '__main__':
    initServer()
    daemon = Daemon()
    daemon.connect(PriceString(), "pricestring")
    daemon.requestLoop()


#### Client
from Pyro.core import getProxyForURI

if __name__ == '__main__':
    remote = getProxyForURI(r"PYROLOC://localhost:7766/pricestring")
    for _ in xrange(10000):
        print remote.pricestring(2000, 20)

Rpyc (Remote Python call) による実装です。


OS: Windows XP
CPU: Celeron 1.4GHz
RAM: 480MB
Python: 2.5
Rpyc: 2.6

同一サーバー内でテスト。
実行時間: 約12秒
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# -*- coding: utf-8 -*-

# ./pricestring.py
# サーバーj実行時のsys.pathの通ったところに置く。
# サーバーの実行は、
# > python -m Rpyc.Servers.simple_server.py

def Y(f):
    "simple Y-combinator"
    _f = lambda v: f(_f, v)
    return _f

def commify(v, num=3, sep=","):
    return Y(lambda f,s:(lambda a,b:(f(a)+sep+b if a else b))(s[:-num],s[-num:]))(str(v))

def calc_sales_price(price, discount):
    return int((price/100.0) * (100-discount))

def pricestring(price, discount):
    sales_price = commify(calc_sales_price(price, discount))
    price = commify(price)
    return u"販売価格%(sales_price)s円 (定価%(price)s円から%(discount)d%%引き)" % vars()

#### クライアント

from time import sleep
from Rpyc import SocketConnection, Async

connection = SocketConnection("localhost")
pricestring = Async(connection.modules.pricestring.pricestring)
for _ in xrange(10000):
    c = pricestring(2000,20)
    while not c.is_ready:
        sleep(0.1)
    print c.result

お題とは関係ないけど、commify関数に使ってる、
Y-combinatorの表記の手続きなし版。
1
2
3
4
5
6
7
8
def Y(f):
    return (lambda g: lambda arg: f(g(g),arg))((lambda g: lambda arg: f(g(g),arg)))


# デコレータとしての利用例
    @Y
    def fact(f, n):
        return 1 if n == 0 else n*f(n-1)

Thrift使ってみました。release-20070401版です。
サーバ/クライアント共にPython。
初めてなので、Thriftの作法等は、まだよく解ってません。

※注意点: サーバー・クライアントのスクリプト 共に、
gen-py以下をモジュール探索対象のPATHに含めること。
PYTHONPATHに設定、もしくはモジュールのインポート前に、下のコードを追加。
import sys
sys.path.append("./gen-py")

※ メモ
C++のTNonblockingServerがlibeventを利用してるので、
kqueueやepollが使える環境だと、パフォーマンス向上が期待できそうです。(未計測)
Thriftは、言語以外にも各言語毎にserverやprotocllも組み合わせて使うことが出来るみたいなので、
C++/TNonblockingServer/boost.Pythonもやってみようかな。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
##############################
# pricestring.thrift

service PriceStringService {
  string pricestring(1:i32 price, 2:i32 discount)
}

##############################
# Server
from locale import format

class PriceStringHandler:
    def pricestring(self, price, discount):
        calc_sales_price = lambda n,m: n*(1-(m*0.01))
        commify = lambda n: format("%d", n, grouping=True)
        template = u"販売価格%s円 (%s円から%s%引き)"
        return (template % (commify(calc_sales_price(price,discount)), commify(price), discount)).encode('utf-8')

if __name__ == '__main__':
    from locale import setlocale, LC_NUMERIC
    setlocale(LC_NUMERIC, '')

    from pricestring import PriceStringService
    from thrift.transport.TSocket import TServerSocket
    from thrift.server.TServer import TSimpleServer

    handler = PriceStringHandler()
    processor = PriceStringService.Processor(handler)
    server = TSimpleServer(processor, TServerSocket(9090))
    server.serve()

##############################
# Client
# データはutf-8 で渡ってきます。< client.pricestring
# 端末に表示する時など、文字コードは適切なものにエンコードしてください。

if __name__ == '__main__':
 
    from pricestring import PriceStringService

    from thrift import Thrift
    from thrift.transport.TSocket import TSocket
    from thrift.transport.TTransport import TBufferedTransport
    from thrift.protocol.TBinaryProtocol import TBinaryProtocol

    transport = TBufferedTransport(TSocket("localhost", 9090))
    protocol = TBinaryProtocol(transport)
    client = PriceStringService.Client(protocol)

    transport.open()
    for _ in xrange(10000):
        print client.pricestring(2000, 20).decode('utf-8')
    transport.close()

Index

Feed

Other

Link

Pathtraq

loading...