C++ 整数类包装器性能

C++ 整数类包装器性能,c++,c++11,fixed-point,C++,C++11,Fixed Point,我正在寻找重塑一个固定点数的现有库。目前,该库只是在32位有符号整数上运行的命名空间函数。我想扭转这一局面,创建一个封装整数的定点类,但不想为这种细粒度的东西支付任何与类相关的性能损失,因为性能是用例的一个问题 由于预期类有如此简单的数据需求,并且没有资源,我认为可以使类“面向值”,利用非修改操作并在合理的情况下按值传递实例。如果实现,这将是一个简单的类,而不是层次结构的一部分 我想知道是否有可能编写一个整数包装类,与使用原始整数相比,不会产生实际的性能损失。我几乎可以肯定这是真的,但我对编译过

我正在寻找重塑一个固定点数的现有库。目前,该库只是在32位有符号整数上运行的命名空间函数。我想扭转这一局面,创建一个封装整数的定点类,但不想为这种细粒度的东西支付任何与类相关的性能损失,因为性能是用例的一个问题

由于预期类有如此简单的数据需求,并且没有资源,我认为可以使类“面向值”,利用非修改操作并在合理的情况下按值传递实例。如果实现,这将是一个简单的类,而不是层次结构的一部分

我想知道是否有可能编写一个整数包装类,与使用原始整数相比,不会产生实际的性能损失。我几乎可以肯定这是真的,但我对编译过程了解不够,无法直接投入其中

我知道有人说stl迭代器被编译成简单的指针操作,并且只想对整数操作进行类似的操作

作为项目的一部分,该库将被更新为c++11,因此我希望至少通过constexpr和其他新特性(如右值引用),我可以将该类的性能提高到接近纯整数操作的性能


此外,对于两种实现之间的性能差异进行基准测试的任何建议都将不胜感激。

使用值语义实现定点算法将产生较差的性能,因为

#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);
}
#包括
使用名称空间std;

内联intfoo(inta){returna使用值语义执行定点算法将产生较差的性能,因为

#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);
}
#包括
使用名称空间std;

内联intfoo(inta){returna这个问题的有趣之处在于它是如此依赖于编译器

#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);
}
毫不奇怪,生成的程序集完全相同:

    .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怎么会把事情搞砸。

这个问题的有趣之处在于它是如此依赖于编译器。使用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);
}
毫不奇怪,生成的程序集完全相同:

    .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怎么会把事情搞砸。

constexpr
会很有用,我怀疑你对右值引用会有任何用处,因为你的类一开始复制成本很低。使用大量的内衬,保持构造函数和析构函数的平凡性,您应该会看到良好的性能。
constexpr
将是有用的,我怀疑您对右值引用是否有任何用处,因为您的类在一开始就复制起来非常便宜。使用大量的内联,保持构造函数和析构函数的平凡性,您应该会看到良好的性能。我认为你应该让你的观点对原始问题更明确。我猜你的意思是“将定点算法与值语义相结合会产生较差的性能,因为……”在我看到这样的结果的一半时间里,我忽略了一些微妙的问题(一旦发现,往往可以纠正),而不仅仅是“编译器提供的代码没有你想象的那么好"…这是一个简洁但含糊不清的答案,我在分解和使用你的代码后得到了相同的结果。你知道这里有什么阻碍优化吗?我想知道gcc怎么会把这搞砸。我用Clang/LLVM运行了相同的代码,生成的IR和汇编在结构上完全相同。你确定它是相同的吗?Noinline和传递的of argc的存在有非常具体的原因。我认为你应该让你的观点对原始问题更加明确。我猜你的意思是“使用值语义嵌入定点算法会产生较差的性能,因为……”我看到这种结果的一半时间里,有一些微妙的问题我忽略了(一旦发现,通常可以更正),而不仅仅是“编译器提供的代码没有您想象的那么好”…这是一个简洁但含糊不清的答案,我在分解和使用你的代码后得到了相同的结果。你知道这里有什么阻碍优化吗?我想知道gcc怎么会把这搞砸。我用Clang/LLVM运行了相同的代码,生成的IR和汇编在结构上完全相同。你确定它是相同的吗?Noinline和传递的of argc的存在有非常具体的原因。“我是如何学会停止担心并信任编译器的”,我想。@MetaThermory:一般来说,你应该信任编译器的细节。为了性能,你最好专注于你的算法和I/O(磁盘、网络、数据库等)。如果计算速度较慢,则您必须分析并检查它是否是CPU或内存瓶颈,并在分析程序的指导下尝试改进;但很难达到这一水平。“我是如何学会停止担心并信任编译器的”,我猜。@MetaThermory:一般来说,你应该相信编译器的细节。为了提高性能,你最好专注于你的算法和I/O(磁盘、网络、数据库等)。如果计算速度较慢,则必须分析并检查它是否是CPU或内存瓶颈,并在分析程序的指导下尝试改进;但很少会下降到该级别。