• 検索結果がありません。

偏微分方程式の差分計算 長岡技術科学大学電気電子情報工学専攻出川智啓

N/A
N/A
Protected

Academic year: 2021

シェア "偏微分方程式の差分計算 長岡技術科学大学電気電子情報工学専攻出川智啓"

Copied!
83
0
0

読み込み中.... (全文を見る)

全文

(1)

偏微分方程式の差分計算

(2)

今回の内容

差分法

1階微分,2階微分に対する差分法

2次元拡散方程式

gnuplotによる結果の表示

if分岐の書き方による実行時間の変化

高速化に利用できるいくつかのテクニック

(3)

前回授業

ビットマップを使った画像処理

配列の1要素が物理的な配置に対応

配列の1要素に物理的なデータが定義

(4)

数値計算(差分法)

計算機を利用して数学・物理学的問題の解を計算

微積分を計算機で扱える形に変換

差分法では微分を差分に置き換え

処理自体は単純

精度を上げるために計算量が増加

(5)

シミュレーションの歴史と進歩

(6)

数値計算(差分法)

物理空間に観測点を設置

観測点での物理量の変化を計算

(7)

偏微分方程式(拡散方程式)

物質の拡散を表す方程式

水の中に落ちたインクの拡散,金属中の熱伝導等

時刻

t=0におけるfの分布(初期値)が既知

時間進行に伴い,

fがどのように変化するかを計算

時間積分しながら

fの分布を求める

2

2

(

,

)

)

,

(

x

t

x

f

t

t

x

f

2

2

2

2

(

,

,

)

(

,

,

)

)

,

,

(

y

t

y

x

f

x

t

y

x

f

t

t

y

x

f

(8)

差分法

計算機で微分を計算する方法の一つ

微分の定義

xの関数fについて,

xだけ離れた2点間の傾きを計算し,

2点の間隔を

無限小に近づけたときの極限

差分近似

関数をある間隔でサンプリング

その間隔

x

fの変化に対して十分小さい

と仮定

Δx

x

f

Δx

x

f

dx

df

Δx

)

(

)

(

lim

0

Δx

x

f

Δx

x

f

dx

df

(

)

(

)

(9)

差分法(理論的なお話)

差分の誤差

limを排除したことでどの程度の誤差が入るのか

関数

fのテイラー展開を利用

Δx

x

f

Δx

x

f

Δx

x

f

Δx

x

f

dx

df

Δx

)

(

)

(

)

(

)

(

lim

0

2

2

2

3

3

3

!

3

!

2

)

(

)

(

dx

f

d

Δx

dx

f

d

Δx

dx

df

Δx

x

f

Δx

x

f

2

2

2

3

3

3

!

3

!

2

1

)

(

)

(

dx

f

d

Δx

dx

f

d

Δx

Δx

Δx

x

f

Δx

x

f

dx

df

(10)

差分法(理論的なお話)

空間打ち切り誤差

定義とテイラー展開の比較

テイラー展開を有限項で打ち切ったことによる誤差

2

2

2

3

3

3

!

3

!

2

1

)

(

)

(

dx

f

d

Δx

dx

f

d

Δx

Δx

Δx

x

f

Δx

x

f

dx

df

Δx

x

f

Δx

x

f

Δx

x

f

Δx

x

f

dx

df

Δx

)

(

)

(

)

(

)

(

lim

0

誤差

2

2

2

3

3

!

3

!

2

dx

f

d

Δx

dx

f

d

Δx

(11)

2階微分の離散化

テイラー展開を応用

x方向にx離れた2点で展開

2式を足すと1階微分が消滅

2

2

2

3

3

3

!

3

!

2

)

(

)

(

x

f

Δx

x

f

Δx

x

f

Δx

x

f

Δx

x

f

2

2

2

3

3

3

!

3

!

2

)

(

)

(

x

f

Δx

x

f

Δx

x

f

Δx

x

f

Δx

x

f

2

2

2

!

2

2

)

(

2

)

(

)

