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