Intel® Threading Building Blocks, OpenMP ou threads nativas?

Artigo publicado originalmente por: Michael Voss (Intel)

Qual API você escolheria para implementar paralelismo através de threads em seu software? Será que existe alguma resposta que funciona sempre? Neste artigo, analisamos diversas considerações que um desenvolvedor deve fazer antes de tomar uma decisão. Os principais fatores a considerar são o seu ambiente de desenvolvimento e a complexidade do seu modelo de paralelismo. Os recursos serão comparados e serão feitas observações sobre a coexistência destas APIs em seu software.

O Ambiente de Desenvolvimento

Considerações sobre Simplicidade/Complexidade

O modelo de programação por threads nativas introduz muito mais complexidade ao código do que o uso de OpenMP ou de Intel® Threading Building Blocks (TBB), aumentando o desafio para a manutenção do código. Um dos benefícios da adoção de Intel® TBB ou OpenMP é que estas APIs criam e gerenciam o pool de threads para você: o agendamento e a sincronização entre threads são realizados automaticamente.

Considerações sobre Linguagens de Programação, Suporte do Compilador e Portabilidade


Se o código está escrito em C++, provavelmente Intel® TBB será a melhor escolha por ser adequado para o uso em código altamente orientado a objetos e que faz uso intenso de templates C++ e tipos definidos pelo usuário. Já em casos em que o código está escrito em C ou FORTRAN, OpenMP pode ser a melhor solução porque adapta-se melhor do que Intel® TBB ao estilo de código estruturado e - para casos simples - introduz menor overhead na codificação.
No entanto, mesmo com código C++, se o processamento de arrays predomina nos algoritmos, OpenMP pode ser uma escolha melhor do que TBB em termos de complexidade de código. A complexidade do modelo de programação por threads nativas é equivalente em linguagens C e C++. Porém, uma vez que a atividade paralelizada (“threaded”) deve ser descrita como uma função, programar usando threads nativas pode parecer mais natural com linguagens como C. Para programas altamente orientados a objetos em C++, o uso de threads nativas pode conflitar com o estilo e com o design porque é difícil expressar elegantemente as threads sob a forma de objetos
.

Intel® TBB e threads nativas não necessitam de suporte específico do compilador, enquanto OpenMP requer. O uso de OpenMP exige que você utiliza um compilador que reconheça OpenMP pragmas. Os compiladores C++ e FORTRAN da Intel suportam OpenMP. Recentemente, muitos outros compiladores C++ e FORTRAN incluíram ao menos algum nível de suporte a OpenMP.

