LoginSignup
6
7

More than 5 years have passed since last update.

PythonとJythonにおけるマルチスレッド処理の違い

Last updated at Posted at 2016-09-11

Python (CPython) のthreading.Threadは、GIL(Global Interpreter Lock)の影響で同時に2つ以上のスレッドが並列で動作しないらしい(並行では動作するけど)。
では、他の実装ならどうなのかと思い、Jythonで確認してみました。あと、JythonはJavaのAPIも使用できるので、java.lang.Threadもついでに確認してみた。

検証環境

  • CPU: Intel(R) Celeron(R) CPU N2830 @ 2.16GHz × 2
  • Ubuntu 16.04
  • Python
    • Python 2.7.12
  • Jython
    • Jython 2.7.0
    • OpenJDK 1.8.0_91 64bit ServerVM

検証用ソースコード

4〜100,000の範囲の素数を列挙するタスクを分散して計算するワーカーを作成。以下がthreading.Threadを使った場合。

py_worker.py
from threading import Thread

class Worker(Thread):
    def __init__(self, start, end):
        super(Worker, self).__init__()
        self._start = start
        self._end = end

    def run(self):
        self.prime_nums = []
        for i in xrange(self._start, self._end):
            if not 0 in self._remainders(i):
                self.prime_nums.append(i)

    def _remainders(self, end, start=2):
        for i in xrange(start, end):
            yield end % i

以下がjava.lang.Threadを使った場合。(importするクラスが異なるだけ)

jy_worker.py
from java.lang import Thread

class Worker(Thread):
    def __init__(self, start, end):
        super(Worker, self).__init__()
        self._start = start
        self._end = end

    def run(self):
        self.prime_nums = []
        for i in xrange(self._start, self._end):
            if not 0 in self._remainders(i):
                self.prime_nums.append(i)

    def _remainders(self, end, start=2):
        for i in xrange(start, end):
            yield end % i

そして、ワーカースレッドをキックして経過時間を測る処理が以下です。

main.py
import sys
from threading import Thread
from datetime import datetime

def total_seconds(td):
    return (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6

if __name__ == '__main__':
    argv = sys.argv
    argc = len(argv)
    if argc < 4:
        print 'ERROR: <worker_module> <n_workers> <max_value>'
        sys.exit(1)
    worker_module = argv[1]
    n_workers = int(argv[2])
    max_value = int(argv[3])
    min_value = 4
    interval = (max_value - min_value) / n_workers
    Worker = __import__(worker_module).Worker

    workers = []
    for start in xrange(4, max_value, interval):
        print 'Worker: %s, %s' % (start, start+interval)
        worker = Worker(start, start+interval)
        workers.append(worker)

    start_time = datetime.utcnow()
    for worker in workers:
        worker.start()
    for worker in workers:
        worker.join()
    end_time = datetime.utcnow()
    elapsed_time = end_time - start_time
    elapsed_sec = total_seconds(elapsed_time)
    n_primes = sum([len(w.prime_nums) for w in workers])
    print '# of primes = %s, time = %s sec' % (n_primes, elapsed_sec)

結果

ワーカー処理が終わるまでの経過時間は以下の通りとなりました。

実装 クラス 1 thread 2 threads
Python threading.Thread 100 sec 125 sec
Jython threading.Thread 101 sec 73 sec
Jython java.lang.Thread 101 sec 77 sec

Pythonは、同時に1つのスレッドしか動作できないので、2スレッドで分散しても速くならない (むしろ遅くなってる) ですが、Jythonでは違う結果になりました。

1スレッドでの経過時間はPythonとJythonでほとんど同じなので、今回用いた処理に対する基本的な性能は変わらないと見ています。(本当はJavaの動的コンパイルが効いてJythonの方が速くならないかなーと期待したのですが)
そして、Jythonの場合、1スレッドより2スレッドの方が早く終わったので、ちゃんと並列で動作してくれている感じです。
ここらへんの動作は実装依存なのかな。

6
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
7