EVaillant / lockfree-object-pool

Object Pool LockFree in Rust
Boost Software License 1.0
43 stars 2 forks source link

Performance Issue for Single Thread on LinearObjectPool #5

Open dave-fl opened 5 months ago

dave-fl commented 5 months ago

Based on the docs this doesn't look right. I am getting much slower numbers with LinearObjectPool.

Edit:

After looking at the code, I can see that the pages are a linked list so as pages become occupied the time required to find a new page increases. I suppose this is to be expected.

     Running benches\my_benchmarks.rs (target\release\deps\my_benchmarks-1aeba3f202e206f0.exe)
Gnuplot not found, using plotters backend
using pool              time:   [1.6677 ms 1.6886 ms 1.7122 ms]
                        change: [+0.2426% +1.6519% +3.1663%] (p = 0.02 < 0.05)
                        Change within noise threshold.
Found 12 outliers among 100 measurements (12.00%)
  1 (1.00%) high mild
  11 (11.00%) high severe

no pool                 time:   [13.575 µs 13.926 µs 14.290 µs]
                        change: [-5.0569% +3.7812% +11.991%] (p = 0.46 > 0.05)
                        No change in performance detected.
Found 3 outliers among 100 measurements (3.00%)
  2 (2.00%) high mild
  1 (1.00%) high severe

using other pool        time:   [242.83 µs 245.38 µs 248.24 µs]
                        change: [+4.1452% +6.1576% +8.3049%] (p = 0.00 < 0.05)
                        Performance has regressed.
Found 2 outliers among 100 measurements (2.00%)
  1 (1.00%) high mild
  1 (1.00%) high severe

Here is the test:

const VEC_SIZE: usize = 16384;
const BATCH_SIZE: usize = 8192;
struct Test {
    id: u64
}

impl Default for Test {
    fn default() -> Self {
        Self {id: 0}
    }
}

fn using_pool(c: &mut Criterion) {
    c.bench_function("using pool", |b| {
        b.iter_batched(
            || {
                let pool = lockfree_object_pool::LinearObjectPool::<Test>::new(|| Default::default(), |_v| {});
                let v: Vec<_> = (0..VEC_SIZE).map(|_| pool.pull()).collect();
                drop(v);
                (pool, Vec::with_capacity(VEC_SIZE))

            },
            |(pool, mut vec)| {
                for index in 0..BATCH_SIZE {
                    vec.insert(index, black_box(pool.pull()));
                }
            },
            BatchSize::SmallInput
        );
    });
}

fn no_pool(c: &mut Criterion) {
    c.bench_function("no pool", |b| {
        b.iter_batched(
            || {
                Vec::with_capacity(VEC_SIZE)

            },
            |mut vec| {
                for index in 0..BATCH_SIZE {
                    vec.insert(index, black_box(Test{id: 0}));
                }
            },
            BatchSize::SmallInput
        );
    });
}

fn using_other_pool(c: &mut Criterion) {
    c.bench_function("using other pool", |b| {
        b.iter_batched(
            || {
                let pool: Pool<Test> = Pool::new(VEC_SIZE, || Test{id: 0});
                (pool, Vec::<Reusable<Test>>::with_capacity(VEC_SIZE))

            },
            |(pool, mut vec)| {
                for index in 0..BATCH_SIZE {
                    let t = pool.try_pull().unwrap();
                    vec.insert(index, black_box(t));
                }
            },
            BatchSize::SmallInput
        );
    });
}

criterion_group!(benches, using_pool, no_pool,using_other_pool);
criterion_main!(benches);
EVaillant commented 5 months ago

Maybe :

which pool do you use

 Pool<Test>

?

dave-fl commented 5 months ago
crate 'object-pool'

When using SpinLockObjectPool things are similar. This is really a function of that page taking O(N) to find. If it was being pulled from a last known free page or tail, things would be different.

EVaillant commented 5 months ago

I add a bench with your test case https://evaillant.github.io/lockfree-object-pool/benches/criterion/reuse/report/index.html I think you're right.

jon-chuang commented 1 month ago

I have atrocious perf for allocation. It was a waste of time for me.