As explained in the introduction, Seastar-based programs run a single
thread on each CPU. Each of these threads runs its own event loop, known
as the engine in Seastar nomenclature. By default, the Seastar
application will take over all the available cores, starting one thread
per core. We can see this with the following program, printing
seastar::smp::count
which is the number of started
threads:
#include <seastar/core/app-template.hh>
#include <seastar/core/reactor.hh>
#include <iostream>
int main(int argc, char** argv) {
::app_template app;
seastar.run(argc, argv, [] {
appstd::cout << seastar::smp::count << "\n";
return seastar::make_ready_future<>();
});
}
On a machine with 4 hardware threads (two cores, and hyperthreading enabled), Seastar will by default start 4 engine threads:
$ ./a.out
4
Each of these 4 engine threads will be pinned (a la taskset(1)) to a different hardware thread. Note how, as we mentioned above, the app’s initialization function is run only on one thread, so we see the ouput “4” only once. Later in the tutorial we’ll see how to make use of all threads.
The user can pass a command line parameter, -c
, to tell
Seastar to start fewer threads than the available number of hardware
threads. For example, to start Seastar on only 2 threads, the user can
do:
$ ./a.out -c2
2
When the machine is configured as in the example above - two cores with two hyperthreads on each - and only two threads are requested, Seastar ensures that each thread is pinned to a different core, and we don’t get the two threads competing as hyperthreads of the same core (which would, of course, damage performance).
We cannot start more threads than the number of hardware threads, as allowing this will be grossly inefficient. Trying it will result in an error:
$ ./a.out -c5
Could not initialize seastar: std::runtime_error (insufficient processing units)
The error is an exception thrown from app.run, which was caught by seastar itself and turned into a non-zero exit code. Note that catching the exceptions this way does not catch exceptions thrown in the application’s actual asynchronous code. We will discuss these later in this tutorial.
As explained in the introduction, Seastar applications shard their
memory. Each thread is preallocated with a large piece of memory (on the
same NUMA node it is running on), and uses only that memory for its
allocations (such as malloc()
or new
).
By default, the machine’s entire memory except a
certain reservation left for the OS (defaulting to the maximum of 1.5G
or 7% of total memory) is pre-allocated for the application in this
manner. This default can be changed by either changing the
amount reserved for the OS (not used by Seastar) with the
--reserve-memory
option, or by explicitly giving the amount
of memory given to the Seastar application, with the -m
option. This amount of memory can be in bytes, or using the units “k”,
“M”, “G” or “T”. These units use the power-of-two values: “M” is a
mebibyte, 2^20 (=1,048,576) bytes, not a
megabyte (10^6 or 1,000,000 bytes).
Trying to give Seastar more memory than physical memory immediately fails:
$ ./a.out -m10T
Couldn't start application: std::runtime_error (insufficient physical memory)