Devemos saber diferenciar aplicações IO-bound e CPU-bound para trabalhar de maneira eficiente com Python

Em que casos a utilização das bibliotecas para desenvolvimento de aplicações concorrentes em Python, é apropriada e pode resultar em um aumento de performance?

Para responder essa pergunta, primeiro precisamos discutir sobre como o interpretador do Python funciona. No decorrer do post, ao detalhar o funcionamento do interpretador, estaremos nos referindo ao CPython, que traz a implementação de referência da linguagem Python e também é o interpretador padrão e mais utilizado entre desenvolvedores.

Interpretadores Python

 CPython é o interpretador de referência, criado por Guido van Rossum, criador da linguagem Python. Com a popularização da linguagem, outros interpretadores foram criados pela comunidade, como o IronPython (.NET), Jython (JVM), PyPy, Stackless, etc.

O interpretador, ao executar o código, utiliza um mecanismo de segurança chamado GIL, ou Global Interpreter Lock. Essa estrutura de lock foi implementada originalmente para eliminar problemas relacionados ao gerenciamento de memória em cenários de aplicações concorrentes. Para realizar as execuções das instruções pelo interpretador, é necessária a obtenção do controle do GIL por parte da thread. Isso significa que em qualquer momento durante a execução de uma aplicação Python apenas uma thread estará em execução por processo.

O que é lock?

 Lock é uma técnica de sincronização que é utilizada por meio de uma abstração. Ele garante que, no máximo, uma thread detenha seu controle por vez. Obter um lock é a maneira que uma thread tem para dizer às outras “eu estou controlando esse estado, não mexa nele agora”. — MIT – Software Construction

O GIL é reconhecido por grande parte da comunidade Python como o principal fator responsável pela popularização da linguagem. Devido o GIL, problemas clássicos da área da programação concorrente são evitados e a interoperabilidade com bibliotecas em C é realizada de maneira simples, fator que foi determinante para a adoção do Python pelos desenvolvedores de outras linguagens. Entender o funcionamento do Global Interpreter Lock é essencial para desenvolvermos aplicações eficientes em Python.

Uma thread, ao obter o controle do Global Interpreter Lock, continuará com o controle dele enquanto estiver realizando processamento das instruções. Ao terminar, a thread irá liberar o controle obtido. Durante a execução da aplicação outras threads realizarão o mesmo procedimento de obtenção e liberação do GIL.

Uma exceção ocorre em aplicações que realizam chamadas a um processos externos, como a processos de I/O. Neste caso, a thread em execução libera o controle do GIL ao realizar a chamada de sistema para realizar I/O, e obtém ele novamente apenas após a conclusão da operação de I/O.

Esse tipo de aplicação é denominada de I/O- bound, por ter sua performance relacionada diretamente ao desempenho de operações de I/O. Aplicações I/O -bound não sofrem nenhum tipo de penalização de performance devido ao uso do GIL pelo interpretador.

Como exemplo de I/O -bound, vamos utilizar uma aplicação que obtém as informações de alguns álbuns selecionados utilizando a API do Cover Art Archive. O seguinte trecho de código define as funções que iremos utilizar para realizar o benchmark entre as formas de execução sequencial e paralela, por meio do uso de threads.

import requests

def get_allan_holdsworth_cd_list():
    return [
        '1d9e7f7f-ab75-3f96-a1a3-9977f75c3570',
        '40901f7e-f7dc-33b5-9380-7b3ea9e3094e',
        '9caacf47-99e5-3193-ba29-5302e225cac4',
        '146495af-a0bd-3774-b677-c57f8e2369d2',
        '57b6813d-d0b7-3c70-8d0a-4001cee7b5c2'
    ]

def make_http_album_archive_request(album_id):
    url = f'https://coverartarchive.org/release-group/{album_id}'
    r = requests.get(url)

A execução sequencial é realizada pelo seguinte código:

import time
from io_bound import get_allan_holdsworth_cd_list, make_http_album_archive_request

if __name__ == '__main__':
  start = time.time()

  cds = get_allan_holdsworth_cd_list()
  for cd in cds:
    make_http_album_archive_request(cd)

  duration = time.time() - start
  print(f'Duração total de {duration} segundos.')

A execução paralela, utilizando cinco threads, é feita da seguinte maneira:

import time
from concurrent import futures
from io_bound import get_allan_holdsworth_cd_list, make_http_album_archive_request

if __name__ == '__main__':
  start = time.time()
  
  cd_list = (id for id in get_allan_holdsworth_cd_list())
  threads = 5
  with futures.ThreadPoolExecutor(threads) as executor:
    future = executor.map(make_http_album_archive_request, cd_list)

  duration = time.time() - start
  print(f'Duração total de {duration} segundos.')

Ao comparar a duração total de cada execução, foi possível notar uma melhoria de performance de até 5 vezes utilizando a abordagem com threads. Por ser um processo I/O -bound, o uso de threads proporciona ganhos significativos de performance.

Por outro lado temos as aplicações CPU -bound. Essas aplicações possuem sua performance relacionada diretamente com a velocidade que o processamento de instruções é realizado. Além de possuírem sua performance severamente penalizada pela velocidade de execução das instruções pelo interpretador Python, não podem ser otimizadas por meio de uma estratégia de paralelização via threads, devido o GIL.

