Game Thinking 29: Use EASTL with shared memory to make custom STL (to be continued on 12/28)

1. The opportunity for the emergence of EASTL (for the STL in 2007)

1) Brief introduction

Cross-platform robust container library with high performance

2) The motivation for making EASTL

Some designs and implementations of the standard STL are not ideal for game developers. The reasons are listed below:
1. The memory allocation of STL can easily lead to code expansion and subsequent low performance. Reasons Portal
2. STL Extended containers such as slist, hash_map, shared_ptr, etc. are not so convenient to use in STL. After all, they are inconsistent in different versions of STL. 3. STL lacks the functionality required by game
developers (eg.instrusive containers), and optimization Excellent and easy-to-use STL environment 4. STL implements deep function calls, which will bring inefficient performance to compilers with weak inlining capabilities. The compiler is still on schedule. The popular open source C++ compiler, Portal 1 and Portal 2 5. STL is difficult to debug 6. STL cannot directly support container space that allocates fixed-size memory 7. STL containers won't let you insert an entry into a container without supplying an entry to copy from. This can be inefficient in the case of elements that are expensive to construct. 8. STL has performance defects on most PCs, boxes, and TV game platforms; many implementations do not specifically consider the behavior of compilers and hardware. 9. Same as 5 . STL containers are difficult to track and debug. Some STL implementations use very cryptic variable names, and the documentation does not explain. 10. The secret implementation of STL containers prevents others from adjusting its data structure







11. Many recent STL memory allocators use sub-optimal methods, resulting in some container memory optimizations that cannot be implemented. These implementations can significantly improve performance. Empty containers should not allocate memory
12. All existing STL algorithm implementations cannot support the use of predicates
13. Except for memory allocation, STL values ​​correctness before practicality and performance

3) Differences between STL and EASTL

4) Summary of the mechanism of action of EASTL

5) Questions for game developers

6) The memory allocation of STL and EASTL is different

7) Disadvantages of EASTL

8) EASTL container introduction

2. STL pre-knowledge learning (refer to STL source code analysis)

1) extraction

(1) The type of the object pointed to by the iterator - value_type

  • Remarks
    Because iterators need to know the type of the object pointed to
<1> The first limitation - the return parameter needs to specify the value_type of the iterator

The type of the object pointed to by the iterator is called the value type of the iterator. The above-mentioned parameter type deduction is only applicable to deriving function parameters, but it cannot be used as the return value of the function.

  • The solution is
    to declare an embedded type
template <class T>
struct MysIter
{
    
    
	typedef T value_type;   //内嵌类型声明
	T*  Ptr;
	MyIter(T	* p=0):ptr(p){
    
    }
	T& operator*() const {
    
    return *ptr};
	//...
};

template <class T>
typename I::value_type; //这一整行是func的返回值类型
func(I ite)
{
    
    
	return *ite;
}


//...
MyIter<int> ite(new int(8));
cout<<func(ite); //输出8
  • Note that
    the keyword typename must be added to the drawing of func(), because T is a template parameter, but before it is specialized by the compiler, the compiler knows nothing about T. The role of the keyword typename is to tell the compiler that this is a type in order to pass the compilation
<2> The second limitation pit - not all iterators are class type, native pointers are not

Not all iterators are class type, raw pointers are not, but if they are not class type, they cannot define embedded types for them, but STL (and the whole generic thinking) absolutely must accept raw pointers as an iterator, so the above not enough.

  • Solution to pits
    :偏特化(template partial specialization)
  • Way of thinking
    Raw pointers are not classes, so they cannot be defined as embedded types. Now, you can 迭代器template参数为指针set specialized iterators for them
template <class I>
struct iterator_traits  // traits 意为特性
{
    
    
	typedef typename I::value_type  value_type;	
};

The meaning of this so-called traits is: if I is defined 有自己的value_type, then through the function of this traits, the extracted value_type is I::value_type. In other words, if I has its own value_type, which func() can be rewritten as this: (more this layer, then traits可以拥有特例化版本)

template <class I>
typename iterator_traits<I>::value_type  //这一样是返回值类型
func(I ite)
{
    
    
	return *ite;
}

Now do the specialized version as follows:

template <class T>
struct iterator_traits<T*>  //偏特化版本,迭代器是原生指针
{
    
    
	typedef T value_type;
};
  • As a result
    , although native int* is not a class type, its value_type can also be obtained through traits, which solves the previous problem
<3> The third restriction pit point - if the value obtained for "pointer to constant object (point-to-const)" with const is not what we expect
  • example
iterator_traits<const int*>::value_type  获得的是const int 而非int
  • Solution
    只需要设计一个特例化版本
template <class T>
struct iterator_traits<const T*>   //偏特化-当迭代器是point-to-const版本时,萃取出来T,
									//而非const T
{
    
    
	typedef T value_type;  
};

(2) There are five most commonly used iterator types:

insert image description here

<1>value_type
  • Concept introduction Refers to
    the type of object pointed to by the iterator
  • Meaning
    Any class that matches perfectly with stl should define its own value_type embedded type
<2>difference_type
  • Concept introduction
    Indicates the distance between two iterators, so it can be used to indicate the maximum capacity of a container, because for a continuous container, the distance between the head and the tail is the maximum capacity

  • Example usage
    If a generic algorithm provides a counting function, such as STL's count(), its return value must use the difference_type of the iterator

template<class I,class T>
typename iterator_traits<I>::difference_type //这一整行是返回值类型
count(I first,I last ,const T& value)
{
    
    
	typename iterator_traits<I>::difference_type n = 0;
	for(;first != last ;++first)
		if(*first == value)
			++n;
	return n;
}
  • difference_type for two specializations of native pointers (using C++ built-in ptrdiff_t as the difference_type of native pointers)
template<class T>
struct iterator_traits
{
    
    
	...
	typedef typename I::difference_type difference_type;
};

① Partially specialized version designed for native pointers

template<class T>
struct iterator_traits<T*>
{
    
    
	...
	typedef ptrdiff_t difference_type;
};

②Partial specialization for the original pointer-to-const design

template<class T>
struct iterator_traits<const T*>
{
    
    
	...
	typedef ptrdiff_t difference_type;
};
  • Summary
    Now when we need the difference_type of any iterator I, we can write
typename iterator_traits<I>::difference_type;
<3>pointer_type
  • Function
    Returns a pointer pointing to what the iterator points to
  • example
Item& operator*() const  {
    
    return *ptr;}  //这个就是reference_type
Item* operator->() const {
    
    return  ptr;}  //这个就是pointer_type
  • Add both reference_type and pointer_type to traits
template <class I>
struct iterator_traits
{
    
    
	...
	typedef typename I::pointer      pointer;
	typedef typename I::reference    reference;
};
  • Two specializations for raw pointers
    ① Partial specialization for raw pointers
template <class I>
struct iterator_traits<T*>
{
    
    
	...
	typedef typename T*      pointer;
	typedef typename T&      reference;
};

② Partial specialization for native pointer-to-const

template <class I>
struct iterator_traits<const T*>
{
    
    
	...
	typedef typename T*      pointer;
	typedef typename T&      reference;
};
<4>reference_type
  • Principle
    When we want to return a modifiable iterator mutable iterator, the acquisition should be an lvalue, because rvalues ​​do not allow assignment operations (assignment), lvalues ​​are allowed.
  • Solution
    In C++, if a function returns an lvalue, it must be passed by reference. If the value_type is T, then the type of *p should not be T, but T&; similarly, if it is a constant iterator, the type of *p is const T&, not const T
<5>iterator_category
  • Iterator classification (according to 移动特性和操作)
    ①Input iterator: read-only iterator object
    ②Output iterator: write-only iterator object
    ③Forward iterator: read and write operations can be performed on this iterator
    ④Bidirectional iterator: can move in both directions
    ⑤Random Access iterator: the first four The value provides part of the pointer arithmetic capability (for example: the first three support operator++, the fourth plus operator–), and the fifth one covers all pointer arithmetic capabilities, including: p+n, pn, p[n], p1- p2,p1<p2

  • Iterator concepts and enhancements
    The highest-ranked iterator type does not mean the best
    insert image description here

  • Give an example to illustrate that the highest-level iterator is not necessarily the best (take advanced() as an example)
    ① This function has two parameters, the iterator p and the value n, and the function moves p by n bits internally. There are three definitions below

