1 /**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 Benchmark.cpp
6
7 Dominic Mazzoni
8
9 *******************************************************************//**
10
11 \class BenchmarkDialog
12 \brief BenchmarkDialog is used for measuring performance and accuracy
13 of sample block storage.
14
15 *//*******************************************************************/
16
17
18
19 #include "Benchmark.h"
20
21 #include <wx/app.h>
22 #include <wx/log.h>
23 #include <wx/textctrl.h>
24 #include <wx/button.h>
25 #include <wx/checkbox.h>
26 #include <wx/choice.h>
27 #include <wx/dialog.h>
28 #include <wx/sizer.h>
29 #include <wx/stattext.h>
30 #include <wx/timer.h>
31 #include <wx/utils.h>
32 #include <wx/valgen.h>
33 #include <wx/valtext.h>
34 #include <wx/intl.h>
35
36 #include "SampleBlock.h"
37 #include "ShuttleGui.h"
38 #include "Project.h"
39 #include "WaveClip.h"
40 #include "WaveTrack.h"
41 #include "Sequence.h"
42 #include "Prefs.h"
43 #include "ProjectRate.h"
44 #include "ViewInfo.h"
45
46 #include "FileNames.h"
47 #include "SelectFile.h"
48 #include "widgets/AudacityMessageBox.h"
49 #include "widgets/wxPanelWrapper.h"
50
51 // Change these to the desired format...should probably make the
52 // choice available in the dialog
53 #define SampleType short
54 #define SampleFormat int16Sample
55
56 class BenchmarkDialog final : public wxDialogWrapper
57 {
58 public:
59 // constructors and destructors
60 BenchmarkDialog( wxWindow *parent, AudacityProject &project );
61
62 void MakeBenchmarkDialog();
63
64 private:
65 // WDR: handler declarations
66 void OnRun( wxCommandEvent &event );
67 void OnSave( wxCommandEvent &event );
68 void OnClear( wxCommandEvent &event );
69 void OnClose( wxCommandEvent &event );
70
71 void Printf(const TranslatableString &str);
72 void HoldPrint(bool hold);
73 void FlushPrint();
74
75 AudacityProject &mProject;
76 const ProjectRate &mRate;
77
78 bool mHoldPrint;
79 wxString mToPrint;
80
81 wxString mBlockSizeStr;
82 wxString mDataSizeStr;
83 wxString mNumEditsStr;
84 wxString mRandSeedStr;
85
86 bool mBlockDetail;
87 bool mEditDetail;
88
89 wxTextCtrl *mText;
90
91 private:
92 DECLARE_EVENT_TABLE()
93 };
94
RunBenchmark(wxWindow * parent,AudacityProject & project)95 void RunBenchmark( wxWindow *parent, AudacityProject &project )
96 {
97 /*
98 int action = AudacityMessageBox(
99 XO("This will close all project windows (without saving)\nand open the Audacity Benchmark dialog.\n\nAre you sure you want to do this?"),
100 XO("Benchmark"),
101 wxYES_NO | wxICON_EXCLAMATION,
102 NULL);
103
104 if (action != wxYES)
105 return;
106
107 for ( auto pProject : AllProjects{} )
108 GetProjectFrame( *pProject ).Close();
109 */
110
111 BenchmarkDialog dlog{ parent, project };
112
113 dlog.CentreOnParent();
114
115 dlog.ShowModal();
116 }
117
118 //
119 // BenchmarkDialog
120 //
121
122 enum {
123 RunID = 1000,
124 BSaveID,
125 ClearID,
126 StaticTextID,
127 BlockSizeID,
128 DataSizeID,
129 NumEditsID,
130 RandSeedID
131 };
132
BEGIN_EVENT_TABLE(BenchmarkDialog,wxDialogWrapper)133 BEGIN_EVENT_TABLE(BenchmarkDialog, wxDialogWrapper)
134 EVT_BUTTON( RunID, BenchmarkDialog::OnRun )
135 EVT_BUTTON( BSaveID, BenchmarkDialog::OnSave )
136 EVT_BUTTON( ClearID, BenchmarkDialog::OnClear )
137 EVT_BUTTON( wxID_CANCEL, BenchmarkDialog::OnClose )
138 END_EVENT_TABLE()
139
140 BenchmarkDialog::BenchmarkDialog(
141 wxWindow *parent, AudacityProject &project)
142 :
143 /* i18n-hint: Benchmark means a software speed test */
144 wxDialogWrapper( parent, 0, XO("Benchmark"),
145 wxDefaultPosition, wxDefaultSize,
146 wxDEFAULT_DIALOG_STYLE |
147 wxRESIZE_BORDER)
148 , mProject(project)
149 , mRate{ ProjectRate::Get(project) }
150 {
151 SetName();
152
153 mBlockSizeStr = wxT("64");
154 mNumEditsStr = wxT("100");
155 mDataSizeStr = wxT("32");
156 mRandSeedStr = wxT("234657");
157
158 mBlockDetail = false;
159 mEditDetail = false;
160
161 HoldPrint(false);
162
163 MakeBenchmarkDialog();
164 }
165
166 // WDR: handler implementations for BenchmarkDialog
167
OnClose(wxCommandEvent & WXUNUSED (event))168 void BenchmarkDialog::OnClose(wxCommandEvent & WXUNUSED(event))
169 {
170 EndModal(0);
171 }
172
MakeBenchmarkDialog()173 void BenchmarkDialog::MakeBenchmarkDialog()
174 {
175 ShuttleGui S(this, eIsCreating);
176
177 // Strings don't need to be translated because this class doesn't
178 // ever get used in a stable release.
179
180 S.StartVerticalLay(true);
181 {
182 S.SetBorder(8);
183 S.StartMultiColumn(4);
184 {
185 //
186 S.Id(BlockSizeID)
187 .Validator<wxTextValidator>(wxFILTER_NUMERIC, &mBlockSizeStr)
188 .AddTextBox(XXO("Disk Block Size (KB):"),
189 wxT(""),
190 12);
191
192 //
193 S.Id(NumEditsID)
194 .Validator<wxTextValidator>(wxFILTER_NUMERIC, &mNumEditsStr)
195 .AddTextBox(XXO("Number of Edits:"),
196 wxT(""),
197 12);
198
199 //
200 S.Id(DataSizeID)
201 .Validator<wxTextValidator>(wxFILTER_NUMERIC, &mDataSizeStr)
202 .AddTextBox(XXO("Test Data Size (MB):"),
203 wxT(""),
204 12);
205
206 ///
207 S.Id(RandSeedID)
208 .Validator<wxTextValidator>(wxFILTER_NUMERIC, &mRandSeedStr)
209 /* i18n-hint: A "seed" is a number that initializes a
210 pseudorandom number generating algorithm */
211 .AddTextBox(XXO("Random Seed:"),
212 wxT(""),
213 12);
214
215 }
216 S.EndMultiColumn();
217
218 //
219 S.Validator<wxGenericValidator>(&mBlockDetail)
220 .AddCheckBox(XXO("Show detailed info about each block file"),
221 false);
222
223 //
224 S.Validator<wxGenericValidator>(&mEditDetail)
225 .AddCheckBox(XXO("Show detailed info about each editing operation"),
226 false);
227
228 //
229 mText = S.Id(StaticTextID)
230 /* i18n-hint noun */
231 .Name(XO("Output"))
232 .Style( wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH )
233 .MinSize( { 500, 200 } )
234 .AddTextWindow(wxT(""));
235
236 //
237 S.SetBorder(10);
238 S.StartHorizontalLay(wxALIGN_LEFT | wxEXPAND, false);
239 {
240 S.StartHorizontalLay(wxALIGN_LEFT, false);
241 {
242 S.Id(RunID).AddButton(XXO("Run"), wxALIGN_CENTRE, true);
243 S.Id(BSaveID).AddButton(XXO("Save"));
244 /* i18n-hint verb; to empty or erase */
245 S.Id(ClearID).AddButton(XXO("Clear"));
246 }
247 S.EndHorizontalLay();
248
249 S.StartHorizontalLay(wxALIGN_CENTER, true);
250 {
251 // Spacer
252 }
253 S.EndHorizontalLay();
254
255 S.StartHorizontalLay(wxALIGN_NOT | wxALIGN_LEFT, false);
256 {
257 /* i18n-hint verb */
258 S.Id(wxID_CANCEL).AddButton(XXO("Close"));
259 }
260 S.EndHorizontalLay();
261 }
262 S.EndHorizontalLay();
263 }
264 S.EndVerticalLay();
265
266 Fit();
267 SetSizeHints(GetSize());
268 }
269
OnSave(wxCommandEvent & WXUNUSED (event))270 void BenchmarkDialog::OnSave( wxCommandEvent & WXUNUSED(event))
271 {
272 /* i18n-hint: Benchmark means a software speed test;
273 leave untranslated file extension .txt */
274 auto fName = XO("benchmark.txt").Translation();
275
276 fName = SelectFile(FileNames::Operation::Export,
277 XO("Export Benchmark Data as:"),
278 wxEmptyString,
279 fName,
280 wxT("txt"),
281 { FileNames::TextFiles },
282 wxFD_SAVE | wxRESIZE_BORDER,
283 this);
284
285 if (fName.empty())
286 return;
287
288 mText->SaveFile(fName);
289 }
290
OnClear(wxCommandEvent & WXUNUSED (event))291 void BenchmarkDialog::OnClear(wxCommandEvent & WXUNUSED(event))
292 {
293 mText->Clear();
294 }
295
Printf(const TranslatableString & str)296 void BenchmarkDialog::Printf(const TranslatableString &str)
297 {
298 auto s = str.Translation();
299 mToPrint += s;
300 if (!mHoldPrint)
301 FlushPrint();
302 }
303
HoldPrint(bool hold)304 void BenchmarkDialog::HoldPrint(bool hold)
305 {
306 mHoldPrint = hold;
307
308 if (!mHoldPrint)
309 FlushPrint();
310 }
311
FlushPrint()312 void BenchmarkDialog::FlushPrint()
313 {
314 while(mToPrint.length() > 100) {
315 mText->AppendText(mToPrint.Left(100));
316 mToPrint = mToPrint.Right(mToPrint.length() - 100);
317 }
318 if (mToPrint.length() > 0)
319 mText->AppendText(mToPrint);
320 mToPrint = wxT("");
321 }
322
OnRun(wxCommandEvent & WXUNUSED (event))323 void BenchmarkDialog::OnRun( wxCommandEvent & WXUNUSED(event))
324 {
325 TransferDataFromWindow();
326
327 if (!Validate())
328 return;
329
330 // This code will become part of libaudacity,
331 // and this class will be phased out.
332 long blockSize, numEdits, dataSize, randSeed;
333
334 mBlockSizeStr.ToLong(&blockSize);
335 mNumEditsStr.ToLong(&numEdits);
336 mDataSizeStr.ToLong(&dataSize);
337 mRandSeedStr.ToLong(&randSeed);
338
339 if (blockSize < 1 || blockSize > 1024) {
340 AudacityMessageBox(
341 XO("Block size should be in the range 1 - 1024 KB.") );
342 return;
343 }
344
345 if (numEdits < 1 || numEdits > 10000) {
346 AudacityMessageBox(
347 XO("Number of edits should be in the range 1 - 10000.") );
348 return;
349 }
350
351 if (dataSize < 1 || dataSize > 2000) {
352 AudacityMessageBox(
353 XO("Test data size should be in the range 1 - 2000 MB.") );
354 return;
355 }
356
357 bool editClipCanMove = true;
358 gPrefs->Read(wxT("/GUI/EditClipCanMove"), &editClipCanMove);
359 gPrefs->Write(wxT("/GUI/EditClipCanMove"), false);
360 gPrefs->Flush();
361
362 // Remember the old blocksize, so that we can restore it later.
363 auto oldBlockSize = Sequence::GetMaxDiskBlockSize();
364 Sequence::SetMaxDiskBlockSize(blockSize * 1024);
365
366 const auto cleanup = finally( [&] {
367 Sequence::SetMaxDiskBlockSize(oldBlockSize);
368 gPrefs->Write(wxT("/GUI/EditClipCanMove"), editClipCanMove);
369 gPrefs->Flush();
370 } );
371
372 wxBusyCursor busy;
373
374 HoldPrint(true);
375
376 const auto t =
377 WaveTrackFactory{ mRate,
378 SampleBlockFactory::New( mProject ) }
379 .NewWaveTrack(SampleFormat);
380
381 t->SetRate(1);
382
383 srand(randSeed);
384
385 uint64_t nChunks, chunkSize;
386 //chunkSize = 7500ull + (rand() % 1000ull);
387 chunkSize = 200ull + (rand() % 100ull);
388 nChunks = (dataSize * 1048576ull) / (chunkSize*sizeof(SampleType));
389 while (nChunks < 20 || chunkSize > (blockSize*1024)/4)
390 {
391 chunkSize = std::max( uint64_t(1), (chunkSize / 2) + (rand() % 100) );
392 nChunks = (dataSize * 1048576ull) / (chunkSize*sizeof(SampleType));
393 }
394
395 // The chunks are the pieces we move around in the test.
396 // They are (and are supposed to be) a different size to
397 // the blocks that make the sample blocks. That way we get to
398 // do some testing of when edit chunks cross sample block boundaries.
399 Printf( XO("Using %lld chunks of %lld samples each, for a total of %.1f MB.\n")
400 .Format( nChunks, chunkSize, nChunks*chunkSize*sizeof(SampleType)/1048576.0 ) );
401
402 int trials = numEdits;
403
404 using Samples = ArrayOf<SampleType>;
405 Samples small1{nChunks};
406 Samples block{chunkSize};
407
408 Printf( XO("Preparing...\n") );
409
410 wxTheApp->Yield();
411 FlushPrint();
412
413 int v;
414 int bad;
415 int z;
416 long elapsed;
417 wxString tempStr;
418 wxStopWatch timer;
419
420 for (uint64_t i = 0; i < nChunks; i++) {
421 v = SampleType(rand());
422 small1[i] = v;
423 for (uint64_t b = 0; b < chunkSize; b++)
424 block[b] = v;
425
426 t->Append((samplePtr)block.get(), SampleFormat, chunkSize);
427 }
428 t->Flush();
429
430 // This forces the WaveTrack to flush all of the appends (which is
431 // only necessary if you want to access the Sequence class directly,
432 // as we're about to do).
433 t->GetEndTime();
434
435 if (t->GetClipByIndex(0)->GetSequence()->GetNumSamples() != nChunks * chunkSize) {
436 Printf( XO("Expected len %lld, track len %lld.\n")
437 .Format(
438 nChunks * chunkSize,
439 t->GetClipByIndex(0)->GetSequence()->GetNumSamples()
440 .as_long_long() ) );
441 goto fail;
442 }
443
444 Printf( XO("Performing %d edits...\n").Format( trials ) );
445 wxTheApp->Yield();
446 FlushPrint();
447
448 timer.Start();
449 for (z = 0; z < trials; z++) {
450 // First chunk to cut
451 // 0 <= x0 < nChunks
452 const uint64_t x0 = rand() % nChunks;
453
454 // Number of chunks to cut
455 // 1 <= xlen <= nChunks - x0
456 const uint64_t xlen = 1 + (rand() % (nChunks - x0));
457 if (mEditDetail)
458 Printf( XO("Cut: %lld - %lld \n")
459 .Format( x0 * chunkSize, (x0 + xlen) * chunkSize) );
460
461 Track::Holder tmp;
462 try {
463 tmp = t->Cut(double (x0 * chunkSize), double ((x0 + xlen) * chunkSize));
464 }
465 catch (const AudacityException&) {
466 Printf( XO("Trial %d\n").Format( z ) );
467 Printf( XO("Cut (%lld, %lld) failed.\n")
468 .Format( (x0 * chunkSize), (x0 + xlen) * chunkSize) );
469 Printf( XO("Expected len %lld, track len %lld.\n")
470 .Format(
471 nChunks * chunkSize,
472 t->GetClipByIndex(0)->GetSequence()->GetNumSamples()
473 .as_long_long() ) );
474 goto fail;
475 }
476
477 // Position to paste
478 // 0 <= y0 <= nChunks - xlen
479 const uint64_t y0 = rand() % (nChunks - xlen + 1);
480
481 if (mEditDetail)
482 Printf( XO("Paste: %lld\n").Format( y0 * chunkSize ) );
483
484 try {
485 t->Paste((double)(y0 * chunkSize), tmp.get());
486 }
487 catch (const AudacityException&) {
488 Printf( XO("Trial %d\nFailed on Paste.\n").Format( z ) );
489 goto fail;
490 }
491
492 if (t->GetClipByIndex(0)->GetSequence()->GetNumSamples() != nChunks * chunkSize) {
493 Printf( XO("Trial %d\n").Format( z ) );
494 Printf( XO("Expected len %lld, track len %lld.\n")
495 .Format(
496 nChunks * chunkSize,
497 t->GetClipByIndex(0)->GetSequence()->GetNumSamples()
498 .as_long_long() ) );
499 goto fail;
500 }
501
502 // Permute small1 correspondingly to the cut and paste
503 auto first = &small1[0];
504 if (x0 + xlen < nChunks)
505 std::rotate( first + x0, first + x0 + xlen, first + nChunks );
506 std::rotate( first + y0, first + nChunks - xlen, first + nChunks );
507 }
508
509 elapsed = timer.Time();
510
511 if (mBlockDetail) {
512 auto seq = t->GetClipByIndex(0)->GetSequence();
513 seq->DebugPrintf(seq->GetBlockArray(), seq->GetNumSamples(), &tempStr);
514 mToPrint += tempStr;
515 }
516 Printf( XO("Time to perform %d edits: %ld ms\n").Format( trials, elapsed ) );
517 FlushPrint();
518 wxTheApp->Yield();
519
520
521 #if 0
522 Printf( XO("Checking file pointer leaks:\n") );
523 Printf( XO("Track # blocks: %ld\n").Format( t->GetBlockArray()->size() ) );
524 Printf( XO("Disk # blocks: \n") );
525 system("ls .audacity_temp/* | wc --lines");
526 #endif
527
528 Printf( XO("Doing correctness check...\n") );
529 FlushPrint();
530 wxTheApp->Yield();
531
532 bad = 0;
533 timer.Start();
534 for (uint64_t i = 0; i < nChunks; i++) {
535 v = small1[i];
536 t->Get((samplePtr)block.get(), SampleFormat, i * chunkSize, chunkSize);
537 for (uint64_t b = 0; b < chunkSize; b++)
538 if (block[b] != v) {
539 bad++;
540 if (bad < 10)
541 Printf( XO("Bad: chunk %lld sample %lld\n").Format( i, b ) );
542 b = chunkSize;
543 }
544 }
545 if (bad == 0)
546 Printf( XO("Passed correctness check!\n") );
547 else
548 Printf( XO("Errors in %d/%lld chunks\n").Format( bad, nChunks ) );
549
550 elapsed = timer.Time();
551
552 Printf( XO("Time to check all data: %ld ms\n").Format( elapsed ) );
553 Printf( XO("Reading data again...\n") );
554
555 wxTheApp->Yield();
556 FlushPrint();
557
558 timer.Start();
559
560 for (uint64_t i = 0; i < nChunks; i++) {
561 v = small1[i];
562 t->Get((samplePtr)block.get(), SampleFormat, i * chunkSize, chunkSize);
563 for (uint64_t b = 0; b < chunkSize; b++)
564 if (block[b] != v)
565 bad++;
566 }
567
568 elapsed = timer.Time();
569
570 Printf( XO("Time to check all data (2): %ld ms\n").Format( elapsed ) );
571
572 Printf( XO("At 44100 Hz, %d bytes per sample, the estimated number of\n simultaneous tracks that could be played at once: %.1f\n" )
573 .Format( SAMPLE_SIZE(SampleFormat), (nChunks*chunkSize/44100.0)/(elapsed/1000.0) ) );
574
575 goto success;
576
577 fail:
578 Printf( XO("TEST FAILED!!!\n") );
579
580 success:
581
582 Printf( XO("Benchmark completed successfully.\n") );
583 HoldPrint(false);
584 }
585