Como exemplo de aplicação CPU -bound, utilizaremos uma função que realiza a soma de números de 0 até n, dez vezes. Esse procedimento é definido pelo seguinte trecho de código:

def __sum_from_zero_to_num(num):
  return sum(i for i in range(num))

def cpu_bound_processing(start, end):
  large_number = 10_000_000

  for i in range(start, end):
    increment = i ** 2
    __sum_from_zero_to_num(large_number + increment)

A execução sequencial é realizada da seguinte maneira:

import time
from cpu_bound import cpu_bound_processing

if __name__ == '__main__':
  start = time.time()

  cpu_bound_processing(0, 10)

  duration = time.time() - start
  print(f'Duração total de {duration} segundos.')

A execução paralela, também utilizando cinco threads, é realizada da seguinte forma:

import time
from concurrent import futures
from cpu_bound import cpu_bound_processing

if __name__ == '__main__':
  start = time.time()
  
  threads = 5
  parameters = [(0, 2), (2, 4), (4, 6), (6, 8), (8, 10)]
  with futures.ThreadPoolExecutor(threads) as executor:
    future = executor.map(cpu_bound_processing, *zip(*parameters))

  duration = time.time() - start
  print(f'Duração total de {duration} segundos.')

Ao realizar a comparação final, não foi possível notar nenhuma melhoria entre as implementações. Por se tratar de uma aplicação CPU -bound, o uso de threads não resulta em melhoria de performance, devido ao Global Interpreter Lock.

Neste caso, recomendamos que outras opções sejam consideradas, desde a utilização de múltiplos processos, o uso de outro interpretador Python mais adequado, ou, se possível, a utilização de outra linguagem mais apropriada para tarefas CPU -bound, a fim de maximizar a performance de sua aplicação.

O processo de categorização da aplicação entre as categorias CPU -bound e I/O -bound é determinante para a definição da arquitetura de qualquer aplicação. Em Python, entender o funcionamento do interpretador de código é fundamental para garantir que os requisitos de performance sejam atendidos pelo sistema.

Compartilhe este insight:

Comentários

Participe deixando seu comentário sobre este artigo a seguir:

Subscribe
Notify of
guest
0 Comentários
Inline Feedbacks
View all comments

AUTOR

Thiago Candido
Especialista em inteligência artificial e engenharia de dados com experiência profissional na indústria e setor financeiro

INSIGHTS EXIMIACO

Desenvolvimento de Software

Gestão de times para acelerar entregas e atuar na resolução de problemas complexos.

NOVOS HORIZONTES PARA O SEU NEGÓCIO

Nosso time está preparado para superar junto com você grandes desafios tecnológicos.

Entre em contato e vamos juntos utilizar a tecnologia do jeito certo para gerar mais resultados.

Insights EximiaCo

Confira os conteúdos de negócios e tecnologia desenvolvidos pelos nossos consultores:

Desenvolvimento de Software

Maximizando a Eficiência: O Que Seu Código Está Escondendo?

Arquiteto de Software especializado em desenvolver soluções de alta escalabilidade e performance
Desenvolvimento de Software

A Importância da Proposta de Valor na Modernização de Sistemas Legados

Especialista em Comunicação Digital, UX/UI e Design Thinking
Desenvolvimento de Software

Construindo Produtos Inteligentes, Atraentes e Fáceis de Usar

Especialista em Comunicação Digital, UX/UI e Design Thinking
0
Queremos saber a sua opinião, deixe seu comentáriox

A sua inscrição foi realizada com sucesso!

O link de acesso à live foi enviado para o seu e-mail. Nos vemos no dia da live.

Muito obrigado!

Deu tudo certo com seu envio!
Logo entraremos em contato

Devemos saber diferenciar aplicações IO-bound e CPU-bound para trabalhar de maneira eficiente com Python

Para se candidatar nesta turma aberta, preencha o formulário a seguir:

Devemos saber diferenciar aplicações IO-bound e CPU-bound para trabalhar de maneira eficiente com Python

Para se candidatar nesta turma aberta, preencha o formulário a seguir:

Condição especial de pré-venda: R$ 14.000,00 - contratando a mentoria até até 31/01/2023 e R$ 15.000,00 - contratando a mentoria a partir de 01/02/2023, em até 12x com taxas.

Tenho interesse nessa capacitação

Para solicitar mais informações sobre essa capacitação para a sua empresa, preencha o formulário a seguir:

Tenho interesse em conversar

Se você está querendo gerar resultados através da tecnologia, preencha este formulário que um de nossos consultores entrará em contato com você:

O seu insight foi excluído com sucesso!

O seu insight foi excluído e não está mais disponível.

O seu insight foi salvo com sucesso!

Ele está na fila de espera, aguardando ser revisado para ter sua publicação programada.

Tenho interesse em conversar

Se você está querendo gerar resultados através da tecnologia, preencha este formulário que um de nossos consultores entrará em contato com você:

Tenho interesse nessa solução

Se você está procurando este tipo de solução para o seu negócio, preencha este formulário que um de nossos consultores entrará em contato com você:

Tenho interesse neste serviço

Se você está procurando este tipo de solução para o seu negócio, preencha este formulário que um de nossos consultores entrará em contato com você:

× Precisa de ajuda?