Reading Notes: programming technology that improves C ++ Performance,

Source: Internet
Author: User

Reading Notes: programming technology that improves C ++ Performance,
Chapter 1 tracking examples 1st concerns

The actual problem introduced in this chapter is: defining a simple Trace class to output the current function name to the log file. The Trace object has certain overhead, so the Trace function is not enabled by default. The problem is: how to design the Trace class to minimize the overhead introduced when the Trace function is not enabled.

1.2 use the status variable switch function

It is very easy to use macros to switch the Trace function. When the Trace function is not enabled, there is no overhead at all:

#ifdef TRACETrace trace("aaa");#endif

The disadvantage is that each switch needs to be re-compiled.

Using status variables has certain runtime overhead, but can ensure flexibility. It is a reasonable choice:

class Trace {public:    ...    static bool isTraceEnabled;    void Debug() {        if (isTraceEnabled) {            ...        }    }}

 

1.3 delayed Creation

The Original Trace class has built-in string members, so that the constructor and constructor overhead are also required when Trace is not enabled. You can change it to string * and create the Member when it needs to be enabled.

If the Trace enabling time is much less than the total time, this method is very effective. Otherwise, the original method is better when the overhead of dynamic creation is greater than the fixed overhead of one constructor and destructor.

Chapter 2 constructor and destructor 2nd concerns

Inheritance and synthesis will cause the constructor and destructor overhead to exceed your expectation. The trade-off between code reusability and running performance is worth noting.

2.2 deredundant objects

Removing unnecessary object usage, whether it is a member variable in the class or a function parameter, will lead to unnecessary Construction and Analysis overhead.

2.3 de-redundant base class

Some base classes do not have member variables or provide interfaces. Such base classes are meaningless base classes, removing such a base class at the inheritance level can reduce the following Overhead:

2.4 balance between embedded objects and embedded pointers

If the object is directly embedded, the construction of object A will lead to the construction of object B and C, and the construction of object B1 B2 C1 C2 ...... A constructor will be formed, and the overhead will be huge when there are many layers.

If the embedded pointer is delayed, it is constructed only when used for the first time, but it brings new overhead. If it is frequently used, it is better to embed objects directly.

2.5 delayed Creation

Here, the definition and creation of objects are delayed as much as possible until all conditions are met before creation.

2.6 Remove redundant construction overhead

This mainly refers to unnecessary replication:

String s; s = "a"; // there is an extra overhead for assigning values. It is also possible to convert "a" to the constructor and destructor overhead of a string temporary object.

 

And initialization that is not in the initialization list:

Class A {public: A (const string & s) {name = s; // there is an additional overhead for assigning values} private: string name ;};

 

Chapter 2 virtual functions 3rd concerns

Advantages of virtual functions: dynamic binding and code saving. Avoid the overhead caused by virtual functions.

3.2 virtual function overhead

There are three types:

3.3 solution 1: do not inherit

If it is not inherited, the sub-classes are independent. The disadvantage is that the Code is flooded with a large number of switches, which are not flexible and can be ruled out.

3.4 solution 2: Inheritance

As described in section 3.2.1, member functions cannot be inline. Especially short and frequently used functions increase the overhead.

3.5 solution 3: Template

Use a template to implement the implicit interface:

template <typename LockType>void func(LockType &lock) {    lock.Lock();    ...    lock.Unlock();}

 

Implements an implicit interface that requires Lock and Unlock. Because the template is determined during compilation, the generated function can be inline, while saving the overhead of indirect jump of pointers.

The disadvantage is that compilation errors caused by templates are very difficult to debug. At the same time, C ++ does not support this implicit interface, and the template interface requirements are often wrong during development.

Chapter 2 Return Value optimization 4th considerations

At any time, as long as the object is created and cleared, the performance gains will be obtained. The compiler removes the creation and cleanup of temporary objects whenever possible. This optimization is called the return value optimization (RVO ).

4.2 The compiler can perform RVO on anonymous objects

An anonymous object is directly returned at the end of the function. RVO can often be performed:

string Func() {    string a = "a";    return a;}string FuncRVO() {    return string("a");}

 

FuncRVO is easier to implement RVO than Func. the compiler removes the returned object and assigns its value directly to the object that receives the returned value.

4.3 active RVO

If Class A has the "+" Operation:

const A operator +(const A &x, const A &y) {    A z(x);    z += y;    return z;}

 

Then add A constructor in:

class A {public:    A(const A &x, const A &y); // A = x + y;};

 

Then, change "+" to the following form to get RVO benefits:

123 const A operator +(const A &x, const A &y) {    return A(x, y);}

  

The advantage is that it reduces the construction and analysis structure of a temporary object, and adds a similar constructor for all operations that require RVO, which is too flexible. If you have high performance requirements, you can consider this optimization method.

Chapter 2 temporary objects 5th concerns

How to avoid unnecessary temporary objects.

5.2 Type Mismatch

Assignment between different types can easily lead to the creation of temporary objects. This implicit conversion can be avoided by adding explicit it before a single-parameter constructor.

5.3 avoid repeated creation of the same temporary object

In the following loop:

Complex a;for (int i = 0; i < 10; ++i) {    a += 1.0;}

 

In each loop, a Complex object with a value of 1.0 is created. You can create a Complex object with a value of 1.0 outside the loop to reduce this overhead:

Complex one(1.0);for (int i = 0; i < 10; ++i) {    a += one;}

 

Chapter 2 single-thread memory pool 6th concerns

The performance of the default general memory manager may cause certain performance bottlenecks in specific scenarios. This chapter discusses how to implement a memory pool manager with better performance than general new/delete in a single-threaded environment when a fixed-size or unfixed-size memory is allocated each time.

6.2 test code
Rational *array[1000];for (int j = 0; j < 500; ++j) {    for (int i = 0; i < 1000; ++i) {        array[i] = new Rational(i);    }    for (int i = 0; i < 1000; ++i) {        delete array[i];    }}

 

6.3 Rational dedicated memory pool

Each time a Rational memory block is allocated, an idle linked list is used to maintain the allocated idle memory. When the memory block is released, it is re-stored in the linked list:

class Rational {    ...    static list<char *> freeList;    void *operator new(size_t size) {        if (freeList.empty()) {            return new char[sizeof(Rational)];        } else {            void *buf = freeList.back();            freeList.pop_back();            return buf;        }    }    void operator delete(void *ptr, size_t size) {        freeList.push_back(ptr);    }};

 

The memory pool of this version never shrinks. To release the memory, you need to add an interface.

Compared with the general memory manager, the memory pool of this version has the following benefits:

6.4 fixed size memory pool

Not only for Rational, but also for classes of any fixed size:

template <typename T>class FixedSizeMemoryPool {public:    FixedSizeMemoryPool(): size_(sizeof(T))    {}    ~FixedSizeMemoryPool() {        for(char *&p: freeList_) {            delete[] p;        }    }    void *Alloc() {        if (freeList_.empty()) {            return new char[size_];        } else {            void *buf = freeList_.back();            freeList_.pop_back();            return buf;        }    }    void Free(void *buf) {        freeList_.push_back(buf);    }private:    list<char *> freeList_;    const size_t size_;};

 

For Rational, you need to change it:

class Rational {public:    void *operator new(size_t size) {        return pool.Alloc();    }    void operator delete(void *ptr, size_t size) {        pool.Free(ptr);    }private:    static FixedSizeMemoryPool<Rational> pool;};

 

6.5 unlimited memory pool

The management method for memory pools of an indefinite size is different from the previous version, because there is no way to directly return a memory block from the linked list (the size is different ). Here, we allocate a large fixed-size memory block as needed. Each time we allocate the memory of a single object, we will allocate it from this memory block. When there is not enough space, we will allocate a large memory block.

With the increase of universality, the performance is also decreasing. Therefore, the sacrifice of flexibility and versatility may have a good effect when performance is very required.

Chapter 2 multi-thread memory pool 7th concerns

A mutex lock is added to the memory pool of a single thread to implement a working distributor in a multi-threaded environment. You can pass in the lock parameters when initializing the distributor.

7.2 pthread_mutex Memory Pool

Add a new template parameter typename LockType to the MutableSizeMemoryPool in the previous chapter, which allows the input of a LockType * and the lock is applied when Alloc and Free are used. The other parameters remain unchanged.

The performance of the distributor in this version is not good, because the performance of pthread_mutex exceeds our needs, and we may only need a simple lock for the function. If you can pass in a mutex lock of a more primitive version, it will have better performance.

7.3 increase the scalability of the Memory Pool

The current version of the memory pool has poor performance in highly concurrent environments, because access to memory blocks (Alloc and Free) must be serialized. You can add a list of multiple memory blocks and lock each list separately. This way, you can distribute multiple requests to different lists for processing at the same time.

Chapter 2 inline basics 8th considerations

Inline may improve performance, but may also reduce performance. This chapter focuses on how to avoid negative effects and make good use of positive benefits.

8.2 benefits: Remove function calls

Removes the overhead of function calls. Common function calls include:

In addition, the loss of CPU idling caused by redirection is also avoided.

8.3 benefits: Cross-Function Optimization

This allows the compiler to optimize variables across functions:

int Inc(int x) {    return x + 1;}void Func() {    int y = Inc(1);}

 

Func is equivalent:

void Func() {    int x = 1;    int y = x + 1;}

 

The compiler can even be further optimized:

void Func() {    int y = 2;}

 

Through inline, the compiler can rearrange a large number of methods, omitting a large number of unnecessary statements, and even including object creation and clearing.

8.4 benefits: shorten the Key Path

Inline functions on the Key Path can shorten the key path without changing the program logic, thus greatly improving the performance.

8.5 loss: size after compilation

After inline, the function code is displayed at each call point. If the number of function code and call points is large, the code size will expand. On the one hand, it will slow down code loading, and on the other hand, it will lead to more frequent page defects.

However, if the function length is particularly short and shorter than the entire call process, the inline function will reduce the code size, which must be inline.

8.6 loss: the Code must be re-compiled after modification

If an inline function modifies the code, it must be re-compiled where it is used, because the code of this function has been expanded in various call points. Therefore, large projects are often inline at the end.

Chapter 2 inline-performance considerations 9th concerns

This chapter focuses on the 2nd benefits of inline: After the code of inline functions is expanded, the compiler optimizes the performance of these functions.

9.2 inter-call Optimization

After inline, the code for calling a function is mixed with the code for calling, which allows the compiler to perform many advanced optimizations, just like reorganizing the code. Especially for the optimization of direct traffic:

int Choice(int x) {    switch (x) {    case 1: return 5; break;    case 2: ...    ...    case 100: return 301; break;    default: return 0; break;    }}int x = Choice(100);

 

After inline optimization, the above Code may only be left:

int x = 301;

 

9.3 why not use inline

The main disadvantage of inline is that it may increase the size of the Code. Especially when a relatively large method is embedded in multiple layers, the volume index expands.

Disadvantage 2: all modifications need to be re-compiled.

Disadvantage 3 is that it is difficult to debug inline functions because the actual function is no longer available and the function Entry and Exit cannot be traced.

9.4 configuration-based inline

The size, number of calls, call points, and other information of each function after compilation should be counted before the internal connection. The configuration information is used to determine which functions are inline.

Function dimensions can be divided:

For functions with smaller static sizes and larger dynamic sizes, inline operations will greatly benefit. For functions with only one call point, such as calls within a loop, inline is almost always correct. For functions with many call points and calls, it is best to rewrite them to show their fast paths and then inline them.

A function is as follows:

void FuncX {    if (/* error handle code */) {        ... // 30 lines    }    ... // real work (5 lines)}

 

FuncX has about 40 lines of code, which is not suitable for inline on the surface, but if you split its error handling code into a separate function:

void FuncX {    if (...)        FuncY();    ... // real work (5 lines)}void FuncY {    ... // error handle code (30 lines)}

 

At this time, FuncX only has 7 lines of code, which is suitable for inline. This is also equivalent to splitting code segments with Large static sizes and small dynamic sizes, so that the remaining code with small static sizes and Large Dynamic sizes can be inline.

9.5 suitable for inline functions chapter 1 inline skills 10th considerations

Some tips that can help you better inline.

10.2 condition inline

If you want to use a pre-compilation option to control when to inline certain functions and when to disable inline, you can use the conditional inline technique.

Put the inline function definition in. inl, put the definition of other functions in. cpp, and then add the following in. h:

#ifdef INLINE#include "*.inl"#endif

 

Add:

#ifndef INLINE#define inline  // let inline be void#endifinline FuncX(...){}

 

Add:

#ifndef INLINE#include "*.inl"#endif

 

10.3 selective inline

You can inline a function at some call points, but not at other call points. I am not very fond of the specific content. I skipped it.

10.4 recursive inline

The tail recursion function can be changed to the iteration function, and then the inline method can be searched.

If a non-tail recursive function is very performance-oriented, you can expand the function to a certain extent:

void RecursiveInline() {    ...    Recursive();    ...}void Recursive() {    ...    RecursiveInline();    ...}

 

Inline the previous function, which will speed up the operation, but will also significantly increase the size after compilation.

It can also be expanded manually or maintained using a macro, but it is easy to cause problems.

10.5 special architecture

In some architectures (such as Linux), the overhead of function calling is very low when there are few calling layers. In this case, the benefits of inline functions that are not tiny are not obvious. Therefore, any internal connection to non-tiny functions must be based on the configuration information.

Chapter 1 Standard Template Library 11th focus 11.1 better than STL

If it is better than STL, it is often necessary to sacrifice a certain degree of versatility and flexibility to start optimization from some specific environmental factors.

Chapter 2 reference count 12th concerns

C ++ uses reference counting to solve the garbage collection problem. The basic idea is to transfer the responsibility for clearing objects from the client code to the object itself.

The reference count can reduce memory usage and avoid Memory leakage, but it may have disadvantages in terms of execution speed, especially in multi-threaded environments.

12.2 Implementation of reference count

Implementation A: Class built-in reference count. Class RefCountBase encapsulates the operations related to the reference count, and the class that needs to implement the reference count inherits it:

class RefCountBase {public:    Attach() {        ++refCount_;    }    Detach() {        if (--refCount_ == 0) {            delete this;        }    }protected:    RefCountBase(): refCount_(0) {}    RefCountBase(const RefCountBase &rc): refCount_(0) {}    RefCountBase &operator=(const RefCountBase &rc) {        return *this;    }    virtual ~RefCountBase() {}        size_t refCount_;};

 

For example, Class A inherits from RefCountBase. To implement reference counting, A proxy class SmartPtr is also required to act as A smart pointer:

template <typename T>class SmartPtr {public:    SmartPtr(T *ptr = nullptr): ptr_(ptr) {}    SmartPtr(const SmartPtr &sptr): ptr_(sptr.ptr_) {        if (ptr_) {            ptr_->Attach();        }    }    SmartPtr &operator=(const SmartPtr &sptr) {        if (sptr.ptr_) {            sptr.ptr_->Attach();        }        if (ptr_)            ptr_->Detach();        ptr_ = sptr.ptr_;        return *this;    }    T *operator->() { return ptr_; }    T &operator*() ( return *ptr_; )private:    T *ptr_;};

 

Implement B: add the count function to SmartPtr. Remove RefCountBase, but add a size_t * count _ in SmartPtr. The Attach operation on ptr _ is changed to ++ * count _, And the Detach operation is changed to -- * count _. Others are the same.

12.3 concurrent reference count

In SmartPtr, you must perform operations on count _ and ptr _ at the same time. In a concurrent environment, this means that you need to lock before and after the operation to ensure atomic operations on the two objects.

12.4 performance of reference count

To implement A, you must modify the original class. If you cannot perform such modification, you can only use B. In implementation B, because you need to operate the members (count _ and ptr _) on the two stacks, the creation and clearing performance will be worse than the Implementation of.

The benefit of reference counting is:

Disadvantages of reference count:

The following conditions increase the benefit of reference count:

Chapter 2 code optimization 13th considerations

Many performance problems will be introduced in the application coding stage. These problems are usually small-scale problems. To solve them, you do not need to look at too much code or change the in-depth design, however, it may lead to significant performance improvement.

13.2 Cache

Remember the results of frequent computation and high computing costs. Typically, the fixed results that need to be calculated repeatedly in the loop are stored in a variable outside the loop and used in the loop.

13.3 pre-Calculation

Some computing results that need to be frequently used in key paths can be computed in advance, and the results are saved. In this way, you only need to perform a simple search.

13.4 reduced flexibility

If the scope of the target code is fixed, you do not need to consider too many general situations in the Code. Instead, you can make bold assumptions about some specific situations that are currently known to speed up running.

13.5 increase the speed of common paths.

80% of the time is spent on 20% of function calls, so the time required to reduce the 20% function can greatly improve the performance of the entire system.

Similar examples appear in if (and1 & and2) And if (or1 | or2). if the two conditions have no dependency, in this case, the conditions that are more likely to determine the entire Relational Value are put in front, that is, if and1 is more likely to be false than and2, then and1 is put in front, if or1 is more likely to be false than or2, or2 will be created first.

In addition to the possibility of a condition value, you can also take into account the number of instructions for each operation, so that the minimum number of conditions can be placed in front.

If 5% of all external parameters are special and the other 95% is similar, we can design a path for these 5% special parameters separately, this accelerates the processing of 95% of common cases.

13.6 slow computing

Delay computing to what is actually needed, so that expensive computing results are not used at the end. There is nothing new in this section.

13.7 useless computing

There is nothing new in this section.

13.8 Architecture

The influence of the architecture is taken into account when designing the object layout, mainly because of the impact of the system cache. For example, if the row length of the matrix is exactly the same as that of the cache row, frequent cache miss occurs when the matrix is transposed. When designing two members that often need to be accessed together, it is best to make them in the same cache row. This also requires the first accessed members to be put in front.

13.9 Memory Management

Performance is a transaction. Nothing new.

13.10 database and system calls

Many performance details are hidden behind libraries and system calls. Therefore, you need to have a detailed understanding of these details during design and select the one with just enough functions among multiple available tools, its performance is often better than those with more comprehensive and powerful functions.

13.11 Compiler Optimization

Enabling Compiler Optimization in release may greatly improve the performance.

Chapter 1 design optimization 14th considerations

The design optimization is global and relies on other components and code.

14.2 design flexibility

In the early stages of software development, if you do not know the hot spots of the program, you can fully use STL. When you have a certain understanding of program running, you can use some flexibility in exchange for performance.

14.3 cache: Timestamp

Each request in the web Service needs to write logs with a timestamp. If a single request requires multiple writes, the computed Timestamp can be cached for all these logs.

14.4 cache: Data Expansion

If the object often needs to return the value of an operation, you can embed the value in the object, such as the size of various containers.

14.5 cache: Public Code traps

If a piece of code needs to judge the request type each time to determine the running path, you can split it into two sections of code for a single operation. This is a field that virtual functions are very good.

14.6 efficient data structure

Nothing new.

14.7 slow computing, useless computing, and invalid code

Nothing new.

Chapter 1 scalability 15th concerns

Performance issues in parallel or concurrent environments.

15.2 SMP system

One performance bottleneck of the SMP system is that multiple processors need to share the bus with the memory.

The solution is to configure a large cache for each processor, but the main problem is cache consistency.

The preceding two problems make it difficult to increase the parallel performance to a multiple of the increase in the number of cores.

15.3 Amdahl's Law

Sequential computing is a major obstacle to scalability. The increase caused by a single acceleration segment will not be greater than the total overhead of the segment.

15.4 split the task

Splitting a single task into multiple concurrent subtasks can improve the following indicators:

I/O-intensive tasks are more suitable for concurrent execution.

15.5 cache shared data

Example: A thread serves a request. during the lifetime of the thread, many operations of the thread must act on the request. One idea is to call pthread_getspecific in each operation, but this will cause serious lock overhead. Another idea is to get a pointer at the beginning of the thread and pass it to all subsequent functions.

15.6 No sharing

In the above example, a better way is to directly put thread-related items in the structure associated with the thread, so that the serialized part can be completely removed.

Part 1 sharing

When you do not know the number of requests, you can use a fixed size thread pool for service, which has good scalability.

15.8 lock Granularity

Generally, it is not a good idea to integrate multiple irrelevant resources into the protection of a single lock. The exception is when the following conditions are met:

The granularity of the lock is too coarse, leading to a reduction in parallelism, And the granularity is too small, leading to an increase in the lock overhead and a growth in the processing time of a single task.

15.9 pseudo-sharing

On the SMP system, if two locks are in the same cache row, the p1 lock on m1 will cause the entire cache row to become invalid on p2, as a result, p2 needs to re-read the memory when accessing m2. To avoid this problem, manually insert a blank space between m2 and m2.

15.10 Starbird

If multiple threads are used for accept, such as 100, 100 threads will wake up when connecting requests, but only one thread can get the request, and the other 99 threads will continue to sleep. This CPU impact will cause the server to shrink and seriously damage the throughput. When throughput drops, the system may increase more threads, resulting in more serious problems.

The solution is to use only one accept thread to distribute requests to other threads.

15.11 read/write lock

Nothing new.

Chapter 1 system architecture relevance 16th concerns

The content discussed in this chapter is as follows:

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.