《提高C++性能的编程技术》阅读随笔 —— 第三章 虚函数

1. 虚函数的性能损耗

    a. 构造函数必须初始化vptr(指向虚函数表的指针)

    b. 虚函数是通过指针间接调用的,所以必须先得到指向虚函数表的指针,然后再获得正确地函数偏移量

    c. 内联是编译时决定的,编译器不可能把运行时才解析的虚函数设置为内联

    总结:客观看待C++,前两项不能算是性能损失。

    对于a,因为在构造函数中设置vptr的代价与在子类的实现中初始化成员了变量的代价相当。

    对于b,简介调用函数的代价,因为必须区分是哪一个子类调用该函数,所以是和switch语句的代价相当。

总结:为了消除虚函数调用, 必须允许编译器在编译期间就解析函数的绑定。通过对类选择进行硬编码或者将它作为模板参数来传递,可以避免动态绑定。(硬编码:将可变变量用一个固定值来代替的方法,这里是指直接在子类内部实现该方法)

基于模板的设计把解析步骤从运行期间提前到编译期间,因此模板提高了性能。示例如下:

2. 实现线程安全的string类。被并发线程安全的使用:3种方法

    class Locker{

        public:

            Locker() {}

            virtural ~Locker(){}

            virtural void lock() = 0;

            virtural void unlock() = 0;

    }

    class CriticalSectionLock : public Locker { ... }; //这里会实现虚函数

    class MutexLock : public Locker { ... };

    a. 硬编码:从string类中派生出三个独立的子类,每个类实现各自名字所代表的同步机制。

    例:class CriticalSectionString : public string{

        public :

            ...

            int length();

        private:

            CriticalSectionLock cs;

    }

    int CriticalSectionString:: length(){

        cs.lock();

        int len = string::length();

        cs.unlock();

        return len;

    }

    虽然从父类string中得到了字符串的实际长度,但是将最后一步操作放在临界区内以保证计算的完整性。

    这种设计在性能上具有优势,尽管lock()和unlock()方法是虚函数,但是它们可以被适当的编译器静态的解析。因此编译器可以绕过动态绑定来选择正确的lock()和unlock()方法。同时编译器可以内联这两个方法,不足在于重用性较低,需要为每种同步机制编写各自的string类。

    b. 继承

    把所选择的同步机制作为构造函数的参数

    class ThreadSafeString : public string {

    public:

        ThreadSafeString (const char *s, Locker *lockPtr): string(s), pLock(LockPtr){}

        ...

        int length();

    private:

        Locker *pLock;

    }


    int ThreadSafeString::length(){

        pLock->lock();

        int len = string::length();

        pLock->unlock();

        return len;

    }

    这个类根据传递给构造函数的Locker指针使用所有可用的同步策略,可以按下面的方式使用临界区:

    {

        CriticalSectionLock cs;

        ThreadSafeString csString("Hello", &cs);

        ...

    }

    或者可以这样使用互斥锁:

    {

        MutexLock mtx;

        ThreadSafeString csString("Hello", &mtx);

        ...

    }

    这一种更加简洁,但是带来了性能损失,虚函数调用lock() 和unlock()尽在执行期间解析,因此不能对他们内联。

    c. 模板

    基于模板的设计集合两方面有点:重用和效率。 ThreadSafeString 是作为模板实现的,它的参数由Locker模板参数决定。

    template

    class ThreadSafeString : public string{

    public:

        ThreadSafeString (const char *s ): string(s) {}

        ...

        int length();

    private:

        LOCKER lock;

    };

    template

    inline

    int ThreadSafeString:: length{

        lock.lock();

        int len = string::length();

        lock.unlock();

        return len;

    }

    如果我们想使用临界区保护策略,则

    {

        ThreadSafeString csString = "Hello";

        ...

    }

    或者使用它互斥:

    {

        ThreadSafeString mtxString = "Hello";

        ...

    }

    这种设计避免了对lock和unlock的虚函数调用。 ThreadSafeString 声明在实例化模板是选择特定的同步类型,如同硬编码一样,

    编译器可以解析这两个虚函数调用并且内联他们。

    3. 总结:

    a、虚函数的代价在于无法内联函数调用

    b、模板比继承提供更好的性能,应为他对类型的解析在编译阶段。

你可能感兴趣的:(《提高C++性能的编程技术》阅读随笔 —— 第三章 虚函数)