• Nenhum resultado encontrado

Resultados experimentais para o método CRP

B.4 Modelo roofline para a GPU Tesla K40

5.7 Resultados experimentais para o método CRP

• CRP-OpenMP: Implementação da busca de parâmetros para a migração CRP em tempo usando OpenCL. Esse mesmo código pode ser executado tanto em CPUs comuns como em aceleradores como GPUs e o Xeon Phi.

• CRP-OpenCL: Implementação da busca de parâmetros para a migração CRP em tempo OpenMP. Essa implementação só é executada em CPUs multicores.

Os experimentos foram realizados com um dado de entrada real (foi obtido da bacia do Jequitinhonha chamado de Jequitinhonha). Além dos dados de entrada, usamos no DE (Diferential Evolution) os seguintes parâmetros: 30 indivíduos na população, C de 0,9 e 70 gerações. Nome das Entradas População X Traços por CDP Amostras

por traço CDPs T Gerações w

Semblance- Trace

Jequitinhonha 30 30 168 1701 201 1000 70 9 1,62 × 1014

Tabela 5.19: Descrição do dado de entrada usado nos testes do CRP

Os resultados dos experimentos com a implementação CRP obtiveram ganhos de de- sempenho muito abaixo do esperado com o uso de aceleradores, como pode se ver na Figura 5.20, mas, mesmo assim, os dados dos experimentos mostram um ganho de de- sempenho que chega até 7 vezes quando a implementação CRP-OpenMP é comparada com a CRP-OpenCL. O desempenho da implementação CRP-OpenMP em dispositivos diferentes apresenta resultados não esperados, como o caso em que a CPU-E2630 só não é mais rápida que a GPU GTX-770, mesmo sendo o dispositivo com o menor pico de desempenho. Isso é evidência de que a implementação em OpenCL do CRP tem sérios problemas com o escalonamento, já que aumentar o número de partes do algoritmo que são executadas em paralelo piora o desempenho na maioria dos casos, mesmo que essa piora seja relativamente pequena. Um dos principais motivos do desempenho ruim da implementação CRP-OpenMP, se deve aos fatores listados abaixo:

• Meta heurística utilizada: O DE é um dos principais fatores da perda de desempe- nho, já que para calcular a geração n é preciso calcular a geração n − 1.

• Cálculo da semblance: Diferente da implementação do CRS e do CMP, aqui o DE deixa que a organização dos traços, para se calcular a semblance, não possa ser a mesma usada no CMP e CRS.

Para conseguir evidências que corroborem que a causa do baixo ganho de desempenho das GPUs são as causas anteriormente citadas, é necessário realizar um experimento para obter contadores de eventos e hardware; para isso, usaremos os contadores da R9- 290x. Desses contadores medidos pode-se tirar algumas métricas. A primeira que se pode observar é a banda da memória global. Essa implementação em OpenCL atinge 0,9

GB/s, que é muito abaixo do pico máximo para a GPU R9-290x. Esse é o gargalo da implementação OpenCL e o motivo de ela não escalar nos dispositivos disponíveis.

Figura 5.20: Resultados experimentais para o CRP usando o Jequitinhonha como dado de entrada, descrito na tabela 5.19.

Capítulo 6

Conclusões

Este trabalho oferece uma gama de implementações paralelas para métodos de busca de parâmetros sísmicos. Entre as implementações descritas no trabalho estão uma imple- mentação em OpenCL para os métodos CMP, CRS e migração CRP em tempo. Além disso, o trabalho descreve uma série de experimentos para comprovar a eficácia de cada implementação descrita. Esses experimentos são realizados em vários hardwares diferentes e tendo como controle uma implementação em OpenMP de cada método.

Em todos os casos apresentados pelos experimentos, conseguiu-se melhor desempe- nho em todos os hardwares testados utilizando a implementação OpenCL, sendo que em alguns casos o ganho de desempenho chegou por volta de 58 vezes em relação a uma im- plementação em OpenMP. Entretanto, para a implementação OpenCL da migração CRP em tempo, os resultados não são tão animadores, enquanto que para o CRS e para o CMP os aceleradores sempre se mostraram mais eficientes para executar a implementação. Para a migração CRP em tempo, alguns aceleradores tiveram resultados piores do que o uso de simples CPUs multicore.

