Асинхронный http-клиент, или почему многопоточность — лишнее

    Какое-то время назад Хабре проскакивала заметка про клиент-парсер сайтиков на Питоне. Автор на этом примере разбирал проблемы многопоточных сетевых приложений.

    Но мне показалось, что ту же задачу (вернее, ее главную часть — параллельные соединения c http-cервером) вполне можно эффективно решить и без потоков.



    Заглянув, для начала, в свою статейку про Twisted и Tornado, почесав затылок и накопавшись в документации по неблокирующим сокетам, я набросал асинхронный сервер http-клиент.

    Ниже — исходник ключевой части приложения с пояснениями:

    import socket
    import select
    import sys
    import errno
    import time

    from config import *

    def ioloop(ip_source, request_source):
        ""«Асинхронный цикл собственной персоной

        ip_source — бесконечный iterable, выдающий ip-адреса для соединений ;
        request_source — iterable, генерирующий тела запросов;
        »""
        starttime = time.time()

        # открываем пул сокетов; словари, хранящие соединения тела запросов и ответов
        epoll = select.epoll()
        connections = {}; responses = {}; requests = {}
        bytessent = 0.0
        bytesread = 0.0
        timeout = 0.3

        # выбираем первый запрос
        request = request_source.next()
        try:
            while True:
                # проверяем число соединений, если их меньше минимально
                # возможного и остались запросы — добавляем еще одно.
                #
                connection_num = len(connections)
                    
                if connection_num<CLIENT_NUM and request:
                    ip = ip_source.next()
                    print «Opening a connection to %s.» % ip
                    clientsocket = socket.socket(socket.AF_INET,
                                                 socket.SOCK_STREAM)
                    # Несколько нетривиально. Неблокирующий сокет выбрасывает
                    # исключение-ошибку EINPROGRESS, если не может сразу соединиться сразу.
                    # Игнорируем ошибку и начинаем ждать события на сокете.
                    #
                    clientsocket.setblocking(0)
                    try:
                        res = clientsocket.connect((ip, 80))
                    except socket.error, err:
                        #
                        if err.errno != errno.EINPROGRESS:
                            raise
                    # Вносим сокет в пул и словарь соединений
                    epoll.register(clientsocket.fileno(), select.EPOLLOUT)
                    connections[clientsocket.fileno()] = clientsocket
                    requests[clientsocket.fileno()] = request
                    responses[clientsocket.fileno()] = ""
                    
                # «Пулинг» — то есть сбор событий
                #
                events = epoll.poll(timeout)
                for fileno, event in events:
                    if event & select.EPOLLOUT:
                        # Посылаем часть запроса...
                        #
                        try:
                            byteswritten = connections[fileno].send(requests[fileno])
                            requests[fileno] = requests[fileno][byteswritten:]
                            print byteswritten , «bytes sent.»
                            bytessent += byteswritten
                            if len(requests[fileno]) == 0:
                                epoll.modify(fileno, select.EPOLLIN)
                                print «switched to reading.»
                        except socket.error, err:
                            print «Socket write error: „, err
                        except Exception, err:
                            print “Unknown socket error: „, err
                    elif event & select.EPOLLIN:
                        # Читаем часть ответа...“
                        #
                        try:
                            bytes = connections[fileno].recv(1024)
                        except socket.error, err:
                            # Вылавливаем ошибку „connection reset by peer“ —
                            #случается при большом числе соединений
                            #
                            if err.errno == errno.ECONNRESET:
                                epoll.unregister(fileno)
                                connections[fileno].close()
                                del connections[fileno]
                                print »Connection reset by peer."
                                continue
                            else:
                                raise err

                        print len(bytes) , «bytes read.»
                        bytesread += len(bytes)
                        responses[fileno] += bytes
                        if not bytes:
                            epoll.unregister(fileno)
                            connections[fileno].close();
                            del connections[fileno]
                            print «Done reading...Closed.»

        # выбираем следующий запрос
                if request:
                    request = request_source.next()

                print «Connections left: „, len(connections)
                if not len(connections):
                    break
        except KeyboardInterrupt:
            print “Looping interrupted by a signal.»
            for fd, sock in connections.items():
                sock.close()
        epoll.close()

        endtime = time.time()
        timespent = endtime - starttime
        return responses, timespent, bytesread, bytessent

    Мораль тут простая — не всюду следует пихать потоки, более того, существуют ситуации, когда многопоточность только снизит надежность программы, создаст известные проблемы в тестировании и станет источником неуловимых багов. Если некритична производительность, но очень хочется что-то распараллелить, то часто вполне оправдывают себя даже обычные процессы и примитивный IPC.

    Кроме того, в Питоне все равно не существует настоящих потоков уровня ядра, а здравствует и по сей день треклятый GIL. Соответственно, никаких преимуществ в производительности на многоядерных процессорах получить нельзя.

    Данный скрипт, конечно, жутковато и на скорую руку исполнен, не обрабатывает обрывы соединения сервером и ошибки на операциях чтения/записи в сокет, не разбирает ответы сервера, но зато тащит многократно корень сайта cnn.com на пределе возможностей моего канала — 800-1000 Кб/с. :)

    Целиком исходники скрипта можно найти где-то тут

    PS Может, у кого есть мысли, для чего можно использовать производительные
    асинхронные клиенты? :)
    Метки:
    Поделиться публикацией
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 76
    • +4
      Если хотите копипастить исходники в топик, то хотя бы используйте тот же s-c.me/
      • 0
        А за топик — спасибо
        • 0
          :) В следующий раз обязательно воспользуюсь советом. Сейчас уже лень. Это не какой-то фундаментальный топик и скрипт, а так, скорее быстрая вечерняя поделка.
      • +4
        А почему не использовали asyncore? Это принципиальная позиция?
        • 0
          хотелось порыться именно в сокетах и проблемах, с ними связанных. Там было несколько неочевидных вещей вроде всплывающего исключения с кодом ошибки errno.EINPROGRESS.
        • +2
          Черно-белая подсветка кода выглядит классно. Еще бы оттенков серого добавить и был бы шедевр.
          • –2
            Отформатируйте, пожалуйста, код моноширинным шрифтом
            • НЛО прилетело и опубликовало эту надпись здесь
              • +1
                На самом деле нужно просто-напросто и всего-навсего примерно набросать области применения различных методов работы с сетью и решить для себя, в каком случае какой применять. Я по большому счету знаю три:
                1) Классический многопроцессорный, я форком на каждое соединение.
                + Самый надежный и в принципе простой в написании
                — Плохо подходит на Виндовс
                — Нужно заморачиваться с IPC
                2) Мультитредовый
                + Более производительный, чем 1)
                — Менее надежный, чем 1)
                + Лучше работает на Виндовс
                3) Асинхронный
                + Самый производительный
                — надежность хуже чем 1)
                + работает и на Вин и на Лин (Unix)
                Дисклаймер: Все вышесказанное — мое ИМХО, ни за что не отвечаю.
                • 0
                  третий вариант хоть и самый производительный, но на нём одном к сожалению не реализовать ничего сложного, видимо поэтому он у вас и получается самым производительным :)
                  а нам приходиться дополнительно городить свои планировщики и выбирать использование безстэковых/стэковых coroutine для простоты программирования. либо вырисовывая различные конечные автоматы, восстановление стэка в которых тоже бывает не такой уж скоростной операцией с кучей кэш миссов.
                  • 0
                    > но на нём одном к сожалению не реализовать ничего сложного,

                    что вы имеете ввиду?
                    • +1
                      1) Классический многопроцессорный
                      стэйт хранится в стэке

                      2) Мультитредовый
                      стэйт хранится в стэке

                      3) Асинхронный
                      >а нам приходиться дополнительно городить…
                      • +1
                        Вообще, необходимо расширить описание этих трех пунктов. Поправьте, если я ошибаюсь

                        Примеры прилжожений:
                        1) Apache по классической схеме (не мультитредовый) — Известен своей надежностью
                        2) IIS, Apache версии 2.0, мультитредовый. Пока не рекомендуется там, где нужна высокая надежность
                        3) Nginx — отличается самой высокой производительностью

                        Рекомендации к выбору решения:
                        1) Если нужно быстро сделать приложение, которое будет работать достаточно надежно, ДАЖЕ при присутствии в коде немалого количества ошибок. Трудозатраты низкие.
                        2) Если нужно сделать преносимое Unix/Win32 решение. Трудозатраты на разработку низкие, на отладку и доведение до уровня надежности 1) высокие
                        3) Если нужна максимальная производительность. Самые высокие трудозатраты.
                        • +1
                          1 и 2) легко расширяются с помощью модулей, можно делать блокирующие вызовы, какую-нибудь тяжёлую работу, зависимость в модулях от проприетарщины, которая делает блокирующие вызовы.
                          3) нужно подстраиваться под стэйт машину, которая была реализована в этом приложении. Не знаю как сейчас, но раньше с nginx'ом небыло возможности даже сделать простое синхронное логирование без блокирующего вызова в своём модуле. Но это не проблема данного подхода, просто так уж сделали.
                • 0
                  Реальная многопоточность несет оверхед, больше переключений контекста, например (кстати, почему разработчики процессоров не сделают эту операцию быстрой, рах ее постоянно упоминают как причинц снижения производительности?). Плюс нужна синхронизация для доступа к общим переменным, а это опять-так и ломает всю производительность.

                  И вообще, кто вам сказал, что многопоточность — хорошо? Как вообще может быть хорошей программа, в которой несколько потоков работают в общей области памяти и любой может вызвать трудно обнаружимую ошибку?
                  • 0
                    >больше переключений контекста
                    А это уж как реализовано в ОС.
                    >Как вообще может быть хорошей программа, в которой несколько потоков работают в общей области памяти
                    Если сделать доступ атомарным, то программа будет чрезвычайно хорошей.

                    Вобщем, ничего плохого в потоках нет, надо просто уметь их готовить.
                    • 0
                      >Если сделать доступ атомарным, то программа будет чрезвычайно хорошей.
                      Хочется тоже попасть в эту сказку :)

                      >надо просто уметь их готовить.
                      И вы конечно же работали с libnuma
                      • 0
                        > Если сделать доступ атомарным

                        Ага, только этот атомарный доступ останавливает все ядра.
                      • 0
                        > Как вообще может быть хорошей программа,
                        > в которой несколько потоков работают в общей области памяти,
                        > и любой может вызвать трудно обнаружимую ошибку?
                        Есть хороший способ избежать этой проблемы — отказаться от общих переменных.
                    • 0
                      В статье не раскрыт вопрос почему асинхронность (и данное решение в частности) лучше многопоточности.
                      • +2
                        Ест меньше ресурсов. Отлично было раскрыто в одной из статей Ивана Сагалаева, про то как он писал некий кусок кода для скачивания медиа-файликов, и в итоге пришел к asyncore. Найдите, прочтите, просветление настигнет внезапно.
                        • 0
                          А, почему не раскрыто. Автор видимо не подумал, что это надо раскрывать в сотый раз — на хабре есть уже множество статей, как однопоточное приложение может обойти многопоточное.
                          • 0
                            А смысл раскрывать очевидный вопрос?

                            Асинхронность ортогональна многопоточности, как их сравнивать вообще?
                            • 0
                              Неблокирующую работу с сетью можно использовать вместе с многопоточностью, где кол-во потоков сделать равным кол-ву логических процессоров и все будут довольны :)
                            • –7
                              А может все же проще плюнуть на сервер пачку запросов аяксом?

                              Эффект тот же
                              :-)
                              • 0
                                «Кроме того, в Питоне все равно не существует настоящих потоков уровня ядра, а здравствует и по сей день треклятый GIL. Соответственно, никаких преимуществ в производительности на многоядерных процессорах получить нельзя.»

                                Можно пояснить? Я сейчас написал сприптик на питоне — создаю 4 потока в каждом деалаю 1000*1000, вижу в топ 4 потока отъдающие по ~100% cpu и еще один ~0,01 (я полагаю GIL). Те вроде как получается занять все 4 ядра. python25 если это важно.
                                • 0
                                  угу. А попробуйте сначала сделать 100 тыс раз (1000*1000) тупо в одном потоке, а потом по 25 тыс. раз в 4 четырех параллельных и сравните время )
                                  • +1
                                    4 потока по 2500000 итрераций умножения

                                    real 0m3.319s
                                    user 0m3.299s
                                    sys 0m0.018s

                                    без потоков 10000000 итераций

                                    real 0m4.704s
                                    user 0m4.758s
                                    sys 0m0.310s

                                    Выйрыша небольшой, но 1) он есть 2) потоки native 3) на других задачах разница может быть выше. Натив threads в питон в версии 2.4 уже точно были. Журнал Dr. Dobb's в свое время писал на эту тему.

                                    • 0
                                      Видимо я просто не понимаю как это все работает. Ну вот смотрите например gil картинках.
                                      www.dabeaz.com/blog/2010/01/python-gil-visualized.html
                                      • 0
                                        По ссылке все написано верно, но там пример 2 потока I/O и CPU интенсивыный, CPU интенсивный и дергает постоянно GIL, потому что только в нем и интрпретируется python код, в то время как I/O вызовы умеют освобождать GIL. Те в случае двух I/0 процессов — картина была бы совсем иной.
                                        • 0
                                          ммм. А где там написано вообще что-то про потоки IO? Там мне кажется как раз ваш случай — 2 CPU потока без IO которые переодически переключаются между собой по тикеру.
                                          • 0
                                            In this graph, you're seeing how difficult it is for the I/O bound to get the GIL in order to perform its small amount of processing.
                                            Там совершенно не мой случай, мой случай 4 CPU-bound thread, а по ссылке 1CPU-bound а втрой merely echoes UDP packets.
                                            • 0
                                              Так это про последний рисунок. Ваш случай это ведь второй. А по нему видно что несмотря на то, что 2 треда исполняются на 2 процессорах на самом деле полезную работу в каждый момент времени выполняет только один.
                                      • 0
                                        Если вы делаете просто:

                                        for i in range(2500000):
                                        1000 * 1000

                                        то интерпретатор оптимизирует это выражение и умножения просто не будет при каждой итерации.

                                        добавьте: * i

                                        тогда увидите реальную картину при которой вариант с тредами медленнее.
                                        • 0
                                          Я как раз это понимаю, результаты выше для такого кода
                                          hcount = 2500000
                                          while (hcount>1):
                                          j=1000*1000
                                          hcount=hcount-1
                                          4 потока с таким циклом
                                          • 0
                                            Вот как раз тут у вас нет умножения:-)

                                            Лучше вообще взять классический пример с счетчиком:

                                            COUNT = 100000000
                                            CPU_COUNT = 4

                                            def count(n):
                                            while n:
                                            n -= 1

                                            def sequence():
                                            for i in range(CPU_COUNT):
                                            count(COUNT)

                                            def parallel():
                                            pool = []
                                            for i in range(CPU_COUNT):
                                            t = threading.Thread(target=count, args=(COUNT,))
                                            t.start()
                                            pool += [t]

                                            [t.join() for t in pool]
                                            • 0
                                              Скорее да — но вот ведь прикол — пустой цикл в 4 потока отработал чуть быстрее чем в 1.
                                              • 0
                                                Трудно анализировать такое поведения, не видя код.
                                                • 0
                                                  #!/usr/bin/python3.1
                                                  import threading

                                                  class mythread(threading.Thread): # subclass Thread object
                                                  def __init__(self, myId, hc ):
                                                  self.myId = myId
                                                  self.hcount = hc
                                                  threading.Thread.__init__(self)

                                                  def run(self): # run provides thread logic
                                                  hcount = 2500000
                                                  while (hcount>1):
                                                  j=1000*1000
                                                  hcount=hcount-1

                                                  MAXTHR=4


                                                  threads = []
                                                  for t in range(MAXTHR):
                                                  thread = mythread(t, 0) # make/start 10 threads
                                                  thread.start( ) # start run method in a thread
                                                  threads.append(thread)

                                                  for thread in threads:
                                                  thread.join( ) # wait for thread exits

                                                  Вот такой вот запускал
                                                  • 0
                                                    А говорили что питон 2.5:-)

                                                    Слишком мало повторений. На таких объемах накладные расходы на переключение GIL не так отчетливо проявляются. А полученный временной результат может зависеть от общей нагруженности системы в какой-то конкретный момент запуска теста.

                                                    Суть в том, что треды как минимум не быстрее в CPU-bound задачах.
                                                    • 0
                                                      Кстати, судя по всему, в питоне 3.2 сделают быстрый GIL. Он останется, но не будет так тупить на счетных задачах. Раньше переключение шло каждые N инструкций (что, понятное дело, убивало все к черту на счетных циклах), теперь будет идти каждые N миллисекунд.

                                                      www.dabeaz.com/python/NewGIL.pdf
                                                      • 0
                                                        > что, понятное дело, убивало все к черту на счетных циклах

                                                        Убивало не это, а то что тред, отпустив GIL, мог сам же его снова захватить. Так было из-за того, что он не знает есть ли кто-то другой ждущий выполнения, а OS в свою очередь не успевала в этот интервал переключить контекст.

                                                        В новом варианте он отпускает его сам только если флаг выставлен и потом засыпает до того момента как GIL захватит другой тред.

                                                        То что всю эту системы перевели на временные интервалы в место числа инструкций, можно сказать, побочный эффект нового подхода:-)
                                                        • 0
                                                          Спецом по GIL не являюсь, код питона не читал, сужу по разным записям в интернете. Берем первоисточник:

                                                          mail.python.org/pipermail/python-dev/2009-October/093321.html

                                                          Автор патча пишет 3 вещи, которые его в GIL не устраивали, снижали производительность и побудили, в конце концов, написать патч. Первые 2 причины — это то, что я написал, 3я — то, что ты.
                                                        • НЛО прилетело и опубликовало эту надпись здесь
                                    • –2
                                      что за жизнь без multiprocessing? (на худой конец хоть multi-threading)

                                      в CPython dev team как клала на это болт так и кладёт.

                                      Хорошо хоть мульти-ядерники стали чаще появляться на столах. Команда CPython стала немножко шевелиться в эту сторону.

                                      вся надежда на unladen swallow, но и им не легко.
                                      • –1
                                        кст, проблема в том, что в CPython нет и настоящих процессов…
                                        • 0
                                          Т.е.? Не писал, врать не буду, но вроде ведь можно форк вызвать.
                                          • 0
                                            можно и просто multiprocessing module пользовать.

                                            но GIL и ref counting убивают любое не слишком тривиальное приложение даже сидящее на мультипроцессинге.
                                            • 0
                                              По идее у каждого процесса будет свой GIL и refcounting.
                                              • +1
                                                верно. а проблема таки вылазит:

                                                если есть обычная жирная структура в памяти, к которой процессы лазят даже лишь на чтение, то каждый процесс из-за этих ref counters получает себе copy-on-write копию этого дурацкого реф-каунтера со всей его жирной окрестностью в RAM.

                                                вот и получается: там где вы в С можете делать 100 процессов без проблем, там же в CPython вы имеете 100 кратное увеличение ерундового 500Мб куска в памяти.
                                                • 0
                                                  s/увеличение/размножение
                                                  • 0
                                                    man execve
                                                    execve() does not return on success, and the text, data, bss, and stack
                                                    of the calling process are overwritten by that of the program loaded.

                                                    Никакого увеличения куска памяти. Используйте инструменты с умом.
                                                    • 0
                                                      мы вроде о CPython тут?
                                                        • 0
                                                          а теперь раскройте вашу мысль применительно к этому конкретному короткому примеру, пожалуйста:
                                                          habrahabr.ru/blogs/python/81716/#comment_2422547
                                                          • 0
                                                            в контретно том примере используется только fork, из-за которого получаем всё наследие родителя на которое вы жалуетесь.
                                                            • 0
                                                              > Используйте инструменты с умом.
                                                              > [...] snip [...]

                                                              ни на что я не жалуюсь.
                                                              • +1
                                                                Тут возможно недопонимание COW? Нам нужно наследство родителя, мы хотим его, мы хотим им пользоваться. но без лишних затрат. Это дает нам fork() и любим мы его именно за это.
                                                                В классическом случае мы получаем наследие родителя полностью, но если мы его только читаем, то нм получение наследия никаких затрат мы не несем.
                                                                Но в примере vak мы казалось бы получили в дочерние процессы данные, которые только читаем, по идее у каждого дочернего процесс должна быть большая виртуальная память и маленькая резидентная. На Си или Спп так бы и было, однако на Pythone получается не так из-за указанных vak особенностей реализации сборщика мусора.
                                                                Это ни плохо ни хорошо, это реальность данная нам в ощущениях.
                                                                А вот если от этого эффекта удасться уйти без больших затрат, это вообще будет суперкул!
                                                                • 0
                                                                  100% ditto.

                                                                  кст, есть неплохая заплатка от итальянца, который хотя бы сдвигает эти реф каунтеры в одно место. пару недель назад он собирался «дополировать» его (я пока не смотрел чем дело кончилось).
                                                                  • 0
                                                                    Так нужно чтобы задача выполнялась быстрее в CPython'е или наследство родителя? На Си вообще большинство вещей делалось бы иначе. В питоне свои особенности, зачем пытаться делать так же как в си…
                                                                    Можно попробовать через пайп прокидывать всё нужное наследство, так получим параллелизм(если конечно кол-во логических процессоров больше одного)
                                                                    ещё можно приклеить clone или posix_spawn к питону и делать clone/exec без оверхеда на копирование таблиц для работы cow.
                                                        • 0
                                                          Пока процессы не пишут в этот кусок, они пользуются физически одной и той же памятью, так что увеличение использованной памяти в многопроцессорной системе не происходит. (Называется этот механизм как раз copy-on-write, суть в том, что страница памяти, если не обшибаюсь, как правило 4k, физически выделяется дочернему процессу тогда и только в тот момент, когда он попробует в нее записать)
                                                          • 0
                                                            Да, это все, что я написал, это Linux и fork(), в Виндовсе все не так гладко.
                                                            • 0
                                                              когда-то было не так гладко не только в виндовсе :) что изобретали всякие vfork (BSD)
                                                            • +1
                                                              именно.
                                                              и, казалось бы, всё шоколадно и CPython должен бы профитировать от этого механизма тоже?

                                                              беда в том, как я попытался уже написать выше, что ref counters изменяются даже при операциях чтения. а это провоцирует copy-on-write копирование.

                                                              последний гвоздь — ref counters не живут в одном лепрозории месте в памяти,
                                                              а лежат в заголовке структуры, которую они обслуживают.
                                                              результат? — несложно догадаться.

                                                              нужен 10 строчный пример кода? дайте знать.

                                                              P.S. и, как обычно, те, кто не в теме, умеют, однако, жать на минус вполне уверенно. ну, ничего, меньше возможностей писать, больше времени на что-то полезное :)
                                                              • 0
                                                                Несколько отвлеченное соображение. Я в Питоне недавно, где-то 1-2+ года. Для меня вообще стало открытием, что универсальный скриптовый язык используется для написания неплохо работающих, в том числе и по производительности серверов, в том числе и многопоточных. Это приятная неожиданность. Надеюсь, что и проблему с GIL решат. Вот тогда и наступит коммунизм!
                                                                • 0
                                                                  > и, казалось бы, всё шоколадно и CPython должен бы профитировать от этого механизма тоже?

                                                                  Но ведь, ИМХО, должен. Ведь для разных процессов и GIL-ы разные и проэтому процессы могут исполняться на разных ядрах без простоев. Единственное, что экономия памяти и времени будет меньше из-за выделения страниц, в которые подпадают ref-counters.

                                                                  > последний гвоздь — ref counters не живут в одном лепрозории месте в памяти,
                                                                  а лежат в заголовке структуры, которую они обслуживают.
                                                                  результат? — несложно догадаться.

                                                                  > нужен 10 строчный пример кода? дайте знать.

                                                                  Давайте. Не для спора, а просто интересно для углубления в проблему.
                                                                  • +2
                                                                    import time
                                                                    from multiprocessing import Pool
                                                                    def f(_):
                                                                        time.sleep(5)
                                                                        res = sum(parent_x) # "read-only"
                                                                        return res
                                                                    
                                                                    if __name__ == '__main__':
                                                                        parent_x = [1./i for i in xrange(1,10000000)]# read-only data 
                                                                        p = Pool(7)
                                                                        res= list(p.map(f, xrange(10)))
                                                                        print res
                                                                    


                                                                    заходите в ps/top или в любой другой ваш любимой монитор памяти и смотрите как процессы взрывают mem usage.
                                                                    • 0
                                                                      Спасибо, проверил, все действительно так, будем учитывать эту фичу. Поигрался немного с текстом программы, прогнал три варианта:
                                                                      1) Ваш вариант
                                                                      2) То же, на форке (переписал для себя, для понятности)
                                                                      3) На форке, с вычислением суммы внутри функции
                                                                      res = sum(1./i for i in xrange(1,10000000))
                                                                      Результаты получились ожидаемые, большой точности не добивался, но все-таки:
                                                                      Вариант Время ПамВирт ПамРез ПамШар
                                                                      1)          14с     200        198      836
                                                                      2)          11с     198        193      432
                                                                      3)          17с     5456      1780     460
                                                                      

                                                                      Выводы:
                                                                      1) Описанный Вами эффект оказывает сильное вредное влияние при больших объемах часто используемых данных
                                                                      2) Разница во времени между 1) и 2) — скорее всего погрешности эксперимента
                                                                      3) При вычислениях по функциональному типу даже вычисляя одно и то же отдельно в каждом процессе проигрыш по времени неожиданно мал. Объяснить — не хватает моих знаний внутренностей Python
                                                                      4) Вычисления по функциональному типу дают большую экономию памяти.
                                                                      В принципе, все это давно известно, но интересно было потрогать своими руками.
                                                                      Еще раз спасибо.
                                                                      • +2
                                                                        gern geschehen!

                                                                        надеюсь, команда unladen swallow не сдастся и добьёт эту важную тему до конца.

                                                                        З.Ы. хотя мне самому осталось не очень ясно зачем я тут минусы собираю. пополз-ка я обратно, в читателей ;)
                                                                        • 0
                                                                          Пардон, заметил недочет. Таблицу следует читать так:
                                                                          Вариант Время ПамВирт ПамРез ПамШар
                                                                          1)          14с     200M        198M      836
                                                                          2)          11с     198M        193M      432
                                                                          3)          17с     5456        1780     460
                                                                          
                                                                          • 0
                                                                            хм, 1780 без «М»?.. если ошибки нет, то такое впечатление, что форковые версии процессов практически не пересекались во времени исполнения, но это маловероятно…

                                                                            З.Ы. и, кст, спасибо за :)
                                                                            • 0
                                                                              Вот код:
                                                                              #! /usr/bin/python
                                                                              # http://habrahabr.ru/blogs/python/81716/
                                                                              
                                                                              import time,os,sys
                                                                              
                                                                              def f():
                                                                                  time.sleep(5)
                                                                                  res = sum(1./i for i in xrange(1,10000000))
                                                                                  return res
                                                                              
                                                                              if __name__ == '__main__':
                                                                              
                                                                                  t0 = time.time()
                                                                                  for i in range(7):
                                                                                      pid = os.fork()
                                                                                      if(pid == 0):
                                                                                          res = f()
                                                                                          print res
                                                                                          sys.exit(0)
                                                                                      else:
                                                                                          next
                                                                                  os.wait()
                                                                                  print "time =", time.time() - t0
                                                                              
                                                                              
                                                                              • 0
                                                                                коварное исчезновение квадратных скобок я-то и не заметил сначала :)
                                                                                о, если генераторы используются, то тогда конечно большого блока памяти и не появляется — ни у родителя, ни у детишек.

                                                                                ОК, всё сошлось.
                                                      • +1
                                                        напомнило мои извращения с php, только функции слегка другие, а суть таже, см php.net/socket_select
                                                        • 0
                                                          А чего не libevent? :) Как phpdaemon делает.
                                                        • 0
                                                          >[b]параллельные[/b] соединения c http-cервером
                                                          Parallel != Concurrent
                                                          en.wikipedia.org/wiki/Parallel_computing
                                                          en.wikipedia.org/wiki/Concurrent_computing

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