LINUX.ORG.RU

Java vs Python: треды и скорость разработки


0

0

Решил тут сравнить производительность Java и Python в "большой" мультитредовости.

Задача: запустить 10000 тредов, каждый из которых тупо подождёт секунду и закроется. Выйти из системы.

Исходный уровень: совершенно не знаю, как это делается в Питоне и ничего сложнее мелких системных скриптов на нём не писал. На Java довольно плотное программирование около 2.5 лет в весьма крупном проекта, но с тредами сам на низком уровне не работал, только обработка уже запущенных процессов.

Питон: через [b]5 минут[/b] (буквально) копания в Гугле и тестов родил такое:

#!/usr/bin/env python # -*- coding: utf-8 -*-

import threading import time

def proc(n): time.sleep(1)

for i in xrange(10000): threading.Thread(target=proc, name="t"+str(i), args=[i]).start()

Время работы скрипта - *42 секунды*. Расход памяти или ресурсов процессора замечен не был.

Дальше начинается веселье. Я принялся за Java. Как сделать просто запуск тредов я так и не нашёл. Сделал запуск через шедулер. После серии экспериментов исходную задачу... так и не решил :) Треды отрабатывают успешно, но основной процесс не завершается, а так и остаётся запущенным. Если проигнорировать на завершение и закрывать потоки вручную, то выходит код примерно такой:

import java.io.IOException; import java.io.PrintStream; import java.util.Date; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture;

import static java.util.concurrent.TimeUnit.*;

public class test implements Runnable { public static void main(String[] args) { final int MAX=10000;

ScheduledFuture<?>[] t = new ScheduledFuture<?>[MAX];

System.out.println("Init"); for(int i=0; i<MAX; i++) { ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); t[i] = scheduler.schedule(new test(i), 0, SECONDS); }

System.out.println("Stop"); for(int i=0; i<MAX; i++) t[i].cancel(true); System.exit(0); }

int n;

public test(int _n) { n = _n; }

public void run() try { Thread.sleep(1000); } catch(Exception e){} } }

То есть - открываем 10000 тредов, не дожидаясь их завершения (всё равно не работает) тут же закрываем, выходим в систему принудительно.

Облом подстерёг после того, как с тестовых 10 тредов взял 1000. "Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread".

Никакие -Xms/Xmx/Xss не помогали. Рост потребления памяти замечен не был.

5000 - сработало. Время выполнения около *7 секунд.*

Итого, неполноценно реализованный вариант на Java примерно втрое быстрее работающего как задумано на Питоне.

Теперь веселье - на тесты и эксперименты с Java ушло около *45 минут* :D

...

Правда, не всё так радужно. Говорят, у Питона есть страшный Global Interpreter Lock, сильно снижающий эффективность тредов, но я пока недопонял, где на него нарываются :)

...

Кто-то предложит рабочее решение на Java? А то я выдохся ;)

★★★★★

Млять. Тупой форум. Преформатированный текст обломал с 
"Ошибка отправки" и тут же отправил неформатированный.
Вот код в формате:

#!/usr/bin/env python 
# -*- coding: utf-8 -*- 

import threading
import time

def proc(n):
    time.sleep(1)

for i in xrange(10000):
    threading.Thread(target=proc, name="t"+str(i), args=[i]).start()

====

import java.io.IOException;
import java.io.PrintStream;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;

import static java.util.concurrent.TimeUnit.*;

public class test implements Runnable
{
    public static void main(String[] args)
    {
        final int MAX=10000;

        ScheduledFuture<?>[] t = new ScheduledFuture<?>[MAX];

        System.out.println("Init");
        for(int i=0; i<MAX; i++)
        {
            ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
            t[i] = scheduler.schedule(new test(i), 0, SECONDS);
        }

        System.out.println("Stop");
        for(int i=0; i<MAX; i++)
            t[i].cancel(true);
        System.exit(0);
    }

       int n;

        public test(int _n)
    {
        n = _n;
        }

        public void run()
            try { Thread.sleep(1000); } catch(Exception e){}
        }
}

KRoN73 ★★★★★
() автор топика
Ответ на: комментарий от KRoN73

Шедулер не нужен. 

public class test{
  public static void main(String args[]){
    for (int i = 0; i < 1000; i++){
      Runnable s = new Runnable(){
        public void run(){
          try{
           Thread.sleep(1000l);
          }catch(InterruptedException e){
            e.printStackTrace();
          }
       }
     };
     new Thread(s).start();
    }
  }
}


