cd-riper.livejournal.com

Categories:

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

Сам скрипт в цикле бежит по списку файлов, и в одной итерации делает две "тяжелые" вещи. Первое -- он читает целый файл с диска в память. Второе -- ищет в нем заданные строки. Понятно, что первая операция это чистое 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 * 1024def SingleAction():
        sum =  0
        for i in range(2 * 1024):
            sum += i
        return sumdef Benchmark(durationSec):
        t = time.clock()
        i =  0
        while time.clock() - t < durationSec:
            SingleAction()
            i += 1
        t = time.clock() - t
        return i / tdef 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): breakprint("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

Детские мозаики - игрушки для детей. Игрушки для детей (Москва).

LJ Video

Read the original on cd-riper.livejournal.com ↗