[image]

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

 
+
-
edit
 

Balancer

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

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

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

Питон: через 5 минут (буквально) копания в Гугле и тестов родил такое:
code python
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3.  
  4. import threading
  5. import time
  6.  
  7. def proc(n):
  8.     time.sleep(1)
  9.  
  10. for i in xrange(10000):
  11.     threading.Thread(target=proc, name="t"+str(i), args=[i]).start()


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

Дальше начинается веселье. Я принялся за Java. Как сделать просто запуск тредов я так и не нашёл. Сделал запуск через шедулер. После серии экспериментов исходную задачу... так и не решил :) Треды отрабатывают успешно, но основной процесс не завершается, а так и остаётся запущенным. Если проигнорировать на завершение и закрывать потоки вручную, то выходит код примерно такой:
code java
  1. import java.io.IOException;
  2. import java.io.PrintStream;
  3. import java.util.Date;
  4. import java.util.concurrent.Executors;
  5. import java.util.concurrent.ScheduledExecutorService;
  6. import java.util.concurrent.ScheduledFuture;
  7.  
  8. import static java.util.concurrent.TimeUnit.*;
  9.  
  10. public class test implements Runnable
  11. {
  12.     public static void main(String[] args)
  13.     {
  14.         final int MAX=10000;
  15.  
  16.         ScheduledFuture<?>[] t = new ScheduledFuture<?>[MAX];
  17.  
  18.         System.out.println("Init");
  19.         for(int i=0; i<MAX; i++)
  20.         {
  21.             ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
  22.             t[i] = scheduler.schedule(new test(i), 0, SECONDS);
  23.         }
  24.  
  25.         System.out.println("Stop");
  26.         for(int i=0; i<MAX; i++)
  27.             t[i].cancel(true);
  28.         System.exit(0);
  29.     }
  30.  
  31.        int n;
  32.  
  33.         public test(int _n)
  34.     {
  35.         n = _n;
  36.         }
  37.  
  38.         public void run()
  39.             try { Thread.sleep(1000); } catch(Exception e){}
  40.         }
  41. }


То есть - открываем 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? А то я выдохся ;)
   
+
-
edit
 

Balancer

администратор
★★★★★
Вот работающий под Java вариант (без шедулера):
code java
  1. public class TestThread implements Runnable
  2. {
  3.     int n;
  4.  
  5.     TestThread(int n)
  6.     {
  7.         this.n = n;
  8.     }
  9.  
  10.     public void run()
  11.     {
  12.         try
  13.         {
  14.             Thread.sleep(1000);
  15.         } catch (InterruptedException e)
  16.         {
  17.             e.printStackTrace();
  18.         }
  19.     }
  20. }
  21.  
  22. public class Main
  23. {
  24.     public static void main(String[] args)
  25.     {
  26.         System.out.println(System.currentTimeMillis());
  27.         for (int i = 0; i < 10000; i++)
  28.         {
  29.             (new Thread(new TestThread(i))).start();
  30.         }
  31.         System.out.println(System.currentTimeMillis());
  32.     }
  33. }


Работает 4 секунды. Т.е. в Java имеем десятикратный выигрышь :)
   
US Сергей-4030 #03.07.2007 21:03  @Balancer#03.07.2007 19:02
+
-
edit
 

Сергей-4030

исключающий третье
★★
Balancer> Вот работающий под Java вариант (без шедулера):
Balancer> Работает 4 секунды. Т.е. в Java имеем десятикратный выигрышь :)

А зачем нужно это? В чем сакральный смысл? :)

Я имею в виду - (new Thread(new TestThread(i))).start()

Почему не наследовать TestThread от Thread и потом (new TestThread(i)).start() ?

ЗЫ На практике, такое решение не будет хорошо работать. Если 10 тысяч тредов просто ждут - ну, пусть. Если нет - попа. Не говоря о том, что тест, собственно, ни о чем. Тест нам говорит вот что: добавить 10000 записей в очередь на создание новой нити плюс отработать неизвестно сколько методов sleep(недешевых, между прочим), занимает 4 секунды.

Если изменить программу так:


package samples;

public class ThreadSample extends Thread {

static final int NUM_SAMPLES = 10000;

static volatile int count = NUM_SAMPLES;

static Object lock = new byte[0];

int n;

ThreadSample (int n)
{
this.n = n;
}

public void run()
{
synchronized(lock) {
count--;
}
}

public static void main(String[] args)
{
long in=System.currentTimeMillis();
for (int i = 0; i < NUM_SAMPLES; i++)
{
(new ThreadSample(i)).start();
}
while(count>0);
long out=System.currentTimeMillis();
System.out.println("time="+((out-in)/1000.));
}
}


То исполнение возьмет 1 секунду. Кстати, переменная lock в данном случае не нужна, но я поставил для общности.
   
