c++的模板在编译期间有"合并优化"的行为吗?还是说必然"一种类型生成一份代码"?

2019-12-20 10:51:24 +08:00
 everlost

像下面这个简单的例子,一个类模板,只有一个指针成员 ref,一个打印函数 print().

template <class T>
class Refer{
  T* ref;
  Refer(T* _ref){
    this->ref = _ref;
  }
  void print(){
    printf("%p", this->ref);
  }
}

//测试
int main(){
  int i;
  char c;
  Refer<int> i_ref(&i);
  Refer<char> c_ref(&c);
  i_ref.print();
  c_ref.print();
}

虽然 ref 会指向各种 T 类型(上面的例子里是 char 和 int),但鉴于 ref 字段本身的 size 是确定的,而 print()函数也并不访问 T 的内部,编译器似乎生成一份代码就够用了.对吗?或者说,编译时为了类型检查,可以给 char 和 int 各自生成一份代码,但到了链接,也许应该"优化合并"成一份代码?因为这两份代码的汇编似乎是一样的. c++的类型信息不需要带到运行时.

我用 g++ -O 编译(默认),发现构造函数和 print()函数分别生成两份.(一共 call 了 4 个不同的函数) 用 g++ -O3 编译,构造和 print()函数都被优化掉了.

我自己还在努力测试中,各位如有类似的经验能否分享一下?先说声感谢.我最终想搞清的是,模板是不是一定会引起代码膨胀,感觉对指针类型的模板类,又不解引用的话,"一个类型生成一份代码",有些浪费.

4833 次点击
所在节点    C++
27 条回复
secondwtq
2019-12-20 20:01:06 +08:00
http://hubicka.blogspot.com/2015/04/GCC5-IPA-LTO-news.html

"On the other hand proving that two functions are identical in compiler is much harder than comparing a binary blobs with relocations though. Not only the instructions needs to match each other, but all the additional meta-data maintained by the compiler needs to be matched and merged. This include type based aliasing analysis information, polymorphic call contexts, profile, loop dependencies and more. For this reason the pass does not replace Gold's feature."

可能是因为这个 ... 大概也能解释为啥 MSVC 也是在 linker 里面做
qieqie
2019-12-20 20:12:18 +08:00
你举的例子里这种情况写个 union 就行了,不需要 template
secondwtq
2019-12-20 20:41:11 +08:00
LLVM 有点硬核啊 ... 只运行这个 Pass: http://llvm.org/docs/MergeFunctions.html 就能实现楼主要的效果 ... 只是默认没打开

clang++ -O0 -Xclang -fmerge-functions ./ipo.cpp

原 IR:
%13 = call i32 @_ZL8bisearchIiEiP5ReferIT_Ei(%class.Refer* %4, i32 10)
%15 = call i32 @_ZL8bisearchIjEiP5ReferIT_Ei(%class.Refer.0* %5, i32 10)

优化后的 IR:
%13 = call i32 @_ZL8bisearchIiEiP5ReferIT_Ei(%class.Refer* %4, i32 10)
%15 = call i32 bitcast (i32 (%class.Refer*, i32)* @_ZL8bisearchIiEiP5ReferIT_Ei to i32 (%class.Refer.0*, i32)*)(%class.Refer.0* %5, i32 10)

另外真心佩服这个文档写得比代码还多的 ... 说实话 LLVM 里面文档写得这么详细的 Pass 不多
GCC 我不熟悉,还是去某东买茴香豆吧 ...
everlost
2019-12-20 20:52:45 +08:00
@hehheh  冤枉,我绝没有依赖编译器行为的打算.只是有点儿担心类似 shared_ptr 之流的模板造成的代码膨胀.
everlost
2019-12-20 21:56:55 +08:00
@secondwtq  非常感谢!先看了前半部分,英文烂原谅,回头我再慢慢看...看到有人在做这个东西我就放心了好多.他文档里提到了相似函数的识别算法,我觉得这种算法,由 c++编译器的前部来配合支持更好,前头先把"模板"生成的函数和结构体(毕竟这块儿是重灾区)打上标记,优化时重点检查,不然 N*N 的复杂度,怕会一直被人家拒绝.当然我随便说的...编译器挺有意思的,何时我也能研究一下就好了.哎.
secondwtq
2019-12-20 22:23:24 +08:00
@everlost 你仔细看的话会发现那个 n*n 的算法只是面试的时候做题给的第一版 ... 他后面紧接着介绍了两种优化,一种是 O(lgn) 的 treeset 一种是 O(n) 的 hash,但是实际测试 hash 要慢一点,所以就上了个 treeset

之后又加了一个 hash 来做 candidate 的预处理
不知道为啥默认没启用
secondwtq
2019-12-20 22:44:41 +08:00
找到了这个东西 https://groups.google.com/d/msg/llvm-dev/mJFOYABEyKs/PXcZ7h4OGwAJ
看起来是担心做得不够安全
不过 Rust 和 Swift 是默认开启的

以及这个新鲜出炉的黑科技 paper,号称可以合并任意函数 ... http://homepages.inf.ed.ac.uk/hleather/publications/2019_functionmergesequencealign_cgo2019.pdf

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/630765

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX