转载

CPPFormatLibary提升效率的优化原理

CPPFormatLibary,以下简称FL,介绍:关于CPPFormatLibary。

与stringstream,甚至C库的sprintf系列想比,FL在速度上都有优势,而且是在支持.net格式化风格的基础上。要实现这一点,需要多种优化结合在一起,有代码技巧方面的,也有设计策略上的。下面简要的对这些内容进行讲解:

1.  Pattern缓存

在C库函数sprintf中,比如这行代码:

1          char szBuf[64]; 2          sprintf_s(szBuf, "%d--#--%8.2f--#--%s", 100, -40.2f, " String ");

格式化字符串"%d--#--%8.2f--#--%s"在每次函数调用的时候,都需要分析一次,依次找出对应的格式化符,在实际开发过程中,多数情况下,格式化字符串并没有任何不同,因此这个分析属于重复分析。因此在设计FL时将这样的格式化字符串称为PatternList,并使用Hash容器对这个PatternList进行存储,在每次格式化之前,首先在容器中查找对应字符串的Pattern是否已经存在,有的话则直接使用已经分析的结果。

下面的代码是Pattern的定义,PatternList则为对应的数组:

 1         /**  2         * @brief This is the description of a Format unit  3         * @example {0} {0:d}  4         */  5         template < typename TCharType >  6         class TFormatPattern  7         {  8         public:  9             typedef TCharType                                    CharType; 10             typedef unsigned char                                ByteType; 11             typedef std::size_t                                  SizeType; 12  13             enum EFormatFlag 14             { 15                 FF_Raw, 16                 FF_None, 17                 FF_Decimal, 18                 FF_Exponent, 19                 FF_FixedPoint, 20                 FF_General, 21                 FF_CSV, 22                 FF_Percentage, 23                 FF_Hex 24             }; 25  26             enum EAlignFlag 27             { 28                 AF_Right, 29                 AF_Left 30             }; 31  32             TFormatPattern() : 33                 Start((SizeType)-1), 34                 Len(0), 35                 Flag(FF_Raw), 36                 Align(AF_Right), 37                 Index((ByteType)-1), 38                 Precision((ByteType)-1), 39                 Width((ByteType)-1) 40  41             { 42             } 43  44             SizeType  GetLegnth() const 45             { 46                 return Len; 47             } 48  49             bool    IsValid() const 50             { 51                 return Start != -1 && Len != -1 && Index >= 0; 52             } 53  54             bool    HasWidth() const 55             { 56                 return Width != (ByteType)-1; 57             } 58  59             bool    HasPrecision() const 60             { 61                 return Precision != (ByteType)-1; 62             } 63  64         public: 65             SizeType      Start; 66             SizeType      Len; 67             EFormatFlag   Flag; 68             EAlignFlag    Align; 69  70             ByteType      Index; 71             ByteType      Precision; 72             ByteType      Width; 73         };

这个Pattern就代表了分析格式化字符串的每一个单元。

1         StandardLibrary::FormatTo(str, "{0}--#--{1,8}--#--{2}", 100, -40.2f, " String ");

在这行代码中,PatternList一共有5个Pattern,分别是:

{0} 参数0   --#-- 原始类型  {1,8} 参数1 宽度8  --#-- 原始类型 纯字符串  {2} 参数2 

这样设计可以优化掉重复的字符串Parse。

2.各种类型到字符串转换的算法优化

