Data-oriented design & programming - A simple benchmark

> Coding, hacking, computer graphics, game dev, and such...
User avatar
fips
Site Admin
Posts: 166
Joined: Wed Nov 12, 2008 9:49 pm
Location: Prague
Contact:

Data-oriented design & programming - A simple benchmark

Post by fips »

Since I've been recently very interested in Data Oriented Design (DOD) and wanted to have some real numbers at hand, I've decided to write a simple benchmark that compares the Object and Data Oriented approaches in a fictive particle system. Basically, I'm trying to measure the processing speed (system throughput) at various particle counts.

The graph below shows the dependency:
Image

Here's my configuration:
Windows Vista 64-bit
Visual Studio 2010
Intel Core 2 Quad Q8400 (4 MB Cache, 2.66 GHz, 1333 MHz FSB)
4GB RAM (at 416 MHz)

And here's the code that drives the benchmark:

Code: Select all

/*
(c) 2012 +++ Filip Stoklas, aka FipS, http://www.4FipS.com +++
THIS CODE IS FREE - LICENSED UNDER THE MIT LICENSE
ARTICLE URL: http://forums.4fips.com/viewtopic.php?f=3&t=588
*/

#include <fs/system.h>
#include <fs/math.h>

using namespace fs;
using namespace fs::system;
using namespace fs::math;

struct Cold_data
{
    char dummy[32];
};

struct Particle_oop
{
    Vector3_f pos;
    Vector3_f vel;
    Vector4_f color;
    float size;
    Uint32 updates_to_death;
    Cold_data cold_data;

    void reset()
    {
        pos = Vector3_f(0.f, 0.f, 0.f);
        vel = Vector3_f(1.f, 1.f, 1.f);
        color = Vector4_f(0.f, 0.f, 0.f, 0.f);
        size = 1.f;
        updates_to_death = 1000000;
    }

    void update(float dt)
    {
        if(updates_to_death > 0) // alive
        {
            --updates_to_death;
            pos += vel * dt;
            color = Vector4_f(0.f, pos.y, 0.f, dt);
            size += pos.x;
        }
    }
};

struct Particle_system_oop // AoS
{
    std::vector<Particle_oop> particles;

    explicit Particle_system_oop(size_t num_particles):
    particles(num_particles)
    {
        for(size_t i = 0, n = particles.size(); i < n; ++i)
            particles[i].reset();
    }

    void update(float dt)
    {
        for(size_t i = 0, n = particles.size(); i < n; ++i)
            particles[i].update(dt);
    }
};

struct Particle_system_dop // SoA
{
    std::vector<Vector3_f> pos_vec;
    std::vector<Vector3_f> vel_vec;
    std::vector<Vector4_f> color_vec;
    std::vector<float> size_vec;
    std::vector<Uint32> updates_to_death_vec;
    std::vector<Cold_data> cold_data_vec;

    explicit Particle_system_dop(size_t num_particles):
    pos_vec(num_particles),
    vel_vec(num_particles),
    color_vec(num_particles),
    size_vec(num_particles),
    updates_to_death_vec(num_particles),
    cold_data_vec(num_particles)
    {
        for(size_t i = 0, n = pos_vec.size(); i < n; ++i)
            reset_particle(i);
    }

    void update(float dt)
    {
        for(size_t i = 0, n = pos_vec.size(); i < n; ++i)
            update_particle(i, dt);
    }

    void reset_particle(size_t i)
    {
        pos_vec[i] = Vector3_f(0.f, 0.f, 0.f);
        vel_vec[i] = Vector3_f(1.f, 1.f, 1.f);
        color_vec[i] = Vector4_f(0.f, 0.f, 0.f, 0.f);
        size_vec[i] = 1.f;
        updates_to_death_vec[i] = 1000000;
    }