As contribuições deste trabalho podem ser enumeradas da seguinte forma : 1) este trabalho oferece uma implementação eficiente em OpenCL do empilhamento CMP e CRS capaz de ser executada em CPUs multicores, no Intel Xeon Phi e em GPUs da NVIDIA e da AMD; 2) este trabalho oferece uma implementação em OpenCL para a migração CRP em tempo, e, apesar de não ter conseguir ter um bom desempenho das GPUs, a implementação consegue um ganho considerável de aproximadamente 7 vezes quando comparada com a implementação em OpenMP; 3) este trabalho também oferece uma análise sobre erros numéricos para o CMP em GPUs; 4) por fim, este trabalho também apresenta uma análise minuciosa dos resultados de desempenho obtidos nos experimentos realizados.

Podemos mencionar, como trabalhos futuros, melhoras nas implementações OpenCL dos métodos CMP e CRS, já que, apesar dos grandes ganhos de desempenho mostrados, as implementação OpenCL ainda estão bem abaixo do pico de desempenho dos hardware testados. Também pode-se mencionar estudos sobre como melhorar a implementação da migração CRP em tempo, o que incluiria o estudo de meta-heurísticas mais paralelizáveis.

Referências Bibliográficas

[1] Time migration. http://www.glossary.oilfield.slb.com/Terms/t/time_ migration.aspx. Acessado: 12-08-2017.

[2] Ieee standard for floating-point arithmetic. IEEE Std 754-2008, pages 1–70, Aug 2008.

[3] The Khronos Group - Connecting Software to Silicon. See https://www.khronos.org/, Nov 2015.

[4] High performance geophysics. See https://www.khronos.org/, Nov 2017.

[5] R. L. Adema and C. Schlatter Ellis. Memory allocation constructs to complement numa memory management. In Proceedings of the Third IEEE Symposium on Parallel and Distributed Processing, pages 878–885, Dec 1991.

[6] Tiago Barros, Rafael Ferrari, Rafael Krummenauer, and Renato Lopes. Differential evolution-based optimization procedure for automatic estimation of the common- reflection surface traveltime parameters. GEOPHYSICS, 80(6):WD189–WD200, 2015.

[7] André R. Brodtkorb, Trond R. Hagen, and Martin L. Sætra. Graphics processing unit (gpu) programming strategies and trends in gpu computing. Journal of Parallel and Distributed Computing, 73(1):4 – 13, 2013. Metaheuristics on GPUs.

[8] Tiago A. Coimbra, Jorge H. Faccipieri, Dany S. Rueda, and Martin Tygel. Common- reflection-point time migration. Studia Geophysica et Geodaetica, 60(3):500–530, 2016.

[9] Tiago A. Coimbra, Jorge H. Faccipieri, and Martin Tygel. Time-migration in the Common-Reflection-Point (CRP) domain, pages 1228–1233. 2015.

[10] Christian Collberg and Todd A. Proebsting. Repeatability in computer systems research. Commun. ACM, 59(3):62–69, February 2016.

[11] T. S. Czajkowski, U. Aydonat, D. Denisenko, J. Freeman, M. Kinsner, D. Neto, J. Wong, P. Yiannacouras, and D. P. Singh. From opencl to high-performance hard- ware on fpgas. In 22nd International Conference on Field Programmable Logic and Applications (FPL), pages 531–534, Aug 2012.

[12] Leonardo Dagum and Ramesh Menon. Openmp: an industry standard api for shared- memory programming. IEEE computational science and engineering, 5(1):46–55, 1998.

[13] S. Das and P. N. Suganthan. Differential evolution: A survey of the state-of-the-art. IEEE Transactions on Evolutionary Computation, 15(1):4–31, Feb 2011.

[14] Arnaldo Carvalho de Melo. The new linux’perf’tools. In Slides from Linux Kongress, volume 18, 2010.

[15] L. de P. Veronese and R. A. Krohling. Differential evolution algorithm on the gpu with c-cuda. In IEEE Congress on Evolutionary Computation, pages 1–7, July 2010. [16] Naznin Fauzia, Louis-Noël Pouchet, and P. Sadayappan. Characterizing and enhan- cing global memory data coalescing on gpus. In Proceedings of the 13th Annual IEEE/ACM International Symposium on Code Generation and Optimization, CGO ’15, pages 12–22, Washington, DC, USA, 2015. IEEE Computer Society.

