• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //
2 //  Copyright (c) 2012 Artyom Beilis (Tonkikh)
3 //  Copyright (c) 2019 - 2020 Alexander Grund
4 //
5 //  Distributed under the Boost Software License, Version 1.0. (See
6 //  accompanying file LICENSE or copy at
7 //  http://www.boost.org/LICENSE_1_0.txt)
8 //
9 
10 #define BOOST_NOWIDE_TEST_NO_MAIN
11 
12 #include <boost/nowide/convert.hpp>
13 #include <boost/nowide/cstdio.hpp>
14 #include <boost/nowide/fstream.hpp>
15 #include <algorithm>
16 #include <chrono>
17 #include <cstdio>
18 #include <fstream>
19 #include <iomanip>
20 #include <iostream>
21 #include <map>
22 #include <stdexcept>
23 #include <vector>
24 
25 #include "test.hpp"
26 
27 template<typename Key, typename Value, typename Key2>
get(const std::map<Key,Value> & map,const Key2 & key)28 Value get(const std::map<Key, Value>& map, const Key2& key)
29 {
30     typename std::map<Key, Value>::const_iterator it = map.find(key);
31     if(it == map.end())
32         throw std::runtime_error("Key not found");
33     return it->second;
34 }
35 
36 namespace nw = boost::nowide;
37 template<typename FStream>
38 class io_fstream
39 {
40 public:
io_fstream(const char * file,bool read)41     explicit io_fstream(const char* file, bool read)
42     {
43         f_.open(file, read ? std::fstream::in : std::fstream::out | std::fstream::trunc);
44         TEST(f_);
45     }
46     // coverity[exn_spec_violation]
~io_fstream()47     ~io_fstream()
48     {
49         f_.close();
50     }
write(const char * buf,int size)51     void write(const char* buf, int size)
52     {
53         TEST(f_.write(buf, size));
54     }
read(char * buf,int size)55     void read(char* buf, int size)
56     {
57         TEST(f_.read(buf, size));
58     }
rewind()59     void rewind()
60     {
61         f_.seekg(0);
62         f_.seekp(0);
63     }
flush()64     void flush()
65     {
66         f_ << std::flush;
67     }
68 
69 private:
70     FStream f_;
71 };
72 
73 class io_stdio
74 {
75 public:
io_stdio(const char * file,bool read)76     io_stdio(const char* file, bool read)
77     {
78         f_ = nw::fopen(file, read ? "r" : "w+");
79         TEST(f_);
80     }
~io_stdio()81     ~io_stdio()
82     {
83         std::fclose(f_);
84         f_ = 0;
85     }
write(const char * buf,int size)86     void write(const char* buf, int size)
87     {
88         TEST(std::fwrite(buf, 1, size, f_) == static_cast<size_t>(size));
89     }
read(char * buf,int size)90     void read(char* buf, int size)
91     {
92         TEST(std::fread(buf, 1, size, f_) == static_cast<size_t>(size));
93     }
rewind()94     void rewind()
95     {
96         std::rewind(f_);
97     }
flush()98     void flush()
99     {
100         std::fflush(f_);
101     }
102 
103 private:
104     FILE* f_;
105 };
106 
107 #if defined(_MSC_VER)
108 extern "C" void _ReadWriteBarrier(void);
109 #pragma intrinsic(_ReadWriteBarrier)
110 #define BOOST_NOWIDE_READ_WRITE_BARRIER() _ReadWriteBarrier()
111 #elif defined(__GNUC__)
112 #if(__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) > 40100
113 #define BOOST_NOWIDE_READ_WRITE_BARRIER() __sync_synchronize()
114 #else
115 #define BOOST_NOWIDE_READ_WRITE_BARRIER() __asm__ __volatile__("" : : : "memory")
116 #endif
117 #else
118 #define BOOST_NOWIDE_READ_WRITE_BARRIER() (void)
119 #endif
120 
121 struct perf_data
122 {
123     // Block-size to read/write performance in MB/s
124     std::map<size_t, double> read, write;
125 };
126 
rand_char()127 char rand_char()
128 {
129     // coverity[dont_call]
130     return static_cast<char>(std::rand());
131 }
132 
get_rand_data(int size)133 std::vector<char> get_rand_data(int size)
134 {
135     std::vector<char> data(size);
136     std::generate(data.begin(), data.end(), rand_char);
137     return data;
138 }
139 
140 static const int MIN_BLOCK_SIZE = 32;
141 static const int MAX_BLOCK_SIZE = 8192;
142 
143 template<typename FStream>
test_io(const char * file)144 perf_data test_io(const char* file)
145 {
146     namespace chrono = std::chrono;
147     typedef chrono::high_resolution_clock clock;
148     typedef chrono::duration<double, std::milli> milliseconds;
149     perf_data results;
150     // Use vector to force write to memory and avoid possible reordering
151     std::vector<clock::time_point> start_and_end(2);
152     const int data_size = 64 * 1024 * 1024;
153     for(int block_size = MIN_BLOCK_SIZE / 2; block_size <= MAX_BLOCK_SIZE; block_size *= 2)
154     {
155         std::vector<char> buf = get_rand_data(block_size);
156         FStream tmp(file, false);
157         tmp.rewind();
158         start_and_end[0] = clock::now();
159         BOOST_NOWIDE_READ_WRITE_BARRIER();
160         for(int size = 0; size < data_size; size += block_size)
161         {
162             tmp.write(&buf[0], block_size);
163             BOOST_NOWIDE_READ_WRITE_BARRIER();
164         }
165         tmp.flush();
166         start_and_end[1] = clock::now();
167         // heatup
168         if(block_size >= MIN_BLOCK_SIZE)
169         {
170             const milliseconds duration = chrono::duration_cast<milliseconds>(start_and_end[1] - start_and_end[0]);
171             const double speed = data_size / duration.count() / 1024; // MB/s
172             results.write[block_size] = speed;
173             std::cout << "  write block size " << std::setw(8) << block_size << " " << std::fixed
174                       << std::setprecision(3) << speed << " MB/s" << std::endl;
175         }
176     }
177     for(int block_size = MIN_BLOCK_SIZE; block_size <= MAX_BLOCK_SIZE; block_size *= 2)
178     {
179         std::vector<char> buf = get_rand_data(block_size);
180         FStream tmp(file, true);
181         tmp.rewind();
182         start_and_end[0] = clock::now();
183         BOOST_NOWIDE_READ_WRITE_BARRIER();
184         for(int size = 0; size < data_size; size += block_size)
185         {
186             tmp.read(&buf[0], block_size);
187             BOOST_NOWIDE_READ_WRITE_BARRIER();
188         }
189         start_and_end[1] = clock::now();
190         const milliseconds duration = chrono::duration_cast<milliseconds>(start_and_end[1] - start_and_end[0]);
191         const double speed = data_size / duration.count() / 1024; // MB/s
192         results.read[block_size] = speed;
193         std::cout << "  read block size " << std::setw(8) << block_size << " " << std::fixed << std::setprecision(3)
194                   << speed << " MB/s" << std::endl;
195     }
196     TEST(std::remove(file) == 0);
197     return results;
198 }
199 
200 template<typename FStream>
test_io_driver(const char * file,const char * type)201 perf_data test_io_driver(const char* file, const char* type)
202 {
203     std::cout << "Testing I/O performance for " << type << std::endl;
204     const int repeats = 5;
205     std::vector<perf_data> results(repeats);
206 
207     for(int i = 0; i < repeats; i++)
208         results[i] = test_io<FStream>(file);
209     for(int block_size = MIN_BLOCK_SIZE; block_size <= MAX_BLOCK_SIZE; block_size *= 2)
210     {
211         double read_speed = 0, write_speed = 0;
212         for(int i = 0; i < repeats; i++)
213         {
214             read_speed += get(results[i].read, block_size);
215             write_speed += get(results[i].write, block_size);
216         }
217         results[0].read[block_size] = read_speed / repeats;
218         results[0].write[block_size] = write_speed / repeats;
219     }
220     return results[0];
221 }
222 
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)223 void print_perf_data(const std::map<size_t, double>& stdio_data,
224                      const std::map<size_t, double>& std_data,
225                      const std::map<size_t, double>& nowide_data)
226 {
227     std::cout << "block size"
228               << "     stdio    "
229               << " std::fstream "
230               << "nowide::fstream" << std::endl;
231     for(int block_size = MIN_BLOCK_SIZE; block_size <= MAX_BLOCK_SIZE; block_size *= 2)
232     {
233         std::cout << std::setw(8) << block_size << "  ";
234         std::cout << std::fixed << std::setprecision(3) << std::setw(8) << get(stdio_data, block_size) << " MB/s ";
235         std::cout << std::fixed << std::setprecision(3) << std::setw(8) << get(std_data, block_size) << " MB/s ";
236         std::cout << std::fixed << std::setprecision(3) << std::setw(8) << get(nowide_data, block_size) << " MB/s ";
237         std::cout << std::endl;
238     }
239 }
240 
test_perf(const char * file)241 void test_perf(const char* file)
242 {
243     perf_data stdio_data = test_io_driver<io_stdio>(file, "stdio");
244     perf_data std_data = test_io_driver<io_fstream<std::fstream> >(file, "std::fstream");
245     perf_data nowide_data = test_io_driver<io_fstream<nw::fstream> >(file, "nowide::fstream");
246     std::cout << "================== Read performance ==================" << std::endl;
247     print_perf_data(stdio_data.read, std_data.read, nowide_data.read);
248     std::cout << "================== Write performance =================" << std::endl;
249     print_perf_data(stdio_data.write, std_data.write, nowide_data.write);
250 }
251 
main(int argc,char ** argv)252 int main(int argc, char** argv)
253 {
254     std::string filename = "perf_test_file.dat";
255     if(argc == 2)
256     {
257         filename = argv[1];
258     } else if(argc != 1)
259     {
260         std::cerr << "Usage: " << argv[0] << " [test_filepath]" << std::endl;
261         return 1;
262     }
263     try
264     {
265         test_perf(filename.c_str());
266     } catch(const std::runtime_error& err)
267     {
268         std::cerr << "Benchmarking failed: " << err.what() << std::endl;
269         return 1;
270     }
271     return 0;
272 }
273