RU Balancer #04.07.2007 11:40  @Сергей-4030#03.07.2007 21:03
+
-
edit
 

Balancer

администратор
★★★★★
Сергей-4030> А зачем нужно это? В чем сакральный смысл? :)

Оверхед на создание треда.
   

Murkt

Pythoneer

Странные результаты у Питона. Вот мои:
code python
  1. import timeit
  2.  
  3. t = timeit.Timer("""
  4. for i in xrange(10000):
  5.    threading.Thread(target=proc, name="t%d" % i, args=[i]).start()
  6. """, """
  7. import threading
  8. def proc(n):
  9.    n+=2
  10. """)
  11.  
  12. print t.timeit(1)


Результат:
code text
  1. >threads.py
  2. 1.52544359688
   

Murkt

Pythoneer

Кстати, если изменить так, чтоб было эквивалентно коду на Java:
code python
  1. t1 = timeit.Timer("""
  2. for i in xrange(10000):
  3.    threading.Thread(target=proc, name="t%d" % i, args=[i]).start()
  4. print count[0]
  5. """, """
  6. import threading
  7. count = [10000]
  8. def proc(n):
  9.    count[0] -= 1
  10. """)


То результат не меняется (погрешность измерения).
Count сделан через список для обхода UnboundLocalError: local variable 'count' referenced before assignment, ну а print count[0] для того чтоб видеть, что всё правильно.

PS надо сейчас ещё Java-код померять.
   

Murkt

Pythoneer

Результаты измерения Java-кода:
code text
  1. >javac ThreadSample.java
  2. >java ThreadSample.class
  3. Exception in thread "main" java.lang.NoClassDefFoundError: ThreadSample/class

Балансеров код:
code text
  1. >javac Main.java
  2. >java Main.class
  3. Exception in thread "main" java.lang.NoClassDefFoundError: Main/class


Мда. Не помню, чтоб раньше проблемы были. Сейчас эклипсом попробую.
   

Murkt

Pythoneer

Эклипсом код Сергея-4030: time=1.531. Разницы нет вообще. Балансер, что там про десятикратное превосходство? Все эти полторы секунды работает не язык, а операционка, создавая и убивая потоки.
   
+
-
edit
 

Balancer

администратор
★★★★★
Муркт> Эклипсом код Сергея-4030: time=1.531. Разницы нет вообще.

Что-то там у тебя не то, если подумать. И/или у Сергея :)

Муркт> Все эти полторы секунды работает не язык, а операционка, создавая и убивая потоки.

Операционка физически не в состоянии обеспечить 10000 потоков. У Линукса проблемы начинаются уже на единицах тысяч потоков. WinXP - где-то чуть хуже, ЕМНИП. Win2K3 - до 4000..5000. Потому 10000 потоков и было выбрано.
   
+
-
edit
 

Balancer

администратор
★★★★★
Муркт> Странные результаты у Питона. Вот мои:

Гы :D
code text
  1. $ time python murkt.py
  2. 42.177216053
  3.  
  4. real    0m42.240s
  5. user    0m0.040s
  6. sys     0m0.004s
   
+
-
edit
 

Balancer

администратор
★★★★★
Для сравнения код Сергея:
code text
  1.  $ time java samples/ThreadSample
  2. time=1.359
  3.  
  4. real    0m1.512s
  5. user    0m0.744s
  6. sys     0m0.692s


Судя по всему, можно предположить, что под Linux у Питона очень неэффективный менеджер процессов. А Java одинаково хорошо себя показывает и под Linux, и под Windows.
   

Murkt

Pythoneer

Ну, это у тебя что-то неправильно, а у меня как раз всё ок. Надо ещё Stackless попробовать, но у меня, к сожалению, стаклесс только 2.5, а питон уже 2.5.1.

И почему ты не меряешь встроенными средствами, а средствами ОС? Я не верю, что у тебя JVM так быстро стартует :) Просто запускай их, а они сами покажут сколько оно работало.
   
+
-
edit
 

Balancer

администратор
★★★★★
Муркт> Ну, это у тебя что-то неправильно, а у меня как раз всё ок.

Код - твой. Python-2.5.1. Что ещё не так? Кроме ОС, которая, как я понимаю, у тебя - Windows? :) А про неэффективность питоновских процессов в Linux я уже где-то что-то видел :)

Муркт> И почему ты не меряешь встроенными средствами, а средствами ОС?

А какая разница? Десятая доля секунды что-то изменит? :)

Муркт> Я не верю, что у тебя JVM так быстро стартует :)

Кхм. А с чего ей долго-то стартовать? :)
code text
  1. $ echo 'public class HelloWorld { public static void main(String[] argv) { System.out.println("Hello!"); } }' > HelloWorld.java && javac HelloWorld.java && time java HelloWorld
  2. Hello!
  3.  
  4. real    0m0.156s
  5. user    0m0.076s
  6. sys     0m0.016s