svr69 ★★
()
Ответ на: комментарий от KRoN73

гхм. Скока-скока с java работали? %) сомнительно мне....

public class TestThread implements Runnable {

public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

public class Main {

public static void main(String[] args) {
System.out.println(System.currentTimeMillis());
for (int i = 0; i < 10000; i++) {
(new Thread(new TestThread())).start();
}
System.out.println(System.currentTimeMillis());
}
}

результат:
1183473644656
1183473648515
т.е. 4 секунды.

JFreeM ★★★☆
()
Ответ на: комментарий от JFreeM

Да, выходит около 4-х секунд (твой вариант не точно соответствует, нет передачи параметра, но с ней скорость такая же).

Значит, в 10 раз быстрее.

Но время написания всё равно само за себя говорит :D К тому же - два класса получается. Хотя, с другой стороны, это правильно.

KRoN73 ★★★★★
() автор топика
Ответ на: комментарий от JFreeM

real 0m1.303s user 0m0.136s sys 0m0.150s

Это с помощью time. Чтобы изнутри мерять - джойниться надо, т.к. main не будет ждать завершения каждого треда, он их только запускает.

svr69 ★★
()

Вообще, твой тест - фигня полная :)

Питон запросто может использовать пул потоков (??), что может сильно повлиять на общее время (10000 / 42 ~ 238 потоков на один пул), хотя замечу, что в питоне не силен. Что касается, явовских шедулеров, то там запросто могут использоваться внутренние обслуживающие потоки (надо смотреть исходники). Тривиальное же решение через Runnable упрется скорее всего в лимиты системы, и результаты будут сильно зависеть от того, линукс это, винда, солярис или еще какая другая система.

Короче говоря, многопоточное программирование - очень сложная штука и такими тестами ты вряд ли чего полезного намеряешь...

dave ★★★★★
()
Ответ на: комментарий от KRoN73

Время написания... В принципе, если бы сразу в javadoc класса Thread (а оно логично, правда?) посмотрел, то может быть, минут пять и получилось бы :)

svr69 ★★
()
Ответ на: комментарий от svr69

>Время написания... В принципе, если бы сразу в javadoc класса Thread (а оно логично, правда?) посмотрел, то может быть, минут пять и получилось бы :)

Может быть. Но мозги уже были отравлены шедулерами, сейчас почти все многопоточные части в L2 на шедулерах :)

KRoN73 ★★★★★
() автор топика
Ответ на: комментарий от JFreeM

> гхм. Скока-скока с java работали? %) сомнительно мне....

Быдлокодер, хуле?...

mv ★★★★★
()

pi@lorn:~$ cat test.rb
#!/usr/bin/env ruby
#
def f 
        sleep(1)
end

for i in (1..10000) do
        Thread.new {f}
end
pi@lorn:~$ time ruby test.rb

real    0m5.578s
user    0m5.348s
sys     0m0.228s

;)

Pi ★★★★★
()
Ответ на: комментарий от Pi

#include <assert.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
#include <stdio.h>

void *func(void *arg)
{
        sleep(1);
        return NULL;
}

int main()
{
        int i, r;
        pthread_attr_t attr;
        pthread_t thread;

        r=pthread_attr_init(&attr); assert(0==r);
        r=pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN+4096); assert(0==r);
        for (i=0; i<10000; ++i) {
                if (pthread_create(&thread, &attr, func, (void *)i)) {
                        printf("%s on %d\n", strerror(errno), i);
                        break;
                }
        }
        r=pthread_attr_destroy(&attr); assert(0==r);
        return 0;
}

real    0m0.612s
user    0m0.048s
sys     0m0.528s

:P

Dr_ZLO
()

> Теперь веселье - на тесты и эксперименты с Java ушло около *45 минут* :D

Спасибо, повеселило :D

> Говорят, у Питона есть страшный Global Interpreter Lock, сильно снижающий эффективность тредов, но я пока недопонял, где на него нарываются :)

В неожиданных местах. В имплементации питона, как извесно, есть "особенности", которые служат для увеличения быстродействия и не доставляют проблем... в 99% случаев :D

bugmaker ★★★★☆
()
Ответ на: комментарий от Dr_ZLO

Вариант с ожиданием завершения всех потоков:
#include <assert.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
#include <stdio.h>

void *func(void *arg)
{
	int *p=(int *)arg;
	size_t s;
	char c=0;

	sleep(1);
	s=write(p[1], &c, 1); assert(1==s);
	return NULL;
}

