我可以用宏实现这种分析代码吗?

Can I implement this kind of profiling code with a macro?

不是预处理器宏技巧方面的专家,所以如果这里的问题只是我不熟悉一些常见的宏习惯用语,我会很高兴 Google。 X 宏与我之前所掌握的差不多,我很确定我不能用它们做任何事情。

现在我在代码中做了这样的事情:

std::size_t trial = 0;

std::array<std::array<uint64_t, 5>, MAX_TRIALS> results_f;

void f()
{
  unsigned int core;

  results_f[trial][0] = __rdtscp(&core);

  // do stuff

  results_f[trial][1] = __rdtscp(&core);

  // do some more stuff

  results_f[trial][2] = __rdtscp(&core);

  // do yet more stuff

  results_f[trial][3] = __rdtscp(&core);

  // do even more stuff

  results_f[trial][4] = __rdtscp(&core);

  if(++trial == MAX_TRIALS)
  {
    process_timestamps_f(results);
    trial = 0;
  }
}

(此处 __rdtscp 是一个 x86 内在函数,它从 CPU 获取一个刻度号。)

我反而希望能够写出这样的东西:

STOPWATCH_BOILERPLATE_PRE(f);

void f()
{
  STOPWATCH_BEGIN;

  // do stuff

  STOPWATCH_LAP;  

  // do some more stuff

  STOPWATCH_LAP;

  // do yet more stuff

  STOPWATCH_LAP;

  // do even more stuff

  STOPWATCH_END; 
};

STOPWATCH_BOILERPLATE_POST(f);

所以基本上,我需要能够计算 STOPWATCH_LAP 出现在 f 内部的次数,并使用它来设置在 f 内部可见的数组的大小。

奖励:如果在 process_timestamps_f 中,我可以编写类似 LINE_ID(f, 3) 的内容,并在 __LINE__ 的第三个实例处获得预处理器宏的结果,那就太好了=13=] 在 f.

(我想实际的代码可能看起来和我上面写的不完全一样。开放任何需要修改才能使这个工作。唯一真正的要求是我不必不断地计算有多少这些圈点我已经放入一个函数并更新相应的代码以匹配。)

我假设您不想涉及任何动态内存管理?因为否则你可以简单地使用 std::vector 并为每个结果做一个 push_back()...

否则,我不认为仅使用标准语言元素可以轻松实现这一点。但是 MSVC、clang 和 gcc 支持 __COUNTER__,这是一个特殊的宏,每次使用都会递增,可以在这里利用。在函数之前存储初始值,然后在每个“LAP”中使用它,您可以计算函数内的圈数。此外,如果通过 extern 使用 C-array,则无需在函数前指定第一个维度即可声明结果数组,然后用现在已知的圈数定义它。 您也可以在存储 __rdtscp() 结果的同时简单地存储 __LINE__ 结果。 请参见以下示例。这一切都非常脆弱,并假设宏按该顺序使用,但根据实际代码,它可能就足够了 (https://godbolt.org/z/crrGY7n4P):

#include <array>
#include <cstdint>
#include <iostream>

#ifndef _MSC_VER 
    // https://code-examples.net/en/q/e19526
    uint64_t __rdtscp( uint32_t * aux )
    {
        uint64_t rax,rdx;
        asm volatile ( "rdtscp\n" : "=a" (rax), "=d" (rdx), "=c" (*aux) : : );
        return (rdx << 32) + rax;
    }
#else 
    #include <intrin.h>
#endif

constexpr std::size_t MAX_TRIALS = 3;

struct ResultElem
{
    uint64_t timing;
    unsigned line;
};


void process_timestamps(ResultElem results[][MAX_TRIALS], std::size_t numResult, char const * const func)
{
    std::cout << func << ": Num = " << numResult << std::endl;
    for (std::size_t trial = 0; trial < MAX_TRIALS; ++trial) {
        std::cout << "\tTrial=" << trial << std::endl;
        for (std::size_t i = 0; i < numResult; ++i) {
            std::cout << "\t\tLine=" << results[i][trial].line << ", time=" << results[i][trial].timing << std::endl;
        }
    }
}


#define STOPWATCH_BOILERPLATE_PRE(f) \
    extern ResultElem results_ ## f[][MAX_TRIALS]; \
    constexpr std::size_t counterStart_ ## f = __COUNTER__; \
    std::size_t trial_ ## f = 0;

#define STOPWATCH_BEGIN(f) uint32_t core; STOPWATCH_LAP(f)

#define STOPWATCH_LAP(f) results_ ## f[__COUNTER__ - counterStart_ ## f - 1][trial_ ## f] = {__rdtscp(&core), __LINE__}

#define STOPWATCH_END(f) \
  STOPWATCH_LAP(f); \
  if(++trial_ ## f == MAX_TRIALS) { \
    process_timestamps(results_ ## f, __COUNTER__ - counterStart_ ## f - 1, #f); \
    trial_ ## f = 0; \
  }

// Needs to be used directly after STOPWATCH_END() because we subtract 2 from __COUNTER__.
#define STOPWATCH_BOILERPLATE_POST(f) \
    constexpr std::size_t numResult_ ## f = __COUNTER__ - counterStart_ ## f - 2; \
    ResultElem results_ ## f[numResult_ ## f][MAX_TRIALS];


STOPWATCH_BOILERPLATE_PRE(f)

void f()
{
  STOPWATCH_BEGIN(f);

  // do stuff

  STOPWATCH_LAP(f);

  // do some more stuff

  STOPWATCH_LAP(f);

  // do even more stuff

  STOPWATCH_END(f);
}

STOPWATCH_BOILERPLATE_POST(f)

我能想到的替代方案:

  • 如果没有动态分配和保持标准,您可能会使用 BOOST_PP_COUNTER 构建一些东西。 STOPWATCH_LAP 然后可能会变成某种形式的 #include 语句。

  • 我还可以想象,使用 weird loophole in C++14 可以在没有宏的情况下构建一些东西,但这会变得非常复杂。