You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在编译时扩展性能分析用的字符串查找表(LUT)

问题描述

我希望优化自研的性能分析器以降低性能开销。当前实现中,我使用接收字符串参数的ProfilerHelper,将其置于待分析函数f()中:构造函数启动计时,析构函数结束计时并将时间差记录在以该字符串为键的unordered_map中。

现在我想将其改造为更高效的实现:首先在编译时生成包含函数f()名称的字符串查找表(LUT),并将unordered_map替换为与该LUT配对的普通vector。

目前我已用std::string_view实现了基础LUT,但无法找到编译时扩展它的方法。我的初步尝试代码如下:

template<unsigned N>
constexpr auto LUT() {
   std::array<std::string_view, N> Strs{};
   for (unsigned n = 0; n < N; n++) {
      Strs[n] = "";
   }

   return Strs;
};

constexpr std::array<std::string_view, 0> StringsLUT { LUT<0>() };

constexpr auto AddString(std::string_view const& Str)
{
   constexpr auto Size = StringsLUT.size();
   std::array<std::string_view, Size + 1> Copy{};
   for (auto i = 0; i < Size; ++i)
      Copy[i] = StringsLUT[i];
   Copy[Size] = Str;
   return Copy;
};

int main()
{
   constexpr auto Strs = AddString(__builtin_FUNCTION());

   //for (auto const Str : Strs)
      std::cout << Strs[0] << std::endl;
}

我的设想是在每个待分析函数f()中调用AddString,以在编译时扩展这个列表,但需要每次用返回的Copy替换StringsLUT,最终得到包含所有函数名的StringsLUT。请问是否有办法在编译时实现这一需求?我刚接触constexpr在LUT中的应用,感谢您的支持。


解决方案

你的核心问题在于:constexpr全局变量是编译时固定的,无法通过函数调用动态扩展——每个AddString调用只会生成一个新的数组,而无法修改原有的StringsLUT。要实现编译时收集所有待分析函数名并生成可索引的LUT,需要换用模板元编程+静态注册的思路,以下是可行的实现方案:

核心思路

  • 为每个待分析函数分配一个唯一的编译时索引。
  • 利用静态变量的初始化机制,自动将函数名注册到全局LUT中。
  • 用vector存储计时数据,通过索引直接访问,完全替代unordered_map的哈希查找。

代码实现

1. 全局LUT与索引生成器

#include <array>
#include <string_view>
#include <vector>
#include <atomic>
#include <iostream>
#include <source_location>

// 全局LUT,存储所有待分析函数的名称
inline std::vector<std::string_view> g_profiler_lut;
// 原子计数器,用于生成唯一索引(线程安全)
inline std::atomic_size_t g_profiler_index_counter = 0;

// 模板函数:为每个函数生成唯一索引,并注册函数名到LUT
template<typename Func>
constexpr size_t get_profiler_index(Func&&, const std::source_location& loc = std::source_location::current()) {
    // 静态变量:每个函数实例化时只会初始化一次
    static const size_t index = []() {
        // 将函数名插入LUT
        g_profiler_lut.emplace_back(loc.function_name());
        // 返回当前计数器值,然后自增
        return g_profiler_index_counter++;
    }();
    return index;
}

2. 改造后的ProfilerHelper

#include <chrono>
#include <thread>

class ProfilerHelper {
public:
    // 接收函数的索引(编译时确定)
    explicit ProfilerHelper(size_t func_index)
        : m_func_index(func_index),
          m_start(std::chrono::steady_clock::now()) {}

    ~ProfilerHelper() {
        auto end = std::chrono::steady_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - m_start).count();
        
        // 确保计时数据vector的大小足够
        static std::vector<unsigned long long> g_timing_data;
        if (m_func_index >= g_timing_data.size()) {
            g_timing_data.resize(m_func_index + 1, 0);
        }
        // 累加计时(线程安全可加锁,此处简化处理)
        g_timing_data[m_func_index] += duration;
    }

private:
    size_t m_func_index;
    std::chrono::steady_clock::time_point m_start;
};

// 方便使用的宏,自动获取当前函数的索引
#define PROFILE_FUNCTION() \
    ProfilerHelper __profiler(get_profiler_index([](){}))

3. 使用示例

void foo() {
    PROFILE_FUNCTION();
    // 模拟函数逻辑
    std::this_thread::sleep_for(std::chrono::microseconds(100));
}

void bar() {
    PROFILE_FUNCTION();
    // 模拟函数逻辑
    std::this_thread::sleep_for(std::chrono::microseconds(200));
}

int main() {
    foo();
    bar();
    foo();

    // 输出统计结果
    static auto& timing_data = *new std::vector<unsigned long long>;
    for (size_t i = 0; i < g_profiler_lut.size(); ++i) {
        std::cout << "函数: " << g_profiler_lut[i] 
                  << " 总耗时: " << timing_data[i] << " 微秒\n";
    }

    return 0;
}

关键说明

  • 编译时索引:get_profiler_index模板函数为每个调用它的函数生成唯一的静态索引,这个索引在编译阶段就已确定(静态变量的初始化在程序启动前完成)。
  • LUT自动注册:每个函数第一次被调用时,静态lambda会将函数名插入全局LUT,保证所有待分析函数的名称都被收集。
  • 性能优势:使用vector的索引访问代替unordered_map的哈希查找,完全消除了字符串哈希和键值对查找的开销,性能提升显著。
  • 跨平台兼容:使用C20标准的std::source_location获取函数名,若编译器不支持C20,可替换为__builtin_FUNCTION()等编译器内置宏。

若你坚持要完全在编译时生成固定大小的LUT(而非运行时动态填充),可利用C++20的constexpr vector或模板元编程实现,但这种方式需要显式声明所有待分析函数,灵活性较差。上述方案兼顾了编译时确定索引和运行时自动收集的便利性,是性能分析器常用的优化方式。

内容的提问来源于stack exchange,提问作者riccardoventrella

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 12:35:23