int main()
{
	const int MAXT=10000;
	int i, r;
	pthread_attr_t attr;
	pthread_t thread;
	int p[2];
	size_t s;
	char buf[256];

	r=pthread_attr_init(&attr); assert(0==r);
	r=pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN+4096); assert(0==r);
	r=pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); assert(0==r);
	r=pipe(p); assert(0==r);
	for (i=0; i<MAXT; ++i) {
		if (pthread_create(&thread, &attr, func, (void *)p)) {
			printf("%s on %d\n", strerror(errno), i);
			break;
		}
	}
	r=pthread_attr_destroy(&attr); assert(0==r);
	for (i=0; i<MAXT; i+=s)
		s=read(p[0], buf, sizeof(buf)); assert(0<=s);
	close(p[0]); close(p[1]);
	return 0;
}

real    0m1.519s
user    0m0.028s
sys     0m0.400s

Dr_ZLO
()
Ответ на: комментарий от Dr_ZLO

тогда давай мериться длиной строк и их количеством :)

Pi ★★★★★
()
Ответ на: комментарий от execve

pthread_join будет останавливаться на каждой нити, нужна будет память на 10000 pthread_t

никто ошибку в моем коде и не обнаружил -- условие последнего цикла сделать зависимым от кол-ва созданных нитей

Dr_ZLO
()

> Задача: запустить 10000 тредов

Хм... Оптимистично. Если не делать припрыжек, то тред = 8M виртуального
адресного пространства на стек => 10000*8M=80G. В 32-bit системе такого
адресного пространства у процесса не бывает (с припрыжками, возможно,
бывает).

> #!/usr/bin/env python

Не знаю, какой х-ней этот скрипт у тебя 42 секунды занимался, у меня
слегка модифицированный скрипт:

#!/usr/bin/env python

import threading
import time
import traceback

def proc():
    time.sleep(1)

try:
    for i in xrange(10000):
        threading.Thread(target=proc).start()
except:
    print "Thread", i
    traceback.print_exc()

Выдал:
~/tmp$ time python thr.py
Thread 64
Traceback (most recent call last):
  File "thr.py", line 13, in ?
    threading.Thread(target=proc).start()
  File "threading.py", line 416, in start
    _start_new_thread(self.__bootstrap, ())
error: can't start new thread


real    0m1.042s
user    0m0.028s
sys     0m0.004s

watashiwa_daredeska ★★★★
()
Ответ на: комментарий от Pi

тогда и на эрланге заодно: для 10000 потоков на создание уходит ~0.1с, с ожиданием завершения каждого ~1.1 секунды
%% sleep_test(10000). 
sleep_test(N) ->
	sleep_test(N,now(),N).
sleep_test(0,T,0) ->
	timer:now_diff(now(),T);
sleep_test(0,T,M) ->
	receive ok ->
		sleep_test(0,T,M-1)
	end;
sleep_test(N,T,M) ->
	spawn(?MODULE,sleep,[1,self()]),
	sleep_test(N-1,T,M).

sleep(N,Pid) -> 
	timer:sleep(N*1000),
	Pid ! ok. 

bik ★★
()
Ответ на: комментарий от Pi

>pi@lorn:~$ time ruby test.rb

Надо же, неожиданно неплохо. А он там точно дожидается завершения работы всех тредов? Хотя раньше 1.9 с байткодом в сторону Руби всё равно смотреть не буду ;)

Плюс - уже не в первой задаче забывается передача параметра треду.

KRoN73 ★★★★★
() автор топика
Ответ на: комментарий от Dr_ZLO

>Вариант с ожиданием завершения всех потоков:

Вот, и даже на такой низкоуровневой задаче, время выполнения оказалось не столь уж микросокпическим в сравнении с тем же Руби :) Но число строк... :D

KRoN73 ★★★★★
() автор топика
Ответ на: комментарий от watashiwa_daredeska

>Если не делать припрыжек, то тред = 8M виртуального адресного пространства на стек

Зачем столько?

>error: can't start new thread

Ну, я даже не знаю :) Мой вариант попробуй :D Можно добавить print в задачу с печатью номер до и после паузы, запустить десяток тредов и убедиться, что запускаются они честно (печать 20000 раз не пробовал, ибо нефиг :D)

KRoN73 ★★★★★
() автор топика
Ответ на: комментарий от bik

>тогда и на эрланге заодно

Ну так, то - эрланг :D Был бы он ещё в вычислительных задачах пошустрее, знало бы его народа побольие и было б под него больше библиотек и биндингов... А так - у него своя ниша :)

