整数类包装器性能
Integer class wrapper performance
我正在为不动点数字改造现有的库。目前,该库只是在32位有符号整数上操作的命名空间函数。我想扭转这一局面,创建一个封装整数的定点类,但不想为这种细粒度的东西支付与类相关的任何性能损失,因为性能是用例的一个问题。
由于预期类具有如此简单的数据需求,并且没有资源,我认为可能使类"面向价值",利用非修改操作并在合理的情况下按值传递实例。如果实现的话,这将是一个简单的类,而不是层次结构的一部分。
我想知道是否有可能以这样的方式编写一个整数包装类,即与使用原始整数相比,不会产生真正的性能损失。我几乎确信这是事实,但对编译过程的了解还不够,无法直接投入其中
我知道有人说stl迭代器被编译成简单的指针操作,并且只想对整数操作做类似的事情。
无论如何,作为项目的一部分,该库将更新为c++11,所以我希望至少有了constexpr和其他新功能(如右值引用),我可以将此类的性能提高到接近纯整数运算的性能。
此外,对于两种实现之间的性能差异进行基准测试的任何建议都将受到赞赏。
这个问题的有趣之处在于它依赖于编译器。使用Clang/LLVM:
#include <iostream>
using namespace std;
inline int foo(int a) { return a << 1; }
struct Bar
{
int a;
Bar(int x) : a(x) {}
Bar baz() { return a << 1; }
};
void out(int x) __attribute__ ((noinline));
void out(int x) { cout << x; }
void out(Bar x) __attribute__ ((noinline));
void out(Bar x) { cout << x.a; }
void f1(int x) __attribute ((noinline));
void f1(int x) { out(foo(x)); }
void f2(Bar b) __attribute ((noinline));
void f2(Bar b) { out(b.baz()); }
int main(int argc, char** argv)
{
f1(argc);
f2(argc);
}
给出以下IR:
define void @_Z3outi(i32 %x) uwtable noinline {
%1 = tail call %"class.std::basic_ostream"*
@_ZNSolsEi(%"class.std::basic_ostream"* @_ZSt4cout, i32 %x)
ret void
}
define void @_Z3out3Bar(i32 %x.coerce) uwtable noinline {
%1 = tail call %"class.std::basic_ostream"*
@_ZNSolsEi(%"class.std::basic_ostream"* @_ZSt4cout, i32 %x.coerce)
ret void
}
define void @_Z2f1i(i32 %x) uwtable noinline {
%1 = shl i32 %x, 1
tail call void @_Z3outi(i32 %1)
ret void
}
define void @_Z2f23Bar(i32 %b.coerce) uwtable noinline {
%1 = shl i32 %b.coerce, 1
tail call void @_Z3out3Bar(i32 %1)
ret void
}
不出所料,生成的程序集完全相同:
.globl _Z2f1i
.align 16, 0x90
.type _Z2f1i,@function
_Z2f1i: # @_Z2f1i
.Ltmp6:
.cfi_startproc
# BB#0:
addl %edi, %edi
jmp _Z3outi # TAILCALL
.Ltmp7:
.size _Z2f1i, .Ltmp7-_Z2f1i
.Ltmp8:
.cfi_endproc
.Leh_func_end2:
.globl _Z2f23Bar
.align 16, 0x90
.type _Z2f23Bar,@function
_Z2f23Bar: # @_Z2f23Bar
.Ltmp9:
.cfi_startproc
# BB#0:
addl %edi, %edi
jmp _Z3out3Bar # TAILCALL
.Ltmp10:
.size _Z2f23Bar, .Ltmp10-_Z2f23Bar
.Ltmp11:
.cfi_endproc
.Leh_func_end3:
通常,只要类上的方法是内联的,就可以很容易地省略this
参数和引用。我不太明白gcc怎么会把这件事搞砸。
用值语义实现不动点算法将产生较差的性能,因为。。。
#include <iostream>
using namespace std;
inline int foo(int a) { return a << 1; }
struct Bar
{
int a;
Bar(int x) : a(x) {}
Bar baz() { return a << 1; }
};
void out(int x) __attribute__ ((noinline));
void out(int x) { cout << x; }
void out(Bar x) __attribute__ ((noinline));
void out(Bar x) { cout << x.a; }
void f1(int x) __attribute ((noinline));
void f1(int x) { out(foo(x)); }
void f2(Bar b) __attribute ((noinline));
void f2(Bar b) { out(b.baz()); }
int main(int argc, char** argv)
{
f1(argc);
f2(argc);
}
现在让我们来看看f1和f2的拆卸…
00000000004006e0 <f1(int)>:
4006e0: 01 ff add edi,edi
4006e2: e9 d9 ff ff ff jmp 4006c0 <out(int)>
4006e7: 66 0f 1f 84 00 00 00 nop WORD PTR [rax+rax*1+0x0]
4006ee: 00 00
00000000004006f0 <f2(Bar)>:
4006f0: 48 83 ec 08 sub rsp,0x8
4006f4: 01 ff add edi,edi
4006f6: e8 d5 ff ff ff call 4006d0 <out(Bar)>
4006fb: 48 83 c4 08 add rsp,0x8
4006ff: c3 ret
正如您所看到的,f2对堆栈指针有一些额外的干扰,这也防止了ret被忽略。
(这是在-O3时的g++4.6.1)
相关文章:
- 删除一个线程上有数百万个字符串的大型哈希映射会影响另一个线程的性能
- OpenMP阵列性能较差
- 如何在c++17中制作一个模板包装器/装饰器
- 递归列出所有目录中的C++与Python与Ruby的性能
- std::vector的包装器,使数组的结构看起来像结构的数组
- 如何在c++迭代器类型中包装std::chrono
- 大小相等但成员数量不同的结构之间的性能差异
- 是否可以用"iostream"包装现有的TCP/OOpenSSL会话
- 为什么constexpr的性能比正常表达式差
- 用pybind11包装C++抽象类时出错
- 使用双包装器类进行位操作(C++、clang)修复性能下降问题
- 通过简单的包装指针C++智能指针性能和差异
- 为性能库制作高效包装器的智能方法
- 在类中包装int的任何性能惩罚
- 性能:基元类型的类型定义与包装类
- 整数类包装器性能
- 库,这有助于包装结构具有良好的性能
- 性能:boost.compute vs . opencl c++包装器
- 在try-catch块中包装循环是否会导致性能问题?
- 通过P/Invoke包装器或在c#中运行进程调用本机代码的性能