(

x

f

Δx

x

f

Δx

x

f

Δx

x

f

(12)

2階微分の離散化

2階微分の式に整理

2

2

2

(

)

2

(

)

(

)

Δx

Δx

x

f

x

f

Δx

x

f

x

f

x

f(x)

f(x−

x)

f(x)

x

x=0 ・・・ x−

x

x x+

x

f(x+

x)

(13)

2階微分の離散化

2階微分の式に整理

2

2

2

(

)

2

(

)

(

)

Δx

Δx

x

f

x

f

Δx

x

f

x

f

dx

サンプリングされた関数値

を配列f[]で保持

f[i]

f[i‐1]

f[i+1]

2階の中心差分

(f[i+1]‐2*f[i]+f[i‐1]) 

/(dx*dx)

f[i]

i

(14)

2階微分の離散化

サンプリングされた関数値fの保持

x方向長さ(計算領域の長さ)を等間隔xに分割

間隔

xの両端に離散点(観測点)を置き,そこでfの値を定義

原点から順に離散点に番号を付与し,iで表現

離散点iにおけるfの値を配列f[i]に保存

配列f[]

i=0   ・・・

i−1  i

i+1

f[i]

i

離散点

配列(メモリ)

(15)

差分法の実装

計算領域内部

d2fdx2[i]=(f[i‐1]‐2*f[i]+f[i+1])/dxdx;

境界条件(関数値が無いため処理を変更)

d2fdx2[0  ]=(2*f[0  ]‐5*f[1  ]+4*f[2  ]‐f[3  ])/dxdx;

d2fdx2[N‐1]=(2*f[N‐1]‐5*f[N‐2]+4*f[N‐3]‐f[N‐4])/dxdx;

d2fdx2[i]

f[i]

2 1 Δx

(16)

差分法の実装

計算領域内部

d2fdx2[i]=(f[i‐1]‐2*f[i]+f[i+1])/dxdx;

境界条件(関数値が無いため処理を変更)

d2fdx2[0  ]=(2*f[0  ]‐5*f[1  ]+4*f[2  ]‐f[3  ])/dxdx;

d2fdx2[N‐1]=(2*f[N‐1]‐5*f[N‐2]+4*f[N‐3]‐f[N‐4])/dxdx;

d2fdx2[i]

f[i]

2 1 Δx

(17)

差分法の実装

計算領域内部

d2fdx2[i]=(f[i‐1]‐2*f[i]+f[i+1])/dxdx;

境界条件(関数値が無いため処理を変更)

d2fdx2[0  ]=(2*f[0  ]‐5*f[1  ]+4*f[2  ]‐f[3  ])/dxdx;

d2fdx2[N‐1]=(2*f[N‐1]‐5*f[N‐2]+4*f[N‐3]‐f[N‐4])/dxdx;

d2fdx2[i]

f[i]

2 1 Δx

(18)

#include<stdlib.h>

#include<math.h>/*‐lmオプションが必要*/

#define Lx

(2.0*M_PI)

#define Nx

(256)

#define dx

(Lx/(Nx‐1))

#define Nbytes

(Nx*sizeof(double))

#define dxdx

(dx*dx)

void init(double *f){

int i;

for(i=0; i<Nx; i++){

f[i] = sin(i*dx);

}

}

void differentiate(double *f,

double *d2fdx2){

int i;

d2fdx2[0] = ( 2.0*f[0]

‐5.0*f[1]

+4.0*f[2]

f[3])/dxdx;

for(i=1; i<Nx‐1; i++)

d2fdx2[i] = (     f[i+1]

‐2.0*f[i

]

+    f[i‐1])/dxdx;

d2fdx2[Nx‐1]=(‐

f[Nx‐4]

+4.0*f[Nx‐3]

‐5.0*f[Nx‐2]

+2.0*f[Nx‐1])/dxdx;

}

int main(void){

double *f,*d2fdx2;

f      = (double *)malloc(Nbytes);

d2fdx2 = (double *)malloc(Nbytes);

init(f);

differentiate(f,d2fdx2);

return 0;

}

CPUプログラム

differentiate.c

(19)

関数の離散化

計算領域の長さと離散点の数,離散点の間隔の関係

f(x)

x

x

0

2

L

x

0からL

x

の間に設けられた点の数

N

x

=L

x

/

x + 1

(20)

実行結果

f,

d2f

/dx2

(21)

GPUへの移植

計算領域内部を計算するスレッド

d2fdx2[i]=(f[i‐1]‐2*f[i]+f[i+1])/dxdx;

境界を計算するスレッド

d2fdx2[0  ]=(2*f[0  ]‐5*f[1  ]+4*f[2  ]‐f[3  ])/dxdx;

d2fdx2[N‐1]=(2*f[N‐1]‐5*f[N‐2]+4*f[N‐3]‐f[N‐4])/dxdx;

d2fdx2[i]

f[i]

2 1 Δx

(22)

#include<stdio.h>

#include<stdlib.h>

#include<math.h>/*‐lmオプションが必要*/

#define Lx

(2.0*M_PI)

#define Nx

(256)

#define dx

(Lx/(Nx‐1))

#define Nbytes

(Nx*sizeof(double))

#define dxdx

(dx*dx)

#define NT      (128)

#define NB      (Nx/NT)

void init(double *f){

int i;

for(i=0; i<Nx; i++){

f[i] = sin(i*dx);

}

}

__global__ 

void differentiate

(double *f, double *d2fdx2){

int i = blockIdx.x*blockDim.x

+ threadIdx.x;

if(i==0)

d2fdx2[i] = ( 2.0*f[i

]

‐5.0*f[i+1]

+4.0*f[i+2]

f[i+3])/dxdx;

if(0<i && i<Nx‐1)

d2fdx2[i] = (     f[i+1]

‐2.0*f[i

]

+    f[i‐1])/dxdx;

if(i==Nx‐1)

d2fdx2[i]=(‐

f[i‐3]

+4.0*f[i‐2]

‐5.0*f[i‐1]

+2.0*f[i

])/dxdx;

}

GPUプログラム

differentiate.cu

(23)

int main(void){

double *host_f,*host_d2fdx2;

double *f,*d2fdx2;

host_f =(double *)malloc(Nbytes);

cudaMalloc((void **)&f,Nbytes);

cudaMalloc((void **)&d2fdx2,Nbytes);

init(host_f);

cudaMemcpy(f, host_f, Nbytes, cudaMemcpyHostToDevice);

differentiate<<<NB, NT>>>(f, d2fdx2);

//host_d2fdx2=(double *)malloc(Nbytes);

//cudaMemcpy(host_d2fdx2, d2fdx2, Nbytes, cudaMemcpyDeviceToHost);

//for(int i=0; i<Nx; i++)printf("%f,%f,%f¥n",i*dx,host_f[i],host_d2fdx2[i]);

free(host_f);

free(host_d2fdx2);

cudaFree(f);

cudaFree(d2fdx2);

return 0;

}

GPUプログラム

differentiate.cu

(24)

2次元への拡張

拡散方程式

1次元

2次元

2

2

(

,

)

)

,

(

x

t

x

f

t

t

x

f

2

2

2

2

(

,

,

)

(

,

,

)

)

,

,

(

y

t

y

x

f

x

t

y

x

f

t

t

y

x

f

differentiate.cuで計算

どのように2次元

に拡張するか

どのように離散化

するか

(25)

2次元への拡張

x方向2階偏微分

y方向を固定してx方向に偏微分

y方向2階偏微分

x方向を固定してy方向に偏微分

2

2

2

(

,

)

2

(

,

)

(

,

)

Δx

y

Δx

x

f

y

x

f

y

Δx

x

f

x

f

2

2

2

(

,

)

2

(

,

)

(

,

)

Δy

Δy

y

x

f

y

x

f

Δy

y

x

f

y

f

(26)

時間積分

時間微分項の離散化

時間微分項を前進差分で離散化

右辺の

t+tの項を移行

Δt

t

y

x

f

Δt

t

y

x

f

t

f

(

,

,

)

(

,

,

)

f

t

f

2

t

f

Δt

t

y

x

f

Δt

t

y

x

f

)

(

,

,

)

,

,

(

拡散方程式を代入

)

,

,

(

)

,

,

(

)

,

,

