Fri Mar 31 18:56:04 UTC 2023
This is an exploration of the headers referenced in the cppreference.com concurrency support library documentation. The source is compiled, tested and benchmarked on each commit. Additionally, the C-style comments are converted to markdown prior to rendering this web page; this is purely to aid legibility: all documentation really is in the code itself. Just for kicks – and to keep repeat visits fresh – the chapters are shuffled nightly.
Each source file can be run standalone in Godbolt.
g++-12 file.cxx -std=c++23 -O1 -lgtest -lgtest_main
A latch is a single-use synchronisation primitive; single-use meaning you have to reset it. Really you could just always use a barrier which doesn’t need resetting.
#include "gtest/gtest.h"
#include <latch>
#include <thread>
(latch, latch) {
TEST// Initialise latch with the number of threads
auto l = std::latch{2};
// Variable to test how many threads have been allowed through the latch
auto i = std::atomic{0uz};
// Thread function waits for the others then updates a variable atomically
const auto func = [&]() {
.arrive_and_wait();
l++i;
};
// Create our threads
auto t1 = std::thread{func};
auto t2 = std::thread{func};
// Remember to join
.join();
t1.join();
t2
(i, 2);
EXPECT_EQ}
The go-to platform-independent thread API. It’s been a lot easier
since std::thread
was introduced in C++11.
#include "gtest/gtest.h"
#include <algorithm>
#include <ranges>
#include <thread>
#include <vector>
std::thread
You must call join()
on the thread after creating it,
otherwise bad things. Typically this is managed using a vector of
threads.
(thread, thread) {
TEST
auto threads = std::vector<std::thread>{};
// Create threads
for ([[maybe_unused]] const auto _ : std::ranges::iota_view{1, 10})
.emplace_back([] {});
threads
// Catch threads
for (auto &t : threads)
if (t.joinable())
.join();
t}
std::jthread
A joining thread is the same as a regular thread but has an implicit
join()
in the destructor. You can still join a
std::jthread
, of course, which can be a convenient
synchronisation point.
(thread, jthread) {
TEST
{
std::jthread t{[] {}};
}
// join() is called when it goes out of scope
}
std::this_thread
Useful functions within a thread.
(thread, functions) {
TEST
// Suggest reschedule of threads
std::this_thread::yield();
// Get the ID of the current thread, useful for tracking/logging
const auto id = std::this_thread::get_id();
(id, std::thread::id{});
EXPECT_NE}
A barrier is a multi-use latch. It is released when enough threads are queued up. Unlike a regular latch it also gives you the option of calling a routine when the latch is released.
#include "gtest/gtest.h"
#include <barrier>
#include <thread>
(latch, barrier) {
TEST// Function to call when barrier opens
auto finished = bool{false};
const auto we_are_done = [&]() { finished = true; };
// Initialise barrier with the number of threads
std::barrier b(2, we_are_done);
// Thread function
const auto func = [&]() { b.arrive_and_wait(); };
// Create our threads (note braces for scope)
{
std::jthread t1{func};
std::jthread t2{func};
}
(finished);
EXPECT_TRUE}
You can query cache line size programmatically with these constants. Ensure data used together by a single thread are co-located; and conversely, avoid false sharing by keeping unrelated data apart.
#include "gtest/gtest.h"
#include <new>
(new, interference) {
TEST
struct {
alignas(std::hardware_destructive_interference_size) int x0;
alignas(std::hardware_destructive_interference_size) int x1;
alignas(std::hardware_destructive_interference_size) int x2;
} together;
struct {
alignas(std::hardware_constructive_interference_size) int x0;
alignas(std::hardware_constructive_interference_size) int x1;
alignas(std::hardware_constructive_interference_size) int x2;
} apart;
(&together.x1 - &together.x0, 16);
EXPECT_GE(&apart.x1 - &apart.x0, std::hardware_destructive_interference_size);
EXPECT_LT(std::hardware_destructive_interference_size, 64);
EXPECT_EQ(std::hardware_constructive_interference_size, 64);
EXPECT_EQ}
#include "gtest/gtest.h"
#include <condition_variable>
#include <mutex>
#include <queue>
#include <thread>
Like a mutex but with an additional predicate.
(condition_variable, notify_one_with_predicate) {
TESTstd::mutex m;
std::condition_variable cv;
std::queue<int> q;
// Consumer thread waits for data to change
std::jthread consumer{[&]() {
std::unique_lock<std::mutex> lock(m);
// Wait until the data change
.wait(lock, [&] { return not q.empty(); });
cv
// Empty the queue
while (not q.empty())
.pop();
q}};
// Producer thread updates data
std::jthread producer{[&]() {
std::unique_lock<std::mutex> lock(m);
.push(0);
q.push(1);
q.push(2);
q
(q.empty());
EXPECT_FALSE
// Notify the other thread that something has changed
.notify_one();
cv}};
// You don't have to join a thread but it gives us a convenient
// synchonrisation point
.join();
consumer(q.empty());
EXPECT_TRUE}
Like std::mutex
but you’re saying you have multiple
instances of a resource available for concurrent access.
#include "gtest/gtest.h"
#include <atomic>
#include <chrono>
#include <semaphore>
#include <thread>
(semaphore, counting_sempaphore) {
TEST// Initialise to 2 resources and make them both available
auto sem = std::counting_semaphore<2>{2};
auto i = std::atomic{0uz};
// Grab one before starting the threads
.acquire();
sem
// Create threads, the first will pass straight through
std::jthread t1{[&] {
.acquire();
sem++i;
}};
// This thread will block
std::jthread t2{[&] {
.acquire();
sem++i;
}};
// Check only one thread has updated the value
std::this_thread::sleep_for(std::chrono::microseconds{1});
(i, 1);
EXPECT_EQ
// Release the second thread
.release();
sem
// Confirm it has changed afterwards
std::this_thread::sleep_for(std::chrono::microseconds{1});
(i, 2);
EXPECT_EQ}
A binary semaphore is just a specialisation of a counting semaphore. These are equivalent:
auto sem = std::binary_semaphore{1};
auto sem = std::counting_semaphore<1>{1};
Let’s create one and make it available immediately.
(semaphore, binary_semaphore) {
TEST// Initialise semaphore to available
auto sem = std::binary_semaphore{1};
auto i = size_t{0};
// Grab the semaphore before starting the thread
.acquire();
sem
// Create thread and try semaphore
std::jthread t{[&] {
.acquire();
sem++i;
}};
// Check the thread is blocked
std::this_thread::sleep_for(std::chrono::microseconds{1});
(i, 0);
EXPECT_EQ
// Release the thread
.release();
sem
// Confirm value has changed afterwards
std::this_thread::sleep_for(std::chrono::microseconds{1});
(i, 1);
EXPECT_EQ}
std::async
is a powerful way to handle return values
from multiple threads, think of it like pushing a calculation into the
background. It executes a function asynchronously and returns a
std::future
that will eventually hold the result of that
function call. Quite a nice way to reference the result of calculation
executed in another thread. Of course you must factor in the overhead of
actually creating the thread – 20µs, say; but your mileage may vary.
#include "gtest/gtest.h"
#include <future>
(thread, async) {
TEST// Calculate some things in the background
auto a = std::async(std::launch::async, [] { return 1; });
auto b = std::async(std::launch::async, [] { return 2; });
auto c = std::async(std::launch::async, [] { return 3; });
// Block until they're all satisfied
const auto sum = a.get() + b.get() + c.get();
(sum, 6);
EXPECT_EQ}
A mutual exclusion lock is the starting point for all things concurrent, offering a standard way to protect access a resource; but there are multiple ways to unlock it.
#include "gtest/gtest.h"
#include <mutex>
namespace {
int value{};
std::mutex mux{};
}; // namespace
To safely – i.e., predictably – update a value concurrently we must first lock it. You can lock/unlock explicitly (below), but this can quickly go wrong if the unlock is skipped: e.g, by bad logic or exceptions.
(mutex, mutex) {
TEST.lock();
mux= 1;
value .unlock();
mux
(value, 1);
EXPECT_EQ}
Missing an unlock may result in a deadlock, so the Standard Library offers a few ways to mitigate this risk and unlock automatically using the RAII paradigm. Multiple mutexes can be acquired safely using scoped locks.
(mutex, lock_guards) {
TEST// Basic locking of a single mutex.
{
std::lock_guard lock(mux);
++value;
(value, 2);
EXPECT_EQ}
// Deadlock safe locking of one or more mutexes
{
std::mutex mux2;
std::scoped_lock lock(mux, mux2);
++value;
(value, 3);
EXPECT_EQ}
}
This can be emulated with a static IIFE function, but “call once” does express intention more directly.
(mutex, call_once) {
TEST
// It's a bit of a shame you need this flag
std::once_flag flag;
auto i = size_t{0};
std::call_once(flag, [&]() { ++i; });
(i, 1);
EXPECT_EQ
std::call_once(flag, [&]() { ++i; });
(i, 1);
EXPECT_EQ
std::call_once(flag, [&]() { ++i; });
(i, 1);
EXPECT_EQ}
Many of the Standard Library algorithms can take an execution policy, which is quite an exciting way to parallelise existing code. But remember it offers no thread safety: you must still protect your data as you would for any other threads. You also need to link against the TBB library.
#include "gtest/gtest.h"
#include <execution>
#include <numeric>
// Create a big (-ish) chunk of data to play with
static const std::vector<int> vec = [] {
std::vector<int> v(10000);
std::iota(begin(v), end(v), 0);
return v;
}();
Some algorithms also have an _if
version that takes
predicate: e.g., std::replace
and
std::replace_if
.
std::sort
std::copy
std::transform
std::accumulate
std::for_each
std::reduce
std::inclusive_scan
std::exclusive_scan
std::transform_reduce
std::remove
std::count
std::max_element
std::min_element
std::find
std::generate
Let’s test each policy, and confirm the sums are equal.
(thread, execution_policy) {
TEST
// Sequential -- bof
const auto s0 = std::reduce(std::execution::seq, begin(vec), cend(vec));
// Parallel -- simple threads only
const auto s1 = std::reduce(std::execution::par, cbegin(vec), cend(vec));
// Parallel -- throw the whole tool shed at it
const auto s2 =
std::reduce(std::execution::par_unseq, cbegin(vec), cend(vec));
// Parallel -- vectorisation only
const auto s3 = std::reduce(std::execution::unseq, cbegin(vec), cend(vec));
(s0, s1);
EXPECT_EQ(s0, s2);
EXPECT_EQ(s0, s3);
EXPECT_EQ}
Built-in method to make a jthread
stop.
#include "gtest/gtest.h"
#include <semaphore>
#include <stop_token>
#include <thread>
Instruct the thread to exit its processing loop. I’ve used a semaphore to make the calling thread wait for the processing thread to tidy up.
(thread, stop_token_explicit) {
TESTusing namespace std::literals::chrono_literals;
// Update this variable when we leave the stop token loop
auto i = 0uz;
// Create a semaphore to interact with the processing thread
std::binary_semaphore sem{0};
// Create processing thread
std::jthread t{[&](std::stop_token stop_token) {
// Do some work until told otherwise
while (not stop_token.stop_requested())
std::this_thread::sleep_for(1us);
// Our work here is done, update variable
++i;
// Tell the calling thread to continue
.release();
sem}};
// Check variable hasn't been updated, the processing thread is in its main
// loop at this point
(i, 0);
EXPECT_EQ
// Tell the processing thread to stop
.request_stop();
t
// Wait for it to finish
.acquire();
sem
// Check variable has been updated
(i, 1);
EXPECT_EQ}
std::jthread
also stops implicitly when the thread goes
out of scope.
(thread, stop_token_implicit) {
TEST// Update this variable when we leave the stop token loop
auto i = 0uz;
// Create a semaphore to interact with the processing thread
std::binary_semaphore sem{0};
// Create thread, it stops when it goes out of scope
{
std::jthread t{[&](std::stop_token stop_token) {
// Do some work until told otherwise
while (not stop_token.stop_requested())
std::this_thread::sleep_for(std::chrono::microseconds{1});
// Our work here is done, update variable and poke the calling thread
++i;
.release();
sem}};
// Check variable hasn't been updated, processing thread is doing its thing
(i, 0);
EXPECT_EQ
// Thread goes out of scope and calls stop implicitly
}
// Check variable has been updated when processing thread signals
.acquire();
sem(i, 1);
EXPECT_EQ}
Update a variable thread safely. Can be used with any built-in type, or in fact, anything that is “trivially constructible”.
#include "gtest/gtest.h"
#include <atomic>
(atomic, atomic) {
TESTauto i = std::atomic{0uz};
// This is thread safe
++i;
(i, 1);
EXPECT_EQ}
Wrap a non-atomic thing in atomic love.
(atomic, atomic_ref) {
TESTauto i = 0uz;
auto ii = std::atomic_ref{i};
// This is also thread safe
++ii;
(ii, i);
EXPECT_EQ(ii, 1);
EXPECT_EQ}
To paraphrase Shakira, the benchmarks don’t lie.
make: Entering directory '/builds/germs-dev/concurrency-support-library/benchmark'
g++ -c atomics.cxx -std=c++23 -O2 --all-warnings --extra-warnings --pedantic-errors -Werror -Wconversion -Wuninitialized -Weffc++ -Wunused -Wunused-variable -Wunused-function -Wshadow -Wfloat-equal -Wdelete-non-virtual-dtor -g -march=native -mtune=native
g++ -c cache.cxx -std=c++23 -O2 --all-warnings --extra-warnings --pedantic-errors -Werror -Wconversion -Wuninitialized -Weffc++ -Wunused -Wunused-variable -Wunused-function -Wshadow -Wfloat-equal -Wdelete-non-virtual-dtor -g -march=native -mtune=native
g++ -c containers.cxx -std=c++23 -O2 --all-warnings --extra-warnings --pedantic-errors -Werror -Wconversion -Wuninitialized -Weffc++ -Wunused -Wunused-variable -Wunused-function -Wshadow -Wfloat-equal -Wdelete-non-virtual-dtor -g -march=native -mtune=native
g++ -c execution.cxx -std=c++23 -O2 --all-warnings --extra-warnings --pedantic-errors -Werror -Wconversion -Wuninitialized -Weffc++ -Wunused -Wunused-variable -Wunused-function -Wshadow -Wfloat-equal -Wdelete-non-virtual-dtor -g -march=native -mtune=native
g++ -c main.cxx -std=c++23 -O2 --all-warnings --extra-warnings --pedantic-errors -Werror -Wconversion -Wuninitialized -Weffc++ -Wunused -Wunused-variable -Wunused-function -Wshadow -Wfloat-equal -Wdelete-non-virtual-dtor -g -march=native -mtune=native
g++ -c semaphore.cxx -std=c++23 -O2 --all-warnings --extra-warnings --pedantic-errors -Werror -Wconversion -Wuninitialized -Weffc++ -Wunused -Wunused-variable -Wunused-function -Wshadow -Wfloat-equal -Wdelete-non-virtual-dtor -g -march=native -mtune=native
g++ -c thread.cxx -std=c++23 -O2 --all-warnings --extra-warnings --pedantic-errors -Werror -Wconversion -Wuninitialized -Weffc++ -Wunused -Wunused-variable -Wunused-function -Wshadow -Wfloat-equal -Wdelete-non-virtual-dtor -g -march=native -mtune=native
g++ -o app.o atomics.o cache.o containers.o execution.o main.o semaphore.o thread.o -lfmt -lgtest -lbenchmark -lpthread -ltbb
timeout 60 ./app.o
------------------------------------------------------------------------------
Benchmark Time CPU Iterations
------------------------------------------------------------------------------
variable_increment_unguarded 0.000 ns 0.000 ns 1000000000
variable_increment_with_atomic 6.80 ns 6.76 ns 103574601
variable_increment_with_mutex 7.47 ns 7.45 ns 93641679
variable_increment_with_scoped_lock 7.63 ns 7.59 ns 92370639
sum_stride1 0.000 ns 0.000 ns 1000000000
sum_stride2 0.000 ns 0.000 ns 1000000000
sum_stride3 0.000 ns 0.000 ns 1000000000
sum_stride4 0.000 ns 0.000 ns 1000000000
sum_stride15 0.000 ns 0.000 ns 1000000000
sum_stride16 0.000 ns 0.000 ns 1000000000
sum_stride31 0.000 ns 0.000 ns 1000000000
sum_stride32 0.000 ns 0.000 ns 1000000000
insert_front_vector 10081211 ns 10035936 ns 70
push_front_deque 37204 ns 37005 ns 19051
push_front_list 439109 ns 437697 ns 1619
push_front_forward_list 416755 ns 414523 ns 1688
push_back_vector 36996 ns 36812 ns 18933
push_back_deque 36566 ns 36375 ns 19236
push_back_list 449544 ns 446127 ns 1570
insert_set 1833189 ns 1820222 ns 396
insert_unordered_set 1330227 ns 1320961 ns 529
emplace_set 1944212 ns 1930136 ns 350
emplace_unordered_set 1338753 ns 1334104 ns 520
map_insert 1309263 ns 1301948 ns 533
unordered_map_insert 1315970 ns 1264552 ns 550
populate_vector 9118 ns 9067 ns 77285
populate_array 0.000 ns 0.000 ns 1000000000
populate_valarray 9148 ns 9100 ns 77393
exec_seq 0.000 ns 0.000 ns 1000000000
exec_par 5239 ns 5109 ns 136245
exec_par_unseq 7599 ns 7352 ns 94277
exec_unseq 0.000 ns 0.000 ns 1000000000
exec_seq/real_time 0.000 ns 0.000 ns 1000000000
exec_par/real_time 5085 ns 5027 ns 137789
exec_par_unseq/real_time 7306 ns 7261 ns 96441
exec_unseq/real_time 0.000 ns 0.000 ns 1000000000
semaphore_acquire_release 675 ns 671 ns 1040490
thread_async 20501 ns 12365 ns 57609
thread_thread 18639 ns 11313 ns 61523
thread_jthread 18717 ns 11365 ns 60837
thread_async/real_time 20476 ns 12312 ns 34502
thread_thread/real_time 19743 ns 11838 ns 35804
thread_jthread/real_time 19679 ns 11813 ns 35561
make: Leaving directory '/builds/germs-dev/concurrency-support-library/benchmark'
make: Entering directory '/builds/germs-dev/concurrency-support-library/test'
g++ -c atomic.cxx -std=c++23 -O2 --all-warnings --extra-warnings --pedantic-errors -Werror -Wconversion -Wuninitialized -Weffc++ -Wunused -Wunused-variable -Wunused-function -Wshadow -Wfloat-equal -Wdelete-non-virtual-dtor -g -march=native -mtune=native
g++ -c barrier.cxx -std=c++23 -O2 --all-warnings --extra-warnings --pedantic-errors -Werror -Wconversion -Wuninitialized -Weffc++ -Wunused -Wunused-variable -Wunused-function -Wshadow -Wfloat-equal -Wdelete-non-virtual-dtor -g -march=native -mtune=native
g++ -c condition_variable.cxx -std=c++23 -O2 --all-warnings --extra-warnings --pedantic-errors -Werror -Wconversion -Wuninitialized -Weffc++ -Wunused -Wunused-variable -Wunused-function -Wshadow -Wfloat-equal -Wdelete-non-virtual-dtor -g -march=native -mtune=native
g++ -c execution.cxx -std=c++23 -O2 --all-warnings --extra-warnings --pedantic-errors -Werror -Wconversion -Wuninitialized -Weffc++ -Wunused -Wunused-variable -Wunused-function -Wshadow -Wfloat-equal -Wdelete-non-virtual-dtor -g -march=native -mtune=native
g++ -c future.cxx -std=c++23 -O2 --all-warnings --extra-warnings --pedantic-errors -Werror -Wconversion -Wuninitialized -Weffc++ -Wunused -Wunused-variable -Wunused-function -Wshadow -Wfloat-equal -Wdelete-non-virtual-dtor -g -march=native -mtune=native
g++ -c latch.cxx -std=c++23 -O2 --all-warnings --extra-warnings --pedantic-errors -Werror -Wconversion -Wuninitialized -Weffc++ -Wunused -Wunused-variable -Wunused-function -Wshadow -Wfloat-equal -Wdelete-non-virtual-dtor -g -march=native -mtune=native
g++ -c mutex.cxx -std=c++23 -O2 --all-warnings --extra-warnings --pedantic-errors -Werror -Wconversion -Wuninitialized -Weffc++ -Wunused -Wunused-variable -Wunused-function -Wshadow -Wfloat-equal -Wdelete-non-virtual-dtor -g -march=native -mtune=native
g++ -c new.cxx -std=c++23 -O2 --all-warnings --extra-warnings --pedantic-errors -Werror -Wconversion -Wuninitialized -Weffc++ -Wunused -Wunused-variable -Wunused-function -Wshadow -Wfloat-equal -Wdelete-non-virtual-dtor -g -march=native -mtune=native
g++ -c semaphore.cxx -std=c++23 -O2 --all-warnings --extra-warnings --pedantic-errors -Werror -Wconversion -Wuninitialized -Weffc++ -Wunused -Wunused-variable -Wunused-function -Wshadow -Wfloat-equal -Wdelete-non-virtual-dtor -g -march=native -mtune=native
g++ -c stop_token.cxx -std=c++23 -O2 --all-warnings --extra-warnings --pedantic-errors -Werror -Wconversion -Wuninitialized -Weffc++ -Wunused -Wunused-variable -Wunused-function -Wshadow -Wfloat-equal -Wdelete-non-virtual-dtor -g -march=native -mtune=native
g++ -c thread.cxx -std=c++23 -O2 --all-warnings --extra-warnings --pedantic-errors -Werror -Wconversion -Wuninitialized -Weffc++ -Wunused -Wunused-variable -Wunused-function -Wshadow -Wfloat-equal -Wdelete-non-virtual-dtor -g -march=native -mtune=native
g++ -o app.o atomic.o barrier.o condition_variable.o execution.o future.o latch.o mutex.o new.o semaphore.o stop_token.o thread.o -lgtest_main -lfmt -lgtest -lbenchmark -lpthread -ltbb
timeout 60 ./app.o
Running main() from ./googletest/src/gtest_main.cc
[==========] Running 18 tests from 7 test suites.
[----------] Global test environment set-up.
[----------] 2 tests from atomic
[ RUN ] atomic.atomic
[ OK ] atomic.atomic (0 ms)
[ RUN ] atomic.atomic_ref
[ OK ] atomic.atomic_ref (0 ms)
[----------] 2 tests from atomic (0 ms total)
[----------] 2 tests from latch
[ RUN ] latch.barrier
[ OK ] latch.barrier (0 ms)
[ RUN ] latch.latch
[ OK ] latch.latch (0 ms)
[----------] 2 tests from latch (0 ms total)
[----------] 1 test from condition_variable
[ RUN ] condition_variable.notify_one_with_predicate
[ OK ] condition_variable.notify_one_with_predicate (0 ms)
[----------] 1 test from condition_variable (0 ms total)
[----------] 7 tests from thread
[ RUN ] thread.execution_policy
[ OK ] thread.execution_policy (0 ms)
[ RUN ] thread.async
[ OK ] thread.async (0 ms)
[ RUN ] thread.stop_token_explicit
[ OK ] thread.stop_token_explicit (0 ms)
[ RUN ] thread.stop_token_implicit
[ OK ] thread.stop_token_implicit (0 ms)
[ RUN ] thread.thread
[ OK ] thread.thread (0 ms)
[ RUN ] thread.jthread
[ OK ] thread.jthread (0 ms)
[ RUN ] thread.functions
[ OK ] thread.functions (0 ms)
[----------] 7 tests from thread (1 ms total)
[----------] 3 tests from mutex
[ RUN ] mutex.mutex
[ OK ] mutex.mutex (0 ms)
[ RUN ] mutex.lock_guards
[ OK ] mutex.lock_guards (0 ms)
[ RUN ] mutex.call_once
[ OK ] mutex.call_once (0 ms)
[----------] 3 tests from mutex (0 ms total)
[----------] 1 test from new
[ RUN ] new.interference
[ OK ] new.interference (0 ms)
[----------] 1 test from new (0 ms total)
[----------] 2 tests from semaphore
[ RUN ] semaphore.counting_sempaphore
[ OK ] semaphore.counting_sempaphore (0 ms)
[ RUN ] semaphore.binary_semaphore
[ OK ] semaphore.binary_semaphore (0 ms)
[----------] 2 tests from semaphore (0 ms total)
[----------] Global test environment tear-down
[==========] 18 tests from 7 test suites ran. (2 ms total)
[ PASSED ] 18 tests.
make: Leaving directory '/builds/germs-dev/concurrency-support-library/test'
PRETTY_NAME="Ubuntu Lunar Lobster (development branch)"
NAME="Ubuntu"
VERSION_ID="23.04"
VERSION="23.04 (Lunar Lobster)"
VERSION_CODENAME=lunar
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=lunar
LOGO=ubuntu-logo
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Address sizes: 46 bits physical, 48 bits virtual
Byte Order: Little Endian
CPU(s): 1
On-line CPU(s) list: 0
Vendor ID: GenuineIntel
BIOS Vendor ID: Google
Model name: Intel(R) Xeon(R) CPU @ 2.30GHz
BIOS Model name: CPU @ 2.0GHz
BIOS CPU family: 1
CPU family: 6
Model: 63
Thread(s) per core: 1
Core(s) per socket: 1
Socket(s): 1
Stepping: 0
BogoMIPS: 4599.99
Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid tsc_known_freq pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm abm invpcid_single pti ssbd ibrs ibpb stibp fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid xsaveopt arat md_clear arch_capabilities
Hypervisor vendor: KVM
Virtualization type: full
L1d cache: 32 KiB (1 instance)
L1i cache: 32 KiB (1 instance)
L2 cache: 256 KiB (1 instance)
L3 cache: 45 MiB (1 instance)
NUMA node(s): 1
NUMA node0 CPU(s): 0
Vulnerability Itlb multihit: Not affected
Vulnerability L1tf: Mitigation; PTE Inversion
Vulnerability Mds: Mitigation; Clear CPU buffers; SMT Host state unknown
Vulnerability Meltdown: Mitigation; PTI
Vulnerability Spec store bypass: Mitigation; Speculative Store Bypass disabled via prctl and seccomp
Vulnerability Spectre v1: Mitigation; usercopy/swapgs barriers and __user pointer sanitization
Vulnerability Spectre v2: Mitigation; Full generic retpoline, IBPB conditional, IBRS_FW, STIBP disabled, RSB filling
Vulnerability Srbds: Not affected
Vulnerability Tsx async abort: Not affected
[email protected]
--------------------------------------------------
OS: Ubuntu Lunar Lobster (development branch) x86_64
Kernel: 5.4.109+
Uptime: 3 mins
Packages: 310 (dpkg)
Shell: bash 5.2.15
CPU: Intel Xeon (1) @ 2.299GHz
Memory: 452MiB / 3685MiB
total used free shared buff/cache available
Mem: 3.6Gi 676Mi 2.0Gi 896Ki 1.1Gi 2.9Gi
Swap: 2.0Gi 0B 2.0Gi