Я работаю над профилировщиком инструментов, который позволяет вам называть различные измерения по строке. Так, например:
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
просто поместил бы метку времени вместе с именем в некоторый глобальный буфер для последующего использования (экспорт измерений и тому подобное).
Проблема заключается в следующем: сохранение названия измерения вместе с самим измерением очень неэффективно. Там также должно произойти много работы для пары MEASURE_START
а также MEASURE_STOP
, потому что проверка, совпадают ли их имена, требует сравнения строк. Гораздо лучшим решением было бы интернирование строки, т.е. где-то есть массив, содержащий все строки:
std::vector<string> = {"text_rendering_code", "password_hashing", "system_call"};
и замените строку в макросах измерений индексом строки в массиве:
MEASURE_SCOPE(0);
...
MEASURE_SCOPE(1);
...
MEASURE_START(2);
...
MEASURE_STOP(2);
Этот способ требует меньше памяти, и проверка соответствия имен становится простым целочисленным сравнением. С другой стороны, это очень недружелюбно по отношению к пользователю, так как он должен заранее знать индекс имени, которое он хочет дать своему измерению.
Есть ли способ сохранить хорошее использование MEASURE_SCOPE(text_rendering_code)
и заменить это более эффективным MEASURE_SCOPE(0)
автоматически? Это потребовало бы создания массива имен во время компиляции, эффективно объединяя строки. Это возможно?
Одинаковые литеральные строки не гарантируют идентичность, но вы можете создать из них тип, который может сравнивать идентичные (без сравнения строк), что-то вроде:
// 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"));
будет по-прежнему возвращать тот же указатель, что вы можете сравнить напрямую.
Вы можете изменить свой макрос MEASURE_SCOPE
включить напрямую 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);
Я могу только догадываться, что вы имеете в виду, потому что вы не даете достаточно подробностей, и они имеют большое значение.
Возможный подход заключается в генерировать некоторый специальный код C или C ++ с вашим собственным генератором. Помните, что некоторый код C или C ++ вашего проекта может быть сгенерирован (это грубая форма или метапрограммированием; Qt MOC, RPCGEN, бизон, SWIG Типичные примеры генераторов C ++ или C, но вы можете легко создать свой собственный, см. Вот; возможно, с помощью некоторого языка сценариев, таких как питон, коварство, AWK, …, или даже в C ++), а ваши автоматизация сборки может справиться с этим (например, какое-то специальное правило или рецепт в вашем Makefile
).
Тогда вы могли бы написать очень простую генерирующую программу, собирающую все случаи MEASURE_SCOPE
а также MEASURE_START
, MEASURE_STOP
вызовы макросов в вашем коде (*.cpp
файлы вашего проекта). Это довольно просто для кода: вы можете читать все построчно все .cpp
файлы и искать MEASURE_SCOPE
(и т. д.), затем пробелы, затем (
в них.
Эта генерирующая программа, имеющая дело с вашими интернированными строками, может генерировать большой заголовочный файл measure-generated.h
например, с вещи как
// in generated header
#define MEASURE_POINT_system_call 1
#define MEASURE_POINT_password_hashing 2
(может быть, вы хотите создать большой enum
вместо)
и это также будет испускать measure-generated-array.cpp
файл как
// generated code
const char* measure_array[] = {
NULL,
"system_call",
"password_hashing",
/// etc....
NULL,
};
И тогда вы могли бы в некоторых из ваших заголовков
#define MEASURE_SCOPE(X) measure_array[MEASURE_POINT_##X]
и т.д., используя приемы препроцессора, такие как stringizing и / или конкатенация
Смотрите также этот.
Это потребовало бы создания массива имен во время компиляции, эффективно объединяя строки. Это возможно?
Да, конечно. Сделайте это в своем собственном генераторе C ++, который знает весь ваш проект *.cpp
файлы, как я предложил. Вы можете создавать файлы C ++ на строить время.