Skip to content
Kavachकवच

GPU: Use cases

Also known as: gpu examples image processing audio simulation mandelbrot particles finance compute shader cuda parallel

● Advanced6 min

Work that does the same thing to many items, with enough math in each, is GPU work: images, audio, maps, finance, science and simulation.

Images: gray from color

Each pixel's brightness, from its red, green and blue, with the weights screens use. The same shape works for brightness, contrast and gamma.

type Rgb {
    r: Float32
    g: Float32
    b: Float32
}

gpu fn gray(r: Float32, g: Float32, b: Float32) -> Float32 {
    0.2126 * r + 0.7152 * g + 0.0722 * b
}

fn main() {
    const colors = [Rgb(r: 1.0, g: 0.0, b: 0.0), Rgb(r: 0.0, g: 1.0, b: 0.0), Rgb(r: 1.0, g: 1.0, b: 1.0)]
    print(colors.map(c => gray(c.r, c.g, c.b)))
}

The program ran and printed:

kavach run gray.kav

[0.2126, 0.7152, 1.0]
gray.kav: Kavach code, then what running it prints

Audio: louder without clipping

Every sample made louder, then clamped to -1 and 1 so it never distorts.

gpu fn limit(sample: Float32, gain: Float32) -> Float32 {
    (sample * gain).clamp(-1.0, 1.0)
}

fn main() {
    const audio: [Float32] = [0.1, -0.3, 0.6, -0.9]
    print(audio.map(s => limit(s, 2.0)))
}

The program ran and printed:

kavach run clamp.kav

[0.2, -0.6, 1.0, -1.0]
clamp.kav: Kavach code, then what running it prints

Maps: distance to every shop

The distance from home to many points, with math.sqrt: the first step of "nearest" searches.

use std.math

type Point {
    x: Float32
    y: Float32
}

gpu fn distance(x: Float32, y: Float32, cx: Float32, cy: Float32) -> Float32 {
    const dx = x - cx
    const dy = y - cy
    math.sqrt(dx * dx + dy * dy)
}

fn main() {
    const shops = [Point(x: 3.0, y: 4.0), Point(x: 6.0, y: 8.0), Point(x: 0.0, y: 1.0)]
    const home_x: Float32 = 0.0
    const home_y: Float32 = 0.0
    print(shops.map(s => distance(s.x, s.y, home_x, home_y)))
}

The program ran and printed:

kavach run distance.kav

[5.0, 10.0, 1.0]
distance.kav: Kavach code, then what running it prints

Finance: savings in ten years

Compound growth for many accounts at once, a loop per item.

gpu fn grow(balance: Float32, rate: Float32, years: Int32) -> Float32 {
    let b = balance
    for _ in 0..years {
        b = b * (1.0 + rate)
    }
    b
}

fn main() {
    const balances: [Float32] = [1000.0, 2500.0, 10000.0]
    const rate: Float32 = 0.07
    print(balances.map(b => grow(b, rate, 10)))
}

The program ran and printed:

kavach run interest.kav

[1967.1522, 4917.882, 19671.527]
interest.kav: Kavach code, then what running it prints

Science: the Mandelbrot set

Heavy work per point: up to 50 rounds each, the kind of job GPUs are built for. Each # is a point that never escaped.

type Point {
    x: Float32
    y: Float32
}

gpu fn escape(cx: Float32, cy: Float32) -> Int32 {
    let x: Float32 = 0.0
    let y: Float32 = 0.0
    let n = 0
    while n < 50 and x * x + y * y <= 4.0 {
        const t = x * x - y * y + cx
        y = 2.0 * x * y + cy
        x = t
        n += 1
    }
    n
}

fn main() {
    let points: [Point] = []
    for row in 0..12 {
        for col in 0..40 {
            points.push(Point(x: -2.2 + Float32(col) * 0.075, y: -1.1 + Float32(row) * 0.2))
        }
    }
    const counts = points.map(p => escape(p.x, p.y))
    for row in 0..12 {
        let line = ""
        for col in 0..40 {
            line += if counts[row * 40 + col] == 50 { "#" } else { "." }
        }
        print(line)
    }
}

The program ran and printed:

kavach run mandelbrot.kav

...........................................................................................................##.................................############...........................##############..................######################..................######################..........................##############...........................############.................................##...........................................................................................
mandelbrot.kav: Kavach code, then what running it prints

Simulation: moving particles

Each particle a record, stepped forward in time.

use std.collections

type Particle {
    x: Float32
    v: Float32
}

gpu fn step(x: Float32, v: Float32) -> Float32 {
    x + v * 0.5
}

gpu fn add(a: Float32, b: Float32) -> Float32 {
    a + b
}

fn main() {
    const particles = [Particle(x: 0.0, v: 2.0), Particle(x: 10.0, v: -4.0)]
    print(particles.map(p => step(p.x, p.v)))

    const xs: [Float32] = [1.0, 2.0, 3.0]
    const ys: [Float32] = [10.0, 20.0, 30.0]
    print(collections.zip(xs, ys).map(t => add(t.0, t.1)))
}

The program ran and printed:

kavach run records.kav

[1.0, 8.0][11.0, 22.0, 33.0]
records.kav: Kavach code, then what running it prints

When not to use the GPU

  • Light work: adding 1 to each number is faster on the CPU than the trip to the GPU.
  • Text, maps and files: a GPU works on numbers only.
  • Sorting and float totals: today these run on all CPU cores, which is already fast; see Loops on All Cores. Whole-number sum, min and max of a list already on the GPU run there.

These lists are short so the output fits on a page; with the same code, long lists run on the GPU.