Date: Thu, 16 Nov 2023 23:44:33 +0000
On Thu, Nov 16, 2023 at 8:38 PM Marcin Jaczewski wrote:
>
> Are you aware that the thing you propose is opposite of this?
> Do you know that each function `static` needs lock and it's not free.
> Or at least condition variable to check if the value was already initialized.
Not sure what you're saying here as your post is a bit vague, but if
you're referring to how since C++11 we've been guaranteed that
static-duration variables inside functions are thread-safe, and that
we therefore need a lock for each static variable inside a function,
well then let's just explore that -- consider the following code:
#include <string> // string
std::string &Func(char const c)
{
static std::string str(c, 20u);
return str;
}
This code will behave exactly as though you had written:
#include <mutex> // call_once, once_flag
#include <optional> // optional
#include <string> // string
std::once_flag myflag;
std::optional<std::string> optional_str;
std::string &Func(char const c)
{
std::call_once(myflag, [c]{ optional_str.emplace(c, 20u); });
std::string &str = optional_str.value();
return str;
}
Ando so yes there's overhead here -- we have a lock. But that's only
because the constructor for std::string is complicated. Let's consider
a much more simple example:
int &Func(void)
{
static int i = 77;
return i;
}
This will simply get compiled to:
constinit int i = 77;
int &Func(void)
{
return i;
}
If we were to make it a little more complicated, then we would need a
lock, for example:
int &Func(int const arg)
{
static int i = arg / 2;
return i;
}
would get compiled to:
#include <mutex> // call_once, once_flag
std::once_flag myflag;
int i;
int &Func(int const arg)
{
std::call_once(myflag, [arg]{ i = arg / 2; });
return i;
}
In my original example code that I gave in a previous post, I had a
char buffer that got initialised to all zeroes as follows:
#include <cstring> // memcpy
#include <algorithm> // min
#include <string_view> // string_view
template <typename = decltype([]{})>
char const *cstr(std::string_view const sv)
{
static char buf[64u] = {};
std::size_t const len = std::min(sizeof buf - 1u, sv.size());
if ( len ) std::memcpy(buf, &sv.front(), len);
buf[len] ='\0';
return buf;
}
There's no need for any lock here, the buffer will be get set to all
zeroes before the function is ever entered.
> In many cases a lot better would be pure `std::array` (or static vector)
> on stack and have a bigger stack to accommodate all data like this.
> This would have another benefits that program will not waste memory
> for things that are used only in one case and many "cases" have no
> overlapping lifetime.
Again not sure what you're saying here. I prefer to waste memory on
microcontrollers instead of using dynamic allocation. If I had my way,
my microcontrollers would have no heap.
>
> Are you aware that the thing you propose is opposite of this?
> Do you know that each function `static` needs lock and it's not free.
> Or at least condition variable to check if the value was already initialized.
Not sure what you're saying here as your post is a bit vague, but if
you're referring to how since C++11 we've been guaranteed that
static-duration variables inside functions are thread-safe, and that
we therefore need a lock for each static variable inside a function,
well then let's just explore that -- consider the following code:
#include <string> // string
std::string &Func(char const c)
{
static std::string str(c, 20u);
return str;
}
This code will behave exactly as though you had written:
#include <mutex> // call_once, once_flag
#include <optional> // optional
#include <string> // string
std::once_flag myflag;
std::optional<std::string> optional_str;
std::string &Func(char const c)
{
std::call_once(myflag, [c]{ optional_str.emplace(c, 20u); });
std::string &str = optional_str.value();
return str;
}
Ando so yes there's overhead here -- we have a lock. But that's only
because the constructor for std::string is complicated. Let's consider
a much more simple example:
int &Func(void)
{
static int i = 77;
return i;
}
This will simply get compiled to:
constinit int i = 77;
int &Func(void)
{
return i;
}
If we were to make it a little more complicated, then we would need a
lock, for example:
int &Func(int const arg)
{
static int i = arg / 2;
return i;
}
would get compiled to:
#include <mutex> // call_once, once_flag
std::once_flag myflag;
int i;
int &Func(int const arg)
{
std::call_once(myflag, [arg]{ i = arg / 2; });
return i;
}
In my original example code that I gave in a previous post, I had a
char buffer that got initialised to all zeroes as follows:
#include <cstring> // memcpy
#include <algorithm> // min
#include <string_view> // string_view
template <typename = decltype([]{})>
char const *cstr(std::string_view const sv)
{
static char buf[64u] = {};
std::size_t const len = std::min(sizeof buf - 1u, sv.size());
if ( len ) std::memcpy(buf, &sv.front(), len);
buf[len] ='\0';
return buf;
}
There's no need for any lock here, the buffer will be get set to all
zeroes before the function is ever entered.
> In many cases a lot better would be pure `std::array` (or static vector)
> on stack and have a bigger stack to accommodate all data like this.
> This would have another benefits that program will not waste memory
> for things that are used only in one case and many "cases" have no
> overlapping lifetime.
Again not sure what you're saying here. I prefer to waste memory on
microcontrollers instead of using dynamic allocation. If I had my way,
my microcontrollers would have no heap.
Received on 2023-11-16 23:44:46