在编译时进行字符串实习以进行性能分析 [英] String-interning at compiletime for profiling
问题描述
我正在研究仪器分析器,它使您能够按字符串命名不同的测量值.例如:
I am working on an instrumenting profiler, that enables you to name different measurements by string. So for example:
MEASURE_SCOPE(text_rendering_code);
...
MEASURE_SCOPE(password_hashing);
...
MEASURE_START(system_call);
...
MEASURE_STOP(system_call);
定义宏的位置:
#define MEASURE_START(name) save_start_event(get_timestamp(), #name);
#define MEASURE_STOP(name) save_stop_event(get_timestamp(), #name);
#define MEASURE_SCOPE(name) Profiling_Class object##name (#name);
class Profiling_Class{
string name;
Profiling_Class(string name){
this->name = name;
save_start_event(get_timestamp(), name);
}
~Profiling_Class(){save_end_event(get_timestamp(), this->name);}
}
save_start_event
和save_end_event
会将时间戳和名称一起放入某个全局缓冲区中,以备后用(导出测量结果等).
save_start_event
and save_end_event
would just put the timestamp along with the name into some global buffer for later use (exporting measurements and such).
问题是这样的:将度量名称与度量本身一起保存是非常低效的.将MEASURE_START
和MEASURE_STOP
配对还需要进行大量工作,因为检查它们的名称是否相同需要字符串比较.更好的解决方案是内联字符串,即在某个位置容纳所有字符串的数组:
The problem is this: saving the name of a measurement along with the measurement itself is very inefficient. There also needs to happen a lot of work to pair MEASURE_START
and MEASURE_STOP
, because checking if their names are same requires a string-compare. A much better solution would be to intern the string, i.e. have some array somewhere that holds all the strings:
std::vector<string> = {"text_rendering_code", "password_hashing", "system_call"};
并用数组中字符串的索引替换度量宏中的字符串:
and substitue the string in the measurement-macros with the index of the string in the array:
MEASURE_SCOPE(0);
...
MEASURE_SCOPE(1);
...
MEASURE_START(2);
...
MEASURE_STOP(2);
这种方式需要较少的存储空间,并且检查名称是否匹配变成简单的整数比较.另一方面,这对用户来说非常不友好,因为他必须事先知道他要提供其度量的名称的索引.
This way requires less storage, and checking if names match becomes a simple integer-compare. On the other hand, it's very unfriendly towards the user, since he has to know in advance the index of the name he wants to give his measurement.
是否有办法保留MEASURE_SCOPE(text_rendering_code)
的用法,并自动将其替换为效率更高的MEASURE_SCOPE(0)
?这将需要在编译时构建名称数组,从而有效地对字符串进行内部处理.这可能吗?
Is there a way to preserve the nice usage of MEASURE_SCOPE(text_rendering_code)
and substitute this with the more efficient MEASURE_SCOPE(0)
automatically? This would require building the name-array at compile time, effectively interning the strings. Is this possible?
推荐答案
相同的文字字符串不能保证相同,但是您可以从中构建可以比较相同的类型(无需比较字符串),例如:
Identical literal strings are not guaranty to be identical, but you can build type from it which can compare identical (without comparing string), something like:
// Sequence of char
template <char...Cs> struct char_sequence
{
template <char C> using push_back = char_sequence<Cs..., C>;
};
// Remove all chars from char_sequence from '\0'
template <typename, char...> struct strip_sequence;
template <char...Cs>
struct strip_sequence<char_sequence<>, Cs...>
{
using type = char_sequence<Cs...>;
};
template <char...Cs, char...Cs2>
struct strip_sequence<char_sequence<'\0', Cs...>, Cs2...>
{
using type = char_sequence<Cs2...>;
};
template <char...Cs, char C, char...Cs2>
struct strip_sequence<char_sequence<C, Cs...>, Cs2...>
{
using type = typename strip_sequence<char_sequence<Cs...>, Cs2..., C>::type;
};
// struct to create a aligned char array
template <typename chars> struct static_string;
template <char...Cs>
struct static_string<char_sequence<Cs...>>
{
static constexpr char str[sizeof...(Cs)] = {Cs...};
};
template <char...Cs>
constexpr
char static_string<char_sequence<Cs...>>::str[sizeof...(Cs)];
// helper to get the i_th character (`\0` for out of bound)
template <std::size_t I, std::size_t N>
constexpr char at(const char (&a)[N]) { return I < N ? a[I] : '\0'; }
// helper to check if the c-string will not be truncated
template <std::size_t max_size, std::size_t N>
constexpr bool check_size(const char (&)[N])
{
static_assert(N <= max_size, "string too long");
return N <= max_size;
}
// Helper macros to build char_sequence from c-string
#define PUSH_BACK_8(S, I) \
::push_back<at<(I) + 0>(S)>::push_back<at<(I) + 1>(S)> \
::push_back<at<(I) + 2>(S)>::push_back<at<(I) + 3>(S)> \
::push_back<at<(I) + 4>(S)>::push_back<at<(I) + 5>(S)> \
::push_back<at<(I) + 6>(S)>::push_back<at<(I) + 7>(S)>
#define PUSH_BACK_32(S, I) \
PUSH_BACK_8(S, (I) + 0) PUSH_BACK_8(S, (I) + 8) \
PUSH_BACK_8(S, (I) + 16) PUSH_BACK_8(S, (I) + 24)
#define PUSH_BACK_128(S, I) \
PUSH_BACK_32(S, (I) + 0) PUSH_BACK_32(S, (I) + 32) \
PUSH_BACK_32(S, (I) + 64) PUSH_BACK_32(S, (I) + 96)
// Macro to create char_sequence from c-string (limited to 128 chars)
#define MAKE_CHAR_SEQUENCE(S) \
strip_sequence<char_sequence<> \
PUSH_BACK_128(S, 0) \
>::type::template push_back<check_size<128>(S) ? '\0' : '\0'>
// Macro to return an static c-string
#define MAKE_STRING(S) \
aligned_string<MAKE_CHAR_SEQUENCE(S)>::str
所以
MEASURE_SCOPE(MAKE_STRING("text_rendering_code"));
仍然会返回与您可以直接比较的指针相同的指针.
would still return same pointer than you can compare directly.
您可以修改宏MEASURE_SCOPE
以直接包含MAKE_STRING
.
You can modify your Macro MEASURE_SCOPE
to include directly MAKE_STRING
.
gcc具有扩展名以简化MAKE_STRING
:
template <typename CHAR, CHAR... cs>
const char* operator ""_c() { return static_string<cs...>{}::str; }
然后
MEASURE_SCOPE("text_rendering_code"_c);
这篇关于在编译时进行字符串实习以进行性能分析的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!