430 lines
11 KiB
C++
430 lines
11 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2017-2019 KiCad Developers, see AUTHORS.txt for contributors.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, you may find one here:
|
|
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
|
* or you may search the http://www.gnu.org website for the version 2 license,
|
|
* or you may write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*/
|
|
|
|
#include "io_benchmark.h"
|
|
|
|
#include <wx/wx.h>
|
|
#include <richio.h>
|
|
|
|
#include <chrono>
|
|
#include <ios>
|
|
#include <functional>
|
|
#include <iostream>
|
|
|
|
#include <fstream>
|
|
|
|
#include <wx/wfstream.h>
|
|
#include <wx/filename.h>
|
|
|
|
#include <qa_utils/stdstream_line_reader.h>
|
|
|
|
|
|
using CLOCK = std::chrono::steady_clock;
|
|
using TIME_PT = std::chrono::time_point<CLOCK>;
|
|
|
|
|
|
struct BENCH_REPORT
|
|
{
|
|
unsigned linesRead;
|
|
|
|
/**
|
|
* Char accumulator, used to prevent compilers optimising away
|
|
* otherwise unused line buffers, and also as a primitive sanity
|
|
* check that the same lines were read
|
|
*/
|
|
unsigned charAcc;
|
|
|
|
std::chrono::milliseconds benchDurMs;
|
|
};
|
|
|
|
|
|
using BENCH_FUNC = std::function<void(const wxFileName&, int, BENCH_REPORT&)>;
|
|
|
|
|
|
struct BENCHMARK
|
|
{
|
|
char triggerChar;
|
|
BENCH_FUNC func;
|
|
wxString name;
|
|
};
|
|
|
|
|
|
/**
|
|
* Benchmark using a raw std::ifstream, with no LINE_READER
|
|
* wrapper. The stream is recreated for each cycle.
|
|
*/
|
|
static void bench_fstream( const wxFileName& aFile, int aReps, BENCH_REPORT& report )
|
|
{
|
|
std::string line;
|
|
|
|
for( int i = 0; i < aReps; ++i)
|
|
{
|
|
std::ifstream fstr( aFile.GetFullPath().ToUTF8() );
|
|
|
|
while( getline( fstr, line ) )
|
|
{
|
|
report.linesRead++;
|
|
report.charAcc += (unsigned char) line[0];
|
|
}
|
|
|
|
fstr.close();
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Benchmark using a raw std::ifstream, with no LINE_READER
|
|
* wrapper. The stream is not recreated for each cycle, just reset.
|
|
*/
|
|
static void bench_fstream_reuse( const wxFileName& aFile, int aReps, BENCH_REPORT& report )
|
|
{
|
|
std::string line;
|
|
std::ifstream fstr( aFile.GetFullPath().ToUTF8() );
|
|
|
|
for( int i = 0; i < aReps; ++i)
|
|
{
|
|
while( getline( fstr, line ) )
|
|
{
|
|
report.linesRead++;
|
|
report.charAcc += (unsigned char) line[0];
|
|
}
|
|
fstr.clear() ;
|
|
fstr.seekg(0, std::ios::beg) ;
|
|
}
|
|
|
|
fstr.close();
|
|
}
|
|
|
|
|
|
/**
|
|
* Benchmark using a given LINE_READER implementation.
|
|
* The LINE_READER is recreated for each cycle.
|
|
*/
|
|
template<typename LR>
|
|
static void bench_line_reader( const wxFileName& aFile, int aReps, BENCH_REPORT& report )
|
|
{
|
|
for( int i = 0; i < aReps; ++i)
|
|
{
|
|
LR fstr( aFile.GetFullPath() );
|
|
while( fstr.ReadLine() )
|
|
{
|
|
report.linesRead++;
|
|
report.charAcc += (unsigned char) fstr.Line()[0];
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Benchmark using a given LINE_READER implementation.
|
|
* The LINE_READER is rewound for each cycle, not recreated.
|
|
*/
|
|
template<typename LR>
|
|
static void bench_line_reader_reuse( const wxFileName& aFile, int aReps, BENCH_REPORT& report )
|
|
{
|
|
LR fstr( aFile.GetFullPath() );
|
|
for( int i = 0; i < aReps; ++i)
|
|
{
|
|
|
|
while( fstr.ReadLine() )
|
|
{
|
|
report.linesRead++;
|
|
report.charAcc += (unsigned char) fstr.Line()[0];
|
|
}
|
|
|
|
fstr.Rewind();
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Benchmark using STRING_LINE_READER on string data read into memory from a file
|
|
* using std::ifstream, but read the data fresh from the file each time
|
|
*/
|
|
static void bench_string_lr( const wxFileName& aFile, int aReps, BENCH_REPORT& report )
|
|
{
|
|
for( int i = 0; i < aReps; ++i)
|
|
{
|
|
std::ifstream ifs( aFile.GetFullPath().ToStdString() );
|
|
std::string content((std::istreambuf_iterator<char>(ifs)),
|
|
std::istreambuf_iterator<char>());
|
|
|
|
STRING_LINE_READER fstr( content, aFile.GetFullPath() );
|
|
while( fstr.ReadLine() )
|
|
{
|
|
report.linesRead++;
|
|
report.charAcc += (unsigned char) fstr.Line()[0];
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Benchmark using STRING_LINE_READER on string data read into memory from a file
|
|
* using std::ifstream
|
|
*
|
|
* The STRING_LINE_READER is not reused (it cannot be rewound),
|
|
* but the file is read only once
|
|
*/
|
|
static void bench_string_lr_reuse( const wxFileName& aFile, int aReps, BENCH_REPORT& report )
|
|
{
|
|
std::ifstream ifs( aFile.GetFullPath().ToStdString() );
|
|
std::string content((std::istreambuf_iterator<char>(ifs)),
|
|
std::istreambuf_iterator<char>());
|
|
|
|
for( int i = 0; i < aReps; ++i)
|
|
{
|
|
STRING_LINE_READER fstr( content, aFile.GetFullPath() );
|
|
while( fstr.ReadLine() )
|
|
{
|
|
report.linesRead++;
|
|
report.charAcc += (unsigned char) fstr.Line()[0];
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Benchmark using an INPUTSTREAM_LINE_READER with a given
|
|
* wxInputStream implementation.
|
|
* The wxInputStream is recreated for each cycle.
|
|
*/
|
|
template<typename S>
|
|
static void bench_wxis( const wxFileName& aFile, int aReps, BENCH_REPORT& report )
|
|
{
|
|
S fileStream( aFile.GetFullPath() );
|
|
|
|
for( int i = 0; i < aReps; ++i)
|
|
{
|
|
INPUTSTREAM_LINE_READER istr( &fileStream, aFile.GetFullPath() );
|
|
|
|
while( istr.ReadLine() )
|
|
{
|
|
report.linesRead++;
|
|
report.charAcc += (unsigned char) istr.Line()[0];
|
|
}
|
|
|
|
fileStream.SeekI( 0 );
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Benchmark using an INPUTSTREAM_LINE_READER with a given
|
|
* wxInputStream implementation.
|
|
* The wxInputStream is reset for each cycle.
|
|
*/
|
|
template<typename S>
|
|
static void bench_wxis_reuse( const wxFileName& aFile, int aReps, BENCH_REPORT& report )
|
|
{
|
|
S fileStream( aFile.GetFullPath() );
|
|
INPUTSTREAM_LINE_READER istr( &fileStream, aFile.GetFullPath() );
|
|
|
|
for( int i = 0; i < aReps; ++i)
|
|
{
|
|
while( istr.ReadLine() )
|
|
{
|
|
report.linesRead++;
|
|
report.charAcc += (unsigned char) istr.Line()[0];
|
|
}
|
|
|
|
fileStream.SeekI( 0 );
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Benchmark using a INPUTSTREAM_LINE_READER with a given
|
|
* wxInputStream implementation, buffered with wxBufferedInputStream.
|
|
* The wxInputStream is recreated for each cycle.
|
|
*/
|
|
template<typename WXIS>
|
|
static void bench_wxbis( const wxFileName& aFile, int aReps, BENCH_REPORT& report )
|
|
{
|
|
WXIS fileStream( aFile.GetFullPath() );
|
|
wxBufferedInputStream bufferedStream( fileStream );
|
|
|
|
for( int i = 0; i < aReps; ++i)
|
|
{
|
|
INPUTSTREAM_LINE_READER istr( &bufferedStream, aFile.GetFullPath() );
|
|
|
|
while( istr.ReadLine() )
|
|
{
|
|
report.linesRead++;
|
|
report.charAcc += (unsigned char) istr.Line()[0];
|
|
}
|
|
|
|
fileStream.SeekI( 0 );
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Benchmark using a INPUTSTREAM_LINE_READER with a given
|
|
* wxInputStream implementation, buffered with wxBufferedInputStream.
|
|
* The wxInputStream is reset for each cycle.
|
|
*/
|
|
template<typename WXIS>
|
|
static void bench_wxbis_reuse( const wxFileName& aFile, int aReps, BENCH_REPORT& report )
|
|
{
|
|
WXIS fileStream( aFile.GetFullPath() );
|
|
wxBufferedInputStream bufferedStream( fileStream );
|
|
|
|
INPUTSTREAM_LINE_READER istr( &bufferedStream, aFile.GetFullPath() );
|
|
|
|
for( int i = 0; i < aReps; ++i)
|
|
{
|
|
while( istr.ReadLine() )
|
|
{
|
|
report.linesRead++;
|
|
report.charAcc += (unsigned char) istr.Line()[0];
|
|
}
|
|
|
|
fileStream.SeekI( 0 );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* List of available benchmarks
|
|
*/
|
|
static std::vector<BENCHMARK> benchmarkList =
|
|
{
|
|
{ 'f', bench_fstream, "std::fstream" },
|
|
{ 'F', bench_fstream_reuse, "std::fstream, reused" },
|
|
{ 'r', bench_line_reader<FILE_LINE_READER>, "RichIO FILE_L_R" },
|
|
{ 'R', bench_line_reader_reuse<FILE_LINE_READER>, "RichIO FILE_L_R, reused" },
|
|
{ 'n', bench_line_reader<IFSTREAM_LINE_READER>, "std::ifstream L_R" },
|
|
{ 'N', bench_line_reader_reuse<IFSTREAM_LINE_READER>, "std::ifstream L_R, reused" },
|
|
{ 's', bench_string_lr, "RichIO STRING_L_R"},
|
|
{ 'S', bench_string_lr_reuse, "RichIO STRING_L_R, reused"},
|
|
{ 'w', bench_wxis<wxFileInputStream>, "wxFileIStream" },
|
|
{ 'W', bench_wxis<wxFileInputStream>, "wxFileIStream, reused" },
|
|
{ 'g', bench_wxis<wxFFileInputStream>, "wxFFileIStream" },
|
|
{ 'G', bench_wxis_reuse<wxFFileInputStream>, "wxFFileIStream, reused" },
|
|
{ 'b', bench_wxbis<wxFileInputStream>, "wxFileIStream. buf'd" },
|
|
{ 'B', bench_wxbis_reuse<wxFileInputStream>, "wxFileIStream, buf'd, reused" },
|
|
{ 'c', bench_wxbis<wxFFileInputStream>, "wxFFileIStream. buf'd" },
|
|
{ 'C', bench_wxbis_reuse<wxFFileInputStream>, "wxFFileIStream, buf'd, reused" },
|
|
};
|
|
|
|
|
|
/**
|
|
* Construct string of all flags used for specifying benchmarks
|
|
* on the command line
|
|
*/
|
|
static wxString getBenchFlags()
|
|
{
|
|
wxString flags;
|
|
|
|
for( auto& bmark : benchmarkList )
|
|
{
|
|
flags << bmark.triggerChar;
|
|
}
|
|
|
|
return flags;
|
|
}
|
|
|
|
|
|
/**
|
|
* Usage description of a benchmakr spec
|
|
*/
|
|
static wxString getBenchDescriptions()
|
|
{
|
|
wxString desc;
|
|
|
|
for( auto& bmark : benchmarkList )
|
|
{
|
|
desc << " " << bmark.triggerChar << ": " << bmark.name << "\n";
|
|
}
|
|
|
|
return desc;
|
|
}
|
|
|
|
|
|
BENCH_REPORT executeBenchMark( const BENCHMARK& aBenchmark, int aReps,
|
|
const wxFileName& aFilename )
|
|
{
|
|
BENCH_REPORT report = {};
|
|
|
|
TIME_PT start = CLOCK::now();
|
|
aBenchmark.func( aFilename, aReps, report );
|
|
TIME_PT end = CLOCK::now();
|
|
|
|
using std::chrono::milliseconds;
|
|
using std::chrono::duration_cast;
|
|
|
|
report.benchDurMs = duration_cast<milliseconds>( end - start );
|
|
|
|
return report;
|
|
}
|
|
|
|
|
|
int io_benchmark_func( int argc, char* argv[] )
|
|
{
|
|
auto& os = std::cout;
|
|
|
|
if (argc < 3)
|
|
{
|
|
os << "Usage: " << argv[0] << " <FILE> <REPS> [" << getBenchFlags() << "]\n\n";
|
|
os << "Benchmarks:\n";
|
|
os << getBenchDescriptions();
|
|
return KI_TEST::RET_CODES::BAD_CMDLINE;
|
|
}
|
|
|
|
wxFileName inFile( argv[1] );
|
|
|
|
long reps = 0;
|
|
wxString( argv[2] ).ToLong( &reps );
|
|
|
|
// get the benchmark to do, or all of them if nothing given
|
|
wxString bench;
|
|
if ( argc == 4 )
|
|
bench = argv[3];
|
|
|
|
os << "IO Bench Mark Util" << std::endl;
|
|
|
|
os << " Benchmark file: " << inFile.GetFullPath() << std::endl;
|
|
os << " Repetitions: " << (int) reps << std::endl;
|
|
os << std::endl;
|
|
|
|
for( auto& bmark : benchmarkList )
|
|
{
|
|
if( bench.size() && !bench.Contains( bmark.triggerChar ) )
|
|
continue;
|
|
|
|
BENCH_REPORT report = executeBenchMark( bmark, reps, inFile );
|
|
|
|
os << wxString::Format( "%-30s %u lines, acc: %u in %u ms",
|
|
bmark.name, report.linesRead, report.charAcc, (int) report.benchDurMs.count() )
|
|
<< std::endl;;
|
|
}
|
|
|
|
return KI_TEST::RET_CODES::OK;
|
|
}
|
|
|
|
|
|
KI_TEST::UTILITY_PROGRAM io_benchmark_tool = {
|
|
"io_benchmark",
|
|
"Benchmark various kinds of IO methods",
|
|
io_benchmark_func,
|
|
}; |