FLINT3.7.0-dev

Binary splitting on several threads

Synopsis#include <flint/thread_support.h>
bsplit_basecase_func_t
bsplit_merge_func_t
bsplit_init_func_t
bsplit_clear_func_t

Callback types for flint_parallel_binary_splitting: void basecase(void * res, slong a, slong b, void * args), void merge(void * res, void * x, void * y, void * args), void init(void * x, void * args) and void clear(void * x, void * args).

void flint_parallel_binary_splitting(void * res, bsplit_basecase_func_t basecase, bsplit_merge_func_t merge, size_t sizeof_res, bsplit_init_func_t init, bsplit_clear_func_t clear, void * args, slong a, slong b, slong basecase_cutoff, int thread_limit, int flags)source

Sets res to f(a) \circ f(a+1) \circ \cdots \circ f(b-1) for an associative operation \circ, by recursive halving of [a, b) with the two halves computed in parallel when workers are available. The intended number of threads running base cases at the same time, the calling thread included, is at most thread_limit (flint_get_num_threads() if \mathit{thread\_limit} \le 0).

FLINT_PARALLEL_BSPLIT_LEFT_INPLACEsource

Flag for flint_parallel_binary_splitting: the merge function supports res aliasing its left operand.

Binary splitting computes a product f(a) \circ f(a+1) \circ \cdots \circ f(b-1) of many factors by splitting [a, b) in the middle, computing both halves recursively and combining them [HP1998; Ber2008]. When the operation is multiplication of integers, polynomials or matrices whose size grows with the number of factors, this balances the operand sizes and lets fast multiplication do most of the work. It is the standard method for factorials, binomial coefficients, and the evaluation of hypergeometric series.

flint_parallel_binary_splitting runs the two halves of each split on different threads, as long as threads are available, so the recursion tree is processed in parallel near its root and serially near its leaves.

The callbacks#

The results of subranges are opaque objects of sizeof_res bytes, which the function allocates itself and manages with the callbacks:

  • init(x, args) and clear(x, args) create and destroy a temporary;
  • basecase(res, a, b, args) computes the result for [a, b) directly, and is used when b - a is at most basecase_cutoff (a cutoff below 1 is treated as 1);
  • merge(res, x, y, args) sets res to x \circ y. The operation must be associative; it need not be commutative, and x always belongs to the left subrange.

With FLINT_PARALLEL_BSPLIT_LEFT_INPLACE, the left result is computed directly in res and merge is called with res equal to x, which saves one temporary at each level of the recursion. Use it when the merge function supports this aliasing, as fmpz_mul does.

Threads#

Let n be thread_limit, or flint_get_num_threads() if thread_limit is not positive. At each split with n \ge 2, the function asks the global pool for one worker. If it gets one, the right half runs on the worker and the left half on the calling thread, and the budget is divided between them: the worker may start \lfloor n/2 \rfloor - 1 further workers and the calling thread at most n - \lfloor n/2 \rfloor - 1. Otherwise both halves run on the calling thread, one after the other. The recursive calls receive these shares as their thread_limit, so at most n threads, the calling thread included, run base cases at the same time.

AlgorithmParallel loops and binary splittingHow flint_parallel_do schedules independent calls over threads, why strided scheduling balances monotone costs to within one call, and how parallel binary splitting divides threads over a recursion tree.

Example#

This computes 20000! as a product of the integers in [1, 20001) and checks it against fmpz_fac_ui.

Examplebsplit_factorial.cchecked by make checkdoc
#include <flint/flint.h>
#include <flint/fmpz.h>
#include <flint/thread_support.h>

/* The product a * (a + 1) * ... * (b - 1), by binary splitting. */
static void basecase(void * res, slong a, slong b, void * args)
{
    slong k;
    fmpz_one(res);
    for (k = a; k < b; k++)
        fmpz_mul_ui(res, res, k);
}

static void merge(void * res, void * x, void * y, void * args)
{
    fmpz_mul(res, x, y);
}

static void init(void * x, void * args) { fmpz_init(x); }
static void clear(void * x, void * args) { fmpz_clear(x); }

int main(void)
{
    fmpz_t f, g;

    flint_set_num_threads(4);
    fmpz_init(f);
    fmpz_init(g);

    /* 20000! = 1 * 2 * ... * 20000; the leaves multiply 64 numbers each */
    flint_parallel_binary_splitting(f, basecase, merge, sizeof(fmpz),
        init, clear, NULL, 1, 20001, 64, -1, 0);

    fmpz_fac_ui(g, 20000);
    flint_printf("20000! has %wu digits\n", (ulong) fmpz_sizeinbase(f, 10));
    flint_printf("agrees with fmpz_fac_ui: %s\n", fmpz_equal(f, g) ? "yes" : "no");

    fmpz_clear(f);
    fmpz_clear(g);
    flint_cleanup_master();
    return 0;
}
Output
20000! has 77338 digits
agrees with fmpz_fac_ui: yes

fmpz_fac_ui is faster for this particular product; the example only shows the mechanics.

Guides#

References#

  • [HP1998] B. Haible and T. Papanikolaou, "Fast multiprecision evaluation of series of rational numbers", Algorithmic Number Theory (ANTS-III), LNCS 1423 (1998), 338–350
  • [Ber2008] D. J. Bernstein, "Fast multiplication and its applications", in Algorithmic Number Theory, MSRI Publications 44 (2008), 325–384
Cite this page

The FLINT team, “Binary splitting on several threads (thread_support.h),” FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev, 2026. https://flintlib.org/doc/thread_support/bsplit.html (generated 2026-10-20).