Skip navigation

Szinkronizáció

Áttekintés

A közös memória használatakor fontos, hogy a szálak közösen írnak és olvasnak egy memóriaterületet, miközben párhuzamosan futnak.

Ez azért fontos, mert ha egy szál fel akarja használni egy másik szál részeredményét, de az a részeredmény még nem készült el, akkor az első szál fals értéket olvashat ki a memóriából. Ennek emgoldására a blokkon belül a szlakat szinkronizálni lehet a __syncthreads() függvény segítségével.

FONTOS!!! hogy a blokkok közötti szál szinkronizációra nincs lehetőség, csak ha megvárjuk, hogy a kernel lefusson, és másikat indítunk.

Fontos tulajdonsága még a közös memóriának, hogy fordítási időben kell foglalni, tehát a fordításkor meg kell adni a méretét konstans-ként.

A lenti kód a szinkronizációból adódó problémákra mutat egy példát. Ebben a példában a szálak közösen számítanak ki egy képnek 32x32 pixeles részleteit, de az eredményt más szál írja ki, amint ami kiszámítja.

A program teljes forrása letölthető innen, vagy a lap alján megtalálható.

Szinkronizáció hiányának hatása

A program kerete a korábbiakhoz nagyon hasonlít, így most csak a kernellel foglalkozunk.

A közzös memória definiálása a kód elején van __shared__ előtaggal. Az így megadott változókat a fordító közösként kezeli, és a szálak közsen használhatják.

    __shared__ float shared[BLOCKDIM][BLOCKDIM];

Ebben az esetében pont akkora közös memóriát használtunk, amekkora a blokk.

A következő lépés a közös memória kinullázása. Mivel annyi szál van ahány elem a meóriában ezt megtehetjük párhuzamosan úgy, hogy minden szál egy-egy memória elemet nulláz ki.

    shared[threadIdx.x][threadIdx.y] = 0;

    __syncthreads();

A nullázást egy szinkronizáció követi, hogy a további számítás előtt minden elem 0-s értékre álljon be.

Ezek után következhet a számítás. Ebbe a kódban egy szinuszos fehér foltot rajzounk minden 32x32-es négyzetbe, de bármi mást is rajzolhatnánk.

    shared[threadIdx.x][threadIdx.y] = 255 * sinf(threadIdx.x * PI / blockDim.x) * sinf(threadIdx.y * PI / blockDim.y);

A kódnak ezena pontján kellene szinkronizálni. A programban szerepel is a megfelelő függvényhívás, de azt kikommenteztük.

Végül pedig az eredmények kiírása következik a globális memóriába.

    result[tid + 1] = shared[threadIdx.y][threadIdx.x];
    result[tid + 0] = shared[threadIdx.y][threadIdx.x];
    result[tid + 2] = shared[threadIdx.y][threadIdx.x];
    result[tid + 3] = 255;

Itt észre kell venni, hogy minden szál egy másik szál áltel beírt értéket olvas a közös memóriából. Ezért a szinkronizáció hiányában lehet olyan szál, ami az eredeti 0-s értéket olvassa, és lesz olyan, ami az új számítás eredményét. A kimenet pedig a lenti képen látható mintázat.

Szinkronizáció nélküli szajos eredmény

A programot ki lehet próbálni úgy is, hogy visszatesszük a megfelelő helyen kikommentezett szinkronizációt.

A program érdekes pontja még, hogy a kiírásban nem volt ellenőrzés, hogy a kimeneti képen létezik-e az elérni kívánt képont. Ahogy korábban is itt is lehet, hogy nagyobb a blokstruktúra mint maga a kép, és vannak túllógó szálak. Ezeket viszont a kernel elején kezeltük. A túllógó szálak visszatértek, így a futásuk leállt.

Teljes Kód

#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include "math_constants.h"

#include "ImageIO.h"

#include <iostream>
#include <ctime>

using namespace std;

#define N           512
#define BLOCKDIM    32

#define PI 3.1415926535897932

__global__ void kernel(unsigned char* result, int w, int h);

int main(int argc, char** argv)
{
    unsigned char* result = new unsigned char[N*N * 4];
    unsigned char* dev_result;

    cudaMalloc((void**)&dev_result, N*N * 4 * sizeof(unsigned char));

    dim3 blockDim = dim3(BLOCKDIM, BLOCKDIM, 1);
    dim3 gridDim = dim3((N + BLOCKDIM - 1) / BLOCKDIM, (N + BLOCKDIM - 1) / BLOCKDIM, 1);

    kernel << <gridDim, blockDim >> > (dev_result, N, N);

    cudaMemcpy(result, dev_result, N*N * 4 * sizeof(unsigned char), cudaMemcpyDeviceToHost);

    writeRGBImageToFile("image.png", result, N, N);

    return 0;
}

__global__ void kernel(unsigned char* result, int w, int h)
{
    int x = blockDim.x *blockIdx.x + threadIdx.x;
    int y = blockDim.y *blockIdx.y + threadIdx.y;

    int tid = (y*w + x) * 4;

    __shared__ float shared[BLOCKDIM][BLOCKDIM];

    if (x >= w || y >= h)
        return;

    shared[threadIdx.x][threadIdx.y] = 0;

    __syncthreads();

    shared[threadIdx.x][threadIdx.y] = 255 * sinf(threadIdx.x * PI / blockDim.x) * sinf(threadIdx.y * PI / blockDim.y);

    //__syncthreads();

    result[tid + 1] = shared[threadIdx.y][threadIdx.x];
    result[tid + 0] = shared[threadIdx.y][threadIdx.x];
    result[tid + 2] = shared[threadIdx.y][threadIdx.x];
    result[tid + 3] = 255;

    return;
}

Feladatok

  • Javítsuk meg a szinkronizációs problémát!
  • Találjunk ki új feladatot, amiben a szinkronizáció hiánya probléma lehet!