GTests: BLI_task: Add basic tests for BLI_task_parallel_listbase(), and some performances benchmarks.

Nothing special to mention about regression test itself, it basically
mimics the one for `BLI_task_parallel_mempool()`...

Basic performances benchmarks do not tell us much, besides the fact that
for very light processing of listbase, even with 100k items,
single-thread remains an order of magnitude faster than threaded code.
Synchronization is just way too expensive in that case with current
code. This should be partially solvable with much bigger (and
configurable) chunk sizes though (current ones are just ridiculous
for such cases ;) )...
This commit is contained in:
Bastien Montagne 2019-06-04 23:23:55 +02:00
parent 30d9366d17
commit 1324659dee
3 changed files with 242 additions and 1 deletions

@ -0,0 +1,192 @@
/* Apache License, Version 2.0 */
#include "testing/testing.h"
#include "BLI_ressource_strings.h"
#include "atomic_ops.h"
#define GHASH_INTERNAL_API
extern "C" {
#include "BLI_utildefines.h"
#include "BLI_listbase.h"
#include "BLI_mempool.h"
#include "BLI_task.h"
#include "PIL_time.h"
#include "MEM_guardedalloc.h"
}
/* *** Parallel iterations over double-linked list items. *** */
#define NUM_RUN_AVERAGED 100
static uint gen_pseudo_random_number(uint num)
{
/* Note: this is taken from BLI_ghashutil_uinthash(), don't want to depend on external code that
* might change here... */
num += ~(num << 16);
num ^= (num >> 5);
num += (num << 3);
num ^= (num >> 13);
num += ~(num << 9);
num ^= (num >> 17);
/* Make final number in [65 - 16385] range. */
return ((num & 255) << 6) + 1;
}
static void task_listbase_light_iter_func(void *UNUSED(userdata), Link *item, int index)
{
LinkData *data = (LinkData *)item;
data->data = POINTER_FROM_INT(POINTER_AS_INT(data->data) + index);
}
static void task_listbase_light_membarrier_iter_func(void *userdata, Link *item, int index)
{
LinkData *data = (LinkData *)item;
int *count = (int *)userdata;
data->data = POINTER_FROM_INT(POINTER_AS_INT(data->data) + index);
atomic_sub_and_fetch_uint32((uint32_t *)count, 1);
}
static void task_listbase_heavy_iter_func(void *UNUSED(userdata), Link *item, int index)
{
LinkData *data = (LinkData *)item;
/* 'Random' number of iterations. */
const uint num = gen_pseudo_random_number((uint)index);
for (uint i = 0; i < num; i++) {
data->data = POINTER_FROM_INT(POINTER_AS_INT(data->data) + ((i % 2) ? -index : index));
}
}
static void task_listbase_heavy_membarrier_iter_func(void *userdata, Link *item, int index)
{
LinkData *data = (LinkData *)item;
int *count = (int *)userdata;
/* 'Random' number of iterations. */
const uint num = gen_pseudo_random_number((uint)index);
for (uint i = 0; i < num; i++) {
data->data = POINTER_FROM_INT(POINTER_AS_INT(data->data) + ((i % 2) ? -index : index));
}
atomic_sub_and_fetch_uint32((uint32_t *)count, 1);
}
static void task_listbase_test_do(ListBase *list,
const int num_items,
int *num_items_tmp,
const char *id,
TaskParallelListbaseFunc func,
const bool use_threads,
const bool check_num_items_tmp)
{
double averaged_timing = 0.0;
for (int i = 0; i < NUM_RUN_AVERAGED; i++) {
const double init_time = PIL_check_seconds_timer();
BLI_task_parallel_listbase(list, num_items_tmp, func, use_threads);
averaged_timing += PIL_check_seconds_timer() - init_time;
/* Those checks should ensure us all items of the listbase were processed once, and only once -
* as expected. */
if (check_num_items_tmp) {
EXPECT_EQ(*num_items_tmp, 0);
}
LinkData *item;
int j;
for (j = 0, item = (LinkData *)list->first; j < num_items && item != NULL;
j++, item = item->next) {
EXPECT_EQ(POINTER_AS_INT(item->data), j);
item->data = POINTER_FROM_INT(0);
}
EXPECT_EQ(num_items, j);
*num_items_tmp = num_items;
}
printf("\t%s: done in %fs on average over %d runs\n",
id,
averaged_timing / NUM_RUN_AVERAGED,
NUM_RUN_AVERAGED);
}
static void task_listbase_test(const char *id, const int nbr, const bool use_threads)
{
printf("\n========== STARTING %s ==========\n", id);
ListBase list = {NULL, NULL};
LinkData *items_buffer = (LinkData *)MEM_calloc_arrayN(nbr, sizeof(*items_buffer), __func__);
BLI_threadapi_init();
int num_items = 0;
for (int i = 0; i < nbr; i++) {
BLI_addtail(&list, &items_buffer[i]);
num_items++;
}
int num_items_tmp = num_items;
task_listbase_test_do(&list,
num_items,
&num_items_tmp,
"Light iter",
task_listbase_light_iter_func,
use_threads,
false);
task_listbase_test_do(&list,
num_items,
&num_items_tmp,
"Light iter with mem barrier",
task_listbase_light_membarrier_iter_func,
use_threads,
true);
task_listbase_test_do(&list,
num_items,
&num_items_tmp,
"Heavy iter",
task_listbase_heavy_iter_func,
use_threads,
false);
task_listbase_test_do(&list,
num_items,
&num_items_tmp,
"Heavy iter with mem barrier",
task_listbase_heavy_membarrier_iter_func,
use_threads,
true);
MEM_freeN(items_buffer);
BLI_threadapi_exit();
printf("========== ENDED %s ==========\n\n", id);
}
TEST(task, ListBaseIterNoThread10k)
{
task_listbase_test("ListBase parallel iteration - Single thread - 10000 items", 10000, false);
}
TEST(task, ListBaseIter10k)
{
task_listbase_test("ListBase parallel iteration - Threaded - 10000 items", 10000, true);
}
TEST(task, ListBaseIterNoThread100k)
{
task_listbase_test("ListBase parallel iteration - Single thread - 100000 items", 100000, false);
}
TEST(task, ListBaseIter100k)
{
task_listbase_test("ListBase parallel iteration - Threaded - 100000 items", 100000, true);
}