Soluções baseadas em OpenMP e Intel® TBB são portáveis entre Windows, Linux, Mac OS X, Solaris e diversos outros sistemas operacionais. Portar soluções baseadas em threads nativas para outro sistema operacional normalmente requer mudanças no código e aumenta o esforço inicial de desenvolvimento/debugging, além de dificultar a manutenção do código, especialmente para quem busca portabilidade entre Windows (onde normalmente utiliza-se threads do Windows e UNIX (onde threads POSIX são mais comuns).

A Complexidade do Modelo Paralelo

 

Observe o que você deseja paralelizar. Use OpenMP quando o paralelismo acontece principalmente em loops limitados e com tipos built-in, ou quando existem estruturas “do-loop” simples para paralelizar.

TBB baseia-se em programação genérica, portanto utilize os padrões de paralelização de loops do TBB quando precisar trabalhar com espaços de iteração customizados ou operações complexas de redução. Considere também o uso de TBB se precisar ir além do paralelismo baseado em loops, uma vez que são disponibilizados patterns genéricos de paralelismo para “while-loops”, modelos de pipeline para o fluxo de dados, prefixos e ordenação paralela.

OpenMP suporta paralelismo aninhado, que pode ser implementado com threads nativas. No entanto, pode ser difícil evitar a utilização intensa de recursos utilizando estas duas APIs. TBB foi projetado para suportar naturalmente paralelismo aninhado e recursivo. Um número fixo de threads são gerenciadas através da técnica de “roubo de tarefa” do scheduler do TBB. Com isto, e com o algoritmo de distribuição de carga dinâmico do scheduler, TBB permite manter todos os núcleos do processador ocupados com trabalho útil, sem “over-subscription” (um número excessivo de threads de software implica em overhead desnecessário) e com o mínimo de “under-subscription” (muito poucas threads podem significar que não se está tirando vantage dos múltiplos núcleos disponíveis no processador).

TBB e OpenMP foram projetados para oferecer threading com desempenho e escalabilidade, disponibilizando constructs que visam a decomposição paralela de dados de forma escalável. Eles são muito úteis para realizar atividades com processamento intenso. Implementar paralelismo e obter um bom desempenho, com escalabilidade, é bem mais difícil usando threads nativas. Há um maior risco de introduzir erros de threading como “data races” e “deadlocks” utilizando threads nativas para implementar algoritmos e patterns já disponibilizados originalmente pelo TBB. Ainda assim, existem muitas situações em que threads nativas podem oferecer a melhor opção, como em casos de threading baseado em eventos ou em I/O.

Comparação de Recursos 

 

Intel® TBB

OpenMP

Threads

Paralelismo em nível de tarefa

+

+

-

Suporte a decomposição de dados

+

+

-

Patterns paralelos complexoss (non-loops)

+

-

-

Patterns paralelos genéricos amplamente aplicáveis

+

-

-

Suporte a paralelismo aninhado escalável

+

-

-

Balanceamento de carga integrado

+

+

-

Suporte a afinidade

-

+

+

Agendamento de tarefas estático

-

+

-

Estruturas de dados concorrentes

+

-

-

Alocação de memória escalável

+

-

-

Tarefas com I/O intenso

-

-

+

Primitivas de sincronização “User-level”

+

+

-

Não requer suporte do compilador

+

-

+

Suporte a múltiplos sistemas operacionais

+

+

-

 

Fizemos algumas considerações sobre o ambiente de desenvolvimento e a complexidade do modelo de programação para decidir qual API utilizar para implementar paralelismo através de threading em seu software. O que fazer, porém, em casos em que tanto TBB quanto OpenMP podem ser opções viáveis? Nessa hora você deve analisar os recursos em cada API. Se você precisa de recursos exclusivos do OpenMP, use OpenMP, e o mesmo raciocínio se aplica a TBB. Caso os recursos estejam disponíveis tanto no TBB quanto no OpenMP, a recomendação é olhar para o custo de manutenção: alguns estilos de programação se encaixam mais naturalmente no TBB ou no OpenMP. Tanto TBB quanto OpenMP são portáveis, mas há um conjunto de requisites diferentes no ambiente de desenvolvimento. TBB e OpenMP podem co-existir mas podem existir questões associadas ao desempenho, dicutidas na seção “Co-existência”. Sendo assim, é melhor escolher um modelo que atenda todas as suas necessidades. Se você está trabalhando num novo projeto e pretende usar C++, então TBB pode ser uma boa opção. TBB permite antecipar paralelismo incremental, permitindo adicionar mais paralellismo posteriormente sem a necessidade de criar threads desnescessárias, evitando assim a sobreutilização.

A expectativa é que soluções baseadas em Intel® TBB, OpenMP e threads nativas apresentem desempenho similar se forem utilizados algoritmos equivalentes. Por outro lado, a quantidade significativa de código adicional associado ao uso de APIs de threads nativas faz com que TBB e OpenMP sejam opções preferenciais.

Co-existência

TBB, OpenMP e threads nativas podem co-existir e interoperar, mas é possível que ocorra “oversubscription” porque as bibliotecas de runtime do TBB e do OpenMP criam pools de threads separados. Por default, cada runtime cria um número de threads igual ao número de núcleos disponíveis no sistema. Ambos os conjuntos de threads de trabalho (worker threads) são usados para processar tarefas de forma intensa, e com isso pode ocorrer “oversubscription”. Por esta razão, é recomendado re-escrever o código OpenMP utilizando Intel® TBB quando TBB attender os critérios do projeto da aplicação. Vale notar que em casos em que o processamento realizado através de OpenMP não ocorre simultaneamente com a atividade que utiliza TBB, “oversubscription” pode não ser um problema.

O scheduler de tarefas do Intel® TBB é tendencioso e não-preemptivo, portanto não é recomendado utilizar TBB em tarefas com intenso I/O. O uso de threads nativas para tais tarefas é normalmente mais adequado, e neste caso threads nativas podem co-existir bem com compontentes que fazem uso de Intel® TBB

Conclusão

 

A escolha do modelo de implementação de paralelismo (threading) é uma parte importante do projeto de aplicações paralelas. Não existe uma solução única que atenda todas as necessidades em todos os cenários. Algumas opções exigem suporte do compilador, algumas não são portáveis ou não são suportadas por ferramentas especializadas de análise de threading. Intel® Threading Building Blocks foram projetados para attender os patterns de projetos paralelos mais frequentemente utilizados, constituindo um framework suficiente para ajudar a criar programas escaláveis de forma ágil, disponibilizando containers de dados concorrentes, primitivas de sincronização, algoritmos paralelos e um alocador de memória escalável.

Para mais informações

1. “Intel Threading Building Blocks: Outfitting C++ for Multi-core Processor Parallelism”, James Reinders, O'Reilly Media, 2007, ISBN 0596514808.
2. Open Source project Web Page: http://www.threadingbuildingblocks.org
3. Product Web Page: /en-us/articles/intel-tbb/
4. Intel® Threading Building Blocks: Scalable Programming for Multi-Core
5. “Demystify Scalable Parallelism with Intel Threading Building Block’s Generic Parallel Algorithms”: http://www.devx.com/cplus/Article/32935
6. “Enable Safe, Scalable Parallelism with Intel Threading Building Block's Concurrent Containers”: http://www.devx.com/cplus/Article/33334
7. Product Review: Intel Threading Building Blocks: http://www.devx.com/go-parallel/Article/33270
8. “The Concurrency Revolution”, Herb Sutter, Dr. Dobb’s 1/19/2005: http://www.ddj.com/cpp/184401916

 

 

 

Para obtener información más completa sobre las optimizaciones del compilador, consulte nuestro Aviso de optimización.