String-interning at compiletime for profiling

前端 未结 2 754
爱一瞬间的悲伤
爱一瞬间的悲伤 2020-12-20 07:51

Context

I am working on an instrumenting profiler, that enables you to name different measurements by string. So for example:

MEASURE_SCOPE(text_re         


        
相关标签:
2条回答
  • 2020-12-20 08:28

    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
    

    So

    MEASURE_SCOPE(MAKE_STRING("text_rendering_code"));
    

    would still return same pointer than you can compare directly.

    You can modify your Macro MEASURE_SCOPE to include directly MAKE_STRING.

    gcc has an extension to simplify MAKE_STRING:

    template <typename CHAR, CHAR... cs>
    const char* operator ""_c() { return static_string<cs...>{}::str; }
    

    and then

    MEASURE_SCOPE("text_rendering_code"_c);
    
    0 讨论(0)
  • 2020-12-20 08:49

    I can only guess what you mean, because you don't give enough details, and they matter a lot.

    A possible approach is to generate some ad-hoc C or C++ code with your own generator. Remember that some C or C++ code of your project can be generated (this is a crude form or metaprogramming; Qt moc, RPCGEN, bison, SWIG are typical examples of C++ or C generators, but you can easily make your own one, see here; perhaps with the help of some scripting language like Python, Guile, AWK, ..., or even in C++), and your build automation could handle that (e.g. some as-hoc rule or recipe in your Makefile).

    Then you could write a very simple generating program collecting all occurrences of MEASURE_SCOPE and MEASURE_START, MEASURE_STOP macro invocations in your code (*.cpp files of your project). This is quite simple to code: you could read line by line all .cpp files and look for MEASURE_SCOPE (etc...) followed by spaces then by ( in them.

    That generating program -dealing with your interned strings- might emit a large header file measure-generated.h with e.g. things like

    // in generated header
    #define MEASURE_POINT_system_call 1
    #define MEASURE_POINT_password_hashing 2
    

    (maybe you want to generate some large enum instead)

    and it would also emit a measure-generated-array.cpp file like

    // generated code
    const char* measure_array[] = {
      NULL,
      "system_call",
      "password_hashing",
      /// etc....
      NULL,
    };
    

    And then you could in some of your headers

    #define MEASURE_SCOPE(X) measure_array[MEASURE_POINT_##X]
    

    etc, using preprocessor tricks like stringizing and/or concatenation

    See also this.

    This would require building the name-array at compile time, effectively interning the strings. Is this possible?

    Yes, of course. Do that in your own C++ generator which knows all your project *.cpp files like I suggested. You can generate C++ files at build time.

    0 讨论(0)
提交回复
热议问题