6f7657f5a6b64a42ef9d9f75eb72f322388af331
[folly.git] / folly / detail / CacheLocality.cpp
1 /*
2  * Copyright 2016 Facebook, Inc.
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 <folly/detail/CacheLocality.h>
18
19 #ifndef _MSC_VER
20 #define _GNU_SOURCE 1 // for RTLD_NOLOAD
21 #include <dlfcn.h>
22 #endif
23 #include <fstream>
24
25 #include <folly/Conv.h>
26 #include <folly/Exception.h>
27 #include <folly/FileUtil.h>
28 #include <folly/Format.h>
29 #include <folly/ScopeGuard.h>
30
31 namespace folly {
32 namespace detail {
33
34 ///////////// CacheLocality
35
36 /// Returns the best real CacheLocality information available
37 static CacheLocality getSystemLocalityInfo() {
38 #ifdef __linux__
39   try {
40     return CacheLocality::readFromSysfs();
41   } catch (...) {
42     // keep trying
43   }
44 #endif
45
46   long numCpus = sysconf(_SC_NPROCESSORS_CONF);
47   if (numCpus <= 0) {
48     // This shouldn't happen, but if it does we should try to keep
49     // going.  We are probably not going to be able to parse /sys on
50     // this box either (although we will try), which means we are going
51     // to fall back to the SequentialThreadId splitter.  On my 16 core
52     // (x hyperthreading) dev box 16 stripes is enough to get pretty good
53     // contention avoidance with SequentialThreadId, and there is little
54     // improvement from going from 32 to 64.  This default gives us some
55     // wiggle room
56     numCpus = 32;
57   }
58   return CacheLocality::uniform(numCpus);
59 }
60
61 template <>
62 const CacheLocality& CacheLocality::system<std::atomic>() {
63   static CacheLocality cache(getSystemLocalityInfo());
64   return cache;
65 }
66
67 // Each level of cache has sharing sets, which are the set of cpus
68 // that share a common cache at that level.  These are available in a
69 // hex bitset form (/sys/devices/system/cpu/cpu0/index0/shared_cpu_map,
70 // for example).  They are also available in a human-readable list form,
71 // as in /sys/devices/system/cpu/cpu0/index0/shared_cpu_list.  The list
72 // is a comma-separated list of numbers and ranges, where the ranges are
73 // a pair of decimal numbers separated by a '-'.
74 //
75 // To sort the cpus for optimum locality we don't really need to parse
76 // the sharing sets, we just need a unique representative from the
77 // equivalence class.  The smallest value works fine, and happens to be
78 // the first decimal number in the file.  We load all of the equivalence
79 // class information from all of the cpu*/index* directories, order the
80 // cpus first by increasing last-level cache equivalence class, then by
81 // the smaller caches.  Finally, we break ties with the cpu number itself.
82
83 /// Returns the first decimal number in the string, or throws an exception
84 /// if the string does not start with a number terminated by ',', '-',
85 /// '\n', or eos.
86 static size_t parseLeadingNumber(const std::string& line) {
87   auto raw = line.c_str();
88   char* end;
89   unsigned long val = strtoul(raw, &end, 10);
90   if (end == raw || (*end != ',' && *end != '-' && *end != '\n' && *end != 0)) {
91     throw std::runtime_error(
92         to<std::string>("error parsing list '", line, "'").c_str());
93   }
94   return val;
95 }
96
97 CacheLocality CacheLocality::readFromSysfsTree(
98     const std::function<std::string(std::string)>& mapping) {
99   // number of equivalence classes per level
100   std::vector<size_t> numCachesByLevel;
101
102   // the list of cache equivalence classes, where equivalance classes
103   // are named by the smallest cpu in the class
104   std::vector<std::vector<size_t>> equivClassesByCpu;
105
106   std::vector<size_t> cpus;
107
108   while (true) {
109     auto cpu = cpus.size();
110     std::vector<size_t> levels;
111     for (size_t index = 0;; ++index) {
112       auto dir =
113           format("/sys/devices/system/cpu/cpu{}/cache/index{}/", cpu, index)
114               .str();
115       auto cacheType = mapping(dir + "type");
116       auto equivStr = mapping(dir + "shared_cpu_list");
117       if (cacheType.size() == 0 || equivStr.size() == 0) {
118         // no more caches
119         break;
120       }
121       if (cacheType[0] == 'I') {
122         // cacheType in { "Data", "Instruction", "Unified" }. skip icache
123         continue;
124       }
125       auto equiv = parseLeadingNumber(equivStr);
126       auto level = levels.size();
127       levels.push_back(equiv);
128
129       if (equiv == cpu) {
130         // we only want to count the equiv classes once, so we do it when
131         // we first encounter them
132         while (numCachesByLevel.size() <= level) {
133           numCachesByLevel.push_back(0);
134         }
135         numCachesByLevel[level]++;
136       }
137     }
138
139     if (levels.size() == 0) {
140       // no levels at all for this cpu, we must be done
141       break;
142     }
143     equivClassesByCpu.emplace_back(std::move(levels));
144     cpus.push_back(cpu);
145   }
146
147   if (cpus.size() == 0) {
148     throw std::runtime_error("unable to load cache sharing info");
149   }
150
151   std::sort(cpus.begin(),
152             cpus.end(),
153             [&](size_t lhs, size_t rhs) -> bool {
154               // sort first by equiv class of cache with highest index,
155               // direction doesn't matter.  If different cpus have
156               // different numbers of caches then this code might produce
157               // a sub-optimal ordering, but it won't crash
158               auto& lhsEquiv = equivClassesByCpu[lhs];
159               auto& rhsEquiv = equivClassesByCpu[rhs];
160               for (int i = std::min(lhsEquiv.size(), rhsEquiv.size()) - 1;
161                    i >= 0;
162                    --i) {
163                 if (lhsEquiv[i] != rhsEquiv[i]) {
164                   return lhsEquiv[i] < rhsEquiv[i];
165                 }
166               }
167
168               // break ties deterministically by cpu
169               return lhs < rhs;
170             });
171
172   // the cpus are now sorted by locality, with neighboring entries closer
173   // to each other than entries that are far away.  For striping we want
174   // the inverse map, since we are starting with the cpu
175   std::vector<size_t> indexes(cpus.size());
176   for (size_t i = 0; i < cpus.size(); ++i) {
177     indexes[cpus[i]] = i;
178   }
179
180   return CacheLocality{
181       cpus.size(), std::move(numCachesByLevel), std::move(indexes)};
182 }
183
184 CacheLocality CacheLocality::readFromSysfs() {
185   return readFromSysfsTree([](std::string name) {
186     std::ifstream xi(name.c_str());
187     std::string rv;
188     std::getline(xi, rv);
189     return rv;
190   });
191 }
192
193 CacheLocality CacheLocality::uniform(size_t numCpus) {
194   CacheLocality rv;
195
196   rv.numCpus = numCpus;
197
198   // one cache shared by all cpus
199   rv.numCachesByLevel.push_back(numCpus);
200
201   // no permutations in locality index mapping
202   for (size_t cpu = 0; cpu < numCpus; ++cpu) {
203     rv.localityIndexByCpu.push_back(cpu);
204   }
205
206   return rv;
207 }
208
209 ////////////// Getcpu
210
211 /// Resolves the dynamically loaded symbol __vdso_getcpu, returning null
212 /// on failure
213 static Getcpu::Func loadVdsoGetcpu() {
214 #if defined(_MSC_VER) || defined(__BIONIC__)
215   return nullptr;
216 #else
217   void* h = dlopen("linux-vdso.so.1", RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
218   if (h == nullptr) {
219     return nullptr;
220   }
221
222   auto func = Getcpu::Func(dlsym(h, "__vdso_getcpu"));
223   if (func == nullptr) {
224     // technically a null result could either be a failure or a successful
225     // lookup of a symbol with the null value, but the second can't actually
226     // happen for this symbol.  No point holding the handle forever if
227     // we don't need the code
228     dlclose(h);
229   }
230
231   return func;
232 #endif
233 }
234
235 Getcpu::Func Getcpu::vdsoFunc() {
236   static Func func = loadVdsoGetcpu();
237   return func;
238 }
239
240 #ifdef FOLLY_TLS
241 /////////////// SequentialThreadId
242
243 template <>
244 std::atomic<size_t> SequentialThreadId<std::atomic>::prevId(0);
245
246 template <>
247 FOLLY_TLS size_t SequentialThreadId<std::atomic>::currentId(0);
248 #endif
249
250 /////////////// AccessSpreader
251
252 template <>
253 const AccessSpreader<std::atomic> AccessSpreader<std::atomic>::stripeByCore(
254     CacheLocality::system<>().numCachesByLevel.front());
255
256 template <>
257 const AccessSpreader<std::atomic> AccessSpreader<std::atomic>::stripeByChip(
258     CacheLocality::system<>().numCachesByLevel.back());
259
260 template <>
261 AccessSpreaderArray<std::atomic, 128>
262     AccessSpreaderArray<std::atomic, 128>::sharedInstance = {};
263
264 /// Always claims to be on CPU zero, node zero
265 static int degenerateGetcpu(unsigned* cpu, unsigned* node, void* /* unused */) {
266   if (cpu != nullptr) {
267     *cpu = 0;
268   }
269   if (node != nullptr) {
270     *node = 0;
271   }
272   return 0;
273 }
274
275 template <>
276 Getcpu::Func AccessSpreader<std::atomic>::pickGetcpuFunc(size_t numStripes) {
277   if (numStripes == 1) {
278     // there's no need to call getcpu if there is only one stripe.
279     // This should not be common, so we don't want to waste a test and
280     // branch in the main code path, but we might as well use a faster
281     // function pointer
282     return &degenerateGetcpu;
283   } else {
284     auto best = Getcpu::vdsoFunc();
285     return best ? best : &FallbackGetcpuType::getcpu;
286   }
287 }
288 }
289 } // namespace folly::detail