Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add multithreaded sample sort and use it to sort memtx keys in secondary indexes on startup #8562

Merged
merged 3 commits into from
Jun 9, 2023

Conversation

nshy
Copy link
Contributor

@nshy nshy commented Apr 12, 2023

In short:

  • now we don't use OpenMP at all
  • sorting does not load TX thread so Tarantool should be responsive while building indexes
  • number of sort threads is limited to configuration parameter memtx_sort_threads

Closes #3389
Closes #7689
Closes #4646

@nshy nshy requested a review from a team as a code owner April 12, 2023 15:45
@nshy nshy requested a review from locker April 12, 2023 15:45
@nshy nshy assigned locker and nshy and unassigned locker and nshy Apr 12, 2023
@coveralls
Copy link

coveralls commented Apr 12, 2023

Coverage Status

coverage: 85.872% (+0.09%) from 85.78% when pulling e02039e on nshy:add-multithread-sample-sort into 8315869
on tarantool:master
.

@@ -1412,6 +1413,9 @@ memtx_engine_new(const char *snap_dirname, bool force_recovery,
memtx->state = MEMTX_INITIALIZED;
memtx->max_tuple_size = MAX_TUPLE_SIZE;
memtx->force_recovery = force_recovery;
if (sort_threads == 0)
sort_threads = get_nprocs();
memtx->sort_threads = sort_threads;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest default behavior sort_threads = max(1, (int)get_nprocs() - 1)

Try not to step on the same rake - do not consume all cores by default.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it won't much help as threads number limit is added to keep others Tarantool instances running while this one is starting. Other instances number can be more then one and also Tarantool have more then one thread that can be loaded with work. I guess it is better to use introduced option to limit starting Tarantool according to the installation needs.

Yet I'll add low limit 1 (and high too) on sorting threads.

Copy link
Contributor

@andreyaksenov andreyaksenov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My suggestion regarding wording in a changelog:

* Added the `box.cfg.memtx_sort_threads` parameter that specifies the number of threads
  used to sort keys of secondary indexes on loading a memtx database (gh-3389).

Copy link
Member

@locker locker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see a few comments. I haven't reviewed the algorithm in detail yet so there may be more.

src/lib/core/sample_sort.c Outdated Show resolved Hide resolved
test/unit/qsort_arg.cc Show resolved Hide resolved
src/lib/core/sample_sort.h Outdated Show resolved Hide resolved
src/lib/core/sample_sort.h Outdated Show resolved Hide resolved
src/lib/core/sample_sort.h Outdated Show resolved Hide resolved
src/lib/core/sample_sort.c Outdated Show resolved Hide resolved
src/lib/core/sample_sort.c Outdated Show resolved Hide resolved
third_party/qsort_arg.c Show resolved Hide resolved
src/box/box.cc Outdated Show resolved Hide resolved
src/box/lua/load_cfg.lua Outdated Show resolved Hide resolved
src/lib/core/sample_sort.c Outdated Show resolved Hide resolved
src/lib/core/sample_sort.c Outdated Show resolved Hide resolved
src/lib/core/sample_sort.c Outdated Show resolved Hide resolved
src/lib/core/sample_sort.c Outdated Show resolved Hide resolved
src/lib/core/sample_sort.c Outdated Show resolved Hide resolved
@locker locker removed their assignment Apr 14, 2023
@nshy nshy force-pushed the add-multithread-sample-sort branch 2 times, most recently from 7cc088c to cd3aad3 Compare April 17, 2023 08:48
@nshy nshy assigned locker and unassigned nshy Apr 17, 2023
test/unit/qsort_arg.cc Outdated Show resolved Hide resolved
test/unit/sort.cc Outdated Show resolved Hide resolved
src/lib/core/sort.c Outdated Show resolved Hide resolved
src/lib/core/sort.c Outdated Show resolved Hide resolved
src/box/box.cc Outdated Show resolved Hide resolved
@locker locker assigned nshy and unassigned locker Apr 17, 2023
@nshy nshy added the do not merge Not ready to be merged label Apr 17, 2023
@nshy nshy force-pushed the add-multithread-sample-sort branch 2 times, most recently from a33b2f5 to 0e17a7a Compare April 17, 2023 15:27
@nshy nshy assigned locker and nshy and unassigned nshy and locker Apr 17, 2023
@nshy nshy force-pushed the add-multithread-sample-sort branch from 0e17a7a to c8cdabb Compare April 18, 2023 08:23
Copy link
Contributor

@alyapunov alyapunov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost good for me, see several comments.

* application faster if there is no much data in database (test cases in
* particular). Otherwise sorting will be done in threads.
*/

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to be an excess line break.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

## feature/box

* Added the `box.cfg.memtx_sort_threads` parameter that specifies the number of
threads used to sort keys of secondary indexes on loading a memtx database
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should directly state that openmp is used no more. Actually the patch is a kind of breaking change: before an advanced user can specify OMP_NUM_THREADS as some value. Now that think doesn't work anymore. Maybe we should read the variable if memtx_sort_threads is not specified, just for backward compatibility.. Perhaps under a compat option..

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

        if (sort_threads == 0) {
                char *ompnum_str = getenv_safe("OMP_NUM_THREADS", NULL, 0);
                if (ompnum_str != NULL) {
                        long ompnum = strtol(ompnum_str, NULL, 10);
                        if (ompnum > 0 && ompnum <= TT_SORT_THREADS_MAX) {
                                say_warn("OMP_NUM_THREADS is used to set number"
                                         " of sorting threads. Use cfg option"
                                         "'memtx_sort_threads' instead.");
                                sort_threads = ompnum;
                        }
                        free(ompnum_str);
                }
                if (sort_threads == 0) {
                        sort_threads = get_nprocs();
                        if (sort_threads < 1)
                                sort_threads = 1;
                        else if (sort_threads > TT_SORT_THREADS_MAX)
                                sort_threads = TT_SORT_THREADS_MAX;
                }
        }
        memtx->sort_threads = sort_threads;

* place most left and most right boundaries at imaginary indexes `-1` and
* `size of splitters` respectively.
*/
static inline int
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Usually we does not use inline in static functions within .c file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

* Find bucket for element using binary search among sorted in ascending
* order splitters.
*
* Bucket number is `thread_count`, thus bucket boundraries (splitters) number
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me number here is misleading. I think count will be correct and sound.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to our coding style, generally, you want your comments to tell WHAT your code does, not HOW.

In other word the comment should describe what find_bucket makes and returns. It would be a good idea (but not necessary) to state that the 0 <= return value < thread_count. This comment is needed when you use the function.

The comment about imaginary indexes is also good, but should be placed in the function body. This comment is needed for better understanding when you read the function code.

Copy link
Contributor Author

@nshy nshy May 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to:

/**
 * Find bucket for element using binary search among sorted in ascending
 * order splitters.
 *
 * Return index of the bucket for the element.
 */
static int
find_bucket(struct tt_sort *sort, void *elem)
{
        /*
         * Bucket count is `thread_count`, thus bucket boundaries (splitters)
         * count is `thread_count - 1` omitting most left and most right
         * boundaries. Let's place most left and most right boundaries at
         * imaginary indexes `-1` and `size of splitters` respectively.
         */

I didn't write the exact assertion for the return value in the at the file level. Looks like term index should be enough.

* Sample sort algorithm data.
*/
struct tt_sort {
/**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: We usually use one-line comments for short descriptions. Sometimes it simplifies code observation:
/** The data being sorted. */

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried one-liners in this structure. IMHO it reduced readability. May be it is a matter of screen size or fonts.

image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Used one-line comment after f2f discussion.

/**
* Data for a single sample sort worker thread.
*/
struct sort_worker {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit inconsistent name. I mean struct tt_sort and struct sort_worker.
I would name them struct sort_data and struct sort_worker, or struct tt_sort and tt_worker..

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to sort_data.

sort.cmp_arg = cmp_arg;
sort.thread_count = thread_count;

if (thread_count == 1) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function (tt_sort) is too big. It seems that if we create a couple of helpers (for example tt_sort_one_thread) it would be much shorter and thus understandable.

@alyapunov alyapunov assigned nshy and unassigned alyapunov Apr 28, 2023
@nshy nshy force-pushed the add-multithread-sample-sort branch 3 times, most recently from 99af2a8 to 74bc106 Compare May 22, 2023 15:42
@nshy
Copy link
Contributor Author

nshy commented May 22, 2023

DО NOT MERGE before tarantool/test-run#391.

@nshy nshy assigned alyapunov and unassigned nshy May 22, 2023
@alyapunov alyapunov assigned nshy and unassigned alyapunov Jun 8, 2023
@nshy nshy force-pushed the add-multithread-sample-sort branch from 74bc106 to 11a963d Compare June 8, 2023 14:41
@nshy nshy removed the do not merge Not ready to be merged label Jun 8, 2023
@nshy nshy force-pushed the add-multithread-sample-sort branch from 11a963d to f3c5427 Compare June 8, 2023 14:50
@nshy
Copy link
Contributor Author

nshy commented Jun 8, 2023

  • Added references to the tickets to commit messages.
  • Rebased to master to fix conflict in 'test: bump test-run to new version' patch.

@nshy nshy added the full-ci Enables all tests for a pull request label Jun 8, 2023
@nshy nshy force-pushed the add-multithread-sample-sort branch from f3c5427 to f83a2cc Compare June 9, 2023 07:21
nshy added 2 commits June 9, 2023 10:48
The algorithm runs sort in multiple threads and does not use OpenMP. It
has better threads utilization right from the beginning but probably
a worse constant than parallel qsort. See details in code comments.

Besides sort is not performed in calling thread but instead in spawned
worker threads. Calling thread yields waiting for worker threads to
finish. Exception is small data size, in this case sorting is executed
in calling thread saving time on spawning a thread. This should speed up
test execution. This is existing behaviour of qsort_arg but data size
threshold is reduced from 128000 to 1024.

Part of tarantool#3389

NO_CHANGELOG=internal
NO_DOC=internal
Bump test-run to new version with the following improvements:

- Drop setting OMP_NUM_THREADS

Part of tarantool#3389

NO_DOC=testing stuff
NO_TEST=testing stuff
NO_CHANGELOG=testing stuff
@nshy nshy force-pushed the add-multithread-sample-sort branch from f83a2cc to f3ed1ad Compare June 9, 2023 07:50
@nshy
Copy link
Contributor Author

nshy commented Jun 9, 2023

Next changes are done to pass full CI:

  1. use sysconf(_SC_NPROCESSORS_ONLN) instead of get_nprocs(). The last one is GNU specific. Also add a warning on fallback to single core.
--- a/src/box/memtx_engine.cc
+++ b/src/box/memtx_engine.cc
@@ -33,7 +33,6 @@
 #include <small/quota.h>
 #include <small/small.h>
 #include <small/mempool.h>
-#include <sys/sysinfo.h>

 #include "fiber.h"
 #include "errinj.h"
@@ -1519,11 +1518,14 @@ memtx_engine_new(const char *snap_dirname, bool force_recovery,
                        free(ompnum_str);
                }
                if (sort_threads == 0) {
-                       sort_threads = get_nprocs();
-                       if (sort_threads < 1)
+                       sort_threads = sysconf(_SC_NPROCESSORS_ONLN);
+                       if (sort_threads < 1) {
+                               say_warn("Cannot get number of processors."
+                                        "Fallback to single processor.");
                                sort_threads = 1;
-                       else if (sort_threads > TT_SORT_THREADS_MAX)
+                       } else if (sort_threads > TT_SORT_THREADS_MAX) {
                                sort_threads = TT_SORT_THREADS_MAX;
+                       }
                }
        }
  1. Fixed test/uni/tt_sort.cc build. The previous version failed to link on some OSXes. Probably it has stricter linking rules.
 create_unit_test(PREFIX tt_sort
                  SOURCES tt_sort.cc core_test_utils.c
-                 LIBRARIES misc unit box
+                 LIBRARIES unit core
 )

@nshy nshy assigned alyapunov and unassigned nshy Jun 9, 2023
@nshy nshy force-pushed the add-multithread-sample-sort branch from f3ed1ad to e8c81ef Compare June 9, 2023 11:35
@nshy
Copy link
Contributor Author

nshy commented Jun 9, 2023

Fixed messages whitespaces:

diff --git a/src/box/box.cc b/src/box/box.cc
index cc61ba743..b9a5e0c66 100644
--- a/src/box/box.cc
+++ b/src/box/box.cc
@@ -1675,7 +1675,7 @@ box_check_memtx_sort_threads(void)
        if (cfg_isnumber("memtx_sort_threads") &&
            (num <= 0 || num > TT_SORT_THREADS_MAX))
                tnt_raise(ClientError, ER_CFG, "memtx_sort_threads",
-                         tt_sprintf("must be greater than 0 and less than or "
+                         tt_sprintf("must be greater than 0 and less than or"
                                     " equal to %d", TT_SORT_THREADS_MAX));
 }

diff --git a/src/box/memtx_engine.cc b/src/box/memtx_engine.cc
index 49cff1f6d..6ba8eba11 100644
--- a/src/box/memtx_engine.cc
+++ b/src/box/memtx_engine.cc
@@ -1512,7 +1512,7 @@ memtx_engine_new(const char *snap_dirname, bool force_recovery,
                        if (ompnum > 0 && ompnum <= TT_SORT_THREADS_MAX) {
                                say_warn("OMP_NUM_THREADS is used to set number"
                                         " of sorting threads. Use cfg option"
-                                        "'memtx_sort_threads' instead.");
+                                        " 'memtx_sort_threads' instead.");
                                sort_threads = ompnum;
                        }
                        free(ompnum_str);
@@ -1520,8 +1520,8 @@ memtx_engine_new(const char *snap_dirname, bool force_recovery,
                if (sort_threads == 0) {
                        sort_threads = sysconf(_SC_NPROCESSORS_ONLN);
                        if (sort_threads < 1) {
-                               say_warn("Cannot get number of processors."
-                                        "Fallback to single processor.");
+                               say_warn("Cannot get number of processors. "
+                                        "Fallback to single processor.");
                                sort_threads = 1;
                        } else if (sort_threads > TT_SORT_THREADS_MAX) {
                                sort_threads = TT_SORT_THREADS_MAX;
diff --git a/test/box-luatest/gh_3389_cfg_option_memtx_sort_threads_test.lua b/test/box-luatest/gh_3389_cfg_option_memtx_sort_threads_test.lua
index 65863d87c..4b9bb8c08 100644
--- a/test/box-luatest/gh_3389_cfg_option_memtx_sort_threads_test.lua
+++ b/test/box-luatest/gh_3389_cfg_option_memtx_sort_threads_test.lua
@@ -46,7 +46,7 @@ g.test_setting_openmp_env_var = function(cg)
     t.helpers.retrying({}, function()
         local p = "OMP_NUM_THREADS is used to set number" ..
                   " of sorting threads. Use cfg option" ..
-                  "'memtx_sort_threads' instead."
+                  " 'memtx_sort_threads' instead."
         t.assert_not_equals(cg.server:grep_log(p), nil)
     end)
     cg.server:stop()

Closes tarantool#3389
Closes tarantool#7689
Closes tarantool#4646

@TarantoolBot document
Title: new box.cfg parameter memtx_sort_threads

The parameter sets the number of threads used to sort keys of secondary
indexes on loading memtx database. The parameter cannot be changed
dynamically (as it does not make sense).

Maximum value is 256, minimum is 1. Default is to use all available cores.

Usage example:
```
box.cfg{memtx_sort_threads=4}
```
@nshy nshy force-pushed the add-multithread-sample-sort branch from e8c81ef to e02039e Compare June 9, 2023 11:41
@alyapunov alyapunov merged commit 4f617b7 into tarantool:master Jun 9, 2023
86 checks passed
@nshy nshy deleted the add-multithread-sample-sort branch June 14, 2023 07:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
full-ci Enables all tests for a pull request
Projects
None yet
7 participants