String-interning во время компиляции для профилирования

контекст

Я работаю над профилировщиком инструментов, который позволяет вам называть различные измерения по строке. Так, например:

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) автоматически? Это потребовало бы создания массива имен во время компиляции, эффективно объединяя строки. Это возможно?

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);
0

Другие решения

Я могу только догадываться, что вы имеете в виду, потому что вы не даете достаточно подробностей, и они имеют большое значение.

Возможный подход заключается в генерировать некоторый специальный код 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 ++ на строить время.

0

По вопросам рекламы [email protected]