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下的输出:

复制代码
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
复制代码

Mac Xcode Release:

复制代码
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
复制代码

       

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

posted @ 2015-09-11 11:08  bodong  阅读(1017)  评论(0编辑  收藏