cd_riper (cd_riper) wrote,

Python: GIL, threading and I/O. Pt.1

Писал уже раньше о маленьком скрипте, который предназначался для патчинга группы файлов. Писал я о нем с точки зрения производительности и в комментах был озвучен вопрос -- а есть ли смысл загрузку файлов сделать асинхронной (т.е. фоновой)?

Сам скрипт в цикле бежит по списку файлов, и в одной итерации делает две "тяжелые" вещи. Первое -- он читает целый файл с диска в память. Второе -- ищет в нем заданные строки. Понятно, что первая операция это чистое I/O, практически не потребляющее CPU, а значит можно получить profit даже на одноядерном камне, запустив ее в фоне в упреждающем режиме (т.е. пока ищем в текущем файле, читаем следующий файл с диска).

Как известно, с многопоточностью в питоне дела обстоят очень грустно. В рамках одного экземпляра интерпретатора в один момент времени может исполнятся только один поток. Потому что GIL. Потому что вот так вот плохо в свое время это все было придумано.

В нашем случае есть зацепка -- документация утверждает, что для I/O операций GIL временно снимается (что логично). Для проверки этого факта я набросал небольшой тестовый скриптик.

Copy Source | Copy HTML
def ThreadTest():
 
    import threading, time
 
    CFile = r"..." # your huge file on HDD
    CBlockSize = 512 * 1024 # 32 * 1024 * 1024
 
    def SingleAction():
        sum =  0
        for i in range(2 * 1024):
            sum += i
        return sum
 
    def Benchmark(durationSec):
        t = time.clock()
        i =  0
        while time.clock() - t < durationSec:
            SingleAction()
            i += 1
        t = time.clock() - t
        return i / t
 
    def LoadFile():
        nonlocal readDone
 
        size =  0
        data = bytearray(CBlockSize * b'\x00')
        with io.open(CFile, 'br') as f:
            while True:
                lx = f.readinto(data)
                size += lx
                if lx != len(data): break
 
        print("Size", size)
        readDone = True
 
 
    # bench
    print('Start...')
    print(Benchmark(1. 0)) # ~2'100
 
    # thread test
    readDone = False
 
    t = time.clock()
    thread = threading.Thread(target = LoadFile)
    thread.start()
 
    i =  0
    while not readDone:
        SingleAction()
        i += 1
        time.sleep( 0)
 
    if i ==  0: i = 1
 
    t = time.clock() - t
 
    print('Time ', t, '; count', i, '; rate ', i / t)
 
 


Вначале он проводит опорный бенчмарк в ситуации, когда процессор больше ничем другим не занимается. Потом -- запускает этот же бенчмарк параллельно с тредом, который читает с диска файл большого размера. Эксперимент заканчивается по факту прочтения файла, мы смотрим на то, как меняется результат бенчмарка, в случае, если в фоне идет чтение файла.

У меня на машине фоновая I/O нагрузка на результат бенчмарка практически не влияет (потери в районе 5%), из этого я сделал вывод, что есть смысл поиграться с упреждающим чтением файлов.
Что из этого получилось -- в следующей части.

Вся правда о GIL -- http://cd-riper.livejournal.com/224538.html
О производительности -- http://cd-riper.livejournal.com/236187.html


Детские мозаики - игрушки для детей. Игрушки для детей (Москва).
Tags: programming, python
  • Post a new comment

    Error

    Comments allowed for friends only

    Anonymous comments are disabled in this journal

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

  • 0 comments