[17] A. M. Ferreiro, J. A. García, J. G. López-Salas, and C. Vázquez. An efficient im- plementation of parallel simulated annealing algorithm in gpus. Journal of Global Optimization, 57(3):863–890, 2013.

[18] Laurent Fousse, Guillaume Hanrot, Vincent Lefèvre, Patrick Pélissier, and Paul Zim- mermann. Mpfr: A multiple-precision binary floating-point library with correct roun- ding. ACM Trans. Math. Softw, 33:00000818, 2007.

[19] Borko Furht, editor. SIMD (Single Instruction Multiple Data Processing), pages 817–819. Springer US, Boston, MA, 2008.

[20] Garabito G., Stoffa P., and Söllner W. Global optimization of the common-offset CRS-attributes: Synthetic and field data application, pages 1565–1568. 2014.

[21] P Hubral, G Höcht, and R Jäger. An introduction to the common reflection surface stack. In 60th EAGE Conference and Exhibition, 1998.

[22] L. Ingber. Very fast simulated re-annealing. Mathematical and Computer Modelling, 12(8):967 – 973, 1989.

[23] John W. Stockwell Jr. The cwp/su: Seismic unix package1,11. Computers & Geos- ciences, 25(4):415 – 419, 1999.

[24] Fernando Lawrens, Aditya Jiwandono, and Rachmat Sule. Implementation of non uniform memory address (NUMA) parallel computation in order to speed up the common reflection surface (CRS) stack optimization process, pages 48–51. 2015. [25] Fernando Lawrens, M. Rachmat Sule, and Afnimar. Parallel computation for speedup

the computation time of direct determination of common-reflection-surface (CRS) attribute, pages 209–212. 2015.

[26] Chris Lomont. Introduction to intel advanced vector extensions. intel white paper, 2011.

[27] P Marchetti, D Oriato, O Pell, AM Cristini, and D Theis. Fast 3d zo crs stack–an fpga implementation of an optimization based on the simultaneous estimate of eight parameters. In 72nd EAGE Conference and Exhibition incorporating SPE EUROPEC 2010, 2010.

[28] Paolo Marchetti, Alessandro Prandi, Bruno Stefanizzi, Herve Chevanne, Ernesto Bonomi, and Antonio Cristini. OpenCL implementation of the 3D CRS optimization algorithm, chapter 678, pages 3475–3479. Society of Exploration Geophysicists, 2011. [29] W. Harry Mayne. Common reflection point horizontal data stacking techniques.

Geophysics, 27(6):927–938, 1962.

[30] Xinxin Mei and Xiaowen Chu. Dissecting gpu memory hierarchy through microben- chmarking. sep 2015.

[31] Aaftab Munshi. The OpenCL Specification. Khronos OpenCL Working Group, 19 edition, nov 2014.

[32] N. S. Neidell and M. Turhan Taner. Semblance and other coherency measures for multichannel data. GEOPHYSICS, 36(3):482–497, 1971.

[33] Yao Ni and Kai Yang. A GPU based 3D Common-Reflection-Surface stack algorithm with the output imaging scheme (3d-crs-ois). In SEG Technical Program Expanded Abstracts, pages 1–5, 2012.

[34] CUDA Nvidia. Programming guide, 2010.

[35] Shane Ryoo, Christopher I. Rodrigues, Sara S. Baghsorkhi, Sam S. Stone, David B. Kirk, and Wen-mei W. Hwu. Optimization principles and application performance evaluation of a multithreaded gpu using cuda. In Proceedings of the 13th ACM SIGPLAN Symposium on Principles and Practice of Parallel Programming, PPoPP ’08, pages 73–82, New York, NY, USA, 2008. ACM.

[36] Samuel Williams, Andrew Waterman, and David Patterson. Roofline: An insightful visual performance model for multicore architectures. Commun. ACM, 52(4):65–76, April 2009.

[37] Bo Wu, Zhijia Zhao, Eddy Zheng Zhang, Yunlian Jiang, and Xipeng Shen. Comple- xity analysis and algorithm design for reorganizing data to minimize non-coalesced memory accesses on gpu. In ACM SIGPLAN Notices, volume 48, pages 57–68. ACM, 2013.

Apêndice A

Prova da equivalência das

implementações

