Добрый час.
| "Код" |
| #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 ?
Здравствуйте, 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();
Честно говоря, делать так — сомнительная затея.
Косвенные вызовы функций — это настоящий кошмар для конвеера процессора, так как их очень сложно правильно предсказывать. Их используют либо от безысходности, либо там где штрафы не так важны, либо там, где они (вопреки всему) всё же хорошо предсказываются :)