@ -6,13 +6,19 @@
#include "atomic_ops.h"
extern "C" {
#include "BLI_utildefines.h"
#include "BLI_listbase.h"
#include "BLI_mempool.h"
#include "BLI_task.h"
#include "BLI_utildefines.h"
#include "MEM_guardedalloc.h"
};
#define NUM_ITEMS 10000
/* *** Parallel iterations over mempool items. *** */
static void task_mempool_iter_func(void *userdata, MempoolIterData *item)
{
int *data = (int *)item;
@ -79,3 +85,45 @@ TEST(task, MempoolIter)
BLI_mempool_destroy(mempool);
BLI_threadapi_exit();
}
/* *** Parallel iterations over double-linked list items. *** */
static void task_listbase_iter_func(void *userdata, Link *item, int index)
{
LinkData *data = (LinkData *)item;
int *count = (int *)userdata;
data->data = POINTER_FROM_INT(POINTER_AS_INT(data->data) + index);
atomic_sub_and_fetch_uint32((uint32_t *)count, 1);
}
TEST(task, ListBaseIter)
{
ListBase list = {NULL, NULL};
LinkData *items_buffer = (LinkData *)MEM_calloc_arrayN(
NUM_ITEMS, sizeof(*items_buffer), __func__);
BLI_threadapi_init();
int i;
int num_items = 0;
for (i = 0; i < NUM_ITEMS; i++) {
BLI_addtail(&list, &items_buffer[i]);
num_items++;
}
BLI_task_parallel_listbase(&list, &num_items, task_listbase_iter_func, true);
/* Those checks should ensure us all items of the listbase were processed once, and only once -
* as expected. */
EXPECT_EQ(num_items, 0);
LinkData *item;
for (i = 0, item = (LinkData *)list.first; i < NUM_ITEMS && item != NULL;
i++, item = item->next) {
EXPECT_EQ(POINTER_AS_INT(item->data), i);
}
EXPECT_EQ(NUM_ITEMS, i);
MEM_freeN(items_buffer);
BLI_threadapi_exit();
}

@ -61,5 +61,6 @@ BLENDER_TEST(BLI_string_utf8 "bf_blenlib")
BLENDER_TEST(BLI_task "bf_blenlib;bf_intern_numaapi")
BLENDER_TEST_PERFORMANCE(BLI_ghash_performance "bf_blenlib")
BLENDER_TEST_PERFORMANCE(BLI_task_performance "bf_blenlib")
unset(BLI_path_util_extra_libs)