    void update_particle(size_t i, float dt)
    {
        Vector3_f &pos = pos_vec[i];
        Vector3_f &vel = vel_vec[i];
        Vector4_f &color = color_vec[i];
        float &size = size_vec[i];
        Uint32 &updates_to_death = updates_to_death_vec[i];

        if(updates_to_death > 0) // alive
        {
            --updates_to_death;
            pos += vel * dt;
            color = Vector4_f(0.f, pos.y, 0.f, dt);
            size += pos.x;
        }
    }
};

struct Particle_system_nop // AoS / no pool
{
    typedef std::unique_ptr<Particle_oop> Particle_oop_ptr;
    std::vector<Particle_oop_ptr> particles;

    explicit Particle_system_nop(size_t num_particles):
    particles(num_particles)
    {
        for(size_t i = 0, n = particles.size(); i < n; ++i)
        {
            particles[i] = std::move(Particle_oop_ptr(new Particle_oop));
            particles[i]->reset();
        }
    }

    void update(float dt)
    {
        for(size_t i = 0, n = particles.size(); i < n; ++i)
            particles[i]->update(dt);
    }
};

double run_particle_system_oop(size_t num_particles, size_t num_iterations)
{
    Particle_system_oop particle_sys_oop(num_particles);
    const double t = time_sec();
    for(size_t j = 0; j < num_iterations; ++j)
        particle_sys_oop.update(.1f);
    const double dur = time_sec() - t;
    printf("run_particle_system_oop(%u, %u) -> %f s\n",
     unsigned(num_particles), unsigned(num_iterations), dur);
    return dur;
}

double run_particle_system_dop(size_t num_particles, size_t num_iterations)
{
    Particle_system_dop particle_sys_dop(num_particles);
    const double t = time_sec();
    for(size_t j = 0; j < num_iterations; ++j)
        particle_sys_dop.update(.1f);
    const double dur = time_sec() - t;
    printf("run_particle_system_dop(%u, %u) -> %f s\n",
     unsigned(num_particles), unsigned(num_iterations), dur);
    return dur;
}

double run_particle_system_nop(size_t num_particles, size_t num_iterations)
{
    Particle_system_nop particle_sys_nop(num_particles);
    const double t = time_sec();
    for(size_t j = 0; j < num_iterations; ++j)
        particle_sys_nop.update(.1f);
    const double dur = time_sec() - t;
    printf("run_particle_system_nop(%u, %u) -> %f s\n",
     unsigned(num_particles), unsigned(num_iterations), dur);
    return dur;
}

int main()
{
    double total_oop = 0.0;
    double total_dop = 0.0;
    double total_nop = 0.0;

    for(size_t j = 1; j <= 100; ++j)
    {
        const size_t num_particles = 1000 * j;
        printf("num_particles = %u\n", (unsigned)num_particles);

        double dur_oop = 0.0;
        double dur_dop = 0.0;
        double dur_nop = 0.0;
        size_t sum_particles = 0;

        for(int i = 1; i <= 5; ++i)
        {
            const size_t num_iterations = 1000 * i;
            dur_oop += run_particle_system_oop(num_particles, num_iterations);
            dur_dop += run_particle_system_dop(num_particles, num_iterations);
            dur_nop += run_particle_system_nop(num_particles, num_iterations);
            sum_particles += num_particles * num_iterations;
        }

        printf("oop -> %f s, dop -> %f s , nop -> %f s\n",
         dur_oop, dur_dop, dur_nop);
        printf("oop -> %f M/s, dop -> %f M/s, nop -> %f M/s\n\n",
         .000001 * sum_particles / dur_oop,
         .000001 * sum_particles / dur_dop,
         .000001 * sum_particles / dur_nop);

        total_oop += dur_oop;
        total_dop += dur_dop;
        total_nop += dur_nop;
    }

    printf("total: oop -> %f s, dop -> %f s, nop -> %f s\n\n",
     total_oop, total_dop, total_nop);

    return 0;
}