Provar que as duas implementações são iguais é relativamente simples, consiste basi- camente em provar que as duas implementações realizam as mesmas operações. Sendo assim, o primeiro passo a se fazer é listar quais funções são implementadas da mesma maneira tanto na implementação OpenMP quanto na OpenCL. Como estão implementa- das da mesma maneira, as operações realizadas por elas, nas duas implementações, são as mesmas. Tais funções, macros e estruturas são listadas abaixo:

• time2d • interpol_linear • su_get_halfoffset • HASH_FIND_INT • vector_push • su_fgettr • cdp_traces

O segundo passo da prova é formalizar as entradas das implementações; dessa forma, podemos ter uma representação para conversar entre as estruturas de dados diferentes das implementações. Seja F 32 o conjunto de números de ponto flutuante de 32 bits no padrão IEEE-754-2008 [2]. E seja o I32 o conjunto de números inteiros de 32 bits.

• c0 ∈ F 32 • c1 ∈ F 32 • N C ∈ I32 • aph ∈ I32 • tau ∈ F 32

• SU = {cdp1, ...., cdpw}

Onde:

• c1 > c0

• cdpi = {tr1, ..., trl}

• tri = {gx, sx, gy, sy, data}

• data = {a1, ..., ans} • ai ∈ F 32 • gx ∈ F 32 • sx ∈ F 32 • gy ∈ F 32 • sy ∈ F 32 1 s t r u c t c d p _ t r a c e s * c d p _ t r a c e s = N U L L ; 2 w h i l e ( s u _ f g e t t r ( fp , & tr ) ) { 3 f l o a t hx , hy ;

4 s u _ g e t _ h a l f o f f s e t (& tr , & hx , & hy ) ;

5 if ( hx * hx + hy * hy > aph * aph ) 6 c o n t i n u e; 7 int cdp = tr . cdp ; 8 s t r u c t c d p _ t r a c e s * val ; 9 H A S H _ F I N D _ I N T ( c d p _ t r a c e s , & cdp , val ) ; 10 if (! val ) { 11 val = m a l l o c (s i z e o f(s t r u c t c d p _ t r a c e s ) ) ; 12 val - > cdp = cdp ; 13 v e c t o r _ i n i t ( val - > t r a c e s ) ; 14 H A S H _ A D D _ I N T ( c d p _ t r a c e s , cdp , val ) ; 15 } 16 v e c t o r _ p u s h ( val - > traces , tr ) ; 17 }

Código A.1: Trecho do código que prepara os dados para o cálculo do CMP na CPU

1 s t r u c t c d p _ t r a c e s * c d p _ t r a c e s = N U L L ;

2 int m i n _ n s = -1;

3 int m a x _ n s = -1;

