zoukankan      html  css  js  c++  java
  • 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
    复制代码

           

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

  • 相关阅读:
    ABAP接口用法
    监听textarea数值变化
    The first step in solving any problem is recognizing there is one.
    Wrinkles should merely indicate where smiles have been.
    God made relatives.Thank God we can choose our friends.
    Home is where your heart is
    ABAP跳转屏幕
    Python 工具包 werkzeug 初探
    atom通过remote ftp同步本地文件到远程主机的方法
    Mongodb学习笔记一
  • 原文地址:https://www.cnblogs.com/bodong/p/4800340.html
Copyright © 2011-2022 走看看