繁体   English   中英

将函数及其实现从主文件移动到不同的文件(.hpp 和 .cpp)时,性能会受到很大影响

[英]When moving a function and it's implementation to a different file (.hpp and .cpp) from the main file, performance greatly suffers

在我的主文件(带有 main 函数的文件)中,我还有另一个函数:

unsigned long generate_random_number()
{
    unsigned long y;
    static unsigned long mag01[2] = {0x0UL, MATRIX_A};

    // mag01[x] = x * MATRIX_A  for x=0,1
    if (mti >= N) // generate N words at one time
    { 
        int kk;
        if (mti == N+1)   // if init_genrand() has not been called
            init_genrand(5489UL); // a default initial seed is used

        for (kk=0;kk<N-M;kk++) {
            y = (mt[kk]&UPPER_MASK)|(mt[kk+1]&LOWER_MASK);
            mt[kk] = mt[kk+M] ^ (y >> 1) ^ mag01[y & 0x1UL];
        }
        for (;kk<N-1;kk++) {
            y = (mt[kk]&UPPER_MASK)|(mt[kk+1]&LOWER_MASK);
            mt[kk] = mt[kk+(M-N)] ^ (y >> 1) ^ mag01[y & 0x1UL];
        }
        y = (mt[N-1]&UPPER_MASK)|(mt[0]&LOWER_MASK);
        mt[N-1] = mt[M-1] ^ (y >> 1) ^ mag01[y & 0x1UL];

        mti = 0;
    }

    y = mt[mti++];
    // Tempering
    y ^= (y >> 11);
    y ^= (y << 7) & 0x9d2c5680UL;
    y ^= (y << 15) & 0xefc60000UL;
    y ^= (y >> 18);
    return y;
}

基本上它来自这个网站: http : //www.the-control-freak.com/Random/Random.htm

如果我将函数和定义移到头文件中,将实现移到 cpp 文件(仍然不在类中)并将.o目标文件链接到我的主程序,则性能会大打折扣。 该函数从开销的约 11% 变为约 15%(来自 google perf)。 关于为什么会这样的任何想法?

一般来说,如果你链接一个目标文件,从目标文件调用一个函数是否需要很多开销?

生成文件:

CXX = clang++
CFLAGS = -std=c++17 -O3 -Wall -Iinclude/ 
SRC = src/
INC = include/

random.o: $(SRC)random.cpp $(INC)random.hpp
    $(CXX) $(CFLAGS) -c $(SRC)random.cpp

myprog: myprog.cpp random.o 
    $(CXX) $(CFLAGS) -o refactor myprog.cpp random.o -lprofiler

myprog.cpp 的简化版本。 case 语句不在 main 中,而是在另一个函数中。 该函数被调用 N 次,平均而言,stdev 是通过套接字发送的。

我的程序

int main()
{
    switch(hurry_ind)
    {
        case 0: return generate_random_number() % 19;
        break;
        case 1: return generate_random_number() % 100;
        break;
        case 2: return generate_random_number() % 9;
        break;
        case 3: return generate_random_number() % 914;
        break;
        case 4: return generate_random_number() % 355;
        break;
        case 5: return generate_random_number() % 348;
        break;
        case 6: return generate_random_number() % 65;
        break;
    }
}

如果函数是在同一编译单元中定义的,编译器可以内联一个经常调用的函数并进行一些积极的优化。 这很可能是所报告的放缓的原因。

我使用 clang 和 gcc 测试了代码。 Clang 始终提供相同的生产力(每 2000000000 次循环 12.5275 秒),因此我无法重现所描述的行为,但是当我将该函数标记为inline时,gcc 显着提高了性能(每 2000000000 次循环为 8.31 与 10.42 秒)。 因此,您尝试在初始版本(同一编译单元) __attribute__((noinline))到函数中。 如果它降低了性能,那么根本原因是内联。

我使用的测试程序:

随机文件

#pragma once
unsigned long generate_random_number();

随机文件

#define N            17U
#define M            13U

#define MATRIX_A     0x9908B0DFUL
#define UPPER_MASK   0x80000000UL
#define LOWER_MASK   0x7FFFFFFFUL

static unsigned long mt [ N ];
static int           mti = N + 1;

void init_genrand ( unsigned long ulSeed )
{

  mt [ 0 ]= ulSeed & 0xFFFFFFFFUL;

  for ( mti = 1; mti < int(N); mti++ )
  {

    /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier.   */
    /* In the previous versions, MSBs of the seed affect     */
    /* only MSBs of the array mt[].                          */
    /* 2002/01/09 modified by Makoto Matsumoto               */

    mt [ mti ] = ( 1812433253UL * ( mt [ mti - 1 ] ^ ( mt [ mti - 1 ] >> 30 ) ) + mti );

    mt [ mti ] &= 0xFFFFFFFFUL;

    /* for >32 bit machines */
  }

}

