Исправление снижения производительности с использованием класса двойной оболочки для битовых манипуляций (C ++, clang) - PullRequest
0 голосов
/ 17 сентября 2018

У меня есть вопрос: «Могу ли я написать класс типа bitset, который можно использовать как с внутренним внутренним представлением unsigned, так и с динамическим_битом, не теряя при этом производительность эксклюзивного класса bitset без знака?»

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

Однако в некоторых случаях мне нужно выйти за пределы 64 или 128 бит, и для этого потребуется использовать динамический битовый набор буста или массив беззнаковых длин, каквнутреннее представление.

Теперь мой первый черновой вариант так же производителен, как и код, который будет просто использовать голую длинную без знака (используя флаг -O3 для моего компилятора).И я полностью понимаю, что я не могу поддерживать эту производительность в случае использования динамического набора битов.Однако я хотел бы написать свои алгоритмы только один раз, используя мой класс, вместо того, чтобы писать один код с беззнаковым представлением и один с использованием динамического набора битов.Поэтому я создал класс bitsetwrapper, который имеет указатель на абстрактный набор битов, который может быть либо набором битов с внутренним длинным набором без знака, либо набором битов с внутренним динамическим набором битов.На какой производный класс он будет указывать, будет определяться количеством битов, которое вам нужно использовать.

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

    class BitsetBase{}
    class UnsignedBitset : public BitsetBase{
            unsigned long representation;
    }
    class DynamicBitsetBitset : public BitsetBase{
            dynamic_bitset<> representation;
    }

    class BitsetWrapper{
        *BitsetBase bitset;
    }

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

Начальные тесты производительности следующие (относительное сравнение):

    Unsinged long code : 1s
    UnsingedBitset code : 1s
    BitsetWrapper code (using UnsingedBitset) : 4s

Чтобы дать вам дополнительный контекст, делается много копий во всех трех случаях.Это то, что вызывает увеличение BitsetWrapper до 4 с.Потому что в моей первоначальной попытке я использовал «new» для инициализации экземпляров Bitset.

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

    Unsinged long code : 1s
    UnsingedBitset code : 1s
    BitsetWrapper code (using UnsingedBitset) : 2.4s

Однако очень важно достичь производительности 1с.Я весьма удивлен тем, что версия UnsignedBitset имеет ту же производительность, что и необработанный длинный код Unsigned.Я предполагаю, что компилятор может каким-то образом оптимизировать его, но больше не может делать это для «двойной» оболочки.У кого-нибудь есть идея, почему производительность намного хуже, и если есть другой способ исправить мои проблемы?(ps. Я также пробовал boost :: варианте, это также в 3 раза медленнее)

Пример кода:

    for(1000000 loops){                
        AnyClass bitset(random_input) 
        while(!bitset.equalsZero()){
            bitset.removeLeastSignificantBit()
            AnyClass bitset2 = bitset
            bitset2.invert()
            while(!bitset2.equalsZero()){
                result += bitset2.someManipulation();
            }
        }
    }

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

Пример вызываемого кода:

    void invert(){
            representation = ~representation;
    )

(без потери производительности), который затем станет:

   void invert(){
       bitset_instance->invert();
   }

в оболочке Bitset (потеря производительности).

1 Ответ

0 голосов
/ 17 сентября 2018

Не видя больше кода, я могу только догадываться, но возможно, что стоимость вызовов виртуальной функции и косвенного обращения перевешивает стоимость битовых операций.Это может быть особенно вообразимо, если каждый переворот происходит посредством вызова виртуальной функции.Это наиболее вероятно, так как вы устранили выделение ресурсов как дополнительное снижение производительности, но опять же, для определенности необходимо знать больше о вашем шаблоне использования.

Вам действительно нужна гибкость?Если вы хотите удалить абстракцию, есть несколько возможностей:

  1. Использовать dynamic_bitset все время.
  2. Использовать другую реализацию, например std::vector<bool> (скорее всего, не болееПроизводительнее, чем 1)
  3. Используйте другой механизм отправки.Если вы знаете случаи во время компиляции, шаблоны являются очевидным вариантом.Другие механизмы времени выполнения (if / else, switch) дают, скорее всего, примерно ту же производительность, что и виртуальные функции.

PS Не уверен, если это просто опущено для краткости, но полиморфные базовые классы должны иметь виртуальный деструктор.Это даже хорошая практика, если вы размещаете их в стеке и просто передаете указатели.

...