1)针对Input Iterator
template<InputIterator, class Distance>
void advance_II(InputIterator& i, Distance n)
{
    
    
	//单向逐一前进
	while(n--)
		++i;
}
2)针对Bidirectional Iterator
template<BidirectionalIterator, class Distance>
void advance_BI(BidirectionalIterator& i, Distance n)
{
    
    
	//双向,逐一前进
	if(n >= 0)
		while(n--) ++i;
	else
		while(n++) --i;
}
3)针对Random Access Iterator
template<RandomAccessIterator, class Distance>
void advance_RAI(RandomAccessIterator& i, Distance n)
{
    
    
	//双向,跳跃前进
	i+=n;
}

Now when the program calls, which call should be called? If you choose advance_II(), it is extremely inefficient for RandomAccessIterator. For example, returning to the previous iterator, the original O(1) complexity requires O(N). If you choose advance_RAI(), it cannot accept Input iterator. If you will Combining the three into one, it will be displayed as follows:

template<class  InputIterator,class Distance>
void advance(InputIterator& i, Distanc n)
{
    
    
	if(is_random_access_iterator(i)) 
		advance_RAI(i,n);
	else if(is_bidirectional_iterator(i))
		advance_BI(i,n);
	else
		advance_II(i,n);
}

弊端: It is only known which step to execute during execution, which will affect the efficiency of the program. It is best to do it well during compilation 重载函数机制to achieve this goal. Five classes are defined below to represent five iterator types:
insert image description here

  • Why use overloading
    Not only can contribute to the successful operation of the overload 1 mechanism, so that the compiler can correctly implement overload resolution, another advantage is that through inheritance, we can不必再写单纯制作传递调用的函数

These classes are only used for internal marking, so members are not required, so the original function needs to take three parameters to make it an internal function __advance()
insert image description here
insert image description here

  • Final Results
template<class InputIterator,class Distance>
inline void advance(InputIterator& i,Distance n)
{
    
    
	__advance(i,n,
		iterator_trais<InputIterator>::iterator_category());
}

注意:iterator_trais<InputIterator>::iterator_category()A temporary object will be generated, similar to int() generating an int temporary object, and then according to this type, the compiler will decide which __advance() overloaded function to call

  • Regarding this line, the source code of SGI STL is:
__advance(i,n,iterator_category(i));
②并另定义函数iterator_category()如下:
template<class I>
inline typename iterator_traits<I>::iterator_categoy
iterator_category(const i&)
{
    
    
	typedef typename iterator_traits<I>::iterator_category category();
	return category();
}
③综合整理后原式子为
__advance(i,n,
	iterator_traits<InputIterator>::iterator_category());
  • Based on the above (in order to increase the efficiency of STL), it is necessary to add an extraction type
template<class I>
struct iterator_traits
{
    
    
	...
	typedef typename I::iterator_category  iterator_category;
};

① Partially specialized version for native pointers
insert image description here

② Partially specialized version designed for pointer-to-const of native pointers
insert image description here

  • Remarks
    Any iterator, 类型永远应该落在”该迭代器所隶属各种类型最强化的哪个“such as int*, is both RandomAccessIterator, BidirectionalIterator, ForwardIterator, and Input Iterator. Then its type attribute should be RandomAccessIterator

(3) STL algorithm naming rules

advanced() can accept various types of iterators, so its type parameter should not be named InputIterator. In fact, it is a naming rule of the STL algorithm. The lowest-order iterator type that the algorithm can accept is used as its iterator type parameter. name

2) Sample code of std::iterator

(1) Iterator specification

In order to comply with the STL specification, any iterator should provide five built-in types to facilitate the extraction of traits,

(2) Template reference provided by STL

template<class Category,
		class  T,
		class  Distance  = ptrdiff_t,
		class  Pointer   = T*,
		class  Reference = T&>
struct iterator
{
    
    
	typedef Category     iterator_category;
	typedef T            value_type;
	typedef Distance     difference_type;
	typedef Pointer      pointer;
	typedef Reference    reference;
};

(3) Summary: the difference between the responsibilities of iterators, containers, and algorithms

①Responsibility of the iterator: Designing an appropriate iterator type
②Responsibility of the container: Designing an appropriate iterator (only the container knows what kind of iterator is suitable for itself, and what operations to perform, such as forward, backward, and value etc.)
③ Algorithm: develop independently of containers and iterators, as long as the iterator is used for external interface during design