4 w h i l e ( s u _ f g e t t r ( fp , & tr ) ) { 5 f l o a t hx , hy ;

6 s u _ g e t _ h a l f o f f s e t (& tr , & hx , & hy ) ;

7 if ( hx * hx + hy * hy > aph * aph )

9 if ( m i n _ n s < 1) m i n _ n s = tr . ns ; // F i r s t t r a c e . 10 if ( m a x _ n s < 1) m a x _ n s = tr . ns ; // F i r s t t r a c e . 11 if ( m i n _ n s > tr . ns ) m i n _ n s = tr . ns ; 12 if ( m a x _ n s < tr . ns ) m a x _ n s = tr . ns ; 13 14 int cdp = tr . cdp ; 15 s t r u c t c d p _ t r a c e s * val ; 16 H A S H _ F I N D _ I N T ( c d p _ t r a c e s , & cdp , val ) ; 17 if (! val ) { 18 val = m a l l o c (s i z e o f(s t r u c t c d p _ t r a c e s ) ) ; 19 val - > cdp = cdp ; 20 v e c t o r _ i n i t ( val - > t r a c e s ) ; 21 H A S H _ A D D _ I N T ( c d p _ t r a c e s , cdp , val ) ; 22 } 23 v e c t o r _ p u s h ( val - > traces , tr ) ; 24 }

Código A.2: Trecho do código que prepara os dados para o cálculo do CMP na GPU O próximo passo da demonstração é mostrar que a leitura dos dados de entrada das duas implementações são equivalentes. Para isso, basta observar que os trechos de códigos A.1 e A.2 possuem poucas diferenças (sendo as únicas o trecho das linhas 9 a 11 do código A.2 que realizam a simples operação de achar o maior valor de ns). O trecho de código A.3 é responsável pela alocação de memória na memória global da GPU e os correspondentes na memória do host. 1 2 # d e f i n e C R E A T E _ I N P U T _ B U F F E R ( name , type , sz ) \ 3 c l _ m e m n a me = c l C r e a t e B u f f e r ( context , C L _ M E M _ R E A D _ O N L Y , s i z e o f ( t y p e ) * sz , NULL , N U L L ) ; \ 4 if (! n a m e ) { \ 5 f p r i n t f ( stderr , " E r r o r w h e n c r e a t i n g i n p u t b u f f e r n a m e d % s \ n ", # n a m e ) ; \ 6 } \ 7 t y p e * _ ## n a m e = m a l l o c (s i z e o f( t y p e ) * sz ) ; \ 8 if (! _ ## n a m e ) { \ 9 f p r i n t f ( stderr , " E r r o r w h e n c r e a t i n g h o s t b u f f e r n a m e d _ % s \ n ", # n a m e ) ; \ 10 } \ 11 do {} w h i l e(0) 12 13 # d e f i n e C R E A T E _ O U T P U T _ B U F F E R ( name , type , sz )

\ 14 c l _ m e m n a me = c l C r e a t e B u f f e r ( context , C L _ M E M _ W R I T E _ O N L Y , s i z e o f( t y p e ) * sz , NULL , N U L L ) ; \ 15 if (! n a m e ) { \ 16 f p r i n t f ( stderr , " E r r o r w h e n c r e a t i n g o u t p u t b u f f e r n a m e d % s \ n ", # n a m e ) ; \ 17 } \ 18 t y p e * _ ## n a m e = m a l l o c (s i z e o f( t y p e ) * sz ) ; \ 19 if (! _ ## n a m e ) { \ 20 f p r i n t f ( stderr , " E r r o r w h e n c r e a t i n g h o s t b u f f e r n a m e d _ % s \ n ", # n a m e ) ; \ 21 } \ 22 do {} w h i l e(0) 23 24 C R E A T E _ I N P U T _ B U F F E R ( cube , float, ns * m a x _ n t r s * ng ) ; 25 C R E A T E _ I N P U T _ B U F F E R ( gx , int, m a x _ n t r s * ng ) ; 26 C R E A T E _ I N P U T _ B U F F E R ( gy , int, m a x _ n t r s * ng ) ; 27 C R E A T E _ I N P U T _ B U F F E R ( sx , int, m a x _ n t r s * ng ) ; 28 C R E A T E _ I N P U T _ B U F F E R ( sy , int, m a x _ n t r s * ng ) ; 29 C R E A T E _ I N P U T _ B U F F E R ( scalco , short, m a x _ n t r s * ng ) ; 30 C R E A T E _ I N P U T _ B U F F E R ( ntrs , int, ng ) ; 31 C R E A T E _ I N P U T _ B U F F E R ( tr0_dt , short, ng ) ; 32 C R E A T E _ O U T P U T _ B U F F E R ( ctr , float, ns * ng ) ; 33 C R E A T E _ O U T P U T _ B U F F E R ( stk , float, ns * ng ) ; 34 C R E A T E _ O U T P U T _ B U F F E R ( sem , float, ns * ng ) ;

Código A.3: Trecho do código que prepara os dados para o cálculo do CMP na GPU Após a execução do trecho de código A.3, são criados os seguintes vetores:

• gx[0, ..., max_ntrs ∗ ng − 1] • gy[0, ..., max_ntrs ∗ ng − 1] • sx[0, ..., max_ntrs ∗ ng − 1] • sy[0, ..., max_ntrs ∗ ng − 1] • gx[0, ..., max_ntrs ∗ ng − 1] • cube[0, ..., max_ntrs ∗ ng ∗ ns − 1] • ctr[0, ..., ns ∗ ng − 1] • sem[0, ..., ns ∗ ng − 1]

• stk[0, ..., ns ∗ ng − 1] onde:

• max_ntrs: número máximo de traços em uma família CMP. • ns: número máximo de amostras em um traço, ou seja max_ns. • ng: número de famílias CMP processadas em paralelo na GPU.

O próximo passo é demonstrar que o laço mais externo do trecho de código A.4 é equivalente ao laço do trecho de código A.5.

1 for ( g l o b a l _ g a t h e r _ i d x = 0; g l o b a l _ g a t h e r _ i d x < t o t a l _ g a t h e r s ;) { 2 int n g a t h e r s = MIN ( ng , t o t a l _ g a t h e r s - g l o b a l _ g a t h e r _ i d x ) ; 3 int l o c a l _ g a t h e r _ i d x ; 4 for ( l o c a l _ g a t h e r _ i d x = 0; l o c a l _ g a t h e r _ i d x < n g a t h e r s && g l o b a l _ g a t h e r _ i d x < t o t a l _ g a t h e r s ; l o c a l _ g a t h e r _ i d x ++ , g l o b a l _ g a t h e r _ i d x ++) { 5 . 6 . 7 . 8 } 9 }

Código A.4: Trecho de código que controla quanta vezes executaremos o kernel na GPU

1 s t r u c t c d p _ t r a c e s * i t e r ; 2 for ( i t e r = c d p _ t r a c e s ; i t e r ; i t e r = iter - > hh . n e x t ) { 3 . 4 . 5 . 6 }

Código A.5: Trecho de código que controla quanta vezes executaremos getmax_C na CPU

O primeiro passo para provar que os laços são equivalentes é provar que os dois laços executam o mesmo número de iterações, o que pode ser feito da seguinte forma: primeiro prova-se para ng = 1. Então, se segue abaixo a prova para quando ng = 1.

O primeiro passo da demonstração é mostrar qual é o valor de total_gathers no trecho código A.4. Essa variável é definida no trecho de código A.6, que é executado antes do trecho A.4. Na linha 2 do código A.6, pode-se ver um laço idêntico ao laço da linha 2 do código A.5; como total_gathers aumenta 1 por iteração do laço, no final do laço total_gathers será igual ao número de iterações do laço da linha 2 do código A.5. Agora basta provar que o laço da linha 4 do código A.4 executa total_gathers, já que é nesse laço que a variável global_gather_idx é incrementada e ela é a variável que controla o laço da linha 1. Para se ter certeza de quantas vezes o laço da linha 4 do código A.4 executa, a linha 2 deve ser analisada. Nesta linha tem-se a chamada de uma macro M IN (a, b); essa macro devolve o menor número entre a e b. Nesse caso, temos que, para todas as chamadas

dentro do laço, a chamada de M IN será M IN (1, total_gathers − global_gather_idx). Essa chamada define quantas vezes o laço da linha 4 vai ser executado, já que é esse trecho que define a variável ngathers, que é uma das variáveis que define a parada do laço da linha 4. Para se provar que o laço da linha executa total_gathers vezes, basta primeiro provar para toda iteração quando ngahters = 1. Para isso uma prova por indução é necessária.

Base da indução:

No caso em que global_gather_idx = 0, total_gathers = 1, temos que:

ngathers = M IN (1, 1 − 0) (A.1)

Logo, ngathers = 1.

Pela condição de parada do laço da linha 1 e da linha 4, tem-se que para toda a iteração global_gather_idx < total_gathers.

Hipótese de indução:

Para global_gather_idx = n, total_gathers = m, onde n < m.

Logo tem-se ngathers = M IN (1, m − n), como n − m, e n, m ∈ I32, logo n − m ≤ 1, então ngathers = 1.

Agora basta provar para n + 1 e m + 1: Então:

global_gather_idx = n + 1 (A.2)

total_gathers = m + 1 (A.3)

Onde n + 1 < m + 1.

Então, ngathers = M IN (1, (m + 1) − (n + 1)), pode-se reescrever essa equação como ngathers = M IN (1, m − n), logo ngathers = 1.

Com isso, prova-se que para ng = 1, ngathers = 1 para todas as iterações do laço da linha 1, logo o laço da linha 1 executa total_gathers iterações e, portanto, o laço da linha 4 também vai executar no total total_gathers iterações.

Agora tem-se que provar que o laço da linha 4 do código A.4 executa total_gathers iterações para ng = α, onde α ∈ I32, α > 0.

Para isso, temos que provar que para todo α a equação A.4 é verdadeira.

nglobal_gathers

X

i=1

ngatheri = total_gathers (A.4)

Onde:

• nglobal_gathers: número de iterações do laço da linha 1 do código A.4.

A condição de parada dos laços das linhas 1 e 4 do trecho de código A.4, implica que: nglobal_gathers < total_gathers (A.5) Então temos dois casos o caso em que total_gathers mod ng = 0 e o caso em que

total_gathers mod ng = β. Primeiro prova-se para total_gathers mod ng = 0, desse modo segue uma prova por indução desse caso, onde se prova que todos os ngathers = ng para cada iteração.

Base: total_gathers = ng, logo o laço da linha 1 executa uma vez e o da linha 4 ng vezes, logo a equação A.4 é verdade, então agora a prova para ngathers = ng:

Hipótese: total_gathers = ng ∗ n.

Então, ngathers = M IN (ng, ng ∗ n − global_gathers) como o laço da linha 4, executa ngather iterações e na primeira iteração do laço da linha 1 global_gathers = 0, na próxima iteração global_gathers = ng e para a iteração j do laço da linha 1, temos que global_gather = ng ∗ (j − 1), logo M IN (ng, ng ∗ n − global_gathers) = ng para toda a iteração j, já que para todas as iterações ng ∗ (j − 1) ≥ ng ∗ n basta provar para total_gather = ng ∗ (n + 1). Então ngathers = M IN (ng, ng ∗ (n + 1) − global_gathers); como o laço da linha 4 executa ngather iterações e na primeira iteração do laço da linha 1 global_gathers = 0, na próxima iteração global_gathers = ng e para a iteração j do laço da linha 1, temos que:

global_gather = ng ∗ (j − 1) (A.6)

Logo:

M IN (ng, ng ∗ (n + 1) − global_gathers) = ng (A.7) Para toda a iteração j, temos ng ∗ (j − 1) ≥ ng ∗ (n + 1). logo:

total_gathers = ng ∗ n (A.8)

Então, basta provar para total_gathersmodng = β. Neste caso, pode-se escrever total_gathers como total_gathers = ng ∗ n + β, onde n é o número inteiro resultante da divisão de total_gathers por ng. Foi provado antes até a iteração n, temos que total_gathers = ng∗n, portanto, na última iteração temos o macro M IN (ng, total_gathers− ng ∗ n), logo ngathers = β, logo, o laço da linha 4 do código A.4 executa total_gathers iterações. Dessa forma, o laço da linha 4 do código A.4 executa o mesmo número de itera- ções do laço da linha 1 do código A.5, já que o número de CDPs na estrutura cdp_traces é igual a total_gathers, como pode ser visto no código A.6.

1 int t o t a l _ g a t h e r s =0; 2 for ( it = c d p _ t r a c e s ; it ; it = it - > hh . n e x t ) { 3 g a t h e r s [ t o t a l _ g a t h e r s ++] = it ; 4 n u m b e r O f C D P s ++; 5 v [ it - > t r a c e s . len ] + + ; 6 if ( m a x _ n t r s < it - > t r a c e s . len ) 7 m a x _ n t r s = it - > t r a c e s . len ; 8 9 }

Código A.6: Trecho de código que controla quanta vezes executaremos getmax_C na CPU

O primeiro passo para isso é provar que o if da linha 7 do código A.8 é equivalente ao o if da linha 2 do código A.9. O if do código A.8 tem a condição que é negação da condição do if do código A.9, e como os ifs tem como resultado das condições efeitos contrários, um executa o código abaixo dele quando a condição é verdadeira e outro não. Como as condições são negadas, eles terão o mesmo comportamento nos dois códigos. Agora basta provar que a estrutura de dados cube recebe os valores da estrutura de dados que armazena os traços de maneira correta, ou seja, que todos os elementos são copiados para o cubo, de maneira que cada elemento da estrutura que armazena os dados tem uma única posição do cubo e de modo que todos os elementos da estrutura podem ser armazenados no cubo.

Para acessar o cubo, usa-se a macro descrita no código A.7, que implementa a equação A.9, para acessar um elemento de cube.

pos = i ∗ (szj ∗ szk) + j ∗ szk + k (A.9)

1 # d e f i n e G E T _ 3 D _ D A T A ( data , i , j , k , szj , szk ) d a t a [ i *( szj * szk ) + j * szk +

k ]

Código A.7: Trecho de código com a definição da macro usada para acessar dados do cube.

1 for( it = c d p _ t r a c e s ; it ; it = it - > hh . n e x t ) {

2 s u _ t r a c e _ t * tri = it - > t r a c e s . a ;

3 f l o a t mx , my ;

4 s u _ g e t _ m i d p o i n t ( tri ,& mx ,& my ) ;

5 f l o a t dx = mx - m0x ; 6 f l o a t dy = my - m0y ;

7 if(!( dx * dx + dy * dy > par . d a p m * par . d a p m ) )

8 {

9 s u _ t r a c e _ t * tr ;

10 for ( iii =0; iii < it - > t r a c e s . len ; iii ++) {

11 G E T _ 2 D _ D A T A ( _scalco , id , l o c a l _ g a t h e r _ i d x , n g a t h e r s ) = (& v e c t o r _ g e t ( it - > traces , iii ) ) - > s c a l c o ; 12 G E T _ 2 D _ D A T A ( _gx , id , l o c a l _ g a t h e r _ i d x , n g a t h e r s ) = (& v e c t o r _ g e t ( it - > traces , iii ) ) -> gx ; 13 G E T _ 2 D _ D A T A ( _gy , id , l o c a l _ g a t h e r _ i d x , n g a t h e r s ) = (& v e c t o r _ g e t ( it - > traces , iii ) ) -> gy ; 14 G E T _ 2 D _ D A T A ( _sx , id , l o c a l _ g a t h e r _ i d x , n g a t h e r s ) = (& v e c t o r _ g e t ( it - > traces , iii ) ) -> sx ; 15 G E T _ 2 D _ D A T A ( _sy , id , l o c a l _ g a t h e r _ i d x , n g a t h e r s ) = (& v e c t o r _ g e t ( it - > traces , iii ) ) -> sy ; 16 int t0 ;

17 tr = & v e c t o r _ g e t ( it - > traces , iii ) ;

18 for ( t0 =0; t0 < tr - > ns ; t0 ++)

19 {

20 G E T _ 3 D _ D A T A ( _cube , t0 , id , l o c a l _ g a t h e r _ i d x , m a x _ n tr s , n g a t h e r s )

21 }

22 id ++;

23 } 24 } 25 }

Código A.8: Trecho de código que realiza o copia dos dados do host para a GPU.

1 for ( it = c d p _ t r a c e s ; it ; it = it - > hh . n e x t ) {

2 if ( dx * dx + dy * dy > par . apm * par . apm )

3 c o n t i n u e;

4 for (int i = 0; i < it - > t r a c e s . len ; i ++)

5 v e c t o r _ p u s h ( ap . traces , & v e c t o r _ g e t ( it - > traces , i ) ) ;

6 }

Código A.9: Trecho de código que organiza os traços em gathers CMPs.

Então temos na linha 20 do código A.8, após a expansão da macro descrito no código A.7, temos a seguinte linha de código:

cube[t0∗(ngathers∗max_ntrs)+id∗ngathers+local_gatheridx] = (&vectorget(it− > traces, iii))− > data[t0]

(A.10) Temos que provar: Dado um traço a ∈ T r, ele é colocado em uma posição única na memória. Para isso uma prova por indução é necessária: vamos provar para o caso base t0 = 0, i = 0. Nesse caso temos que cube[0] ← a0.

Hipótese de indução para t0 = n e i = 0:

cube[n ∗ max_ntrs + 0] ← a0,n (A.11)

provando para n + 1 logo:

cube[n ∗ max_ntrs + max_ntrs] ← a0,n (A.12)

logo, para um único traço, a prova está feita. Agora basta provar para vários traços.

Base t0 = 0, i = 1.

cube[1] ← a1,0 (A.13)

Hipótese t0 = n, i = m.

cube[max_ntrs + m] ← am,n. (A.14)

Agora basta provar para n + 1 e m + 1:

cube[n ∗ max_ntrs + maxntrs + m + 1] ← an+1,m+1. (A.15)

Logo, após essas provas, temos que todos os traços são copiados para posições distintas de memória.

Agora que todos os buffers são devidamente copiados, começamos a prova para o kernel OpenCL, é equivalente ao Código A.10.

O primeiro passo é provar para o código somente um grupo e um work-item, logo: • t0_idx = 0

• gather_idx = 1

• ntraces = ap0− > traces− > len

Então ng*ngahters = n

Documentos relacionados