Все тайминги:
code text
  1. $ time echo 'public class HelloWorld { public static void main(String[] argv) { System.out.println("Hello!"); } }' > HelloWorld.java && time javac HelloWorld.java && time java HelloWorld
  2.  
  3. real    0m0.001s
  4. user    0m0.000s
  5. sys     0m0.000s
  6.  
  7. real    0m0.675s
  8. user    0m0.580s
  9. sys     0m0.036s
  10. Hello!
  11.  
  12. real    0m0.164s
  13. user    0m0.088s
  14. sys     0m0.004s


>Просто запускай их, а они сами покажут сколько оно работало.

Так смотри внимательнее, я вывод приводил. И числа там есть :D С твоим примером: 42.177216053, у Сергея - time=1.359. Внимательнее читай вывод :)
   
US Сергей-4030 #05.07.2007 12:14  @Murkt#05.07.2007 10:11
+
-
edit
 

Сергей-4030

исключающий третье
★★
Муркт> Результаты измерения Java-кода:
Муркт> Мда. Не помню, чтоб раньше проблемы были. Сейчас эклипсом попробую.

когда java запускаешь, не надо .class добавлять. И ThreadSample не в default package, надо путь к классу указывать.
   
UA Murkt #05.07.2007 12:25  @Сергей-4030#05.07.2007 12:14
+
-
edit
 

Murkt

Pythoneer

Муркт>> Результаты измерения Java-кода:
Муркт>> Мда. Не помню, чтоб раньше проблемы были. Сейчас эклипсом попробую.
Сергей-4030> когда java запускаешь, не надо .class добавлять.

Точно :) забыл совсем.

Сергей-4030> И ThreadSample не в default package, надо путь к классу указывать.

Я из него package удалил.

2Balancer: Python в linux использует обычные posix threads, какого ж он так долго там работает? Может у тебя стоит специальная замедлялка Питона? :F

Balancer> Так смотри внимательнее, я вывод приводил.

Сорри, профтыкал :-Р
   

Murkt

Pythoneer

Балансер, выкидывай генту фтопку. Ubuntu 7.04, Python 2.5.1: 1.26254892349
   

Murkt

Pythoneer

Там же, Джава 6. time=1.415.
   
+
-
edit
 

Mishka

модератор
★★★
Блин, похоже, что у кого-то питон собран так, что вместо POSIX threads там используется процессы. :)
   
+
-
edit
 

fireball

новичок

оно?
code text
  1. $ cat test.py
  2. import threading
  3.  
  4. def proc(n):
  5.     print n
  6.  
  7. for i in xrange(50000):
  8.     threading.Thread(target=proc, name="t"+str(i), args=[i]).start()
  9.  
  10. $ time -p python ./test.py > /dev/null
  11. real 55.29
  12. user 0.44
  13. sys 0.10
  14.  
  15.  
  16. $ cat test-sl.py
  17. import stackless
  18.  
  19. def sleep_tasklet(taskletID):
  20.         print taskletID
  21.  
  22. for i in xrange (50000):
  23.         stackless.tasklet(sleep_tasklet)(i)
  24.  
  25.         stackless.run()
  26.  
  27. $ time -p ./opt/bin/python test-sl.py > /dev/null
  28. real 0.26
  29. user 0.25
  30. sys 0.00
   
09.07.2007 19:31, Murkt: +1: Спасибо за работу

Murkt

Pythoneer

Thanks. Balancer, попробуй достичь подобных результатов на Джаве :D
   
+
-
edit
 

Murkt

Pythoneer

Это же Stackless. В нём такие же локи, как в Erlang'е ;-) Правда, желательно соблюдать некоторые принципы - в Эрланге их-то заставляет сам язык соблюдать.
   
RU Jabberwocky #10.07.2007 12:31
+
-
edit
 

Jabberwocky

новичок
У меня на FreeBSD 6.1 вышеупомянутый код дал:
21,31s real 1,98s user 0,41s sys
А вот такой код:
code text
  1. # -*- coding: utf-8 -*-
  2.  
  3. import thread
  4. import time
  5.  
  6. def proc(n,m):
  7.     time.sleep(1)
  8.  
  9. a = (1,2)
  10. for i in xrange(10000):
  11.     thread.start_new_thread(proc, a)

0,73s real 0,20s user 0,45s sys

Никаких stackless как видите нет. Собственно мне уже и самому любопытно, чем отличается thread от threading в этом плане.
   

Murkt

Pythoneer

Надо будет поисследовать вопрос, как свободное время появится.
   
+
-
edit
 

fireball

новичок

Реализация "невесомых нитей" с помощью генераторов Python
http://www.ibm.com/developerworks/ru/library/l-pythrd/index.html
   

в начало страницы | новое
 
Поиск
Настройки
Твиттер сайта
Статистика
Рейтинг@Mail.ru