1 /*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <err.h>
18 #include <inttypes.h>
19 #include <stdio.h>
20 #include <stdio_ext.h>
21 #include <stdlib.h>
22
23 #include <android-base/file.h>
24 #include <benchmark/benchmark.h>
25 #include "util.h"
26
FillFile(TemporaryFile & tf)27 static void FillFile(TemporaryFile& tf) {
28 char line[256];
29 memset(line, 'x', sizeof(line));
30 line[sizeof(line) - 1] = '\0';
31
32 FILE* fp = fopen(tf.path, "we");
33 for (size_t i = 0; i < 4096; ++i) fputs(line, fp);
34 fclose(fp);
35 }
36
37 template <typename Fn>
ReadWriteTest(benchmark::State & state,Fn f,bool buffered)38 void ReadWriteTest(benchmark::State& state, Fn f, bool buffered) {
39 size_t chunk_size = state.range(0);
40
41 FILE* fp = fopen("/dev/zero", "r+e");
42 __fsetlocking(fp, FSETLOCKING_BYCALLER);
43 char* buf = new char[chunk_size];
44
45 if (!buffered) {
46 setvbuf(fp, nullptr, _IONBF, 0);
47 }
48
49 while (state.KeepRunning()) {
50 if (f(buf, chunk_size, 1, fp) != 1) {
51 errx(1, "ERROR: op of %zu bytes failed.", chunk_size);
52 }
53 }
54
55 state.SetBytesProcessed(int64_t(state.iterations()) * int64_t(chunk_size));
56 delete[] buf;
57 fclose(fp);
58 }
59
BM_stdio_fread(benchmark::State & state)60 void BM_stdio_fread(benchmark::State& state) {
61 ReadWriteTest(state, fread, true);
62 }
63 BIONIC_BENCHMARK_WITH_ARG(BM_stdio_fread, "AT_COMMON_SIZES");
64
BM_stdio_fwrite(benchmark::State & state)65 void BM_stdio_fwrite(benchmark::State& state) {
66 ReadWriteTest(state, fwrite, true);
67 }
68 BIONIC_BENCHMARK_WITH_ARG(BM_stdio_fwrite, "AT_COMMON_SIZES");
69
BM_stdio_fread_unbuffered(benchmark::State & state)70 void BM_stdio_fread_unbuffered(benchmark::State& state) {
71 ReadWriteTest(state, fread, false);
72 }
73 BIONIC_BENCHMARK_WITH_ARG(BM_stdio_fread_unbuffered, "AT_COMMON_SIZES");
74
BM_stdio_fwrite_unbuffered(benchmark::State & state)75 void BM_stdio_fwrite_unbuffered(benchmark::State& state) {
76 ReadWriteTest(state, fwrite, false);
77 }
78 BIONIC_BENCHMARK_WITH_ARG(BM_stdio_fwrite_unbuffered, "AT_COMMON_SIZES");
79
80 #if !defined(__GLIBC__)
FopenFgetlnFclose(benchmark::State & state,bool no_locking)81 static void FopenFgetlnFclose(benchmark::State& state, bool no_locking) {
82 TemporaryFile tf;
83 FillFile(tf);
84 while (state.KeepRunning()) {
85 FILE* fp = fopen(tf.path, "re");
86 if (no_locking) __fsetlocking(fp, FSETLOCKING_BYCALLER);
87 size_t length;
88 while (fgetln(fp, &length) != nullptr) {
89 }
90 fclose(fp);
91 }
92 }
93
BM_stdio_fopen_fgetln_fclose_locking(benchmark::State & state)94 static void BM_stdio_fopen_fgetln_fclose_locking(benchmark::State& state) {
95 FopenFgetlnFclose(state, false);
96 }
97 BIONIC_BENCHMARK(BM_stdio_fopen_fgetln_fclose_locking);
98
BM_stdio_fopen_fgetln_fclose_no_locking(benchmark::State & state)99 void BM_stdio_fopen_fgetln_fclose_no_locking(benchmark::State& state) {
100 FopenFgetlnFclose(state, true);
101 }
102 BIONIC_BENCHMARK(BM_stdio_fopen_fgetln_fclose_no_locking);
103 #endif
104
FopenFgetsFclose(benchmark::State & state,bool no_locking)105 static void FopenFgetsFclose(benchmark::State& state, bool no_locking) {
106 TemporaryFile tf;
107 FillFile(tf);
108 char buf[BUFSIZ];
109 while (state.KeepRunning()) {
110 FILE* fp = fopen(tf.path, "re");
111 if (no_locking) __fsetlocking(fp, FSETLOCKING_BYCALLER);
112 while (fgets(buf, sizeof(buf), fp) != nullptr) {
113 }
114 fclose(fp);
115 }
116 }
117
BM_stdio_fopen_fgets_fclose_locking(benchmark::State & state)118 static void BM_stdio_fopen_fgets_fclose_locking(benchmark::State& state) {
119 FopenFgetsFclose(state, false);
120 }
121 BIONIC_BENCHMARK(BM_stdio_fopen_fgets_fclose_locking);
122
BM_stdio_fopen_fgets_fclose_no_locking(benchmark::State & state)123 void BM_stdio_fopen_fgets_fclose_no_locking(benchmark::State& state) {
124 FopenFgetsFclose(state, true);
125 }
126 BIONIC_BENCHMARK(BM_stdio_fopen_fgets_fclose_no_locking);
127
FopenGetlineFclose(benchmark::State & state,bool no_locking)128 static void FopenGetlineFclose(benchmark::State& state, bool no_locking) {
129 TemporaryFile tf;
130 FillFile(tf);
131 while (state.KeepRunning()) {
132 FILE* fp = fopen(tf.path, "re");
133 if (no_locking) __fsetlocking(fp, FSETLOCKING_BYCALLER);
134 char* line = nullptr;
135 size_t n = 0;
136 while (getline(&line, &n, fp) != -1) {
137 }
138 free(line);
139 fclose(fp);
140 }
141 }
142
BM_stdio_fopen_getline_fclose_locking(benchmark::State & state)143 static void BM_stdio_fopen_getline_fclose_locking(benchmark::State& state) {
144 FopenGetlineFclose(state, false);
145 }
146 BIONIC_BENCHMARK(BM_stdio_fopen_getline_fclose_locking);
147
BM_stdio_fopen_getline_fclose_no_locking(benchmark::State & state)148 void BM_stdio_fopen_getline_fclose_no_locking(benchmark::State& state) {
149 FopenGetlineFclose(state, true);
150 }
151 BIONIC_BENCHMARK(BM_stdio_fopen_getline_fclose_no_locking);
152
FopenFgetcFclose(benchmark::State & state,bool no_locking)153 static void FopenFgetcFclose(benchmark::State& state, bool no_locking) {
154 size_t nbytes = state.range(0);
155 while (state.KeepRunning()) {
156 FILE* fp = fopen("/dev/zero", "re");
157 if (no_locking) __fsetlocking(fp, FSETLOCKING_BYCALLER);
158 for (size_t i = 0; i < nbytes; ++i) {
159 benchmark::DoNotOptimize(fgetc(fp));
160 }
161 fclose(fp);
162 }
163 }
164
BM_stdio_fopen_fgetc_fclose_locking(benchmark::State & state)165 static void BM_stdio_fopen_fgetc_fclose_locking(benchmark::State& state) {
166 FopenFgetcFclose(state, false);
167 }
168 BIONIC_BENCHMARK_WITH_ARG(BM_stdio_fopen_fgetc_fclose_locking, "1024");
169
BM_stdio_fopen_fgetc_fclose_no_locking(benchmark::State & state)170 void BM_stdio_fopen_fgetc_fclose_no_locking(benchmark::State& state) {
171 FopenFgetcFclose(state, true);
172 }
173 BIONIC_BENCHMARK_WITH_ARG(BM_stdio_fopen_fgetc_fclose_no_locking, "1024");
174
BM_stdio_printf_literal(benchmark::State & state)175 static void BM_stdio_printf_literal(benchmark::State& state) {
176 while (state.KeepRunning()) {
177 char buf[BUFSIZ];
178 snprintf(buf, sizeof(buf), "this is just a literal string with no format specifiers");
179 }
180 }
181 BIONIC_BENCHMARK(BM_stdio_printf_literal);
182
BM_stdio_printf_s(benchmark::State & state)183 static void BM_stdio_printf_s(benchmark::State& state) {
184 while (state.KeepRunning()) {
185 char buf[BUFSIZ];
186 snprintf(buf, sizeof(buf), "this is a more typical error message with detail: %s",
187 "No such file or directory");
188 }
189 }
190 BIONIC_BENCHMARK(BM_stdio_printf_s);
191
BM_stdio_printf_d(benchmark::State & state)192 static void BM_stdio_printf_d(benchmark::State& state) {
193 while (state.KeepRunning()) {
194 char buf[BUFSIZ];
195 snprintf(buf, sizeof(buf), "this is a more typical error message with detail: %d", 123456);
196 }
197 }
198 BIONIC_BENCHMARK(BM_stdio_printf_d);
199
BM_stdio_printf_1$s(benchmark::State & state)200 static void BM_stdio_printf_1$s(benchmark::State& state) {
201 while (state.KeepRunning()) {
202 char buf[BUFSIZ];
203 snprintf(buf, sizeof(buf), "this is a more typical error message with detail: %1$s",
204 "No such file or directory");
205 }
206 }
207 BIONIC_BENCHMARK(BM_stdio_printf_1$s);
208
BM_stdio_scanf_s(benchmark::State & state)209 static void BM_stdio_scanf_s(benchmark::State& state) {
210 while (state.KeepRunning()) {
211 char s[BUFSIZ];
212 if (sscanf("file /etc/passwd", "file %s", s) != 1) abort();
213 }
214 }
215 BIONIC_BENCHMARK(BM_stdio_scanf_s);
216
BM_stdio_scanf_d(benchmark::State & state)217 static void BM_stdio_scanf_d(benchmark::State& state) {
218 while (state.KeepRunning()) {
219 int i;
220 if (sscanf("size 12345", "size %d", &i) != 1) abort();
221 }
222 }
223 BIONIC_BENCHMARK(BM_stdio_scanf_d);
224
225 // Parsing maps is a common use of sscanf with a relatively complex format string.
BM_stdio_scanf_maps(benchmark::State & state)226 static void BM_stdio_scanf_maps(benchmark::State& state) {
227 while (state.KeepRunning()) {
228 uintptr_t start;
229 uintptr_t end;
230 uintptr_t offset;
231 char permissions[5];
232 int name_pos;
233 if (sscanf("6f000000-6f01e000 rwxp 00000000 00:0c 16389419 /system/lib/libcomposer.so",
234 "%" PRIxPTR "-%" PRIxPTR " %4s %" PRIxPTR " %*x:%*x %*d %n",
235 &start, &end, permissions, &offset, &name_pos) != 4) abort();
236 }
237 }
238 BIONIC_BENCHMARK(BM_stdio_scanf_maps);
239
240 // Hard-coded equivalent of the maps sscanf from libunwindstack/Maps.cpp for a baseline.
ParseMap(const char * line,const char *,uintptr_t * start,uintptr_t * end,char * permissions,uintptr_t * offset,int * name_pos)241 static int ParseMap(const char* line, const char* /*fmt*/, uintptr_t* start, uintptr_t* end,
242 char* permissions, uintptr_t* offset, int* name_pos) __attribute__((noinline)) {
243 char* str;
244 const char* old_str = line;
245
246 // "%" PRIxPTR "-"
247 *start = strtoul(old_str, &str, 16);
248 if (old_str == str || *str++ != '-') return 0;
249
250 // "%" PRIxPTR " "
251 old_str = str;
252 *end = strtoul(old_str, &str, 16);
253 if (old_str == str || !std::isspace(*str++)) return 0;
254 while (std::isspace(*str)) str++;
255
256 // "%4s "
257 if (*str == '\0') return 0;
258 permissions[0] = *str;
259 str++;
260 permissions[1] = *str;
261 str++;
262 permissions[2] = *str;
263 str++;
264 permissions[3] = *str;
265 str++;
266 permissions[4] = 0;
267 if (!std::isspace(*str++)) return 0;
268
269 // "%" PRIxPTR " "
270 old_str = str;
271 *offset = strtoul(old_str, &str, 16);
272 if (old_str == str || !std::isspace(*str)) return 0;
273
274 // "%*x:%*x "
275 old_str = str;
276 (void)strtoul(old_str, &str, 16);
277 if (old_str == str || *str++ != ':') return 0;
278 if (std::isspace(*str)) return 0;
279 old_str = str;
280 (void)strtoul(str, &str, 16);
281 if (old_str == str || !std::isspace(*str++)) return 0;
282
283 // "%*d "
284 old_str = str;
285 (void)strtoul(old_str, &str, 10);
286 if (old_str == str || (!std::isspace(*str) && *str != '\0')) return 0;
287 while (std::isspace(*str)) str++;
288
289 // "%n"
290 *name_pos = (str - line);
291 return 4;
292 }
293
BM_stdio_scanf_maps_baseline(benchmark::State & state)294 static void BM_stdio_scanf_maps_baseline(benchmark::State& state) {
295 while (state.KeepRunning()) {
296 uintptr_t start;
297 uintptr_t end;
298 uintptr_t offset;
299 char permissions[5];
300 int name_pos;
301 if (ParseMap("6f000000-6f01e000 rwxp 00000000 00:0c 16389419 /system/lib/libcomposer.so",
302 "%" PRIxPTR "-%" PRIxPTR " %4s %" PRIxPTR " %*x:%*x %*d %n",
303 &start, &end, permissions, &offset, &name_pos) != 4) abort();
304 }
305 }
306 BIONIC_BENCHMARK(BM_stdio_scanf_maps_baseline);
307