]>
Commit | Line | Data |
---|---|---|
f67539c2 TL |
1 | // |
2 | // Copyright (c) 2012 Artyom Beilis (Tonkikh) | |
20effc67 | 3 | // Copyright (c) 2019 - 2020 Alexander Grund |
f67539c2 TL |
4 | // |
5 | // Distributed under the Boost Software License, Version 1.0. (See | |
20effc67 | 6 | // accompanying file LICENSE or copy at |
f67539c2 TL |
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> | |
f67539c2 | 15 | #include <algorithm> |
20effc67 | 16 | #include <chrono> |
f67539c2 TL |
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> | |
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: | |
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] | |
47 | ~io_fstream() | |
48 | { | |
49 | f_.close(); | |
50 | } | |
51 | void write(const char* buf, int size) | |
52 | { | |
53 | TEST(f_.write(buf, size)); | |
54 | } | |
55 | void read(char* buf, int size) | |
56 | { | |
57 | TEST(f_.read(buf, size)); | |
58 | } | |
59 | void rewind() | |
60 | { | |
61 | f_.seekg(0); | |
62 | f_.seekp(0); | |
63 | } | |
64 | void flush() | |
65 | { | |
66 | f_ << std::flush; | |
67 | } | |
68 | ||
69 | private: | |
70 | FStream f_; | |
71 | }; | |
72 | ||
73 | class io_stdio | |
74 | { | |
75 | public: | |
76 | io_stdio(const char* file, bool read) | |
77 | { | |
78 | f_ = nw::fopen(file, read ? "r" : "w+"); | |
79 | TEST(f_); | |
80 | } | |
81 | ~io_stdio() | |
82 | { | |
83 | std::fclose(f_); | |
84 | f_ = 0; | |
85 | } | |
86 | void write(const char* buf, int size) | |
87 | { | |
88 | TEST(std::fwrite(buf, 1, size, f_) == static_cast<size_t>(size)); | |
89 | } | |
90 | void read(char* buf, int size) | |
91 | { | |
92 | TEST(std::fread(buf, 1, size, f_) == static_cast<size_t>(size)); | |
93 | } | |
94 | void rewind() | |
95 | { | |
96 | std::rewind(f_); | |
97 | } | |
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 | ||
127 | char rand_char() | |
128 | { | |
129 | // coverity[dont_call] | |
130 | return static_cast<char>(std::rand()); | |
131 | } | |
132 | ||
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> | |
144 | perf_data test_io(const char* file) | |
145 | { | |
20effc67 TL |
146 | namespace chrono = std::chrono; |
147 | using clock = chrono::high_resolution_clock; | |
148 | using milliseconds = chrono::duration<double, std::milli>; | |
f67539c2 TL |
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> | |
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 | ||
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 | ||
241 | void test_perf(const char* file) | |
242 | { | |
243 | perf_data stdio_data = test_io_driver<io_stdio>(file, "stdio"); | |
20effc67 TL |
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"); | |
f67539c2 TL |
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 | ||
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 | } |