(

x

y

t

Δt

f

x

y

t

Δt

2

f

x

y

t

f

f(x, y, t)の2階微分を計算できればf(x, y, t+

t)が求められる

(27)

離散化された方程式の記述

簡略化した表現

配列との対応をとるため下付き添字

i, jを利用

時間は,上付き添字

nを利用

j

i

f

y

x

f

(

,

)

,

j

i

f

y

Δx

x

f

(

,

)

1

,

1

,

)

,

(

x

y

Δy

f

i

j

f

n

j

i

f

t

y

x

f

(

,

,

)

,

1

,

)

,

,

(

n

j

i

f

Δt

t

y

x

f

(28)

離散化された拡散方程式

連続系

離散系

t秒後の値

2

2

2

2

(

,

,

)

(

,

,

)

)

,

,

(

y

t

y

x

f

x

t

y

x

f

t

t

y

x

f

2

1

,

,

1

,

2

,

1

,

,

1

,

1

,

2

2

Δy

f

f

f

Δx

f

f

f

Δt

f

f

n

j

i

n

j

i

n

j

i

n

j

i

n

j

i

n

j

i

n

j

i

n

j

i

2

1

,

,

1

,

2

,

1

,

,

1

,

1

,

2

2

Δy

f

f

f

Δx

f

f

f

Δt

f

f

n

j

i

n

j

i

n

j

i

n

j

i

n

j

i

n

j

i

n

j

i

n

j

i

(29)

離散化された関数値の保持

各方向を等間隔

x,

yに分割し,離散点を配置

離散点で関数値を定義し,配列に保存

配列f[][]

i

離散点

配列(メモリ)

j=0

  

 

・・・

j

1

 

j

  

j+1

(30)

拡散方程式(熱伝導方程式)

x

y

i

i+1

i−1

j

j+1

j−1

x

y

x

y

x

y

t

t+

t

2

1

,

,

1

,

2

,

1

,

,

1

,

1

,

2

2

Δy

f

f

f

Δx

f

f

f

Δt

f

f

n

j

i

n

j

i

n

j

i

n

j

i

n

j

i

n

j

i

n

j

i

n

j

i

i

i+1

i−1

j

j+1

j−1

(31)

拡散方程式の安定性

プログラムを正しく作成しても正常な計算結果が得られ

ない場合がある

安定条件

拡散の強さを表す係数

(拡散係数)を使った形

二つの条件を満たすことが必要条件(結果が正しいかは別)

5

.

0

2

Δx

Δt

v

2

0

.

5

Δy

Δt

v

2

1

,

,

1

,

2

,

1

,

,

1

,

1

,

2

2

Δy

f

f

f

Δx

f

f

f

Δt

f

f

n

j

i

n

j

i

n

j

i

n

j

i

n

j

i

n

j

i

n

j

i

n

j

i

(32)

計算手順

1.

計算条件の決定

計算領域の大きさ

L

x

, L

y

計算領域の分割数(離散点の個数)

N

x

, N

y

離散点同士の間隔(格子間隔)

x, y

計算時間間隔

t

2.

初期値の決定

fの初期分布の決定

3.

差分値の計算

fの分布からx, y方向の2階微分値を計算

境界条件に基づいて境界の値を決定



t秒後のfを計算

(33)

CPUプログラム

計算条件の決定

計算領域の大きさ

L

x

, L

y

計算領域の分割数

(離散点の個数)

N

x

, N

y

離散点同士の間隔

(格子間隔)

x, y

計算時間間隔

t

#include<stdio.h>

#include<stdlib.h>

#include<math.h>

#define Lx (1.0)

#define Ly (1.0)

#define Nx 128

#define Ny 128

#define dx (Lx/(Nx‐1))

#define dy (Ly/(Ny‐1))

#define dt 0.0001

(34)

CPUプログラム

初期条件

境界条件

)

2

.

0

(

1

)

2

.

0