3) SGI_STL iterator source code display

  • Five Iterator Types
struct input_iterator_tag{
    
    };
struct output_iterator_tag{
    
    };
struct forward_iterator_tag: public input_iterator_tag {
    
    };
struct bidiretional_iterator_tag:public forward_iterator_tag{
    
    };
struct random_access_iterator_tag:public bidiretional_iterator_tag {
    
    };
  • Iterator construction (in order to adapt to the STL specification, it is best to add five types)
template<class Category,
		class  T,
		class  Distance  = ptrdiff_t,
		class  Pointer   = T*,
		class  Reference = T&>
struct iterator
{
    
    
	typedef Category     iterator_category;
	typedef T            value_type;
	typedef Distance     difference_type;
	typedef Pointer      pointer;
	typedef Reference    reference;
};
  • Common definitions of extraction functions
template<class iterator>
struct iterator_traits
{
    
    
	typedef typename iterator::iterator_category         iterator_category;
	typedef typename iterator::value_type                value_type;
	typedef typename iterator::difference_type           difference_type;
	typedef typename iterator::pointer                   pointer;
	typedef typename iterator::reference                 reference;
};
  • A partial specialization of traits designed for native pointers
template<class T>
struct iterator_traits<T*>
{
    
    
	typedef typename random_access_iterator_tag          iterator_category;
	typedef typename T                                   value_type;
	typedef typename ptrdiff_t                           difference_type;
	typedef typename T*                                  pointer;
	typedef typename T&                                  reference;
};
  • Partially specialized version of raw pointer design for raw const
template<class T>
struct iterator_traits<const T*>
{
    
    
	typedef typename random_access_iterator_tag          iterator_category;
	typedef typename T                                   value_type;
	typedef typename ptrdiff_t                           difference_type;
	typedef typename const T*                            pointer;
	typedef typename const T&                            reference;
};
  • Get a function of an iterator type (category)
template<class Iterator>
inline typename iterator_traits<Iterator>::iterator_category
iterator_category(const Iterator&)
{
    
    
	typedef typename iterator_traits<Iterator>::iterator_category category;
	return category(); 
}
  • It is very convenient to determine the function of an iterator distance_type
template<class Iterator>
inline typename iterator_traits<Iterator>::difference_type*
distance_type(const Iterator&)
{
    
    
	return static_cast<typename iterator_traits<Iterator>::distance_type*>(0);
}
  • It is convenient to determine the value_type of an iterator
template<class InputIterator>
inline iterator_traits<InputIterator>::value_type*
value_type(const Iterator&)
{
    
    
	return static_cast<typename iterator_traits<Iterator>::value_type*>(0);
}

4) SGI_STL private kitchen: __type_traits

  • Remarks
    ①The prefix of __type_traits "__“indicates the internal use of SGI_STL, which is not within the STL standard specification
    ②iterator_traits is responsible for extracting the characteristics of the iterator, and __type_traits is responsible for extracting the characteristics of the iterator type (here, the type characteristics of the iterator refer to: whether it has non-trival-defaultctor ?Does it have a non-trival copy ctor? Etc.) If the answer is no, we can use the most efficient measures (instead of not calling at all, Constructors that don’t do anything), directly use memory direct operations such as malloc()\memcpy(), etc. to obtain the highest efficiency, which is for large-scale and frequently-operated containers.有显著效率提升

  • The role of __type_traits
    Provides a mechanism that allows for different type attributes (type attributes) to complete the function dispatch decision (function dispatch) at compile time.

  • Mechanism of __type_traits
    When we are going to copy an array with unknown "element type", if we know in advance whether its element type has a trival copy constructor, it can help us decide whether to use fast memcpy() or memmove( )

5) Certificate with identity

  • Concept
    After any value passes through this function, there will be no change
  • Function
    ① The authentication function is used in <stl_set.h> to specify the KeyOfValue op required by RB-tree.
    ② Because the key value of the original god of set is the actual value, identity is used
template <class T>
struct identity:public unary_function<T,T>{
	const T& operator()(const T&x) const {return x;)
};

6) select select

7) Cast project

Guess you like

Origin blog.csdn.net/weixin_43679037/article/details/128445110