Параллельный Питон, начало

    Disclaimer


    Родилась у товарища географическая потребность перенести кусочек карты из одного участка Земли в другой. Он это по привычке сделал на дельфях, мне же захотелось попробовать в действии питон, в коем я спецом не являюсь.

    Практика


    Собственно перевести алгоритм оказалось делом совсем несложным, но вот скорость его работы оставляла желать лучшего.
    Первым делом в ход пошел Psyco, ускорив обработку в 6 раз.

    Получить лучший результат без изменения алгоритма уже не представлялось возможным, поэтому в ход пошел метод грубой силы — распараллеливание задач.

    Найден был модуль Parallel Python. Подключить его оказалось делом совсем несложным:

    Сначала import pp, а потом (первый вариант):
    ppservers = ()
    job_server = pp.Server(ppservers=ppservers)                     
    
    job_server.set_ncpus(2)
    print "Starting pp with", job_server.get_ncpus(), "workers"
    
    jobs = [job_server.submit(tighina_check, (), (find_geo_coords, compare, get_dist_bearing,), ("math", ))  for i in range(3)]
    
    for job in jobs:
    	job()
    
    job_server.print_stats()

    Код, в принципе, сам за себя говорящий — используем только локальный сервер (а вообще модуль позволяет распараллеливать и на сетевые), стараемся запустить на 2-х процессорах, указываем какую функцию вызывать и от каких она зависит, импортируем math и запускаем 3 задачи, в конце печатаем статистику.

    Первой засадой оказалось отключение psyco, что опять отбросило нас на стартовую позицию.
    Решение было очевидным — добавить импорт psyco при создании job'а
    jobs = [job_server.submit(tighina_check, (), (find_geo_coords, compare, get_dist_bearing,), ("math", "psyco", )) for i in range(3)]
    и вызывать psyco.full уже в tighina_check:
    def tighina_check():
            psyco.full()
            #а вот тут много математики


    Вторая проблема оказалась весьма неожиданной.
    Код в tighina_check был изначально заточен под импорт вида «from math import sin, pow, cos, asin, sqrt, fabs, acos». Но он не работал под pp, т.к. создает среду выполнения функции только с модулями, указанными при создании job'а. Вполне логичным было переделать все вызовы sin на math.sin и т.д. Вот тут-то и возникло небольшое недоумение — интенсивное и постоянное использоваение мат.функций во втором формате вызова приводило к замедлению в 1.3-1.4 раза.

    Решением было ручное импортирование нужных функций в глобальную область видимости в начале каждого job'a:
    def tighina_check():
         psyco.full()
         math_func_imports = ['sin', 'cos', 'asin', 'acos', 'sqrt', 'pow', 'fabs']
         for func in math_func_imports:
    	 setattr(__builtins__, func, getattr(math, func))
    


    Дальше подумалось, что неплохо бы ускорить сам pp с помощью psyco. Для этого нужно немного подпатчить pyworker.py из комплекта, добавив в начало:
    import psyco
    psyco.full()


    и заменив
    eval(__fobj)
    на
    exec __fobj


    При этом отпадает необходимость в импорте psyco при создании job'а и соответсвенно в вызове psyco.full() в job'e.

    Остальное — только подборка нужного числа процессоров

    Что в итоге?


    Запускалось 100 job'ов.

    Исходный вариант (никакого распараллеливания, только psycho)
    100 последовательных job'ов 257 секунд

    2 процессора (pp, psyco)
    Starting pp with 2 workers
    Job execution statistics:
     job count | % of all jobs | job time sum | time per job | job server
           100 |        100.00 |     389.8933 |     3.898933 | local
    Time elapsed since server creation 195.12789011


    4 процессора (pp, psyco)
    Starting pp with 4 workers
    Job execution statistics:
     job count | % of all jobs | job time sum | time per job | job server
           100 |        100.00 |     592.9463 |     5.929463 | local
    Time elapsed since server creation 148.77167201


    Дальше тестировать не хотелось, казалось, что 2 ядра, каждое с гипертредингом, а значит 4 job'а — оптимальный вариант. Но любопытство взяло вверх (и как оказалось — не зря):
    8 процессоров (pp, psyco)
    Starting pp with 8 workers
    Job execution statistics:
     job count | % of all jobs | job time sum | time per job | job server
           100 |        100.00 |     1072.3920 |    10.723920 | local
    Time elapsed since server creation 137.681350946


    16 процессоров (pp, psyco)

    Starting pp with 16 workers
    Job execution statistics:
     job count | % of all jobs | job time sum | time per job | job server
           100 |        100.00 |     2050.8158 |    20.508158 | local
    Time elapsed since server creation 133.345046043
    


    32 процессора (pp, psyco)

    Starting pp with 32 workers
    Job execution statistics:
     job count | % of all jobs | job time sum | time per job | job server
           100 |        100.00 |     4123.8550 |    41.238550 | local
    Time elapsed since server creation 136.022897005


    Т.о. в лучшем варианте 133 секунды против 257 в первоначальном варианте = ускорение в 1.93 раза для нашей конкретной задачи только за счет распараллеливания.

    Следует отметить, что все 100 job'ов друг от друга не зависят и не нуждаются в «общении» между собой, что облегчает задачу и увеличивает скорость.

    Итоговые примеры кода:
    ppservers = ()
    job_server = pp.Server(ppservers=ppservers)                     
    
    job_server.set_ncpus(16)
    print "Starting pp with", job_server.get_ncpus(), "workers"
    
    jobs = [job_server.submit(tighina_check, (), (find_geo_coords, compare, get_dist_bearing,), ("math", ))  for i in range(3)]
    
    for job in jobs:
        job()
    
    job_server.print_stats()


    def tighina_check():
        math_func_imports = ['sin', 'cos', 'asin', 'acos', 'sqrt', 'pow', 'fabs']
        for func in math_func_imports:
            setattr(__builtins__, func, getattr(math, func)) 
    
            #а вот тут много математики
    Метки:
    Поделиться публикацией
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 37
    • +8
      Можете сравнить результаты параллельного питона и реализации на Delphi?
      • 0
        Постараемся в скором времени — у меня мак, у товарища виндовс, надо будет поставить ему питон и прогнать тесты.
      • +2
        Насколько я понимаю, речь идет о трансформации координат (сомнительно, т.к. не смотря на объем расчетов, задача тривиальна и не требует высоких нагрузок)? Что имели в виду под переносом участка земли? Либо же работа с растрами (тонкопленочный сплайн, аффинные преобразования)?
        • НЛО прилетело и опубликовало эту надпись здесь
          • +2
            Извините, но мне кажется это образец быдлокодинга, когда вместо оптимизации алгоритма просто добавляют ещё один компьютер.

            И настоящей «паралельности» на самом деле у вас никакой нет. Тот же самый эффект вы получили бы если бы банально запустили 16 копий своей программы, ну только расход памяти на питон-машину был бы поболее и всё.

            Вот если бы shared-memory, симафоры и проч проч, это было бы интересно очень.
            • НЛО прилетело и опубликовало эту надпись здесь
              • 0
                Это образец разных приоритетов — для товарища решить свою задачу на дельфях, для меня — поизучать питон, его оптимизацию и распараллеливание.
                • 0
                  А мне казалось приоритет это ускорить выполнение программы…
                  • НЛО прилетело и опубликовало эту надпись здесь
                    • –1
                      Тогда чем не устраивал прога на дельфях, если один раз нужно?
                      • 0
                        Не устраивала не программа, а незнание питона :)
                    • 0
                      Правильно, ускорить, но с доп. условием — в пределах изучения питона, а не алгоритма.
                      Дисклеймер же не зря писался:
                      мне же захотелось попробовать в действии питон, в коем я спецом не являюсь

                      ;)
                • 0
                  Прошу прощение, будучи бывшим геодезистом, у меня в голове не укладывается что есть «перенос объекта с места на место по Земле». Можно конкретизировать, дабы я утешил свое любопытство? :)
                  • НЛО прилетело и опубликовало эту надпись здесь
                    • 0
                      Какую СК Земли (UTM/WGS84/etc) используете, если не секрет? Перенос в пределах одной СК? Мне просто интересно, где здесь можно было применить грубую силу.
                      • НЛО прилетело и опубликовало эту надпись здесь
            • +2
              Перенесите в блог про питон.
            • 0
              Я думаю еще стоит написсать чем же это лучше в сравнении с threads:
              «The most simple and common way to write parallel applications for SMP computers is to use threads. Although, it appears that if the application is computation-bound using 'thread' or 'threading' python modules will not allow to run python byte-code in parallel. The reason is that python interpreter uses GIL (Global Interpreter Lock) for internal bookkeeping. This lock allows to execute only one python byte-code instruction at a time even on an SMP computer.»

              А еще лучше написать так и эдак, и сравнить результаты в числах
              • 0
                Так чем лучше питоновский метод чем треды?
                • 0
                  threading.Thread и PP — оба матода «питоновские»
                  причемthreading есть на docs.python.org.

                  Если верить документации про PP, то он на самом деле более параллелен, т.к. threads «страдают» от того, что обычно в Python нельзя запускать несколько байткодов параллельно, а в PP это как-то обошли
                  • НЛО прилетело и опубликовало эту надпись здесь
                    • 0
                      Ну треды это вещь языко-независимая. Я так понимаю PP это костыль, который запускает ещё один интерпретатор?

                      Я пытаюсь понять, питон для мульттрединга это гуд или нет.
                  • 0
                    Будет время — обязательно проверю.

                    Навскидку могу сказать, что PP:
                    1. создает каждый worker как отдельный python-процесс, т.е. GIL обходится
                    2. поддерживает запуск на разных серверах, т.е. задача распараллеливается по сети, что уже на порядок интересней

                  • +4
                    Различие в числах 136, 133, 137 — скорее всего статистическая ошибка :) Стоило бы провести 5-10 запусков и усреднить результат.

                    Конечно, ускорение можно получить и на 100 воркерах на 2х ядрах, вот только сравнивать в таком случае нужно не ускоренный вариант с однопоточным, а насколько близко полученное ускорение к идеальному линейному росту. Быть может будет продуктивнее запустить программу 4 раза с разными исходными данными с 4мя воркерами, чем один раз с 16тью ;)
                    • 0
                      спасибо, надо попробовать веб запросы так параллелить :)
                      • –1
                        ddos-им AmazonS3? :)
                        • 0
                          Не лучший вариант.
                          • 0
                            а как же чувство любопытства и извечный вопрос — а что будет если… ?:))
                          • 0
                            «Параллелить» веб-запросы лучше всего асинхронностью.
                            pycurl спасет, гуглить «pycurl.CurlMulti»
                          • НЛО прилетело и опубликовало эту надпись здесь
                            • 0
                              Да, можно создавать прямые имена, но sin, cos и т.д. используются в разных функциях, поэтому при выполнении в PP пришлось бы создавать такие имена в каждой. Поэтому закинуть единожды в __builtins__ показалось проще и быстрее.
                            • +1
                              Для научных нужд есть очень хороший модуль pypar — это легкий и простой интерфейс к MPI. С ним распараллеливать простые задачи с циклами — одно удовольствие.
                              Что касается psyco — то насколько я понимаю, он не работает под 64 бита, так что в реальных случаях его особо не поиспользуешь.
                              • 0
                                1. самое простое, как уже сказали выше — просто запустить 2 процесса и дать им разные части задачи (учитывая, что у вас никакой синхронизации в проекте не было) и не геммороиться с распараллеливанием

                                2. multiprocessing (стандартный модуль c 2.6)

                                from multiprocessing import Process
                                p = Process(target=f, args=('bob',))
                                p.start()
                                p.join()

                                3. решение sin/cos и т.п. лежит в cython+gcc(mingw32 под винду) (cython — модуль, гуглить при необходимости: «cython sin») — избавляет от dictionary lookup'ов для названий функций за счет компиляции слегка модифицированного Python кода в Си.
                                • 0
                                  а чем треды не подходят?, интересно посмотреть реализациб через треды питоновские.

                                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.