(

0

r

r

f

1

1

x

y

r

0

n

f

n

n

n: 外向き法線ベクトル

j i j i j i j i

f

f

Δx

f

f

, 1 , 1 , 1 , 1

0

2

   

1 , 1 , 1 , 1 ,

0

2

   

j i j i j i j i

f

f

Δy

f

f

2 2

y

x

r

(35)

#include<stdlib.h> #include<math.h> #define Lx (1.0) #define Ly (1.0) #define Nx 128 #define Ny 128 #define dx (Lx/(Nx‐1)) #define dy (Ly/(Ny‐1)) #define dt 0.0001 #define endT (1.0) #define Nt (int)(endT/dt) #define DIFF (0.01) #define dxdx (dx*dx) #define dydy (dy*dy) #define Nbytes (Nx*Ny*sizeof(double)) void init(double *, double *, double *); void laplacian(double *, double *); void integrate(double *, double *, double *); void update(double *, double *); int main(void){ double *f,*f_new,*f_lap,x,y; int i,j,n; f     = (double *)malloc(Nx*Ny*sizeof(double)); f_new = (double *)malloc(Nx*Ny*sizeof(double)); f_lap = (double *)malloc(Nx*Ny*sizeof(double)); init(f, f_lap, f_new); for(n=0;n<Nt;n++){ laplacian(f,f_lap); integrate(f,f_lap,f_new); update(f,f_new); } return 0; }

CPUプログラム

diffusion.c

(36)

void init(double *f, double *f_lap, double *f_new){ int i,j,ij; double x,y; for(j=0;j<Ny;j++){ for(i=0;i<Nx;i++){ f    [i+Nx*j] = 0.0; f_lap[i+Nx*j] = 0.0; f_new[i+Nx*j] = 0.0; x=i*dx‐Lx/2.0; y=j*dy‐Ly/2.0; if(sqrt(x*x+y*y)<=0.2) f[i+Nx*j] = 1.0; } } } void laplacian(double *f, double *f_lap){ int i,j,ij,ip1j,im1j,ijp1,ijm1; for(j=0;j<Ny;j++){ for(i=0;i<Nx;i++){ ij =  i+Nx*j; im1j=i‐1+Nx* j;   if(i‐1< 0 )im1j=i+1+Nx*j; ip1j=i+1+Nx* j;   if(i+1>=Nx)ip1j=i‐1+Nx*j; ijm1=i +Nx*(j‐1);if(j‐1< 0 )ijm1=i+Nx*(j+1); ijp1=i +Nx*(j+1);if(j+1>=Ny)ijp1=i+Nx*(j‐1); f_lap[ij] = (f[ip1j]‐2.0*f[ij]+f[im1j])/dxdx +(f[ijp1]‐2.0*f[ij]+f[ijm1])/dydy; } } } void integrate (double *f, double *f_lap, double *f_new){ int i,j,ij; for(j=0;j<Ny;j++){ for(i=0;i<Nx;i++){ ij =  i+Nx*j; f_new[ij] = f[ij] + dt*DIFF*f_lap[ij]; } } } void update(double *f, double *f_new){ int i,j,ij; for(j=0;j<Ny;j++){ for(i=0;i<Nx;i++){ ij =  i+Nx*j; f[ij] = f_new[ij]; } } }

CPUプログラム

diffusion.c

(37)

CPUプログラム

差分計算

x方向,y方向偏微分を個別に計算して加算

void laplacian(double *f, double *f_lap){

int i,j,ij,ip1j,im1j,ijp1,ijm1;

for(j=0;j<Ny;j++){

for(i=0;i<Nx;i++){

ij

=i

+Nx* j;

im1j=i‐1+Nx* j;   if(i‐1< 0 )im1j=i+1+Nx*j;

ip1j=i+1+Nx* j;   if(i+1>=Nx)ip1j=i‐1+Nx*j;

ijm1=i

+Nx*(j‐1);if(j‐1< 0 )ijm1=i+Nx*(j+1);

ijp1=i

+Nx*(j+1);if(j+1>=Ny)ijp1=i+Nx*(j‐1);

f_lap[ij] = (f[ip1j]‐2.0*f[ij]+f[im1j])/dxdx

+(f[ijp1]‐2.0*f[ij]+f[ijm1])/dydy;

}

}

}

(38)

ラプラシアン計算のメモリ参照

ある1点i,jのラプラシアンを計算するために,周囲5点

のfを参照

f[]

f_lap[]

i−1 i i+1

j−

1

jj

+1

(39)

ラプラシアン計算のメモリ参照

ある1点i,jのラプラシアンを計算するために,周囲5点

のfを参照

i−1 i i+1

j−

1

jj

+1

f[]

f_lap[]

(40)

ラプラシアン計算のメモリ参照

ある1点i,jのラプラシアンを計算するために,周囲5点

のfを参照

i−1 i i+1

j−

1

jj

+1

f[]

f_lap[]

(41)

ラプラシアン計算のメモリ参照

ある1点i,jのラプラシアンを計算するために,周囲5点

のfを参照

i−1 i i+1

j−

1

jj

+1

f[]

f_lap[]

(42)

ラプラシアン計算のメモリ参照

ある1点i,jのラプラシアンを計算するために,周囲5点

のfを参照

全てのfを参照し,領域内部のf_lapを計算

(43)

CPUプログラム

境界条件

法線方向の1階微分が0

境界での2階の差分式

f_lap[]

0

n

f

n: 外向き法線ベクトル

j i j i j i j i

f

f

Δx

f

f

, 1 , 1 , 1 , 1

0

2

   

1 , 1 , 1 , 1 ,

0

2

   

j i j i j i j i

f

f

Δy

f

f

2 , , 1 2 , , 1 2 , 1 , , 1

2

2

or

2

2

2

Δx

f

f

Δx

f

f

Δx

f

f

f

i j i j i j i j

i j i j

i j

 2 , 1 , 2 , 1 , 2 1 , , 1 ,

2

2

or

2

2

2

Δy

f

f

Δy

f

f

Δy

f

f

f

i j i j i j i j

i j i j

i j

(44)

CPUプログラム

境界条件

参照する点が境界からはみ出して

いる場合は添字を修正

2階微分を計算する式は変更なし

右端ではi+1をi‐1に修正

左端ではi‐1をi+1に修正

上端ではj+1をj‐1に修正

下端ではj‐1をj+1に修正

f_lap[]

if(i‐1< 0 )im1j=i+1+Nx*j;

if(i+1>=Nx)ip1j=i‐1+Nx*j;

if(j‐1< 0 )ijm1=i

+Nx*(j+1);

if(j+1>=Ny)ijp1=i

+Nx*(j‐1);

(45)

CPUプログラム

fの積分

void integrate(double *f, double *f_lap, double *f_new){

int i,j,ij;

for(j=0;j<Ny;j++){

for(i=0;i<Nx;i++){

ij = i+Nx*j;

f_new[ij] = f[ij] + dt*DIFF*f_lap[ij];

}

}

}

2

1

,

,

1

,

2

,

1

,

,

1

,

1

,

2

2

Δy

f

f

f

Δx

f

f

f

Δt

f

f

n

j

i

n

j

i

n

j

i

n

j

i

n

j

i

n

j

i

n

j

i

n

j

i

(46)

CPUプログラム

fの更新

f

n

から

f

n+1

を計算

f

n+1

から

f

n+2

を計算

今の時刻から次の時刻を求める

求められた次の時刻を今の時刻と見なし,次の時刻を求める

void update(double *f, double *f_new){

int i,j,ij;

for(j=0;j<Ny;j++){

for(i=0;i<Nx;i++){

ij = i+Nx*j;

f[ij] = f_new[ij];

}

}

}

同じアルゴリズム

(47)

#include<stdio.h> #include<stdlib.h> #include<math.h> #define Lx (1.0) #define Ly (1.0) #define Nx 2048 #define Ny Nx #define dx (Lx/(Nx‐1)) #define dy (Ly/(Ny‐1)) #define dt 0.000001 #define endT (1.0) #define Nt (int)(endT/dt) #define DIFF (0.01) #define dxdx (dx*dx) #define dydy (dy*dy) #define Nbytes (Nx*Ny*sizeof(double)) #include "dif1.cu" //#include "dif2.cu" int main(void){ int n; double *dev_f,*dev_f_new,*dev_f_lap; dim3 Thread, Block; if(DIFF*dt/dxdx > 0.5){ printf("configuration error¥n"); exit(1); } cudaMalloc( (void**)&dev_f , Nbytes ); cudaMalloc( (void**)&dev_f_new, Nbytes ); cudaMalloc( (void**)&dev_f_lap, Nbytes ); Thread = dim3(THREADX,THREADY,1); Block  = dim3(BLOCKX ,BLOCKY, 1); init<<<Block, Thread>>> (dev_f, dev_f_lap, dev_f_new); for(n=1;n<=Nt;n++){ laplacian<<<Block, Thread>>>(dev_f,dev_f_lap); integrate<<<Block, Thread>>> (dev_f,dev_f_lap,dev_f_new); update<<<Block, Thread>>>(dev_f,dev_f_new); } return 0; }

GPUプログラム(CPU処理用共通部分)

diffusion.cu

(48)

#define THREADX 16 #define THREADY 16 #define BLOCKX  (Nx/THREADX) #define BLOCKY  (Ny/THREADY) __global__ void laplacian(double *f, double *f_lap){ int i,j,ij,im1j,ip1j,ijm1,ijp1; i = blockIdx.x*blockDim.x + threadIdx.x; j = blockIdx.y*blockDim.y + threadIdx.y; ij  =i  +Nx* j; im1j=i‐1+Nx* j;   if(i‐1< 0 )im1j=i+1+Nx*j; ip1j=i+1+Nx* j;   if(i+1>=Nx)ip1j=i‐1+Nx*j; ijm1=i  +Nx*(j‐1);if(j‐1< 0 )ijm1=i+Nx*(j+1); ijp1=i  +Nx*(j+1);if(j+1>=Ny)ijp1=i+Nx*(j‐1); f_lap[ij] = (f[ip1j]‐2.0*f[ij]+f[im1j])/dxdx +(f[ijp1]‐2.0*f[ij]+f[ijm1])/dydy; } __global__ void integrate (double *f, double *f_lap, double *f_new){ int i,j,ij; i = blockIdx.x*blockDim.x + threadIdx.x; j = blockIdx.y*blockDim.y + threadIdx.y; ij =  i+Nx*j; f_new[ij] = f[ij] + dt*DIFF*f_lap[ij]; } __global__ void update(double *f, double *f_new){ int i,j,ij; i = blockIdx.x*blockDim.x + threadIdx.x; j = blockIdx.y*blockDim.y + threadIdx.y; ij  =  i+Nx*j; f[ij] = f_new[ij]; } __global__ void init (double *f, double *f_lap, double *f_new){ int i,j,ij; double x,y; i = blockIdx.x*blockDim.x + threadIdx.x; j = blockIdx.y*blockDim.y + threadIdx.y; ij  =  i+Nx*j; f    [ij] = 0.0; f_lap[ij] = 0.0; f_new[ij] = 0.0; x=i*dx‐Lx/2.0; y=j*dy‐Ly/2.0; if(sqrt(x*x+y*y)<=0.2) f[ij] = 1.0; }

GPUプログラム(1スレッドが1点を計算)

dif1.cu

(49)

gnuplotによる結果の表示

2次元,3次元データをプロットするアプリケーション

コマンドラインで命令を実行してグラフを描画

関数の描画,ファイルから読み込んだデータの表示が可能

tesla??では正しく動作しないため,grouseで実行する

こと

(50)

gnuplotによる結果の表示

表示可能なファイルフォーマット

データはスペース区切り

3次元表示では列(または行)の区切りとして空白行が必要

‐0.500000 ‐0.500000 0.000000e+00

‐0.499022 ‐0.500000 0.000000e+00

...

0.499022 ‐0.500000 0.000000e+00

0.500000 ‐0.500000 0.000000e+00

<‐改行

‐0.500000 ‐0.499022 0.000000e+00

‐0.499022 ‐0.499022 0.000000e+00

...

0.499022 ‐0.499022 0.000000e+00

0.500000 ‐0.499022 0.000000e+00

<‐改行

‐0.500000 ‐0.498045 0.000000e+00

‐0.499022 ‐0.498045 0.000000e+00

(51)

gnuplotによる結果の表示

スクリプトの実行

gnuplotのコマンドを記述したファイルを実行

load 'スクリプトファイル名'

anim.gpl

結果を単色の等値面で3次元表示

anim_color.gpl

結果をカラーの等値面で3次元表示

anim_2d.gpl

結果をカラーの等高線で2次元表示

(52)

gnuplotによる結果の表示

スクリプトanim.gplの内容

set xrange [‐0.5:0.5]

x軸の表示範囲を‐0.5~0.5に固定

set yrange [‐0.5:0.5]

y軸の表示範囲を‐0.5~0.5に固定

set zrange [0:1]

z軸の表示範囲を0~1に固定

set ticslevel 0

xy平面とz軸の最小値表示位置の差

set hidden3d

隠線消去をする

set view 45,30

視点をx方向45°,y方向30°に設定

set size 1,0.75

グラフの縦横比を1:0.75

unset contour

2次元等高線は表示しない

set surface

3次元で等値面を表示

unset pm3d

カラー表示はしない

以下でファイルを読み込み,3次元表示

splot 'f0.00.txt' using 1:2:3 with line title "t=0.00s"  

...

splot 'f1.00.txt' using 1:2:3 with line title "t=1.00s"  

(53)

gnuplotによる結果の表示

(54)

gnuplotによる結果の表示

スクリプトanim_color.gplの内容

set xrange [‐0.5:0.5]

x軸の表示範囲を‐0.5~0.5に固定

set yrange [‐0.5:0.5]

y軸の表示範囲を‐0.5~0.5に固定

set zrange [0:1]

z軸の表示範囲を0~1に固定

set ticslevel 0

xy平面とz軸の最小値表示位置の差

set hidden3d

隠線消去をする

set view 45,30

視点をx方向45°,y方向30°に設定

set size 1,0.75

グラフの縦横比を1:0.75

unset contour

2次元等高線は表示しない

set surface

3次元で等値面を表示

set pm3d

カラー表示する

set cbrange[0:1]

カラーバーの表示範囲を0~1に固定

以下でファイルを読み込み,3次元表示

splot 'f0.00.txt' using 1:2:3 with line title "t=0.00s"  

...

splot 'f1.00.txt' using 1:2:3 with line title "t=1.00s"  

(55)

gnuplotによる結果の表示

(56)

gnuplotによる結果の表示

スクリプトanim_2d.gplの内容

set xrange [‐0.5:0.5]

x軸の表示範囲を‐0.5~0.5に固定

set yrange [‐0.5:0.5]

y軸の表示範囲を‐0.5~0.5に固定

set zrange [0:1]

z軸の表示範囲を0~1に固定

set view 0,0

真上から見下ろす

set size 1,1

グラフの縦横比を1:1

set contour

2次元等高線を表示

unset surface

3次元で等値面を表示しない

unset pm3d

カラー表示しない

set cntrparam levels incremental 0,0.1,1

等高線を0から1まで0.1刻みに設定

以下でファイルを読み込み,3次元表示

splot 'f0.00.txt' using 1:2:3 with line title "t=0.00s"  

...

splot 'f1.00.txt' using 1:2:3 with line title "t=1.00s"  

(57)

gnuplotによる結果の表示

(58)

様々な境界条件の計算

共有メモリを利用してキャッシュを模擬

共有メモリに付加的な領域を追加

袖領域

(59)

様々な境界条件の計算

共有メモリを利用してキャッシュを模擬

共有メモリに付加的な領域を追加

(60)

様々な境界条件の計算

共有メモリを利用してキャッシュを模擬

共有メモリに付加的な領域を追加

f[]

sf[][]

f_lap[]

(61)

付加的な領域(袖領域)の取り扱い

データがグローバルメモリに存在する場合は,グローバ

ルメモリから読み込み

f[]

sf[][]

f_lap[]

(62)

付加的な領域(袖領域)の取り扱い

グローバルメモリに無い場合は境界条件から決定

f[]

sf[][]

(63)

境界条件

1階微分が0

0

2

1 , 1 ,

 

Δy

f

f

y

f

i j i j 1 , 1 ,j i j i

f

f

0

2

, 1 , 1

 

Δx

f

f

x

u

i j i j j i j i

f

f

1,

1,

(64)

境界条件

2階微分が0

0

2

2 , 1 , , 1 2 2

 

Δx

f

f

f

x

f

i j i j i j j i j i j i

f

f

f

1,

2

,

1, 1 , , 1 ,j

2

i j i j i

f

f

f

0

2

2 1 , , 1 , 2 2

 

Δy

f

f

f

y

f

i j i j i j

(65)

境界条件

2階微分を片側差分で計算

3 , 2 , 1 , , 1 ,j

4

i j

6

i j

4

i j

i ji

f

f

f

f

f

2 3 , 2 , 1 , , 1 , 2 2

2

5

4

Δy

f

f

f

f

y

f

i j i j i j i j j i   

 2 , 3 , 2 , 1 , , 1 2 2

2

5

4

Δx

f

f

f

f

x

f

i j i j i j i j j i   

j i j i j i j i j i

f

f

f

f

f

1,

4

,

6

1,

4

2,

3,

(66)

#define THREADX 16 #define THREADY 16 #define BLOCKX  (Nx/THREADX) #define BLOCKY  (Ny/THREADY) __global__ void laplacian(double *f, double *f_lap){ int i,j,ij; int tx,ty; __shared__ float sf[1+THREADX+1][1+THREADY+1]; i = blockIdx.x*blockDim.x + threadIdx.x; j = blockIdx.y*blockDim.y + threadIdx.y; tx = threadIdx.x + 1; ty = threadIdx.y + 1; ij = i+Nx*j; sf[tx][ty] = f[ij]; __syncthreads(); if(blockIdx.x == 0 && threadIdx.x == 0) sf[tx‐1][ty] = sf[tx+1][ty]; if(blockIdx.x != 0 && threadIdx.x == 0) sf[tx‐1][ty] =  f[i‐1+Nx*j]; if(blockIdx.x == gridDim.x‐1  && threadIdx.x == blockDim.x‐1) sf[tx+1][ty] = sf[tx‐1][ty]; if(blockIdx.x != gridDim.x‐1 && threadIdx.x == blockDim.x‐1) sf[tx+1][ty] =  f[i+1+Nx*j]; if(blockIdx.y == 0 && threadIdx.y == 0) sf[tx][ty‐1] = sf[tx][ty+1]; if(blockIdx.y != 0 && threadIdx.y == 0) sf[tx][ty‐1] =  f[i+Nx*(j‐1)]; if(blockIdx.y == gridDim.y‐1 && threadIdx.y == blockDim.y‐1) sf[tx][ty+1] = sf[tx][ty‐1]; if(blockIdx.y != gridDim.y‐1 && threadIdx.y == blockDim.y‐1) sf[tx][ty+1] =  f[i+Nx*(j+1)]; __syncthreads(); f_lap[ij] = (sf[tx‐1][ty  ]‐2.0*sf[tx][ty]+sf[tx+1][ty  ])/dxdx +(sf[tx  ][ty‐1]‐2.0*sf[tx][ty]+sf[tx  ][ty+1])/dydy; }

GPUプログラム(ラプラシアンkernel)

dif2.cu

(67)

GPUプログラム(ラプラシアンkernel)

ブロック内のスレッド数+袖領域分の共有メモ

リを確保

袖領域があるために添字の対応が変化

添字の対応を考えないと,必要なデータを袖領域に置い

てしまう

__syncthreads()を呼んでスレッドを同期

共有メモリにデータが正しく書き込まれた事を保証

__shared__ float sf[

1+

THREADX

+1

][

1+

THREADY

+1

];

i = blockIdx.x*blockDim.x + threadIdx.x;

j = blockIdx.y*blockDim.y + threadIdx.y;

tx = threadIdx.x;

ty = threadIdx.y;  

ij = i+Ny*j;

sf[tx][ty] = f[ij];

__syncthreads();

f[]

sf[][]

(68)

GPUプログラム(ラプラシアンkernel)

ブロック内のスレッド数+袖領域分の共有メモ

リを確保

袖領域があるために添字の対応が変化

添字の対応を考えないと,必要なデータを袖領域に置い

てしまう

__syncthreads()を呼んでスレッドを同期

共有メモリにデータが正しく書き込まれた事を保証

__shared__ float sf[

1+

THREADX

+1

][

1+

THREADY

+1

];

i = blockIdx.x*blockDim.x + threadIdx.x;

j = blockIdx.y*blockDim.y + threadIdx.y;

tx = threadIdx.x

+ 1

;

ty = threadIdx.y;  

ij = i+Ny*j;

sf[tx][ty] = f[ij];

__syncthreads();

f[]

sf[][]

(69)

GPUプログラム(ラプラシアンkernel)

ブロック内のスレッド数+袖領域分の共有メモ

リを確保

袖領域があるために添字の対応が変化

添字の対応を考えないと,必要なデータを袖領域に置い

てしまう

__syncthreads()を呼んでスレッドを同期

共有メモリにデータが正しく書き込まれた事を保証

__shared__ float sf[

1+

THREADX

+1

][

1+

THREADY

+1

];

i = blockIdx.x*blockDim.x + threadIdx.x;

j = blockIdx.y*blockDim.y + threadIdx.y;

tx = threadIdx.x

+ 1

;

ty = threadIdx.y

+ 1

;  

ij = i+Ny*j;

sf[tx][ty] = f[ij];

__syncthreads();

f[]

sf[][]

+1

(70)

GPUプログラム(ラプラシアンkernel)

袖領域の設定

if(blockIdx.x == 0       && threadIdx.x == 0       ) sf[tx‐1][ty] = sf[tx+1][ty]; if(blockIdx.x != 0       && threadIdx.x == 0       ) sf[tx‐1][ty] =  f[i‐1+Nx*j];

if(blockIdx.x == gridDim.x‐1 && threadIdx.x == blockDim.x‐1) sf[tx+1][ty] = sf[tx‐1][ty]; if(blockIdx.x != gridDim.x‐1 && threadIdx.x == blockDim.x‐1) sf[tx+1][ty] =  f[i+1+Nx*j];

if(blockIdx.y == 0       && threadIdx.y == 0       ) sf[tx][ty‐1] = sf[tx][ty+1]; if(blockIdx.y != 0       && threadIdx.y == 0       ) sf[tx][ty‐1] =  f[i+Nx*(j‐1)];

if(blockIdx.y == gridDim.y‐1 && threadIdx.y == blockDim.y‐1) sf[tx][ty+1] = sf[tx][ty‐1]; if(blockIdx.y != gridDim.y‐1 && threadIdx.y == blockDim.y‐1) sf[tx][ty+1] =  f[i+Nx*(j+1)];

__syncthreads();

グローバルメモリにデータがある箇所はグロー

バルメモリから読み込み

グローバルメモリにデータがない箇所は2階微

分が0になるように袖領域の値を決定

ブロックが境界に接しているか否かで処理を切替

(71)

GPUプログラム(ラプラシアンkernel)

共有メモリのデータを利用してラプラシアンを計算

if分岐を排除

f_lap[ij] = (sf[tx‐1][ty  ]‐2.0*sf[tx][ty]+sf[tx+1][ty  ])/dxdx

+(sf[tx  ][ty‐1]‐2.0*sf[tx][ty]+sf[tx  ][ty+1])/dydy;

f_lap[]

sf[][]

(72)

GPUプログラムの評価

共有メモリを使用したdif2.cu

dif1.cu(共有メモリ不使用)と比較して遅い

Fermi世代以降のGPUはキャッシュを搭載

共有メモリを使っても速くならない

共有メモリへ明示的にデータを移動

余分な処理により負荷が増加

せっかくなのでif文の書き方について評価

(73)

GPUプログラムの評価

if分岐の書き方による実行速度の変化

ブロックとスレッドの条件を同時に記述

Warp内のスレッドが分岐すると実行速度が著しく低下

GPUはブロック単位で分岐,Warp単位で分岐しても実行速度

の低下は抑えられる

上の書き方は最適ではない?

ブロック単位の分岐とスレッド単位の分岐を同時に記述

if(blockIdx.x == 0       && threadIdx.x == 0       ) sf[tx‐1][ty] = sf[tx+1][ty]; if(blockIdx.x != 0       && threadIdx.x == 0       ) sf[tx‐1][ty] =  f[i‐1+Nx*j];

if(blockIdx.x == gridDim.x‐1 && threadIdx.x == blockDim.x‐1) sf[tx+1][ty] = sf[tx‐1][ty]; if(blockIdx.x != gridDim.x‐1 && threadIdx.x == blockDim.x‐1) sf[tx+1][ty] =  f[i+1+Nx*j];

if(blockIdx.y == 0       && threadIdx.y == 0       ) sf[tx][ty‐1] = sf[tx][ty+1]; if(blockIdx.y != 0       && threadIdx.y == 0       ) sf[tx][ty‐1] =  f[i+Nx*(j‐1)];

if(blockIdx.y == gridDim.y‐1 && threadIdx.y == blockDim.y‐1) sf[tx][ty+1] = sf[tx][ty‐1]; if(blockIdx.y != gridDim.y‐1 && threadIdx.y == blockDim.y‐1) sf[tx][ty+1] =  f[i+Nx*(j+1)];

(74)

GPUプログラムの評価

if分岐の書き方による実行速度の変化

ブロックとスレッドの条件を分け,ifを入れ子に

ブロック単位で分岐した後,スレッド単位で分岐

ブロック単位での分岐による速度低下が抑えられそうな気がする

ブロック単位の分岐を記述する8個のifは2個一組(同じ色の

行)で排他的に分岐

blockIdx.x==0が成立すると,blockIdx!=0は絶対に成立しない

if(blockIdx.x == 0      )if(threadIdx.x == 0       ) sf[tx‐1][ty] = sf[tx+1][ty]; if(blockIdx.x != 0      )if(threadIdx.x == 0       ) sf[tx‐1][ty] =  f[i‐1+Nx*j];

if(blockIdx.x == gridDim.x‐1)if(threadIdx.x == blockDim.x‐1) sf[tx+1][ty] = sf[tx‐1][ty]; if(blockIdx.x != gridDim.x‐1)if(threadIdx.x == blockDim.x‐1) sf[tx+1][ty] =  f[i+1+Nx*j];

if(blockIdx.y == 0      )if(threadIdx.y == 0       ) sf[tx][ty‐1] = sf[tx][ty+1]; if(blockIdx.y != 0      )if(threadIdx.y == 0       ) sf[tx][ty‐1] =  f[i+Nx*(j‐1)];

if(blockIdx.y == gridDim.y‐1)if(threadIdx.y == blockDim.y‐1) sf[tx][ty+1] = sf[tx][ty‐1]; if(blockIdx.y != gridDim.y‐1)if(threadIdx.y == blockDim.y‐1) sf[tx][ty+1] =  f[i+Nx*(j+1)];

(75)

GPUプログラムの評価

if分岐の書き方による実行速度の変化

2個一組のifの片方をelseに書き換え

2個一組のifにおける条件判断は1回限り

2回ifの条件判断を行うよりは速いような気がする

if(blockIdx.x == 0      ){if(threadIdx.x == 0       ) sf[tx‐1][ty] = sf[tx+1][ty];} else       {if(threadIdx.x == 0       ) sf[tx‐1][ty] =  f[i‐1+Nx*j];}

if(blockIdx.x == gridDim.x‐1){if(threadIdx.x == blockDim.x‐1) sf[tx+1][ty] = sf[tx‐1][ty];} else       {if(threadIdx.x == blockDim.x‐1) sf[tx+1][ty] =  f[i+1+Nx*j];}

if(blockIdx.y == 0      ){if(threadIdx.y == 0       ) sf[tx][ty‐1] = sf[tx][ty+1];} else       {if(threadIdx.y == 0       ) sf[tx][ty‐1] =  f[i+Nx*(j‐1)];}

if(blockIdx.y == gridDim.y‐1){if(threadIdx.y == blockDim.y‐1) sf[tx][ty+1] = sf[tx][ty‐1];} else       {if(threadIdx.y == blockDim.y‐1) sf[tx][ty+1] =  f[i+Nx*(j+1)];}

(76)

if分岐の書き方の違いによる実行速度の変化

格子分割数

N

x

×

N

y

=2048×2048

1ブロックあたりのスレッド数 16×16

if文の書き方

実行時間 [ms]

ブロックとスレッドの条件

を同時に記述

2.29

ブロックとスレッドの条件

を分け,ifを入れ子に

2.49

2個一組のifの片方を

elseに書き換え

2.33

カーネル1

(共有メモリ不使用)

2.00

(77)

その他の処理の高速化

laplacianカーネルとintegrateカーネルの融合

カーネルフュージョン(kernel fusion)

異なる処理を行うカーネルをまとめることでデータを再利用

グローバルメモリへの書き込み,読み出しを回避

__global__ void integrate(double *f, double *f_new){

int i,j,ij;

double f_lap; 

//ある1点のラプラシアンを計算してレジスタに保存

i = blockIdx.x*blockDim.x + threadIdx.x;

j = blockIdx.y*blockDim.y + threadIdx.y;

ij= i+Nx*j;

f_lap = ...

f_new[ij] = f[ij] + dt*DIFF*f_lap;

//f[ij]=f[ij]+...とすると,全ての点のf_lapを計算する前にfを更新する可能性がある

}

(78)

その他の処理の高速化

値の更新

f_newのデータをfにコピーしているだけ

cudaMemcpyで代用可能

__global__ void update(double *f, double *f_new){

int i,j,ij;

i = blockIdx.x*blockDim.x + threadIdx.x;

j = blockIdx.y*blockDim.y + threadIdx.y;

ij

=  i+Nx*j;

f[ij] = f_new[ij];

}

(79)

その他の処理の高速化

値の更新

f_newのデータをfにコピーしているだけ

cudaMemcpyで代用可能

for(n=1;n<=Nt;n++){

integrate<<<Block, Thread>>>(dev_f,dev_f_new);

//update<<<Block, Thread>>>(dev_f,dev_f_new);

cudaMemcpy(dev_f, dev_f_new, Nbytes, cudaMemcpyDeviceToDevice);

}

return 0;

(80)

その他の処理の高速化

値の更新

ダブルバッファリング(第7回授業参照)でコピーを回避するこ

とも可能

double *dev_f[2];

int in=0;out=1‐in;

cudaMalloc((void **)&dev_f[in ],Nbytes);

cudaMalloc((void **)&dev_f[out],Nbytes);

init<<<Block, Thread>>>(dev_f[in

(=0)

], dev_f[out

(=1)

]);

for(n=1;n<=Nt;n++){

//n=1

integrate<<<Block, Thread>>>(dev_f[in

(=0)

],dev_f[out

(=1)

]);

in = out; out = 1‐in; 

//in=1, out=0

}

(81)

その他の処理の高速化

値の更新

ダブルバッファリング(第7回授業参照)でコピーを回避するこ

とも可能

double *dev_f[2];

int in=0;out=1‐in;

cudaMalloc((void **)&dev_f[in ],Nbytes);

cudaMalloc((void **)&dev_f[out],Nbytes);

init<<<Block, Thread>>>(dev_f[in

(=0)

], dev_f[out

(=1)

]);

for(n=1;n<=Nt;n++){

//n=2

integrate<<<Block, Thread>>>(dev_f[in

(=1)

],dev_f[out

(=0)

]);

in = out; out = 1‐in; 

//in=0, out=1

}

(82)

その他の処理の高速化

値の更新

ダブルバッファリング(第7回授業参照)でコピーを回避するこ

とも可能

dev_f[0]

時刻n

dev_f[1]

時刻n+1

integrate<<<Block, Thread>>>(dev_f[in

(=0)

],dev_f[out

(=1)

]);

参照

関連したドキュメント

2.認定看護管理者教育課程サードレベル修了者以外の受験者について、看護系大学院の修士課程

 当図書室は、専門図書館として数学、応用数学、計算機科学、理論物理学の分野の文

板岡優里  芸術学部アート・デザイン表現学科ヒーリング表現領域

清水 悦郎 国立大学法人東京海洋大学 学術研究院海洋電子機械工学部門 教授 鶴指 眞志 長崎県立大学 地域創造学部実践経済学科 講師 クロサカタツヤ 株式会社企 代表取締役.

物質工学課程 ⚕名 電気電子応用工学課程 ⚓名 情報工学課程 ⚕名 知能・機械工学課程

接続対象計画差対応補給電力量は,30分ごとの接続対象電力量がその 30分における接続対象計画電力量を上回る場合に,30分ごとに,次の式