Скачивание 1000 файлов. Asyncio vs Golang

Скачивание 1000 файлов. Asyncio vs Golang

В этой статье сравним скорость работы и реализацию скачивания 1000 файлов с помощью Python Asyncio и Golang. Файлы загружены на минио, запущенном локально на mac OS 2,3 GHz Intel Core i5, 8 GB 2133 MHz LPDDR3

hw.physicalcpu: 4
hw.logicalcpu: 8

Каждый файл имеет размер 6 мегабайт, а размер всех файлов таким образом составляет 6gb.

Golang

package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strconv"
	"strings"
	"sync"
	"time"
)

func downloadFileWorker(urlsQeue chan string, wg *sync.WaitGroup) {
	defer wg.Done()
	for url := range urlsQeue {

		response, err := http.Get(url)
		if err != nil {
			fmt.Printf("error request to %s\n", url)
			panic(err)
		}
		defer response.Body.Close()

		splitURL := strings.Split(url, "/")
		fileName := splitURL[len(splitURL)-1]
		savePath := "saved_files/" + fileName
		file, err := os.Create(savePath)
		if err != nil {
			fmt.Printf("error creating to %s\n", savePath)
			return
		}
		defer file.Close()
		_, err = io.Copy(file, response.Body)
		if err != nil {
			fmt.Printf("error coping to %s\n", savePath)
		}
	}

}

func main() {
	start := time.Now()
	var wg sync.WaitGroup

	bucketURL := "http://localhost:9000/files/"
	urlsQeue := make(chan string)
	for i := 0; i < 100; i++ {
		wg.Add(1)
		go downloadFileWorker(urlsQeue, &wg)
	}

	for i := 1; i < 1000; i++ {
		url := bucketURL + strconv.Itoa(i) + ".pdf"
		urlsQeue <- url
	}

	close(urlsQeue)
	wg.Wait()
	duration := time.Since(start)
	fmt.Println("Time: ")
	fmt.Println(duration)
}

Python

import aiohttp
import aiofiles
import time
import asyncio

async def download_file_worker(queue, session):
    while True:
        try:
            url = queue.get_nowait()
            async with session.get(url) as response:
                if response.status != 200:
                    print(f'Failed to download {url}')
                data = await response.read()
                filename = 'saved_files/' + url.split('/')[-1]
                async with aiofiles.open(filename, 'wb') as file:
                    await file.write(data)

                queue.task_done()
        except asyncio.QueueEmpty:
            print('Queue is empty')
            return 


async def main():
    bucket_url = "http://localhost:9000/files/"
    urls = [
        bucket_url + str(i) + '.pdf' for i in range(1, 1001) 
    ]

    loop = asyncio.get_event_loop()
    queue = asyncio.Queue()
    for url in urls:
        queue.put_nowait(url)
    async with aiohttp.ClientSession() as session:
        await asyncio.gather(*[download_file_worker(queue, session) for i in range(100)])
    

if __name__ == '__main__':
    start = time.time()    
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())
    end = time.time()
    print(end - start)

Результаты

  • golang - 99.590284137s
  • asyncio - aiohttp + aiofiles 127.45670866966248

Asyncio показал себя медленнее на 28% от скорости работы на golang. Помимо того для работы с сетью и файлами асинхронно необходимы пакеты aiohttp и aiofiles.