#ifdef INLINE_THE_FUNCTION
inline
#endif
unsigned long generate_random_number()
{
  unsigned long y;
  static unsigned long mag01[2] = {0x0UL, MATRIX_A};

  // mag01[x] = x * MATRIX_A  for x=0,1
  if (mti >= int(N)) // generate N words at one time
  { 
    int kk;
    if (mti == N+1)   // if init_genrand() has not been called
      init_genrand(5489UL); // a default initial seed is used

    for (kk=0; kk<int(N-M); kk++) {
      y = (mt[kk]&UPPER_MASK)|(mt[kk+1]&LOWER_MASK);
      mt[kk] = mt[kk+M] ^ (y >> 1) ^ mag01[y & 0x1UL];
    }
    for (;kk<int(N-1); kk++) {
      y = (mt[kk]&UPPER_MASK)|(mt[kk+1]&LOWER_MASK);
      mt[kk] = mt[kk+(M-N)] ^ (y >> 1) ^ mag01[y & 0x1UL];
    }
    y = (mt[N-1]&UPPER_MASK)|(mt[0]&LOWER_MASK);
    mt[N-1] = mt[M-1] ^ (y >> 1) ^ mag01[y & 0x1UL];

    mti = 0;
  }

  y = mt[mti++];
  // Tempering
  y ^= (y >> 11);
  y ^= (y << 7) & 0x9d2c5680UL;
  y ^= (y << 15) & 0xefc60000UL;
  y ^= (y >> 18);
  return y;
}

我的程序

#ifdef SAME_COMPILATION_UNIT
#include "random.cpp"
#else
#include "random.hpp"
#endif

#include <iostream>
#include <chrono>

unsigned long calc(int hurry_ind)
{
  switch(hurry_ind)
  {
    case 0: return generate_random_number() % 19;
    case 1: return generate_random_number() % 100;
    case 2: return generate_random_number() % 9;
    case 3: return generate_random_number() % 914;
    case 4: return generate_random_number() % 355;
    case 5: return generate_random_number() % 348;
    case 6: return generate_random_number() % 65;
  }
  return 0;
}

int main(int argc, char** argv)
{
  int n = argc > 1 ? std::atol(argv[1]) : 0;
  int res = 0;
  auto start = std::chrono::high_resolution_clock::now();
  for (int i = 0; i < n; ++i)
    res += calc(i % 7);
  auto end = std::chrono::high_resolution_clock::now();
  std::chrono::duration<double> diff = end-start;
  std::cout << res << "(" << diff.count() << " s)\n";
}

我使用的编译器:

clang++ -v
clang version 8.0.0 (tags/RELEASE_800/final)
Target: x86_64-w64-windows-gnu
Thread model: posix

gcc -v
Using built-in specs.
COLLECT_GCC=C:\GNU\msys64\mingw64\bin\gcc.exe
COLLECT_LTO_WRAPPER=C:/GNU/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.2.1/lto-wrapper.exe
Target: x86_64-w64-mingw32
Configured with: ../gcc-8-20181214/configure --prefix=/mingw64 --with-local-prefix=/mingw64/local --build=x86_64-w64-mingw32 --host=x86_64-w64-mingw32 --target=x86_64-w64-mingw32 --with-native-system-header-dir=/mingw64/x86_64-w64-mingw32/include --libexecdir=/mingw64/lib --enable-bootstrap --with-arch=x86-64 --with-tune=generic --enable-languages=ada,c,lto,c++,objc,obj-c++,fortran --enable-shared --enable-static --enable-libatomic --enable-threads=posix --enable-graphite --enable-fully-dynamic-string --enable-libstdcxx-filesystem-ts=yes --enable-libstdcxx-time=yes --disable-libstdcxx-pch --disable-libstdcxx-debug --disable-isl-version-check --enable-lto --enable-libgomp --disable-multilib --enable-checking=release --disable-rpath --disable-win32-registry --disable-nls --disable-werror --disable-symvers --with-libiconv --with-system-zlib --with-gmp=/mingw64 --with-mpfr=/mingw64 --with-mpc=/mingw64 --with-isl=/mingw64 --with-pkgversion='Rev1, Built by MSYS2 project' --with-bugurl=https://sourceforge.net/projects/msys2 --with-gnu-as --with-gnu-ld
Thread model: posix
gcc version 8.2.1 20181214 (Rev1, Built by MSYS2 project)

当编译器在同一个源文件( translation unit )中看到 2 个函数时,它可以创建一个允许优化寄存器的实现。

这种关于被调用函数和调用函数的知识对于这种形式的优化是必不可少的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM