1 // LzmaBench.cpp
2 
3 #include "StdAfx.h"
4 
5 #include "LzmaBench.h"
6 
7 #ifndef _WIN32
8 #include <time.h>
9 #endif
10 
11 #include "../../../Common/CRC.h"
12 #include "../LZMA/LZMADecoder.h"
13 #include "../LZMA/LZMAEncoder.h"
14 
15 static const UInt32 kAdditionalSize =
16 #ifdef _WIN32_WCE
17 (1 << 20);
18 #else
19 (6 << 20);
20 #endif
21 
22 static const UInt32 kCompressedAdditionalSize = (1 << 10);
23 static const UInt32 kMaxLzmaPropSize = 10;
24 
25 class CRandomGenerator
26 {
27   UInt32 A1;
28   UInt32 A2;
29 public:
CRandomGenerator()30   CRandomGenerator() { Init(); }
Init()31   void Init() { A1 = 362436069; A2 = 521288629;}
GetRnd()32   UInt32 GetRnd()
33   {
34     return
35       ((A1 = 36969 * (A1 & 0xffff) + (A1 >> 16)) << 16) ^
36       ((A2 = 18000 * (A2 & 0xffff) + (A2 >> 16)) );
37   }
38 };
39 
40 class CBitRandomGenerator
41 {
42   CRandomGenerator RG;
43   UInt32 Value;
44   int NumBits;
45 public:
Init()46   void Init()
47   {
48     Value = 0;
49     NumBits = 0;
50   }
GetRnd(int numBits)51   UInt32 GetRnd(int numBits)
52   {
53     if (NumBits > numBits)
54     {
55       UInt32 result = Value & ((1 << numBits) - 1);
56       Value >>= numBits;
57       NumBits -= numBits;
58       return result;
59     }
60     numBits -= NumBits;
61     UInt32 result = (Value << numBits);
62     Value = RG.GetRnd();
63     result |= Value & ((1 << numBits) - 1);
64     Value >>= numBits;
65     NumBits = 32 - numBits;
66     return result;
67   }
68 };
69 
70 class CBenchRandomGenerator
71 {
72   CBitRandomGenerator RG;
73   UInt32 Pos;
74 public:
75   UInt32 BufferSize;
76   Byte *Buffer;
CBenchRandomGenerator()77   CBenchRandomGenerator(): Buffer(0) {}
~CBenchRandomGenerator()78   ~CBenchRandomGenerator() { delete []Buffer; }
Init()79   void Init() { RG.Init(); }
Set(UInt32 bufferSize)80   void Set(UInt32 bufferSize)
81   {
82     delete []Buffer;
83     Buffer = 0;
84     Buffer = new Byte[bufferSize];
85     Pos = 0;
86     BufferSize = bufferSize;
87   }
GetRndBit()88   UInt32 GetRndBit() { return RG.GetRnd(1); }
89   /*
90   UInt32 GetLogRand(int maxLen)
91   {
92     UInt32 len = GetRnd() % (maxLen + 1);
93     return GetRnd() & ((1 << len) - 1);
94   }
95   */
GetLogRandBits(int numBits)96   UInt32 GetLogRandBits(int numBits)
97   {
98     UInt32 len = RG.GetRnd(numBits);
99     return RG.GetRnd(len);
100   }
GetOffset()101   UInt32 GetOffset()
102   {
103     if (GetRndBit() == 0)
104       return GetLogRandBits(4);
105     return (GetLogRandBits(4) << 10) | RG.GetRnd(10);
106   }
GetLen()107   UInt32 GetLen()
108   {
109     if (GetRndBit() == 0)
110       return RG.GetRnd(2);
111     if (GetRndBit() == 0)
112       return 4 + RG.GetRnd(3);
113     return 12 + RG.GetRnd(4);
114   }
Generate()115   void Generate()
116   {
117     while(Pos < BufferSize)
118     {
119       if (GetRndBit() == 0 || Pos < 1)
120         Buffer[Pos++] = Byte(RG.GetRnd(8));
121       else
122       {
123         UInt32 offset = GetOffset();
124         while (offset >= Pos)
125           offset >>= 1;
126         offset += 1;
127         UInt32 len = 2 + GetLen();
128         for (UInt32 i = 0; i < len && Pos < BufferSize; i++, Pos++)
129           Buffer[Pos] = Buffer[Pos - offset];
130       }
131     }
132   }
133 };
134 
135 class CBenchmarkInStream:
136   public ISequentialInStream,
137   public CMyUnknownImp
138 {
139   const Byte *Data;
140   UInt32 Pos;
141   UInt32 Size;
142 public:
143   MY_UNKNOWN_IMP
Init(const Byte * data,UInt32 size)144   void Init(const Byte *data, UInt32 size)
145   {
146     Data = data;
147     Size = size;
148     Pos = 0;
149   }
150   STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize);
151 };
152 
Read(void * data,UInt32 size,UInt32 * processedSize)153 STDMETHODIMP CBenchmarkInStream::Read(void *data, UInt32 size, UInt32 *processedSize)
154 {
155   UInt32 remain = Size - Pos;
156   if (size > remain)
157     size = remain;
158   for (UInt32 i = 0; i < size; i++)
159     ((Byte *)data)[i] = Data[Pos + i];
160   Pos += size;
161   if(processedSize != NULL)
162     *processedSize = size;
163   return S_OK;
164 }
165 
166 class CBenchmarkOutStream:
167   public ISequentialOutStream,
168   public CMyUnknownImp
169 {
170   UInt32 BufferSize;
171   FILE *_f;
172 public:
173   UInt32 Pos;
174   Byte *Buffer;
CBenchmarkOutStream()175   CBenchmarkOutStream(): _f(0), Buffer(0) {}
~CBenchmarkOutStream()176   virtual ~CBenchmarkOutStream() { delete []Buffer; }
Init(FILE * f,UInt32 bufferSize)177   void Init(FILE *f, UInt32 bufferSize)
178   {
179     delete []Buffer;
180     Buffer = 0;
181     Buffer = new Byte[bufferSize];
182     Pos = 0;
183     BufferSize = bufferSize;
184     _f = f;
185   }
186   MY_UNKNOWN_IMP
187   STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize);
188 };
189 
Write(const void * data,UInt32 size,UInt32 * processedSize)190 STDMETHODIMP CBenchmarkOutStream::Write(const void *data, UInt32 size, UInt32 *processedSize)
191 {
192   UInt32 i;
193   for (i = 0; i < size && Pos < BufferSize; i++)
194     Buffer[Pos++] = ((const Byte *)data)[i];
195   if(processedSize != NULL)
196     *processedSize = i;
197   if (i != size)
198   {
199     fprintf(_f, "\nERROR: Buffer is full\n");
200     return E_FAIL;
201   }
202   return S_OK;
203 }
204 
205 class CCrcOutStream:
206   public ISequentialOutStream,
207   public CMyUnknownImp
208 {
209 public:
210   CCRC CRC;
211   MY_UNKNOWN_IMP
Init()212   void Init() { CRC.Init(); }
213   STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize);
214 };
215 
Write(const void * data,UInt32 size,UInt32 * processedSize)216 STDMETHODIMP CCrcOutStream::Write(const void *data, UInt32 size, UInt32 *processedSize)
217 {
218   CRC.Update(data, size);
219   if(processedSize != NULL)
220     *processedSize = size;
221   return S_OK;
222 }
223 
GetTimeCount()224 static UInt64 GetTimeCount()
225 {
226   #ifdef _WIN32
227   LARGE_INTEGER value;
228   if (::QueryPerformanceCounter(&value))
229     return value.QuadPart;
230   return GetTickCount();
231   #else
232   return clock();
233   #endif
234 }
235 
GetFreq()236 static UInt64 GetFreq()
237 {
238   #ifdef _WIN32
239   LARGE_INTEGER value;
240   if (::QueryPerformanceFrequency(&value))
241     return value.QuadPart;
242   return 1000;
243   #else
244   return CLOCKS_PER_SEC;
245   #endif
246 }
247 
248 struct CProgressInfo:
249   public ICompressProgressInfo,
250   public CMyUnknownImp
251 {
252   UInt64 ApprovedStart;
253   UInt64 InSize;
254   UInt64 Time;
InitCProgressInfo255   void Init()
256   {
257     InSize = 0;
258     Time = 0;
259   }
260   MY_UNKNOWN_IMP
261   STDMETHOD(SetRatioInfo)(const UInt64 *inSize, const UInt64 *outSize);
262 };
263 
SetRatioInfo(const UInt64 * inSize,const UInt64 * outSize)264 STDMETHODIMP CProgressInfo::SetRatioInfo(const UInt64 *inSize, const UInt64 *outSize)
265 {
266   if (*inSize >= ApprovedStart && InSize == 0)
267   {
268     Time = ::GetTimeCount();
269     InSize = *inSize;
270   }
271   return S_OK;
272 }
273 
274 static const int kSubBits = 8;
275 
GetLogSize(UInt32 size)276 static UInt32 GetLogSize(UInt32 size)
277 {
278   for (int i = kSubBits; i < 32; i++)
279     for (UInt32 j = 0; j < (1 << kSubBits); j++)
280       if (size <= (((UInt32)1) << i) + (j << (i - kSubBits)))
281         return (i << kSubBits) + j;
282   return (32 << kSubBits);
283 }
284 
MyMultDiv64(UInt64 value,UInt64 elapsedTime)285 static UInt64 MyMultDiv64(UInt64 value, UInt64 elapsedTime)
286 {
287   UInt64 freq = GetFreq();
288   UInt64 elTime = elapsedTime;
289   while(freq > 1000000)
290   {
291     freq >>= 1;
292     elTime >>= 1;
293   }
294   if (elTime == 0)
295     elTime = 1;
296   return value * freq / elTime;
297 }
298 
GetCompressRating(UInt32 dictionarySize,bool isBT4,UInt64 elapsedTime,UInt64 size)299 static UInt64 GetCompressRating(UInt32 dictionarySize, bool isBT4,
300     UInt64 elapsedTime, UInt64 size)
301 {
302   UInt64 numCommandsForOne;
303   if (isBT4)
304   {
305     UInt64 t = GetLogSize(dictionarySize) - (19 << kSubBits);
306     numCommandsForOne = 2000 + ((t * t * 68) >> (2 * kSubBits));
307   }
308   else
309   {
310     UInt64 t = GetLogSize(dictionarySize) - (15 << kSubBits);
311     numCommandsForOne = 1500 + ((t * t * 41) >> (2 * kSubBits));
312   }
313   UInt64 numCommands = (UInt64)(size) * numCommandsForOne;
314   return MyMultDiv64(numCommands, elapsedTime);
315 }
316 
GetDecompressRating(UInt64 elapsedTime,UInt64 outSize,UInt64 inSize)317 static UInt64 GetDecompressRating(UInt64 elapsedTime,
318     UInt64 outSize, UInt64 inSize)
319 {
320   UInt64 numCommands = inSize * 250 + outSize * 21;
321   return MyMultDiv64(numCommands, elapsedTime);
322 }
323 
324 /*
325 static UInt64 GetTotalRating(
326     UInt32 dictionarySize,
327     bool isBT4,
328     UInt64 elapsedTimeEn, UInt64 sizeEn,
329     UInt64 elapsedTimeDe,
330     UInt64 inSizeDe, UInt64 outSizeDe)
331 {
332   return (GetCompressRating(dictionarySize, isBT4, elapsedTimeEn, sizeEn) +
333     GetDecompressRating(elapsedTimeDe, inSizeDe, outSizeDe)) / 2;
334 }
335 */
336 
PrintRating(FILE * f,UInt64 rating)337 static void PrintRating(FILE *f, UInt64 rating)
338 {
339   fprintf(f, "%5d MIPS", (unsigned int)(rating / 1000000));
340 }
341 
PrintResults(FILE * f,UInt32 dictionarySize,bool isBT4,UInt64 elapsedTime,UInt64 size,bool decompressMode,UInt64 secondSize)342 static void PrintResults(
343     FILE *f,
344     UInt32 dictionarySize,
345     bool isBT4,
346     UInt64 elapsedTime,
347     UInt64 size,
348     bool decompressMode, UInt64 secondSize)
349 {
350   UInt64 speed = MyMultDiv64(size, elapsedTime);
351   fprintf(f, "%6d KB/s  ", (unsigned int)(speed / 1024));
352   UInt64 rating;
353   if (decompressMode)
354     rating = GetDecompressRating(elapsedTime, size, secondSize);
355   else
356     rating = GetCompressRating(dictionarySize, isBT4, elapsedTime, size);
357   PrintRating(f, rating);
358 }
359 
ThrowError(FILE * f,HRESULT result,const char * s)360 static void ThrowError(FILE *f, HRESULT result, const char *s)
361 {
362   fprintf(f, "\nError: ");
363   if (result == E_ABORT)
364     fprintf(f, "User break");
365   if (result == E_OUTOFMEMORY)
366     fprintf(f, "Can not allocate memory");
367   else
368     fprintf(f, s);
369   fprintf(f, "\n");
370 }
371 
372 const wchar_t *bt2 = L"BT2";
373 const wchar_t *bt4 = L"BT4";
374 
LzmaBenchmark(FILE * f,UInt32 numIterations,UInt32 dictionarySize,bool isBT4)375 int LzmaBenchmark(FILE *f, UInt32 numIterations, UInt32 dictionarySize, bool isBT4)
376 {
377   if (numIterations == 0)
378     return 0;
379   if (dictionarySize < (1 << 19) && isBT4 || dictionarySize < (1 << 15))
380   {
381     fprintf(f, "\nError: dictionary size for benchmark must be >= 19 (512 KB)\n");
382     return 1;
383   }
384   fprintf(f, "\n       Compressing                Decompressing\n\n");
385   NCompress::NLZMA::CEncoder *encoderSpec = new NCompress::NLZMA::CEncoder;
386   CMyComPtr<ICompressCoder> encoder = encoderSpec;
387 
388   NCompress::NLZMA::CDecoder *decoderSpec = new NCompress::NLZMA::CDecoder;
389   CMyComPtr<ICompressCoder> decoder = decoderSpec;
390 
391   CBenchmarkOutStream *propStreamSpec = new CBenchmarkOutStream;
392   CMyComPtr<ISequentialOutStream> propStream = propStreamSpec;
393   propStreamSpec->Init(f, kMaxLzmaPropSize);
394 
395   PROPID propIDs[] =
396   {
397     NCoderPropID::kDictionarySize,
398     NCoderPropID::kMatchFinder
399   };
400   const int kNumProps = sizeof(propIDs) / sizeof(propIDs[0]);
401   PROPVARIANT properties[kNumProps];
402   properties[0].vt = VT_UI4;
403   properties[0].ulVal = UInt32(dictionarySize);
404 
405   properties[1].vt = VT_BSTR;
406   properties[1].bstrVal = isBT4 ? (BSTR)bt4: (BSTR)bt2;
407 
408   const UInt32 kBufferSize = dictionarySize + kAdditionalSize;
409   const UInt32 kCompressedBufferSize = (kBufferSize / 2) + kCompressedAdditionalSize;
410 
411   if (encoderSpec->SetCoderProperties(propIDs, properties, kNumProps) != S_OK)
412   {
413     fprintf(f, "\nError: Incorrect command\n");
414     return 1;
415   }
416   encoderSpec->WriteCoderProperties(propStream);
417 
418   CBenchRandomGenerator rg;
419   rg.Init();
420   rg.Set(kBufferSize);
421   rg.Generate();
422   CCRC crc;
423   crc.Update(rg.Buffer, rg.BufferSize);
424 
425   CProgressInfo *progressInfoSpec = new CProgressInfo;
426   CMyComPtr<ICompressProgressInfo> progressInfo = progressInfoSpec;
427 
428   progressInfoSpec->ApprovedStart = dictionarySize;
429 
430   UInt64 totalBenchSize = 0;
431   UInt64 totalEncodeTime = 0;
432   UInt64 totalDecodeTime = 0;
433   UInt64 totalCompressedSize = 0;
434 
435   for (UInt32 i = 0; i < numIterations; i++)
436   {
437     progressInfoSpec->Init();
438     CBenchmarkInStream *inStreamSpec = new CBenchmarkInStream;
439     inStreamSpec->Init(rg.Buffer, rg.BufferSize);
440     CMyComPtr<ISequentialInStream> inStream = inStreamSpec;
441     CBenchmarkOutStream *outStreamSpec = new CBenchmarkOutStream;
442     outStreamSpec->Init(f, kCompressedBufferSize);
443     CMyComPtr<ISequentialOutStream> outStream = outStreamSpec;
444     HRESULT result = encoder->Code(inStream, outStream, 0, 0, progressInfo);
445     UInt64 encodeTime = ::GetTimeCount() - progressInfoSpec->Time;
446     UInt32 compressedSize = outStreamSpec->Pos;
447     if(result != S_OK)
448     {
449       ThrowError(f, result, "Encoder Error");
450       return 1;
451     }
452     if (progressInfoSpec->InSize == 0)
453     {
454       fprintf(f, "\nError: Internal ERROR 1282\n");
455       return 1;
456     }
457 
458     ///////////////////////
459     // Decompressing
460 
461     CCrcOutStream *crcOutStreamSpec = new CCrcOutStream;
462     CMyComPtr<ISequentialOutStream> crcOutStream = crcOutStreamSpec;
463 
464     UInt64 decodeTime;
465     for (int j = 0; j < 2; j++)
466     {
467       inStreamSpec->Init(outStreamSpec->Buffer, compressedSize);
468       crcOutStreamSpec->Init();
469 
470       if (decoderSpec->SetDecoderProperties2(propStreamSpec->Buffer, propStreamSpec->Pos) != S_OK)
471       {
472         fprintf(f, "\nError: Set Decoder Properties Error\n");
473         return 1;
474       }
475       UInt64 outSize = kBufferSize;
476       UInt64 startTime = ::GetTimeCount();
477       result = decoder->Code(inStream, crcOutStream, 0, &outSize, 0);
478       decodeTime = ::GetTimeCount() - startTime;
479       if(result != S_OK)
480       {
481         ThrowError(f, result, "Decode Error");
482         return 1;
483       }
484       if (crcOutStreamSpec->CRC.GetDigest() != crc.GetDigest())
485       {
486         fprintf(f, "\nError: CRC Error\n");
487         return 1;
488       }
489     }
490     UInt64 benchSize = kBufferSize - progressInfoSpec->InSize;
491     PrintResults(f, dictionarySize, isBT4, encodeTime, benchSize, false, 0);
492     fprintf(f, "     ");
493     PrintResults(f, dictionarySize, isBT4, decodeTime, kBufferSize, true, compressedSize);
494     fprintf(f, "\n");
495 
496     totalBenchSize += benchSize;
497     totalEncodeTime += encodeTime;
498     totalDecodeTime += decodeTime;
499     totalCompressedSize += compressedSize;
500   }
501   fprintf(f, "---------------------------------------------------\n");
502   PrintResults(f, dictionarySize, isBT4, totalEncodeTime, totalBenchSize, false, 0);
503   fprintf(f, "     ");
504   PrintResults(f, dictionarySize, isBT4, totalDecodeTime,
505       kBufferSize * numIterations, true, totalCompressedSize);
506   fprintf(f, "    Average\n");
507   return 0;
508 }
509