这部分代码完全存在于文件Algorithm.hpp中,这里面包含了诸如int、double等转换为字符串的快速算法,实测性能优于sprintf和atoi之类。通过这些基础算法的优化,性能可以得到相当不错的提升。

 1        template < typename TCharType >  2         inline void StringReverse(TCharType* Start, TCharType* End)  3         {  4             TCharType Aux;  5   6             while (Start < End)  7             {  8                 Aux = *End;  9                 *End-- = *Start; 10                 *Start++ = Aux; 11             } 12         } 13  14         namespace Detail 15         { 16             const char DigitMap[] =  17             {  18                 '0', '1', '2', '3', '4', '5', '6', 19                 '7', '8', '9', 'A', 'B', 'C', 'D', 20                 'E', 'F' 21             }; 22         }        23  24         template < typename TCharType > 25         inline SIZE_T Int64ToString(INT64 Value, TCharType* Buf, INT Base) 26         { 27             assert(Base > 0 && static_cast<SIZE_T>(Base) <= _countof(Detail::DigitMap)); 28  29             TCharType* Str = Buf; 30  31             UINT64 UValue = (Value < 0) ? -Value : Value; 32  33             // Conversion. Number is reversed. 34             do 35             { 36                 *Str++ = Detail::DigitMap[UValue%Base]; 37             } while (UValue /= Base); 38  39             if (Value < 0) 40             { 41                 *Str++ = '-'; 42             } 43  44             *Str = '/0'; 45  46             // Reverse string 47             StringReverse<TCharType>(Buf, Str - 1); 48  49             return Str - Buf; 50         }

上面这段代码展示的是快速整数到字符串的转换。据说基于sse指令的各种转换会更快,然而担心兼容性问题影响跨平台,我并未采用。

3. 栈容器和栈字符串

这部分代码存在于文件Utility.hpp中,这部分代码的优化原理就是在需要的动态内存并不大的时候,直接使用栈内存,当内存需求大到超过一定阀值的时候,自动申请堆内存并将栈数据转移到堆内存上。在大多数情况下,我们需要的内存都是很少,因此在绝大多数情况下,都能起到相当显著的优化效果。

  1         template <   2             typename T,   3             INT DefaultLength = 0xFF,   4             INT ExtraLength = 0   5         >   6         class TAutoArray   7         {   8         public:   9             typedef TAutoArray<T, DefaultLength, ExtraLength>   SelfType;  10   11             enum  12             {  13                 DEFAULT_LENGTH = DefaultLength  14             };  15   16             class ConstIterator : Noncopyable  17             {  18             public:  19                 ConstIterator(const SelfType& InRef) :  20                     Ref(InRef),  21                     Index( InRef.GetLength()>0?0:-1 )  22                 {  23                 }  24   25                 bool IsValid() const  26                 {  27                     return Index < Ref.GetLength();  28                 }  29   30                 bool Next()  31                 {  32                     ++Index;  33   34                     return IsValid();  35                 }  36   37                 const T& operator *() const  38                 {  39                     const T* Ptr = Ref.GetDataPtr();  40   41                     return Ptr[Index];  42                 }  43             private:  44                 ConstIterator& operator = (const ConstIterator&);  45                 ConstIterator(ConstIterator&);  46             protected:  47                 const SelfType&   Ref;  48                 SIZE_T            Index;  49             };  50   51             TAutoArray() :  52                 Count(0),  53                 AllocatedCount(0),  54                 HeapValPtr(NULL)  55             {  56             }  57   58             ~TAutoArray()  59             {  60                 ReleaseHeapData();  61   62                 Count = 0;  63             }  64   65             TAutoArray(const SelfType& Other) :  66                 Count(Other.Count),  67                 AllocatedCount(Other.AllocatedCount),  68                 HeapValPtr(NULL)  69             {  70                 if (Count > 0)  71                 {  72                     if (Other.IsDataOnStack())  73                     {  74                         Algorithm::CopyArray(Other.StackVal, Other.StackVal + Count, StackVal);  75                     }  76                     else  77                     {  78                         HeapValPtr = Allocate(AllocatedCount);  79                         Algorithm::CopyArray(Other.HeapValPtr, Other.HeapValPtr + Count, HeapValPtr);  80                     }  81                 }  82             }  83   84             SelfType& operator = (const SelfType& Other)  85             {  86                 if (this == &Other)  87                 {  88                     return *this;  89                 }  90   91                 ReleaseHeapData();  92   93                 Count = Other.Count;  94                 AllocatedCount = Other.AllocatedCount;  95                 HeapValPtr = NULL;  96   97                 if (Count > 0)  98                 {  99                     if (Other.IsDataOnStack()) 100                     { 101                         Algorithm::CopyArray(Other.StackVal, Other.StackVal + Count, StackVal); 102                     } 103                     else 104                     { 105                         HeapValPtr = Allocate(AllocatedCount); 106                         Algorithm::CopyArray(Other.HeapValPtr, Other.HeapValPtr + Count, HeapValPtr); 107                     } 108                 } 109  110                 return *this; 111             } 112  113             SelfType& TakeFrom(SelfType& Other) 114             { 115                 if (this == &Other) 116                 { 117                     return *this; 118                 } 119  120                 Count = Other.Count; 121                 AllocatedCount = Other.AllocatedCount; 122                 HeapValPtr = Other.HeapValPtr; 123  124                 if (Count > 0 && Other.IsDataOnStack()) 125                 { 126                     Algorithm::MoveArray(Other.StackVal, Other.StackVal + Count, StackVal); 127                 } 128  129                 Other.Count = 0; 130                 Other.AllocatedCount = 0; 131                 Other.HeapValPtr = NULL; 132             } 133  134             void TakeTo(SelfType& Other) 135             { 136                 Other.TakeFrom(*this); 137             } 138  139 #if FL_PLATFORM_HAS_RIGHT_VALUE_REFERENCE 140             TAutoArray( SelfType && Other ) : 141                 Count(Other.Count), 142                 AllocatedCount(Other.AllocatedCount), 143                 HeapValPtr(Other.HeapValPtr) 144             { 145                 if (Count > 0 && Other.IsDataOnStack()) 146                 { 147                     Algorithm::MoveArray(Other.StackVal, Other.StackVal + Count, StackVal); 148                 } 149  150                 Other.Count = 0; 151                 Other.AllocatedCount = 0; 152                 Other.HeapValPtr = NULL; 153             } 154  155             SelfType& operator = (SelfType&& Other ) 156             { 157                 return TakeFrom(Other); 158             } 159 #endif 160  161             bool  IsDataOnStack() const 162             { 163                 return HeapValPtr == NULL; 164             } 165  166             void  AddItem(const T& InValue) 167             { 168                 if (IsDataOnStack()) 169                 { 170                     if (Count < DEFAULT_LENGTH) 171                     { 172                         StackVal[Count] = InValue; 173                         ++Count; 174                     } 175                     else if (Count == DEFAULT_LENGTH) 176                     { 177                         InitialMoveDataToHeap(); 178  179                         assert(Count < AllocatedCount); 180  181                         HeapValPtr[Count] = InValue; 182                         ++Count; 183                     } 184                     else 185                     { 186                         assert(false && "internal error"); 187                     } 188                 } 189                 else 190                 { 191                     if (Count < AllocatedCount) 192                     { 193                         HeapValPtr[Count] = InValue; 194                         ++Count; 195                     } 196                     else 197                     { 198                         ExpandHeapSpace(); 199  200                         assert(Count < AllocatedCount); 201                         HeapValPtr[Count] = InValue; 202                         ++Count; 203                     } 204                 } 205             } 206  207             SIZE_T GetLength() const 208             { 209                 return Count; 210             } 211  212             SIZE_T GetAllocatedCount() const 213             { 214                 return AllocatedCount; 215             } 216  217             T* GetDataPtr() 218             { 219                 return IsDataOnStack() ? StackVal : HeapValPtr; 220             } 221  222             const T* GetDataPtr() const 223             { 224                 return IsDataOnStack() ? StackVal : HeapValPtr; 225             } 226  227             T* GetUnusedPtr() 228             { 229                 return IsDataOnStack() ? StackVal + Count : HeapValPtr + Count; 230             } 231  232             const T* GetUnusedPtr() const 233             { 234                 return IsDataOnStack() ? StackVal + Count : HeapValPtr + Count; 235             } 236  237             SIZE_T GetCapacity() const 238             {                 239                 return IsDataOnStack() ? 240                     DEFAULT_LENGTH - Count : 241                     AllocatedCount - Count; 242             } 243              244             T& operator []( SIZE_T Index ) 245             { 246                 assert( Index < GetLength() ); 247                  248                 return GetDataPtr()[Index]; 249             } 250              251             const T& operator []( SIZE_T Index ) const 252             { 253                 assert( Index < GetLength() ); 254                  255                 return GetDataPtr()[Index]; 256             } 257  258         protected: 259             void  InitialMoveDataToHeap() 260             { 261                 assert(HeapValPtr == NULL); 262  263                 AllocatedCount = DEFAULT_LENGTH * 2; 264  265                 HeapValPtr = Allocate(AllocatedCount); 266  267 #if FL_PLATFORM_HAS_RIGHT_VALUE_REFERENCE 268                 Algorithm::MoveArray(StackVal, StackVal + Count, HeapValPtr); 269 #else 270                 Algorithm::CopyArray(StackVal, StackVal + Count, HeapValPtr); 271 #endif 272             } 273  274             void  ExpandHeapSpace() 275             { 276                 SIZE_T NewCount = AllocatedCount * 2; 277                 assert(NewCount > AllocatedCount); 278  279                 T* DataPtr = Allocate(NewCount); 280  281                 assert(DataPtr); 282  283 #if FL_PLATFORM_HAS_RIGHT_VALUE_REFERENCE 284                 Algorithm::MoveArray(HeapValPtr, HeapValPtr + Count, DataPtr); 285 #else 286                 Algorithm::CopyArray(HeapValPtr, HeapValPtr + Count, DataPtr); 287 #endif 288                 ReleaseHeapData(); 289  290                 HeapValPtr = DataPtr; 291                 AllocatedCount = NewCount; 292             } 293  294             void  ReleaseHeapData() 295             { 296                 if (HeapValPtr) 297                 { 298                     delete[] HeapValPtr; 299                     HeapValPtr = NULL; 300                 } 301  302                 AllocatedCount = 0; 303             } 304  305             static T*  Allocate(const SIZE_T InAllocatedCount) 306             { 307                 // +ExtraLength this is a hack method for saving string on it. 308                 return new T[InAllocatedCount + ExtraLength]; 309             } 310  311         protected: 312             SIZE_T        Count; 313             SIZE_T        AllocatedCount; 314  315             // +ExtraLength this is a hack method for saving string on it. 316             T             StackVal[DEFAULT_LENGTH + ExtraLength]; 317  318             T*            HeapValPtr; 319         };

上面这段代码展示的就是这个设想的实现。这是一个模板类,基于这个类实现了栈字符串。同时默认的PatternList也是使用这个模板来保存的,这样在节约了大量的内存分配操作之后,性能得到进一步的提升。

  1 // String Wrapper   2         template < typename TCharType >   3         class TAutoString :   4             public TAutoArray< TCharType, 0xFF, 2 >   5         {   6         public:   7             typedef TAutoArray< TCharType, 0xFF, 2 > Super;   8             typedef Mpl::TCharTraits<TCharType>      CharTraits;   9             typedef TCharType                        CharType;  10   11 #if !FL_COMPILER_MSVC  12             using Super::Count;  13             using Super::AllocatedCount;  14             using Super::HeapValPtr;  15             using Super::StackVal;  16             using Super::Allocate;  17             using Super::IsDataOnStack;  18             using Super::DEFAULT_LENGTH;  19             using Super::GetDataPtr;  20             using Super::ReleaseHeapData;  21 #endif  22   23             TAutoString()  24             {  25             }  26   27             TAutoString(const CharType* pszStr)  28             {  29                 if (pszStr)  30                 {  31                     const SIZE_T Length = CharTraits::length(pszStr);  32   33                     Count = Length;  34   35                     if (Length <= DEFAULT_LENGTH)  36                     {  37                         CharTraits::copy(pszStr, pszStr + Length, StackVal);  38                         StackVal[Count] = 0;  39                     }  40                     else  41                     {  42                         HeapValPtr = Allocate(Length);  43                         CharTraits::copy(pszStr, pszStr + Length, HeapValPtr);  44                         HeapValPtr[Count] = 0;  45                     }  46                 }  47             }  48   49             void  AddChar(CharType InValue)  50             {  51                 AddItem(InValue);  52   53                 if (IsDataOnStack())  54                 {  55                     StackVal[Count] = 0;  56                 }  57                 else  58                 {  59                     HeapValPtr[Count] = 0;  60                 }  61             }  62   63             void AddStr(const CharType* pszStart, const CharType* pszEnd = NULL)  64             {  65                 const SIZE_T Length = pszEnd ? pszEnd - pszStart : CharTraits::length(pszStart);  66   67                 if (IsDataOnStack())  68                 {  69                     if (Count + Length <= DEFAULT_LENGTH)  70                     {  71                         CharTraits::copy(StackVal+Count, pszStart, Length);  72                         Count += Length;  73   74                         StackVal[Count] = 0;  75                     }  76                     else  77                     {  78                         assert(!HeapValPtr);  79   80                         AllocatedCount = static_cast<SIZE_T>((Count + Length)*1.5f);  81                         HeapValPtr = Allocate(AllocatedCount);  82                         assert(HeapValPtr);  83   84                         if (Count > 0)  85                         {  86                             CharTraits::copy(HeapValPtr, StackVal, Count);  87                         }  88   89                         CharTraits::copy(HeapValPtr+Count, pszStart, Length);  90   91                         Count += Length;  92   93                         HeapValPtr[Count] = 0;  94                     }  95                 }  96                 else  97                 {  98                     if (Count + Length <= AllocatedCount)  99                     { 100                         CharTraits::copy(HeapValPtr+Count, pszStart, Length); 101                         Count += Length; 102  103                         HeapValPtr[Count] = 0; 104                     } 105                     else 106                     { 107                         SIZE_T NewCount = static_cast<SIZE_T>((Count + Length)*1.5f); 108  109                         CharType* DataPtr = Allocate(NewCount); 110  111                         if (Count > 0) 112                         { 113                             CharTraits::copy(DataPtr, HeapValPtr, Count); 114                         } 115  116                         ReleaseHeapData(); 117  118                         CharTraits::copy(DataPtr, pszStart, Length); 119  120                         Count += Length; 121  122                         AllocatedCount = NewCount; 123                         HeapValPtr = DataPtr; 124  125                         HeapValPtr[Count] = 0; 126                     } 127                 } 128             } 129  130             const TCharType* CStr() const 131             { 132                 return GetDataPtr(); 133             } 134  135             // is is a internal function 136             //  137             void InjectAdd(SIZE_T InCount) 138             { 139                 Count += InCount; 140  141                 assert(IsDataOnStack() ? (Count <= DEFAULT_LENGTH) : (Count < AllocatedCount)); 142             } 143  144         protected: 145             void  AddItem(const TCharType& InValue) 146             { 147                 Super::AddItem(InValue); 148             } 149         };

上面展示的是基于栈内存容器实现的栈字符串,在大多数情况下,我们格式化字符串时都采用栈字符串来保存结果,这样可以显著的提升性能。

同时栈容器和栈字符串,都特别适合于当临时容器和临时字符串,因为多数情况下它们都优化掉了可能需要动态内存分配的操作。所以它们的使用并不局限于这一个小地方。

4. 基于C++ 11的优化

除了引入了C++ 11的容器unordered_map之外,还引入了右值引用等新内容,在某些情况下,可以带来一定的性能提升。

 1 #if FL_PLATFORM_HAS_RIGHT_VALUE_REFERENCE  2             TAutoArray( SelfType && Other ) :  3                 Count(Other.Count),  4                 AllocatedCount(Other.AllocatedCount),  5                 HeapValPtr(Other.HeapValPtr)  6             {  7                 if (Count > 0 && Other.IsDataOnStack())  8                 {  9                     Algorithm::MoveArray(Other.StackVal, Other.StackVal + Count, StackVal); 10                 } 11  12                 Other.Count = 0; 13                 Other.AllocatedCount = 0; 14                 Other.HeapValPtr = NULL; 15             } 16  17             SelfType& operator = (SelfType&& Other ) 18             { 19                 return TakeFrom(Other); 20             } 21 #endif

上面展示的是基于右值引用的优化。

除此之外还是用了线程局部存储(TLS),这依赖于编译器是否支持。前面提到了我们采用Hash容器来存储Pattern缓存,然而在单线程的时候自然无需多余考虑,当需要支持多线程时,则全局唯一的Hash容器的访问都需要加锁,而加锁是有性能开销的。幸好C++ 11带来了内置的TLS支持,其结果就是每个线程会独立保存一份这样的Pattern缓存,因此无需对其访问加锁,这样无疑效率会更高。缺陷则是会损失部分内存。所有的这些都可以通过预先的宏定义来进行开关,使用者可以自行决定使用TLS还是Lock,或者不支持多线程。

 1         template < typename TPolicy >  2         class TGlobalPatternStorage :   3             public TPatternStorage<TPolicy>  4         {  5         public:  6             static TGlobalPatternStorage* GetStorage()  7             {  8 #if FL_WITH_THREAD_LOCAL  9                 struct ManagedStorage 10                 { 11                     typedef Utility::TScopedLocker<System::CriticalSection>  LockerType; 12                      13                     System::CriticalSection                                  ManagedCS; 14                     Utility::TAutoArray<TGlobalPatternStorage*>              Storages; 15                      16                     ~ManagedStorage() 17                     { 18                         LockerType Locker(ManagedCS); 19                          20                         for( SIZE_T i=0; i<Storages.GetLength(); ++i ) 21                         { 22                             delete Storages[i]; 23                         } 24                     } 25                      26                     void AddStorage( TGlobalPatternStorage* Storage ) 27                     { 28                         assert(Storage); 29                          30                         LockerType Locker(ManagedCS); 31                          32                         Storages.AddItem(Storage); 33                     } 34                 }; 35                  36                 static ManagedStorage StaticManager; 37                  38                 static FL_THREAD_LOCAL TGlobalPatternStorage* StaticStorage = NULL; 39                  40                 if( !StaticStorage ) 41                 { 42                     StaticStorage = new TGlobalPatternStorage(); 43                      44                     StaticManager.AddStorage(StaticStorage); 45                 } 46                  47                 return StaticStorage; 48 #else 49                 static TGlobalPatternStorage StaticStorage; 50                 return &StaticStorage; 51 #endif 52             } 53         };

如上所示为项目中使用TLS的代码。

总结

在将这一系列的优化结合起来之后,可以使得FL的整体效率处于较高水平,不低于C库函数,同时还具备其它格式化库不具备的功能,对于代码安全性等各方面的增强,都有帮助。下面是Test.cpp的测试结果,FL代表的是使用FL库的耗时,CL代表的C库的耗时,同时此测试模拟了多线程环境。

Windows Visual Studio 2013 Release下的输出:

CPPFormatLibary提升效率的优化原理
0x64 Test20, -10.0050,  X ,  X 0x64 Test20, -10.0050,  X ,  X 1920 FLElapse:0.0762746 1920 CLElapse:0.269722 1636 FLElapse:0.0756153 7732 FLElapse:0.0766446 7956 FLElapse:0.0762051 7956 CLElapse:0.285714 1636 CLElapse:0.288648 7732 CLElapse:0.289193
CPPFormatLibary提升效率的优化原理

Mac Xcode Release:

CPPFormatLibary提升效率的优化原理
99 Test20, -10.0050,  X ,  X  18446744073709551615 FLElapse:0.0901681 18446744073709551615 CLElapse:0.19329 18446744073709551615 FLElapse:0.147378 18446744073709551615 FLElapse:0.150375 18446744073709551615 FLElapse:0.153342 18446744073709551615 CLElapse:0.303508 18446744073709551615 CLElapse:0.308418 18446744073709551615 CLElapse:0.307407
CPPFormatLibary提升效率的优化原理

这并非完全的测试,更多的测试需要在实际使用过程中来验证。

正文到此结束
Loading...