KRoN73 ★★★★★
() автор топика

Lisp (sbcl) до кучи :)

(use-package :sb-thread)
(dotimes (i 10000)
  (make-thread (lambda () (sleep 1))))

16,27s user
1,12s system
99% cpu
17,503 total


на 32 битной машине сие не заработало.

catap ★★★★★
()

Прикол.

Один и тот же код:

import timeit

t = timeit.Timer("""
for i in xrange(10000):
    threading.Thread(target=proc, name="t%d" % i, args=[i]).start()
""", """
import threading
def proc(n):
    n+=2
""")

print t.timeit(1)

работает полторы секунды под Windows и 42 секунды - под Linux :)

KRoN73 ★★★★★
() автор топика

import Control.Concurrent
cyc a 1 = a
cyc a n = a >> cyc a (n-1)
main = cyc (forkIO $ threadDelay $ 10^6) 10000

Ноут Core Duo 1.8, 1024 памяти.

al@localhost hs % time ./a.out +RTS -N20 
./a.out +RTS -N20  24.41s user 0.12s system 187% cpu 13.056 total

al@localhost hs % time ./a.out +RTS -N20
./a.out +RTS -N20  1.99s user 0.11s system 103% cpu 2.038 total

al@localhost hs % time ./a.out +RTS -N20
./a.out +RTS -N20  0.95s user 0.10s system 114% cpu 0.919 total

al@localhost hs % time ./a.out +RTS -N20
./a.out +RTS -N20  13.65s user 0.07s system 179% cpu 7.638 total

...
Как эти легкие потоки реализованы в ghc - понятия не имею.
На forkOS не хватает ресурсов.

AiLr ★★
()
Ответ на: комментарий от KRoN73

$ python
Python 2.3.4 (#1, Oct  9 2006, 18:22:22)
[GCC 3.4.5 20051201 (Red Hat 3.4.5-2)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import timeit
>>>
>>> t = timeit.Timer("""
... for i in xrange(10000):
...     threading.Thread(target=proc, name="t%d" % i, args=[i]).start()
... """, """
... import threading
... def proc(n):
...     n+=2
... """)
>>>
>>> print t.timeit(1)
2.18175005913
^D

$ cat /etc/redhat-release
Scientific Linux SL release 4.4 (Beryllium)

$ cat /proc/cpuinfo
processor       : 0
vendor_id       : GenuineIntel
cpu family      : 15
model           : 4
model name      : Intel(R) Pentium(R) 4 CPU 3.00GHz
stepping        : 1
cpu MHz         : 2995.875
cache size      : 1024 KB
physical id     : 0
siblings        : 2
core id         : 0
cpu cores       : 1
fdiv_bug        : no
hlt_bug         : no
f00f_bug        : no
coma_bug        : no
fpu             : yes
fpu_exception   : yes
cpuid level     : 5
wp              : yes
flags           : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe pni monitor ds_cpl cid xtpr
bogomips        : 5993.63

processor       : 1
vendor_id       : GenuineIntel
cpu family      : 15
model           : 4
model name      : Intel(R) Pentium(R) 4 CPU 3.00GHz
stepping        : 1
cpu MHz         : 2995.875
cache size      : 1024 KB
physical id     : 0
siblings        : 2
core id         : 0
cpu cores       : 1
fdiv_bug        : no
hlt_bug         : no
f00f_bug        : no
coma_bug        : no
fpu             : yes
fpu_exception   : yes
cpuid level     : 5
wp              : yes
flags           : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe pni monitor ds_cpl cid xtpr
bogomips        : 5988.97

$ cat /proc/meminfo
MemTotal:      1034604 kB
MemFree:         25732 kB
Buffers:        113752 kB
Cached:         481908 kB
SwapCached:          0 kB
Active:         478468 kB
Inactive:       317356 kB
HighTotal:      131008 kB
HighFree:         1232 kB
LowTotal:       903596 kB
LowFree:         24500 kB
SwapTotal:     2031608 kB
SwapFree:      2031608 kB
Dirty:               8 kB
Writeback:           0 kB
Mapped:         302996 kB
Slab:           179888 kB
CommitLimit:   2548908 kB
Committed_AS:   611000 kB
PageTables:       4836 kB
VmallocTotal:   106488 kB
VmallocUsed:      9756 kB
VmallocChunk:    96288 kB
HugePages_Total:     0
HugePages_Free:      0
Hugepagesize:     2048 kB

dexpl ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.