Производительность оболочки класса Integer - PullRequest
0 голосов
/ 12 марта 2012

Я хочу изменить существующую библиотеку для номеров с фиксированной запятой. В настоящее время библиотека представляет собой функции пространства имен, работающие с 32-разрядными знаковыми целыми числами. Я хотел бы перевернуть это и создать класс с фиксированной запятой, который оборачивает целое число, но не хочу платить какие-либо потери производительности, связанные с классами, за что-то такое детальное, так как производительность является проблемой для варианта использования.

Поскольку предполагаемый класс предъявляет такие простые требования к данным и не имеет ресурсов, я подумал, что можно было бы сделать класс «ориентированным на ценность», используя немодифицирующие операции и передавая экземпляры по значению там, где это целесообразно. Это будет простой класс, если он реализован, а не является частью иерархии.

Мне интересно, можно ли написать целочисленный класс-обертку таким образом, чтобы не было реального снижения производительности по сравнению с использованием необработанных целых чисел. Я почти уверен, что это так, но не знаю достаточно о процессе компиляции, чтобы просто перейти к нему.

Я знаю, что сказано, что итераторы stl скомпилированы в простые операции с указателями и хотели бы сделать нечто подобное только с целочисленными операциями.

В любом случае библиотека будет обновлена ​​до c ++ 11 как часть проекта, поэтому я надеюсь, что, по крайней мере, с constexpr и другими новыми функциями, такими как ссылки на rvalue, я смогу довести производительность этого класса до уровня, близкого к чистых целочисленных операций.

Кроме того, любые рекомендации по сравнительному анализу различий в производительности между двумя реализациями приветствуются.

Ответы [ 2 ]

3 голосов
/ 12 марта 2012

Что забавно в этом вопросе, так это то, что он зависит от компилятора.Использование 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.

1 голос
/ 12 марта 2012

реализация арифметики с фиксированной точкой и семантики значений приведет к снижению производительности, поскольку ...

#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.

(это g ++ 4.6.1 при -O3)

...