2
0
mirror of https://github.com/boostorg/nowide.git synced 2026-02-14 12:52:17 +00:00
Files
nowide/test/benchmark_fstream.cpp
Alexander Grund 3ff3002a22 Improve performance test
Run multiple times and take mean speed
Present results in a table for easier comparison
Add some tricks to avoid optimization reordering stuff that breaks the benchmark
2020-02-06 16:57:09 +01:00

256 lines
7.6 KiB
C++

//
// Copyright (c) 2012 Artyom Beilis (Tonkikh)
// Copyright (c) 2019 Alexander Grund
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
#include "test.hpp"
#include <boost/nowide/convert.hpp>
#include <boost/nowide/cstdio.hpp>
#include <boost/nowide/fstream.hpp>
#define BOOST_CHRONO_HEADER_ONLY
#include <boost/chrono.hpp>
#include <algorithm>
#include <cstdio>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <map>
#include <vector>
#ifdef BOOST_MSVC
#pragma warning(disable : 4996) // function unsafe/deprecated
#endif
namespace nw = boost::nowide;
template<typename FStream>
class io_fstream
{
public:
explicit io_fstream(const char* file, bool read)
{
f_.open(file, read ? std::fstream::in : std::fstream::out | std::fstream::trunc);
TEST(f_);
}
~io_fstream()
{
f_.close();
}
void write(const char* buf, int size)
{
TEST(f_.write(buf, size));
}
void read(char* buf, int size)
{
TEST(f_.read(buf, size));
}
void rewind()
{
f_.seekg(0);
f_.seekp(0);
}
void flush()
{
f_ << std::flush;
}
private:
FStream f_;
};
class io_stdio
{
public:
io_stdio(const char* file, bool read)
{
f_ = std::fopen(file, read ? "r" : "w+");
TEST(f_);
}
~io_stdio()
{
std::fclose(f_);
f_ = 0;
}
void write(const char* buf, int size)
{
TEST(std::fwrite(buf, 1, size, f_) == static_cast<size_t>(size));
}
void read(char* buf, int size)
{
TEST(std::fread(buf, 1, size, f_) == static_cast<size_t>(size));
}
void rewind()
{
std::rewind(f_);
}
void flush()
{
std::fflush(f_);
}
private:
FILE* f_;
};
#if defined(_MSC_VER)
extern "C" void _ReadWriteBarrier(void);
#pragma intrinsic(_ReadWriteBarrier)
#define BOOST_NOWIDE_READ_WRITE_BARRIER() _ReadWriteBarrier()
#elif defined(__GNUC__)
#if(__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) > 40100
#define BOOST_NOWIDE_READ_WRITE_BARRIER() __sync_synchronize()
#else
#define BOOST_NOWIDE_READ_WRITE_BARRIER() __asm__ __volatile__("" : : : "memory")
#endif
#else
#define BOOST_NOWIDE_READ_WRITE_BARRIER() (void)
#endif
struct perf_data
{
// Block-size to read/write performance in MB/s
std::map<size_t, double> read, write;
};
char rand_char()
{
return static_cast<char>(std::rand());
}
std::vector<char> get_rand_data(int size)
{
std::vector<char> data(size);
std::generate(data.begin(), data.end(), rand_char);
return data;
}
static const int MIN_BLOCK_SIZE = 32;
static const int MAX_BLOCK_SIZE = 8192;
template<typename FStream>
perf_data test_io(const char* file)
{
namespace chrono = boost::chrono;
typedef chrono::high_resolution_clock clock;
typedef chrono::duration<double, boost::milli> milliseconds;
perf_data results;
// Use vector to force write to memory and avoid possible reordering
std::vector<clock::time_point> start_and_end(2);
const int data_size = 64 * 1024 * 1024;
for(int block_size = MIN_BLOCK_SIZE / 2; block_size <= MAX_BLOCK_SIZE; block_size *= 2)
{
std::vector<char> buf = get_rand_data(block_size);
FStream tmp(file, false);
tmp.rewind();
start_and_end[0] = clock::now();
BOOST_NOWIDE_READ_WRITE_BARRIER();
for(int size = 0; size < data_size; size += block_size)
{
tmp.write(&buf[0], block_size);
BOOST_NOWIDE_READ_WRITE_BARRIER();
}
tmp.flush();
start_and_end[1] = clock::now();
const milliseconds duration = chrono::duration_cast<milliseconds>(start_and_end[1] - start_and_end[0]);
// heatup
if(block_size >= MIN_BLOCK_SIZE)
{
const double speed = data_size / duration.count() / 1024; // MB/s
results.write[block_size] = speed;
std::cout << " write block size " << std::setw(8) << block_size << " " << std::fixed
<< std::setprecision(3) << speed << " MB/s" << std::endl;
}
}
for(int block_size = MIN_BLOCK_SIZE; block_size <= MAX_BLOCK_SIZE; block_size *= 2)
{
std::vector<char> buf = get_rand_data(block_size);
FStream tmp(file, true);
tmp.rewind();
start_and_end[0] = clock::now();
BOOST_NOWIDE_READ_WRITE_BARRIER();
for(int size = 0; size < data_size; size += block_size)
{
tmp.read(&buf[0], block_size);
BOOST_NOWIDE_READ_WRITE_BARRIER();
}
start_and_end[1] = clock::now();
const milliseconds duration = chrono::duration_cast<milliseconds>(start_and_end[1] - start_and_end[0]);
const double speed = data_size / duration.count() / 1024; // MB/s
results.read[block_size] = speed;
std::cout << " read block size " << std::setw(8) << block_size << " " << std::fixed << std::setprecision(3)
<< speed << " MB/s" << std::endl;
}
std::remove(file);
return results;
}
template<typename FStream>
perf_data test_io_driver(const char* file, const char* type)
{
std::cout << "Testing I/O performance for " << type << std::endl;
const int repeats = 5;
std::vector<perf_data> results(repeats);
for(int i = 0; i < repeats; i++)
results[i] = test_io<FStream>(file);
for(int block_size = MIN_BLOCK_SIZE; block_size <= MAX_BLOCK_SIZE; block_size *= 2)
{
double read_speed = 0, write_speed = 0;
for(int i = 0; i < repeats; i++)
{
read_speed += results[i].read.at(block_size);
write_speed += results[i].write.at(block_size);
}
results[0].read[block_size] = read_speed / repeats;
results[0].write[block_size] = write_speed / repeats;
}
return results[0];
}
void print_perf_data(const std::map<size_t, double>& stdio_data,
const std::map<size_t, double>& std_data,
const std::map<size_t, double>& nowide_data)
{
std::cout << "block size"
<< " stdio "
<< " std::fstream "
<< "nowide::fstream" << std::endl;
for(int block_size = MIN_BLOCK_SIZE; block_size <= MAX_BLOCK_SIZE; block_size *= 2)
{
std::cout << std::setw(8) << block_size << " ";
std::cout << std::fixed << std::setprecision(3) << std::setw(8) << stdio_data.at(block_size) << " MB/s ";
std::cout << std::fixed << std::setprecision(3) << std::setw(8) << std_data.at(block_size) << " MB/s ";
std::cout << std::fixed << std::setprecision(3) << std::setw(8) << nowide_data.at(block_size) << " MB/s ";
std::cout << std::endl;
}
}
void test_perf(const char* file)
{
perf_data stdio_data = test_io_driver<io_stdio>(file, "stdio");
perf_data std_data = test_io_driver<io_fstream<std::fstream> >(file, "std::fstream");
perf_data nowide_data = test_io_driver<io_fstream<nw::fstream> >(file, "nowide::fstream");
std::cout << "================== Read performance ==================" << std::endl;
print_perf_data(stdio_data.read, std_data.read, nowide_data.read);
std::cout << "================== Write performance =================" << std::endl;
print_perf_data(stdio_data.write, std_data.write, nowide_data.write);
}
int main(int argc, char** argv)
{
std::string filename = "perf_test_file.dat";
if(argc == 2)
{
filename = argv[1];
} else if(argc != 1)
{
std::cerr << "Usage: " << argv[0] << " [test_filepath]" << std::endl;
return 1;
}
test_perf(filename.c_str());
return 0;
}