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     volatile int c __attribute__((unused));
159     for (size_t i = 0; i < nbytes; ++i) {
160       c = fgetc(fp);
161     }
162     fclose(fp);
163   }
164 }
165 
BM_stdio_fopen_fgetc_fclose_locking(benchmark::State & state)166 static void BM_stdio_fopen_fgetc_fclose_locking(benchmark::State& state) {
167   FopenFgetcFclose(state, false);
168 }
169 BIONIC_BENCHMARK_WITH_ARG(BM_stdio_fopen_fgetc_fclose_locking, "1024");
170 
BM_stdio_fopen_fgetc_fclose_no_locking(benchmark::State & state)171 void BM_stdio_fopen_fgetc_fclose_no_locking(benchmark::State& state) {
172   FopenFgetcFclose(state, true);
173 }
174 BIONIC_BENCHMARK_WITH_ARG(BM_stdio_fopen_fgetc_fclose_no_locking, "1024");
175 
BM_stdio_printf_literal(benchmark::State & state)176 static void BM_stdio_printf_literal(benchmark::State& state) {
177   while (state.KeepRunning()) {
178     char buf[BUFSIZ];
179     snprintf(buf, sizeof(buf), "this is just a literal string with no format specifiers");
180   }
181 }
182 BIONIC_BENCHMARK(BM_stdio_printf_literal);
183 
BM_stdio_printf_s(benchmark::State & state)184 static void BM_stdio_printf_s(benchmark::State& state) {
185   while (state.KeepRunning()) {
186     char buf[BUFSIZ];
187     snprintf(buf, sizeof(buf), "this is a more typical error message with detail: %s",
188              "No such file or directory");
189   }
190 }
191 BIONIC_BENCHMARK(BM_stdio_printf_s);
192 
BM_stdio_printf_d(benchmark::State & state)193 static void BM_stdio_printf_d(benchmark::State& state) {
194   while (state.KeepRunning()) {
195     char buf[BUFSIZ];
196     snprintf(buf, sizeof(buf), "this is a more typical error message with detail: %d", 123456);
197   }
198 }
199 BIONIC_BENCHMARK(BM_stdio_printf_d);
200 
BM_stdio_printf_1$s(benchmark::State & state)201 static void BM_stdio_printf_1$s(benchmark::State& state) {
202   while (state.KeepRunning()) {
203     char buf[BUFSIZ];
204     snprintf(buf, sizeof(buf), "this is a more typical error message with detail: %1$s",
205              "No such file or directory");
206   }
207 }
208 BIONIC_BENCHMARK(BM_stdio_printf_1$s);
209 
BM_stdio_scanf_s(benchmark::State & state)210 static void BM_stdio_scanf_s(benchmark::State& state) {
211   while (state.KeepRunning()) {
212     char s[BUFSIZ];
213     if (sscanf("file /etc/passwd", "file %s", s) != 1) abort();
214   }
215 }
216 BIONIC_BENCHMARK(BM_stdio_scanf_s);
217 
BM_stdio_scanf_d(benchmark::State & state)218 static void BM_stdio_scanf_d(benchmark::State& state) {
219   while (state.KeepRunning()) {
220     int i;
221     if (sscanf("size 12345", "size %d", &i) != 1) abort();
222   }
223 }
224 BIONIC_BENCHMARK(BM_stdio_scanf_d);
225 
226 // Parsing maps is a common use of sscanf with a relatively complex format string.
BM_stdio_scanf_maps(benchmark::State & state)227 static void BM_stdio_scanf_maps(benchmark::State& state) {
228   while (state.KeepRunning()) {
229     uintptr_t start;
230     uintptr_t end;
231     uintptr_t offset;
232     char permissions[5];
233     int name_pos;
234     if (sscanf("6f000000-6f01e000 rwxp 00000000 00:0c 16389419   /system/lib/libcomposer.so",
235                "%" PRIxPTR "-%" PRIxPTR " %4s %" PRIxPTR " %*x:%*x %*d %n",
236                &start, &end, permissions, &offset, &name_pos) != 4) abort();
237   }
238 }
239 BIONIC_BENCHMARK(BM_stdio_scanf_maps);
240 
241 // 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)242 static int ParseMap(const char* line, const char* /*fmt*/, uintptr_t* start, uintptr_t* end,
243                     char* permissions, uintptr_t* offset, int* name_pos) __attribute__((noinline)) {
244   char* str;
245   const char* old_str = line;
246 
247   // "%" PRIxPTR "-"
248   *start = strtoul(old_str, &str, 16);
249   if (old_str == str || *str++ != '-') return 0;
250 
251   // "%" PRIxPTR " "
252   old_str = str;
253   *end = strtoul(old_str, &str, 16);
254   if (old_str == str || !std::isspace(*str++)) return 0;
255   while (std::isspace(*str)) str++;
256 
257   // "%4s "
258   if (*str == '\0') return 0;
259   permissions[0] = *str;
260   str++;
261   permissions[1] = *str;
262   str++;
263   permissions[2] = *str;
264   str++;
265   permissions[3] = *str;
266   str++;
267   permissions[4] = 0;
268   if (!std::isspace(*str++)) return 0;
269 
270   // "%" PRIxPTR " "
271   old_str = str;
272   *offset = strtoul(old_str, &str, 16);
273   if (old_str == str || !std::isspace(*str)) return 0;
274 
275   // "%*x:%*x "
276   old_str = str;
277   (void)strtoul(old_str, &str, 16);
278   if (old_str == str || *str++ != ':') return 0;
279   if (std::isspace(*str)) return 0;
280   old_str = str;
281   (void)strtoul(str, &str, 16);
282   if (old_str == str || !std::isspace(*str++)) return 0;
283 
284   // "%*d "
285   old_str = str;
286   (void)strtoul(old_str, &str, 10);
287   if (old_str == str || (!std::isspace(*str) && *str != '\0')) return 0;
288   while (std::isspace(*str)) str++;
289 
290   // "%n"
291   *name_pos = (str - line);
292   return 4;
293 }
294 
BM_stdio_scanf_maps_baseline(benchmark::State & state)295 static void BM_stdio_scanf_maps_baseline(benchmark::State& state) {
296   while (state.KeepRunning()) {
297     uintptr_t start;
298     uintptr_t end;
299     uintptr_t offset;
300     char permissions[5];
301     int name_pos;
302     if (ParseMap("6f000000-6f01e000 rwxp 00000000 00:0c 16389419   /system/lib/libcomposer.so",
303                "%" PRIxPTR "-%" PRIxPTR " %4s %" PRIxPTR " %*x:%*x %*d %n",
304                &start, &end, permissions, &offset, &name_pos) != 4) abort();
305   }
306 }
307 BIONIC_BENCHMARK(BM_stdio_scanf_maps_baseline);
308