Почему в этом коде возникает условный переход?
От: nen777w  
Дата: 26.09.18 20:12
Оценка:
Добрый час.

  "Код"
#include <iostream>

template <class T>
class SmartPtr
{
protected:
    template <typename Q>
    void LockWeakFromWeak(const SmartPtr<Q> &ptr) { std::cout << "LockWeakFromWeak" << std::endl; }

    template <typename Q>
    void LockWeakFromShared(const SmartPtr<Q> &ptr) { std::cout << "LockWeakFromShared" << std::endl; }

    template <typename Q>
    void LockSharedFromWeak(const SmartPtr<Q> &ptr) { std::cout << "LockSharedFromWeak" << std::endl; }

    template <typename Q>
    void LockSharedFromShared(const SmartPtr<Q> &ptr) { std::cout << "LockSharedFromShared" << std::endl; }

public:
    enum Mode {
        Shared, Week
    };

public:
    inline Mode GetMode() const { return m_mode; }

private:
    Mode m_mode;

public:
    template<class Q>
    void Lock(const SmartPtr<Q> &ptr)
    {
        std::cout << "Lock" << std::endl;

        typedef SmartPtr<T> this_type;
        typedef void(SmartPtr<T>::*lock)(const SmartPtr<Q> &);

        static lock methods[2][2] = 
            { 
                { &this_type::LockSharedFromShared<Q>, &this_type::LockSharedFromWeak<Q> }  //Shared -> Shared, Week
              , { &this_type::LockWeakFromShared<Q>, &this_type::LockWeakFromWeak<Q> }      //Week   -> Shared, Week
            };

        (this->*methods[GetMode()][ptr.GetMode()])(ptr);

        std::cout << "Lock END" << std::endl;
    }

    template<class Q>
    void Lock2(const SmartPtr<Q> &ptr)
    {
        std::cout << "Lock2" << std::endl;

        typedef SmartPtr<T> this_type;

        if(GetMode() == this_type::Week)
        {
            if(ptr.GetMode() == SmartPtr<Q>::Week)
            {
                LockWeakFromWeak(ptr);
            } 
            else
            {
                LockWeakFromShared(ptr);
            }
        }
        else
        {
            if(ptr.GetMode() == SmartPtr<Q>::Week)
            {
                LockSharedFromWeak(ptr);
            }
            else
            {
                LockSharedFromShared(ptr);
            }
        }

        std::cout << "Lock2 END" << std::endl;
    }

};


int main()
{
    SmartPtr<int> ptr;
    SmartPtr<char> ptr2;
    
    ptr.Lock(ptr2);

    ptr.Lock2(ptr2);

    return 0;
}


Пытался оптимизировать функцию Lock2(); переписав ее как Lock();

При изучении output кода функции Lock():

https://godbolt.org/z/po63dd

Вижу что для этого вызова
(this->*methods[GetMode()][ptr.GetMode()])(ptr);

генерируется:
        movl    (%r12), %edx
        movl    0(%rbp), %eax
        leaq    (%rax,%rdx,2), %rax
        salq    $4, %rax
        movq    void SmartPtr<int>::Lock<char>(SmartPtr<char> const&)::methods(%rax), %rdx
        movq    void SmartPtr<int>::Lock<char>(SmartPtr<char> const&)::methods+8(%rax), %rdi
        addq    %r12, %rdi
        testb   $1, %dl
        je      .L33
        movq    (%rdi), %rax
        movq    -1(%rax,%rdx), %rdx
.L33:
        movq    %rbp, %rsi
        call    *%rdx


Почему компилятор сегенрировал testb и je ?
Отредактировано 26.09.2018 20:13 nen777w . Предыдущая версия .
Re: Почему в этом коде возникает условный переход?
От: reversecode google
Дата: 26.09.18 20:27
Оценка: 4 (1)
стыдно батенька сколько вы уже в разработке ?
потому что так имплементится указатель на функцию класса
Re: Почему в этом коде возникает условный переход?
От: watchmaker  
Дата: 26.09.18 22:12
Оценка: 75 (3)
Здравствуйте, nen777w, Вы писали:

N>Почему компилятор сегенрировал testb и je ?

Тип lock, описанный выше, задаёт указатель на метод. Причём как на обычный метод, так и на виртуальный. То что дальше массив инициализируется лишь указателями на неверитуальные методы уже не играет большой роли, так как требование поддержать виртуальность записано в самом типе.

Собственно, инструкция testb 1, reg и проверяет признак виртуальности у указателя на функцию, так как механизмы вызова таких функций отличаются. Подробнее можно прочитать в документации: https://itanium-cxx-abi.github.io/cxx-abi/abi.html#member-pointers

Если ты знаешь, что в вышеприведённом коде никогда не понадобится вызывать виртуальный метод через указатель, то разумно было бы просто изменить тип lock, явно в нём это указав (напримерusing lock = void(*)(SmartPtr<T>&, const SmartPtr<Q>&) — и даже без указателей на функцию).


Впрочем, в современных компиляторах есть и развиваются различные механизмы девиртуализации вызовов. Когда-нибудь, совместно с другими техниками опитимизации, они и в этом коде смогут переход убрать как никогда не ветвящийся. Но ждать, я думаю, этого не стоит — лучше просто такой код не писать :)


N>Пытался оптимизировать функцию Lock2(); переписав ее как Lock();

Честно говоря, делать так — сомнительная затея.
Косвенные вызовы функций — это настоящий кошмар для конвеера процессора, так как их очень сложно правильно предсказывать. Их используют либо от безысходности, либо там где штрафы не так важны, либо там, где они (вопреки всему) всё же хорошо предсказываются :)
Re[2]: Почему в этом коде возникает условный переход?
От: nen777w  
Дата: 27.09.18 06:30
Оценка:
R>стыдно батенька сколько вы уже в разработке ?
R>потому что так имплементится указатель на функцию класса

простите, учусь все еще
Re[3]: Почему в этом коде возникает условный переход?
От: reversecode google
Дата: 27.09.18 06:39
Оценка:
учится можно чему то новому
а указатель на метод класса так имплементится начиная с С++98
я надеюсь на простой вопрос сколько он занимает байт в памяти вы тоже знаете ?

а квотинг зло, жаль что этому вы так и не научились
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.