CUDA 通信と計算のオーバーラップを試した
マルチポスト元→http://exth.net/~tgbt/wordpress/2010/05/19/3155/
昨日はPage-LockedHostMemoryでカオスったわけだが、今日はオーバーラップでカオスることにする。
まぁ昨日のよりは納得がいくものが見えてる。
プログラミングガイドを読む
実験してみる
シンプルに、行列積を繰り返し実行するプログラムを書いた。
今回はカーネルがどうでも良いので、呼び出し側だけ晒す。
(転送と演算のバランスがおいしい必要はあるけど。)
シンプルな繰り返し計算
まずは単純に10回繰り返し。
for(i=0; i<LOOP; i++){ h_out[i] = (float*)malloc(sizeof(float)*nSize*nSize); h_in1[i] = (float*)malloc(sizeof(float)*nSize*nSize); h_in2[i] = (float*)malloc(sizeof(float)*nSize*nSize); cudaMalloc((void**)&d_out[i], sizeof(float)*nSize*nSize); cudaMalloc((void**)&d_in1[i], sizeof(float)*nSize*nSize); cudaMalloc((void**)&d_in2[i], sizeof(float)*nSize*nSize); for(int j=0; j<nSize*nSize; j++){ h_in1[i][j] = frand(); h_in2[i][j] = frand(); } } EventCreate(&alls); EventStart(&alls); for(i=0; i<LOOP; i++){ #ifdef _INNER_TIME EventCreate(&send); EventCreate(&calc); EventCreate(&recv); EventStart(&send); #endif cudaMemcpy(d_in1[i], h_in1[i], sizeof(float)*nSize*nSize, cudaMemcpyHostToDevice); cudaMemcpy(d_in2[i], h_in2[i], sizeof(float)*nSize*nSize, cudaMemcpyHostToDevice); #ifdef _INNER_TIME EventStop(&send); EventStart(&calc); #endif mmkernel<<<grid, block>>>(d_out[i], d_in1[i], d_in2[i], nSize); cutilSafeCall(cudaThreadSynchronize()); #ifdef _INNER_TIME EventStop(&calc); EventStart(&recv); #endif cudaMemcpy(h_out[i], d_out[i], sizeof(float)*nSize*nSize, cudaMemcpyDeviceToHost); #ifdef _INNER_TIME EventStop(&recv); EventDelete(&send); EventDelete(&calc); EventDelete(&recv); float fSend, fCalc, fRecv; fSend = GetEventTime(&send); fCalc = GetEventTime(&calc); fRecv = GetEventTime(&recv); printf(" send : %f nsec, %f MByte/sec\n", fSend*1000.0f, (double)(sizeof(float)*nSize*nSize)/1024.0/1024.0/(fSend/1000.0)*2.0); printf(" calc : %f nsec\n", fCalc*1000.0f); printf(" recv : %f nsec, %f MByte/sec\n", fRecv*1000.0f, (double)(sizeof(float)*nSize*nSize)/1024.0/1024.0/(fRecv/1000.0)); #endif } EventStop(&alls); EventDelete(&alls);
LOOPは定数。#ifdefで内部の実行時間確認をON/OFFできるようにしてある。
実行結果は以下の通り。
全体のみ測定: all : 22197.503906 nsec 内部も測定: send : 729.600037 nsec, 2741.228024 MByte/sec calc : 886.432007 nsec recv : 685.311951 nsec, 1459.189448 MByte/sec send : 716.607971 nsec, 2790.926188 MByte/sec calc : 857.280029 nsec recv : 677.247986 nsec, 1476.563974 MByte/sec send : 703.296021 nsec, 2843.752821 MByte/sec calc : 857.919983 nsec recv : 683.104004 nsec, 1463.905981 MByte/sec send : 686.272034 nsec, 2914.296266 MByte/sec calc : 863.712036 nsec recv : 677.824036 nsec, 1475.309180 MByte/sec send : 695.936035 nsec, 2873.827379 MByte/sec calc : 861.024048 nsec recv : 676.639954 nsec, 1477.890811 MByte/sec send : 690.720032 nsec, 2895.529212 MByte/sec calc : 860.319946 nsec recv : 683.583984 nsec, 1462.878062 MByte/sec send : 685.087952 nsec, 2919.333081 MByte/sec calc : 863.648010 nsec recv : 682.847961 nsec, 1464.454804 MByte/sec send : 682.015991 nsec, 2932.482458 MByte/sec calc : 860.416016 nsec recv : 676.736023 nsec, 1477.681111 MByte/sec send : 692.607971 nsec, 2887.636301 MByte/sec calc : 863.615967 nsec recv : 675.328003 nsec, 1480.761905 MByte/sec send : 693.888000 nsec, 2882.309501 MByte/sec calc : 864.416016 nsec recv : 680.351990 nsec, 1469.827443 MByte/sec all : 22779.103516 nsec
直感的に全てのsend/calc/recvを足したらallになってる気がする。
HostAllocした場合
続いてHostAlloc版、やはり10回繰り返し。
for(i=0; i<LOOP; i++){ cutilSafeCall(cudaHostAlloc((void**)&h_out[i], sizeof(float)*nSize*nSize, 0)); cutilSafeCall(cudaHostAlloc((void**)&h_in1[i], sizeof(float)*nSize*nSize, 0)); cutilSafeCall(cudaHostAlloc((void**)&h_in2[i], sizeof(float)*nSize*nSize, 0)); cudaMalloc((void**)&d_out[i], sizeof(float)*nSize*nSize); cudaMalloc((void**)&d_in1[i], sizeof(float)*nSize*nSize); cudaMalloc((void**)&d_in2[i], sizeof(float)*nSize*nSize); for(int j=0; j<nSize*nSize; j++){ h_in1[i][j] = frand(); h_in2[i][j] = frand(); } } EventCreate(&alls); EventStart(&alls); for(i=0; i<LOOP; i++){ #ifdef _INNER_TIME EventCreate(&send); EventCreate(&calc); EventCreate(&recv); EventStart(&send); #endif cudaMemcpy(d_in1[i], h_in1[i], sizeof(float)*nSize*nSize, cudaMemcpyHostToDevice); cudaMemcpy(d_in2[i], h_in2[i], sizeof(float)*nSize*nSize, cudaMemcpyHostToDevice); #ifdef _INNER_TIME EventStop(&send); EventStart(&calc); #endif mmkernel<<<grid, block>>>(d_out[i], d_in1[i], d_in2[i], nSize); cutilSafeCall(cudaThreadSynchronize()); #ifdef _INNER_TIME EventStop(&calc); EventStart(&recv); #endif cudaMemcpy(h_out[i], d_out[i], sizeof(float)*nSize*nSize, cudaMemcpyDeviceToHost); #ifdef _INNER_TIME EventStop(&recv); EventDelete(&send); EventDelete(&calc); EventDelete(&recv); float fSend, fCalc, fRecv; fSend = GetEventTime(&send); fCalc = GetEventTime(&calc); fRecv = GetEventTime(&recv); printf(" send : %f nsec, %f MByte/sec\n", fSend*1000.0f, (double)(sizeof(float)*nSize*nSize)/1024.0/1024.0/(fSend/1000.0)*2.0); printf(" calc : %f nsec\n", fCalc*1000.0f); printf(" recv : %f nsec, %f MByte/sec\n", fRecv*1000.0f, (double)(sizeof(float)*nSize*nSize)/1024.0/1024.0/(fRecv/1000.0)); #endif } EventStop(&alls); EventDelete(&alls);
実行結果は以下の通り。
全体: all : 13931.423828 nsec 詳細: send : 370.175995 nsec, 5402.835591 MByte/sec calc : 884.576050 nsec recv : 179.008011 nsec, 5586.342290 MByte/sec send : 366.944000 nsec, 5450.423172 MByte/sec calc : 857.536011 nsec recv : 179.711990 nsec, 5564.458767 MByte/sec send : 366.272003 nsec, 5460.422818 MByte/sec calc : 857.760010 nsec recv : 178.783997 nsec, 5593.341752 MByte/sec send : 365.824005 nsec, 5467.109659 MByte/sec calc : 856.192017 nsec recv : 178.975998 nsec, 5587.341340 MByte/sec send : 369.376007 nsec, 5414.536894 MByte/sec calc : 859.711975 nsec recv : 178.688004 nsec, 5596.346560 MByte/sec send : 367.040009 nsec, 5448.997261 MByte/sec calc : 858.911987 nsec recv : 179.104004 nsec, 5583.348212 MByte/sec send : 367.488007 nsec, 5442.354627 MByte/sec calc : 860.032043 nsec recv : 178.783997 nsec, 5593.341752 MByte/sec send : 366.239990 nsec, 5460.900034 MByte/sec calc : 858.976013 nsec recv : 178.847992 nsec, 5591.340183 MByte/sec send : 366.751984 nsec, 5453.276348 MByte/sec calc : 858.880005 nsec recv : 179.360001 nsec, 5575.379055 MByte/sec send : 366.048004 nsec, 5463.764193 MByte/sec calc : 858.015991 nsec recv : 179.360001 nsec, 5575.379055 MByte/sec all : 14384.480469 nsec
転送速度が向上している。あとは単純に足しあわせたのと同じくらいっぽい。妥当。
Mappedメモリを使ってみた場合
昨日はまった(芳しい結果が得られなかった)Mappedメモリに挑戦。
for(i=0; i<LOOP; i++){ cutilSafeCall(cudaHostAlloc((void**)&h_out[i], sizeof(float)*nSize*nSize, cudaHostAllocMapped)); cutilSafeCall(cudaHostAlloc((void**)&h_in1[i], sizeof(float)*nSize*nSize, cudaHostAllocMapped)); cutilSafeCall(cudaHostAlloc((void**)&h_in2[i], sizeof(float)*nSize*nSize, cudaHostAllocMapped)); cutilSafeCall(cudaHostGetDevicePointer((void**)&d_out[i], (void*)h_out[i], 0)); cutilSafeCall(cudaHostGetDevicePointer((void**)&d_in1[i], (void*)h_in1[i], 0)); cutilSafeCall(cudaHostGetDevicePointer((void**)&d_in2[i], (void*)h_in2[i], 0)); for(int j=0; j<nSize*nSize; j++){ h_in1[i][j] = frand(); h_in2[i][j] = frand(); } } EventCreate(&alls); EventStart(&alls); for(i=0; i<LOOP; i++){ #ifdef _INNER_TIME EventCreate(&calc); EventStart(&calc); #endif mmkernel<<<grid, block>>>(d_out[i], d_in1[i], d_in2[i], nSize); //cutilSafeCall(cudaThreadSynchronize()); #ifdef _INNER_TIME EventStop(&calc); EventDelete(&calc); float fCalc; fCalc = GetEventTime(&calc); printf(" calc : %f nsec\n", fCalc*1000.0f); #endif } EventStop(&alls); EventDelete(&alls);
これって、この範囲が終わった時点でデータはどこにあるんだろうね。このあと結果を読み出しているんだけど、そこで初めて転送されるのかしら?
実行結果は以下の通り。
全体: all : 80195.867188 nsec 内部: calc : 7945.184082 nsec calc : 8100.064453 nsec calc : 8799.391602 nsec calc : 8135.999512 nsec calc : 8501.215820 nsec calc : 8317.055664 nsec calc : 8582.559570 nsec calc : 8350.496094 nsec calc : 8150.367676 nsec calc : 8263.648438 nsec all : 83270.179688 nsec
……あれ???
どう見ても実行時間が延びまくってる。まぁ合計で83msecしかかかってないんだけどさ……。
Streamを使ってみた場合
Streamを使ってオーバーラップ、というのも有りみたいなので試してみた。
for(i=0; i<LOOP; i++){ cutilSafeCall(cudaHostAlloc((void**)&h_out[i], sizeof(float)*nSize*nSize, 0)); cutilSafeCall(cudaHostAlloc((void**)&h_in1[i], sizeof(float)*nSize*nSize, 0)); cutilSafeCall(cudaHostAlloc((void**)&h_in2[i], sizeof(float)*nSize*nSize, 0)); cudaMalloc((void**)&d_out[i], sizeof(float)*nSize*nSize); cudaMalloc((void**)&d_in1[i], sizeof(float)*nSize*nSize); cudaMalloc((void**)&d_in2[i], sizeof(float)*nSize*nSize); for(int j=0; j<nSize*nSize; j++){ h_in1[i][j] = frand(); h_in2[i][j] = frand(); } } cudaStream_t *stream; stream = (cudaStream_t*)malloc(sizeof(cudaStream_t)*LOOP); EventCreate(&alls); EventStart(&alls); for(i=0; i<LOOP; i++){ cudaStreamCreate(&stream[i]); } for(i=0; i<LOOP; i++){ cudaMemcpyAsync(d_in1[i], h_in1[i], sizeof(float)*nSize*nSize, cudaMemcpyHostToDevice, stream[i]); cudaMemcpyAsync(d_in2[i], h_in2[i], sizeof(float)*nSize*nSize, cudaMemcpyHostToDevice, stream[i]); } for(i=0; i<LOOP; i++){ mmkernel<<<grid, block, 0, stream[i]>>>(d_out[i], d_in1[i], d_in2[i], nSize); } for(i=0; i<LOOP; i++){ cudaMemcpyAsync(h_out[i], d_out[i], sizeof(float)*nSize*nSize, cudaMemcpyDeviceToHost); } EventStop(&alls); EventDelete(&alls);
実行結果は以下の通り。
全体: all : 9522.400391 nsec 内部: ……って、どうすりゃいいんだ?Asyncの測定したら意味ないよね?
HostAllocが
- send 370
- calc 860
- recv 180
だった。理想的に考えると
send+calc*10+recv=9150
なわけだが、これはあながち外れていないなぁ。
ついでにGeForce285でも実験してみた
以下、順番にまとめて貼り付け。
デフォ: all : 32609.570312 nsec send : 742.496033 nsec, 2693.617155 MByte/sec calc : 1901.280029 nsec recv : 705.120056 nsec, 1418.198266 MByte/sec send : 727.263977 nsec, 2750.033049 MByte/sec calc : 1865.216064 nsec recv : 698.848022 nsec, 1430.926306 MByte/sec send : 720.671997 nsec, 2775.187559 MByte/sec calc : 1869.439941 nsec recv : 695.743958 nsec, 1437.310320 MByte/sec send : 702.304016 nsec, 2847.769604 MByte/sec calc : 1858.239990 nsec recv : 688.864014 nsec, 1451.665366 MByte/sec send : 707.647949 nsec, 2826.263988 MByte/sec calc : 1865.952026 nsec recv : 688.480042 nsec, 1452.474976 MByte/sec send : 710.304016 nsec, 2815.695728 MByte/sec calc : 1867.167969 nsec recv : 696.063965 nsec, 1436.649513 MByte/sec send : 696.127991 nsec, 2873.034800 MByte/sec calc : 1860.735962 nsec recv : 689.408020 nsec, 1450.519857 MByte/sec send : 708.031982 nsec, 2824.731038 MByte/sec calc : 1878.559937 nsec recv : 686.496033 nsec, 1456.672685 MByte/sec send : 702.911987 nsec, 2845.306492 MByte/sec calc : 1866.719971 nsec recv : 696.447998 nsec, 1435.857321 MByte/sec send : 693.632019 nsec, 2883.373288 MByte/sec calc : 1864.255981 nsec recv : 692.063965 nsec, 1444.953095 MByte/sec all : 33175.871094 nsec HostAlloc: all : 24316.193359 nsec send : 382.048004 nsec, 5234.944152 MByte/sec calc : 1881.760010 nsec recv : 203.743988 nsec, 4908.120133 MByte/sec send : 377.344025 nsec, 5300.203356 MByte/sec calc : 1864.031982 nsec recv : 203.743988 nsec, 4908.120133 MByte/sec send : 379.743988 nsec, 5266.706085 MByte/sec calc : 1858.080078 nsec recv : 203.328003 nsec, 4918.161824 MByte/sec send : 377.951996 nsec, 5291.677118 MByte/sec calc : 1857.471924 nsec recv : 205.056000 nsec, 4876.716681 MByte/sec send : 379.135986 nsec, 5275.151979 MByte/sec calc : 1867.104004 nsec recv : 204.255997 nsec, 4895.817059 MByte/sec send : 378.527985 nsec, 5283.625004 MByte/sec calc : 1869.567993 nsec recv : 204.288010 nsec, 4895.049986 MByte/sec send : 377.855988 nsec, 5293.021871 MByte/sec calc : 1865.216064 nsec recv : 203.871994 nsec, 4905.038571 MByte/sec send : 379.103973 nsec, 5275.597359 MByte/sec calc : 1874.752075 nsec recv : 203.520004 nsec, 4913.522013 MByte/sec send : 377.183990 nsec, 5302.451803 MByte/sec calc : 1866.271973 nsec recv : 204.160004 nsec, 4898.119006 MByte/sec send : 378.688019 nsec, 5281.392492 MByte/sec calc : 1869.119995 nsec recv : 204.255997 nsec, 4895.817059 MByte/sec all : 24895.201172 nsec Mapped: all : 228410.281250 nsec calc : 22700.255859 nsec calc : 22870.496094 nsec calc : 24236.798828 nsec calc : 22738.974609 nsec calc : 22684.255859 nsec calc : 23392.416016 nsec calc : 22410.273438 nsec calc : 24593.183594 nsec calc : 23837.728516 nsec calc : 23719.263672 nsec all : 233330.015625 nsec Overlap: all : 21263.423828 nsec all : 21241.408203 nsec
まぁ同じような傾向に見えるね。
つーわけで、とりあえずStreamを使ったオーバーラップはできた。Streamごとに使うデータが分かれていないと行けないはずなので、使いどころは注意が必要なんだろうな。
あと、Mappedメモリがやっぱり駄目だ。何か設定間違えるのかなあ?
cudaSetDeviceFlags(cudaDeviceMapHost);
はちゃんとやってるんだけどなあ……。
もっとでかい問題で試さないと駄目だったりするのだろうか。難しいのう。