@img/sharp-libvips-dev-wasm32 1.3.0-rc.5 → 1.3.0-rc.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -23
- package/include/expat.h +14 -2
- package/include/expat_config.h +6 -3
- package/include/expat_external.h +3 -2
- package/include/glib-2.0/glib/gvarianttype.h +16 -16
- package/include/glib-2.0/glib/gversionmacros.h +13 -0
- package/include/hwy/aligned_allocator.h +44 -2
- package/include/hwy/auto_tune.h +22 -6
- package/include/hwy/base.h +60 -90
- package/include/hwy/bit_set.h +410 -0
- package/include/hwy/cache_control.h +3 -2
- package/include/hwy/detect_compiler_arch.h +134 -14
- package/include/hwy/detect_targets.h +82 -36
- package/include/hwy/highway.h +15 -0
- package/include/hwy/nanobenchmark.h +12 -3
- package/include/hwy/ops/arm_neon-inl.h +96 -25
- package/include/hwy/ops/arm_sve-inl.h +250 -95
- package/include/hwy/ops/emu128-inl.h +12 -7
- package/include/hwy/ops/generic_ops-inl.h +271 -131
- package/include/hwy/ops/loongarch_lasx-inl.h +31 -9
- package/include/hwy/ops/loongarch_lsx-inl.h +33 -12
- package/include/hwy/ops/ppc_vsx-inl.h +99 -19
- package/include/hwy/ops/rvv-inl.h +239 -130
- package/include/hwy/ops/scalar-inl.h +22 -7
- package/include/hwy/ops/set_macros-inl.h +163 -107
- package/include/hwy/ops/wasm_128-inl.h +13 -22
- package/include/hwy/ops/x86_128-inl.h +223 -120
- package/include/hwy/ops/x86_256-inl.h +70 -134
- package/include/hwy/ops/x86_512-inl.h +130 -134
- package/include/hwy/print-inl.h +2 -0
- package/include/hwy/profiler.h +355 -264
- package/include/hwy/robust_statistics.h +3 -1
- package/include/hwy/timer.h +44 -0
- package/include/lcms2.h +34 -4
- package/include/libexif/exif-utils.h +1 -1
- package/include/libheif/heif_sequences.h +12 -2
- package/include/libheif/heif_uncompressed.h +8 -0
- package/include/libpng16/png.h +7 -7
- package/include/libpng16/pngconf.h +1 -1
- package/include/libpng16/pnglibconf.h +1 -1
- package/include/png.h +7 -7
- package/include/pngconf.h +1 -1
- package/include/pnglibconf.h +1 -1
- package/include/tiffconf.h +9 -10
- package/include/tiffio.h +10 -9
- package/include/tiffvers.h +1 -1
- package/include/ultrahdr_api.h +5 -11
- package/lib/glib-2.0/include/glibconfig.h +1 -1
- package/lib/libaom.a +0 -0
- package/lib/libcgif.a +0 -0
- package/lib/libexif.a +0 -0
- package/lib/libexif.la +1 -1
- package/lib/libexpat.a +0 -0
- package/lib/libexpat.la +3 -3
- package/lib/libffi.a +0 -0
- package/lib/libgio-2.0.a +0 -0
- package/lib/libglib-2.0.a +0 -0
- package/lib/libgmodule-2.0.a +0 -0
- package/lib/libgobject-2.0.a +0 -0
- package/lib/libgthread-2.0.a +0 -0
- package/lib/libheif.a +0 -0
- package/lib/libhwy.a +0 -0
- package/lib/libimagequant.a +0 -0
- package/lib/libjpeg.a +0 -0
- package/lib/liblcms2.a +0 -0
- package/lib/libpng.a +0 -0
- package/lib/libpng.la +2 -2
- package/lib/libpng16.a +0 -0
- package/lib/libpng16.la +2 -2
- package/lib/libresvg.a +0 -0
- package/lib/libsharpyuv.a +0 -0
- package/lib/libtiff.a +0 -0
- package/lib/libuhdr.a +0 -0
- package/lib/libvips-cpp.a +0 -0
- package/lib/libvips.a +0 -0
- package/lib/libwebp.a +0 -0
- package/lib/libwebpdecoder.a +0 -0
- package/lib/libwebpdemux.a +0 -0
- package/lib/libwebpmux.a +0 -0
- package/lib/libz.a +0 -0
- package/lib/pkgconfig/expat.pc +1 -1
- package/lib/pkgconfig/gio-2.0.pc +1 -1
- package/lib/pkgconfig/glib-2.0.pc +1 -1
- package/lib/pkgconfig/gmodule-2.0.pc +1 -1
- package/lib/pkgconfig/gmodule-export-2.0.pc +1 -1
- package/lib/pkgconfig/gmodule-no-export-2.0.pc +1 -1
- package/lib/pkgconfig/gobject-2.0.pc +1 -1
- package/lib/pkgconfig/gthread-2.0.pc +1 -1
- package/lib/pkgconfig/lcms2.pc +1 -1
- package/lib/pkgconfig/libexif.pc +1 -1
- package/lib/pkgconfig/libhwy.pc +3 -4
- package/lib/pkgconfig/libpng.pc +1 -1
- package/lib/pkgconfig/libpng16.pc +1 -1
- package/lib/pkgconfig/libsharpyuv.pc +1 -1
- package/lib/pkgconfig/libtiff-4.pc +2 -2
- package/lib/pkgconfig/libwebp.pc +1 -1
- package/lib/pkgconfig/libwebpdecoder.pc +11 -0
- package/lib/pkgconfig/libwebpmux.pc +1 -1
- package/package.json +1 -1
- package/versions.json +10 -10
- package/lib/libsharpyuv.la +0 -41
- package/lib/libtiff.la +0 -41
- package/lib/libwebp.la +0 -41
- package/lib/libwebpdemux.la +0 -41
- package/lib/libwebpmux.la +0 -41
package/include/hwy/profiler.h
CHANGED
|
@@ -17,21 +17,29 @@
|
|
|
17
17
|
|
|
18
18
|
#include <stddef.h>
|
|
19
19
|
#include <stdint.h>
|
|
20
|
+
#include <string.h> // strcmp, strlen
|
|
21
|
+
|
|
22
|
+
#include <atomic>
|
|
23
|
+
#include <functional>
|
|
20
24
|
|
|
25
|
+
#include "hwy/base.h"
|
|
21
26
|
#include "hwy/highway_export.h"
|
|
22
27
|
|
|
23
28
|
// High precision, low overhead time measurements. Returns exact call counts and
|
|
24
29
|
// total elapsed time for user-defined 'zones' (code regions, i.e. C++ scopes).
|
|
25
30
|
//
|
|
26
31
|
// Uses RAII to capture begin/end timestamps, with user-specified zone names:
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
// You can reduce the overhead by passing
|
|
31
|
-
//
|
|
32
|
-
//
|
|
32
|
+
// `{ PROFILER_ZONE("name"); /*code*/ }` or the name of the current function:
|
|
33
|
+
// `void FuncToMeasure() { PROFILER_FUNC; /*code*/ }`.
|
|
34
|
+
//
|
|
35
|
+
// You can reduce the overhead by passing `global_idx`, which can be taken from
|
|
36
|
+
// the argument to the `ThreadPool::Run` lambda (if the pool was constructed
|
|
37
|
+
// with non-default `PoolWorkerMapping`), or from a saved copy of the
|
|
38
|
+
// thread-local `Profiler::Thread`: `PROFILER_ZONE2(global_idx, name)`.
|
|
39
|
+
//
|
|
40
|
+
// The preferred API allows passing flags, such as requesting inclusive time:
|
|
33
41
|
// `static const auto zone = profiler.AddZone("name", flags);` and then
|
|
34
|
-
// `PROFILER_ZONE3(profiler,
|
|
42
|
+
// `PROFILER_ZONE3(profiler, global_idx, zone)`.
|
|
35
43
|
//
|
|
36
44
|
// After all threads exit all zones, call `Profiler::Get().PrintResults()` to
|
|
37
45
|
// print call counts and average durations [CPU cycles] to stdout, sorted in
|
|
@@ -44,14 +52,12 @@
|
|
|
44
52
|
|
|
45
53
|
#if PROFILER_ENABLED
|
|
46
54
|
#include <stdio.h>
|
|
47
|
-
#include <string.h> // strcmp, strlen
|
|
48
55
|
|
|
49
56
|
#include <algorithm> // std::sort
|
|
50
|
-
#include <
|
|
57
|
+
#include <utility>
|
|
51
58
|
#include <vector>
|
|
52
59
|
|
|
53
60
|
#include "hwy/aligned_allocator.h"
|
|
54
|
-
#include "hwy/base.h"
|
|
55
61
|
#include "hwy/bit_set.h"
|
|
56
62
|
#include "hwy/timer.h"
|
|
57
63
|
#endif // PROFILER_ENABLED
|
|
@@ -60,8 +66,8 @@ namespace hwy {
|
|
|
60
66
|
|
|
61
67
|
// Flags: we want type-safety (enum class) to catch mistakes such as confusing
|
|
62
68
|
// zone with flags. Base type (`uint32_t`) ensures it is safe to cast. Defined
|
|
63
|
-
// outside the `#if` because callers pass them to `PROFILER_ZONE3`.
|
|
64
|
-
//
|
|
69
|
+
// outside the `#if` because callers pass them to `PROFILER_ZONE3`. When adding
|
|
70
|
+
// flags, also update `kNumFlags` and `ChildTotalMask`.
|
|
65
71
|
enum class ProfilerFlags : uint32_t {
|
|
66
72
|
kDefault = 0,
|
|
67
73
|
// The zone should report cumulative time, including all child zones. If not
|
|
@@ -69,6 +75,78 @@ enum class ProfilerFlags : uint32_t {
|
|
|
69
75
|
kInclusive = 1
|
|
70
76
|
};
|
|
71
77
|
|
|
78
|
+
// Called during `PrintResults` to print results from other modules.
|
|
79
|
+
using ProfilerFunc = std::function<void(void)>;
|
|
80
|
+
|
|
81
|
+
template <size_t kMaxStrings>
|
|
82
|
+
class StringTable {
|
|
83
|
+
static constexpr std::memory_order kRelaxed = std::memory_order_relaxed;
|
|
84
|
+
static constexpr std::memory_order kAcq = std::memory_order_acquire;
|
|
85
|
+
static constexpr std::memory_order kRel = std::memory_order_release;
|
|
86
|
+
|
|
87
|
+
public:
|
|
88
|
+
// Returns a copy of the `name` passed to `Add` that returned the
|
|
89
|
+
// given `idx`.
|
|
90
|
+
const char* Name(size_t idx) const {
|
|
91
|
+
// `kAcq` so that the string contents are also visible after the pointer is
|
|
92
|
+
// published via `kRelease` store.
|
|
93
|
+
return ptrs_[idx].load(kAcq);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Returns `idx < kMaxStrings`. Can be called concurrently. Calls with the
|
|
97
|
+
// same `name` return the same `idx`.
|
|
98
|
+
size_t Add(const char* name) {
|
|
99
|
+
// Linear search if it already exists. `kAcq` ensures we see prior stores.
|
|
100
|
+
const size_t num_strings = next_ptr_.load(kAcq);
|
|
101
|
+
HWY_ASSERT(num_strings < kMaxStrings);
|
|
102
|
+
for (size_t idx = 1; idx < num_strings; ++idx) {
|
|
103
|
+
const char* existing = ptrs_[idx].load(kAcq);
|
|
104
|
+
// `next_ptr_` was published after writing `ptr_`, hence it is non-null.
|
|
105
|
+
HWY_ASSERT(existing != nullptr);
|
|
106
|
+
if (HWY_UNLIKELY(!strcmp(existing, name))) {
|
|
107
|
+
return idx;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Copy `name` into `chars_` before publishing the pointer.
|
|
112
|
+
const size_t len = strlen(name) + 1;
|
|
113
|
+
const size_t pos = next_char_.fetch_add(len, kRelaxed);
|
|
114
|
+
HWY_ASSERT(pos + len <= sizeof(chars_));
|
|
115
|
+
strcpy(chars_ + pos, name); // NOLINT
|
|
116
|
+
|
|
117
|
+
for (;;) {
|
|
118
|
+
size_t idx = next_ptr_.load(kRelaxed);
|
|
119
|
+
HWY_ASSERT(idx < kMaxStrings);
|
|
120
|
+
|
|
121
|
+
// Attempt to claim the next `idx` via CAS.
|
|
122
|
+
const char* expected = nullptr;
|
|
123
|
+
if (HWY_LIKELY(ptrs_[idx].compare_exchange_weak(expected, chars_ + pos,
|
|
124
|
+
kRel, kRelaxed))) {
|
|
125
|
+
// Publish the new count and make the `ptrs_` write visible.
|
|
126
|
+
next_ptr_.store(idx + 1, kRel);
|
|
127
|
+
HWY_DASSERT(!strcmp(Name(idx), name));
|
|
128
|
+
return idx;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// We lost the race. `expected` has been updated.
|
|
132
|
+
if (HWY_UNLIKELY(!strcmp(expected, name))) {
|
|
133
|
+
// Done, another thread added the same name. Note that we waste the
|
|
134
|
+
// extra space in `chars_`, which is fine because it is rare.
|
|
135
|
+
HWY_DASSERT(!strcmp(Name(idx), name));
|
|
136
|
+
return idx;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Other thread added a different name. Retry with the next slot.
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private:
|
|
144
|
+
std::atomic<const char*> ptrs_[kMaxStrings];
|
|
145
|
+
std::atomic<size_t> next_ptr_{1}; // next idx
|
|
146
|
+
std::atomic<size_t> next_char_{0};
|
|
147
|
+
char chars_[kMaxStrings * 55];
|
|
148
|
+
};
|
|
149
|
+
|
|
72
150
|
#if PROFILER_ENABLED
|
|
73
151
|
|
|
74
152
|
// Implementation details.
|
|
@@ -78,13 +156,13 @@ HWY_INLINE_VAR constexpr size_t kNumFlags = 1;
|
|
|
78
156
|
|
|
79
157
|
// Upper bounds for fixed-size data structures, guarded via HWY_DASSERT:
|
|
80
158
|
|
|
81
|
-
// Maximum nesting of zones, chosen such that
|
|
159
|
+
// Maximum nesting of zones, chosen such that `PerWorker` is 256 bytes.
|
|
82
160
|
HWY_INLINE_VAR constexpr size_t kMaxDepth = 13;
|
|
83
161
|
// Reports with more than ~50 are anyway difficult to read.
|
|
84
162
|
HWY_INLINE_VAR constexpr size_t kMaxZones = 128;
|
|
85
|
-
// Upper bound on
|
|
86
|
-
//
|
|
87
|
-
HWY_INLINE_VAR constexpr size_t
|
|
163
|
+
// Upper bound on global worker_idx across all pools. Note that fiber libraries
|
|
164
|
+
// can spawn hundreds of threads. Turin has 128-192 cores.
|
|
165
|
+
HWY_INLINE_VAR constexpr size_t kMaxWorkers = 256;
|
|
88
166
|
|
|
89
167
|
// Type-safe wrapper for zone index plus flags, returned by `AddZone`.
|
|
90
168
|
class ZoneHandle {
|
|
@@ -119,8 +197,11 @@ class ZoneHandle {
|
|
|
119
197
|
|
|
120
198
|
// Returns a mask to zero/ignore child totals for inclusive zones.
|
|
121
199
|
uint64_t ChildTotalMask() const {
|
|
122
|
-
//
|
|
123
|
-
return IsInclusive() ? 0 : ~uint64_t{0};
|
|
200
|
+
// With a ternary operator, clang tends to generate a branch.
|
|
201
|
+
// return IsInclusive() ? 0 : ~uint64_t{0};
|
|
202
|
+
const uint32_t bit =
|
|
203
|
+
bits_ & static_cast<uint32_t>(ProfilerFlags::kInclusive);
|
|
204
|
+
return uint64_t{bit} - 1;
|
|
124
205
|
}
|
|
125
206
|
|
|
126
207
|
private:
|
|
@@ -128,48 +209,96 @@ class ZoneHandle {
|
|
|
128
209
|
};
|
|
129
210
|
|
|
130
211
|
// Storage for zone names.
|
|
131
|
-
class
|
|
132
|
-
static constexpr std::memory_order kRel = std::memory_order_relaxed;
|
|
133
|
-
|
|
212
|
+
class Zones {
|
|
134
213
|
public:
|
|
135
214
|
// Returns a copy of the `name` passed to `AddZone` that returned the
|
|
136
215
|
// given `zone`.
|
|
137
|
-
const char*
|
|
216
|
+
const char* Name(ZoneHandle zone) const {
|
|
217
|
+
return strings_.Name(zone.ZoneIdx());
|
|
218
|
+
}
|
|
138
219
|
|
|
220
|
+
// Can be called concurrently. Calls with the same `name` return the same
|
|
221
|
+
// `ZoneHandle.ZoneIdx()`.
|
|
139
222
|
ZoneHandle AddZone(const char* name, ProfilerFlags flags) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
223
|
+
return ZoneHandle(strings_.Add(name), flags);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private:
|
|
227
|
+
StringTable<kMaxZones> strings_;
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
// Allows other classes such as `ThreadPool` to register/unregister a function
|
|
231
|
+
// to call during `PrintResults`. This allows us to gather data from the worker
|
|
232
|
+
// threads without having to wait until they exit, and decouples the profiler
|
|
233
|
+
// from other modules. Thread-safe.
|
|
234
|
+
class Funcs {
|
|
235
|
+
static constexpr auto kAcq = std::memory_order_acquire;
|
|
236
|
+
static constexpr auto kRel = std::memory_order_release;
|
|
237
|
+
|
|
238
|
+
public:
|
|
239
|
+
// Can be called concurrently with distinct keys.
|
|
240
|
+
void Add(intptr_t key, ProfilerFunc func) {
|
|
241
|
+
HWY_ASSERT(key != 0 && key != kPending); // reserved values
|
|
242
|
+
HWY_ASSERT(func); // not empty
|
|
243
|
+
|
|
244
|
+
for (size_t i = 0; i < kMaxFuncs; ++i) {
|
|
245
|
+
intptr_t expected = 0;
|
|
246
|
+
// Lost a race with a concurrent `Add`, try the next slot.
|
|
247
|
+
if (!keys_[i].compare_exchange_strong(expected, kPending, kRel)) {
|
|
248
|
+
continue;
|
|
146
249
|
}
|
|
250
|
+
// We own the slot: move func there.
|
|
251
|
+
funcs_[i] = std::move(func);
|
|
252
|
+
keys_[i].store(key, kRel); // publishes the `func` write.
|
|
253
|
+
return;
|
|
147
254
|
}
|
|
148
255
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
HWY_ASSERT(
|
|
156
|
-
|
|
256
|
+
HWY_ABORT("Funcs::Add: no free slot, increase kMaxFuncs.");
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Can be called concurrently with distinct keys. It is an error to call this
|
|
260
|
+
// without a prior `Add` of the same key.
|
|
261
|
+
void Remove(intptr_t key) {
|
|
262
|
+
HWY_ASSERT(key != 0 && key != kPending); // reserved values
|
|
263
|
+
|
|
264
|
+
for (size_t i = 0; i < kMaxFuncs; ++i) {
|
|
265
|
+
intptr_t actual = keys_[i].load(kAcq);
|
|
266
|
+
if (actual == key) {
|
|
267
|
+
// In general, concurrent removal is fine, but in this specific context,
|
|
268
|
+
// owners are expected to remove their key exactly once, from the same
|
|
269
|
+
// thread that added it. In that case, CAS should not fail.
|
|
270
|
+
if (!keys_[i].compare_exchange_strong(actual, kPending, kRel)) {
|
|
271
|
+
HWY_WARN("Funcs: CAS failed, why is there a concurrent Remove?");
|
|
272
|
+
}
|
|
273
|
+
funcs_[i] = ProfilerFunc();
|
|
274
|
+
keys_[i].store(0, kRel); // publishes the `func` write.
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
HWY_ABORT("Funcs::Remove: failed to find key %p.",
|
|
279
|
+
reinterpret_cast<void*>(key));
|
|
280
|
+
}
|
|
157
281
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
282
|
+
void CallAll() const {
|
|
283
|
+
for (size_t i = 0; i < kMaxFuncs; ++i) {
|
|
284
|
+
intptr_t key = keys_[i].load(kAcq); // ensures `funcs_` is visible.
|
|
285
|
+
// Safely handles concurrent Add/Remove.
|
|
286
|
+
if (key != 0 && key != kPending) {
|
|
287
|
+
funcs_[i]();
|
|
288
|
+
}
|
|
289
|
+
}
|
|
162
290
|
}
|
|
163
291
|
|
|
164
292
|
private:
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
293
|
+
static constexpr size_t kMaxFuncs = 64;
|
|
294
|
+
static constexpr intptr_t kPending = -1;
|
|
295
|
+
|
|
296
|
+
ProfilerFunc funcs_[kMaxFuncs]; // non-atomic
|
|
297
|
+
std::atomic<intptr_t> keys_[kMaxFuncs] = {};
|
|
169
298
|
};
|
|
170
299
|
|
|
171
|
-
// Holds total duration and number of calls.
|
|
172
|
-
//
|
|
300
|
+
// Holds total duration and number of calls. Worker index is implicit in the
|
|
301
|
+
// index of this class within the `Accumulators` array.
|
|
173
302
|
struct Accumulator {
|
|
174
303
|
void Add(ZoneHandle new_zone, uint64_t self_duration) {
|
|
175
304
|
duration += self_duration;
|
|
@@ -183,7 +312,7 @@ struct Accumulator {
|
|
|
183
312
|
num_calls += 1;
|
|
184
313
|
}
|
|
185
314
|
|
|
186
|
-
void
|
|
315
|
+
void Take(Accumulator& other) {
|
|
187
316
|
duration += other.duration;
|
|
188
317
|
other.duration = 0;
|
|
189
318
|
|
|
@@ -203,94 +332,38 @@ struct Accumulator {
|
|
|
203
332
|
};
|
|
204
333
|
static_assert(sizeof(Accumulator) == 16, "Wrong Accumulator size");
|
|
205
334
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
public:
|
|
210
|
-
// No harm if `i` is already set.
|
|
211
|
-
void Set(size_t i) {
|
|
212
|
-
HWY_DASSERT(i < kMaxZones);
|
|
213
|
-
const size_t idx = i / 64;
|
|
214
|
-
const size_t mod = i % 64;
|
|
215
|
-
bits_[idx].Set(mod);
|
|
216
|
-
HWY_DASSERT(Get(i));
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
void Clear(size_t i) {
|
|
220
|
-
HWY_DASSERT(i < kMaxZones);
|
|
221
|
-
const size_t idx = i / 64;
|
|
222
|
-
const size_t mod = i % 64;
|
|
223
|
-
bits_[idx].Clear(mod);
|
|
224
|
-
HWY_DASSERT(!Get(i));
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
bool Get(size_t i) const {
|
|
228
|
-
HWY_DASSERT(i < kMaxZones);
|
|
229
|
-
const size_t idx = i / 64;
|
|
230
|
-
const size_t mod = i % 64;
|
|
231
|
-
return bits_[idx].Get(mod);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// Returns lowest i such that Get(i). Caller must ensure Any() beforehand!
|
|
235
|
-
size_t First() const {
|
|
236
|
-
HWY_DASSERT(bits_[0].Any() || bits_[1].Any());
|
|
237
|
-
const size_t idx = bits_[0].Any() ? 0 : 1;
|
|
238
|
-
return idx * 64 + bits_[idx].First();
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// Calls `func(i)` for each `i` in the set. It is safe for `func` to modify
|
|
242
|
-
// the set, but the current Foreach call is only affected if changing one of
|
|
243
|
-
// the not yet visited BitSet64 for which Any() is true.
|
|
244
|
-
template <class Func>
|
|
245
|
-
void Foreach(const Func& func) const {
|
|
246
|
-
bits_[0].Foreach([&func](size_t mod) { func(mod); });
|
|
247
|
-
bits_[1].Foreach([&func](size_t mod) { func(64 + mod); });
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
size_t Count() const { return bits_[0].Count() + bits_[1].Count(); }
|
|
251
|
-
|
|
252
|
-
private:
|
|
253
|
-
static_assert(kMaxZones == 128, "Update ZoneSet");
|
|
254
|
-
BitSet64 bits_[2];
|
|
255
|
-
};
|
|
256
|
-
|
|
257
|
-
// Modified from `ZoneSet`.
|
|
258
|
-
class ThreadSet {
|
|
259
|
-
public:
|
|
260
|
-
// No harm if `i` is already set.
|
|
261
|
-
void Set(size_t i) {
|
|
262
|
-
HWY_DASSERT(i < kMaxThreads);
|
|
263
|
-
const size_t idx = i / 64;
|
|
264
|
-
const size_t mod = i % 64;
|
|
265
|
-
bits_[idx].Set(mod);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
size_t Count() const {
|
|
269
|
-
size_t total = 0;
|
|
270
|
-
for (const BitSet64& bits : bits_) {
|
|
271
|
-
total += bits.Count();
|
|
272
|
-
}
|
|
273
|
-
return total;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
private:
|
|
277
|
-
BitSet64 bits_[DivCeil(kMaxThreads, size_t{64})];
|
|
278
|
-
};
|
|
335
|
+
using ZoneSet = hwy::BitSet<kMaxZones>;
|
|
336
|
+
using WorkerSet = hwy::BitSet<kMaxWorkers>;
|
|
337
|
+
using AtomicWorkerSet = hwy::AtomicBitSet<kMaxWorkers>;
|
|
279
338
|
|
|
280
339
|
// Durations are per-CPU, but end to end performance is defined by wall time.
|
|
281
340
|
// Assuming fork-join parallelism, zones are entered by multiple threads
|
|
282
341
|
// concurrently, which means the total number of unique threads is also the
|
|
283
342
|
// degree of concurrency, so we can estimate wall time as CPU time divided by
|
|
284
|
-
// the number of unique threads seen
|
|
343
|
+
// the number of unique threads seen. This is facilitated by unique `global_idx`
|
|
344
|
+
// passed in by callers, or taken from thread-local `GlobalIdx()`.
|
|
285
345
|
//
|
|
286
346
|
// We also want to support varying thread counts per call site, because the same
|
|
287
347
|
// function/zone may be called from multiple pools. `EndRootRun` calls
|
|
288
|
-
// `
|
|
348
|
+
// `CountWorkersAndReset` after each top-level `ThreadPool::Run`, which
|
|
289
349
|
// generates one data point summarized via descriptive statistics. Here we
|
|
290
|
-
// implement a simpler version of `
|
|
350
|
+
// implement a simpler version of `Stats` because we do not require
|
|
291
351
|
// geomean/variance/kurtosis/skewness. Because concurrency is a small integer,
|
|
292
352
|
// we can simply compute sums rather than online moments. There is also only one
|
|
293
|
-
// instance across all threads, hence we do not require `
|
|
353
|
+
// instance across all threads, hence we do not require a `Take`.
|
|
354
|
+
//
|
|
355
|
+
// Note that subsequently discovered prior work estimates the number of active
|
|
356
|
+
// and idle processors by updating atomic counters whenever they start/finish a
|
|
357
|
+
// task: https://homes.cs.washington.edu/~tom/pubs/quartz.pdf and "Effective
|
|
358
|
+
// performance measurement and analysis of multithreaded applications". We
|
|
359
|
+
// instead accumulate zone durations into per-thread storage.
|
|
360
|
+
// `CountWorkersAndReset` then checks how many were nonzero, which avoids
|
|
361
|
+
// expensive atomic updates and ensures accurate counts per-zone, rather than
|
|
362
|
+
// estimates of current activity at each sample.
|
|
363
|
+
// D. Vyukov's https://github.com/dvyukov/perf-load, also integrated into Linux
|
|
364
|
+
// perf, also corrects for parallelism without using atomic counters by tracing
|
|
365
|
+
// context switches. Note that we often pin threads, which avoids migrations,
|
|
366
|
+
// but reduces the number of context switch events to mainly preemptions.
|
|
294
367
|
class ConcurrencyStats {
|
|
295
368
|
public:
|
|
296
369
|
ConcurrencyStats() { Reset(); }
|
|
@@ -324,45 +397,42 @@ class ConcurrencyStats {
|
|
|
324
397
|
};
|
|
325
398
|
static_assert(sizeof(ConcurrencyStats) == (8 + 3 * sizeof(size_t)), "");
|
|
326
399
|
|
|
327
|
-
// Holds the final results across all threads, including `ConcurrencyStats
|
|
328
|
-
//
|
|
400
|
+
// Holds the final results across all threads, including `ConcurrencyStats`
|
|
401
|
+
// and `PoolStats`, updated/printed by the main thread.
|
|
329
402
|
class Results {
|
|
330
403
|
public:
|
|
331
|
-
void
|
|
332
|
-
|
|
333
|
-
HWY_DASSERT(
|
|
404
|
+
void TakeAccumulator(const size_t global_idx, const size_t zone_idx,
|
|
405
|
+
Accumulator& other) {
|
|
406
|
+
HWY_DASSERT(global_idx < kMaxWorkers);
|
|
334
407
|
HWY_DASSERT(zone_idx < kMaxZones);
|
|
335
408
|
HWY_DASSERT(other.zone.ZoneIdx() == zone_idx);
|
|
336
409
|
|
|
337
410
|
visited_zones_.Set(zone_idx);
|
|
338
|
-
totals_[zone_idx].
|
|
339
|
-
|
|
411
|
+
totals_[zone_idx].Take(other);
|
|
412
|
+
workers_[zone_idx].Set(global_idx);
|
|
340
413
|
}
|
|
341
414
|
|
|
342
415
|
// Moves the total number of threads seen during the preceding root-level
|
|
343
416
|
// `ThreadPool::Run` into one data point for `ConcurrencyStats`.
|
|
344
|
-
void
|
|
417
|
+
void CountWorkersAndReset(const size_t zone_idx) {
|
|
345
418
|
HWY_DASSERT(zone_idx < kMaxZones);
|
|
346
|
-
const size_t
|
|
347
|
-
// Although
|
|
419
|
+
const size_t num_workers = workers_[zone_idx].Count();
|
|
420
|
+
// Although workers_[zone_idx] at one point was non-empty, it is reset
|
|
348
421
|
// below, and so can be empty on the second call to this via `PrintResults`,
|
|
349
422
|
// after one from `EndRootRun`. Do not add a data point if empty.
|
|
350
|
-
if (
|
|
351
|
-
concurrency_[zone_idx].Notify(
|
|
423
|
+
if (num_workers != 0) {
|
|
424
|
+
concurrency_[zone_idx].Notify(num_workers);
|
|
352
425
|
}
|
|
353
|
-
|
|
426
|
+
workers_[zone_idx] = WorkerSet();
|
|
354
427
|
}
|
|
355
428
|
|
|
356
|
-
void
|
|
429
|
+
void CountWorkersAndReset() {
|
|
357
430
|
visited_zones_.Foreach(
|
|
358
|
-
[&](size_t zone_idx) {
|
|
431
|
+
[&](size_t zone_idx) { CountWorkersAndReset(zone_idx); });
|
|
359
432
|
}
|
|
360
433
|
|
|
361
|
-
void
|
|
362
|
-
|
|
363
|
-
void Print(const Names& names) {
|
|
364
|
-
const uint64_t t0 = timer::Start();
|
|
365
|
-
const double inv_freq = 1.0 / platform::InvariantTicksPerSecond();
|
|
434
|
+
void PrintAndReset(const Zones& zones) {
|
|
435
|
+
const double inv_freq = 1.0 / hwy::platform::InvariantTicksPerSecond();
|
|
366
436
|
|
|
367
437
|
// Sort by decreasing total (self) cost. `totals_` are sparse, so sort an
|
|
368
438
|
// index vector instead.
|
|
@@ -371,11 +441,13 @@ class Results {
|
|
|
371
441
|
visited_zones_.Foreach([&](size_t zone_idx) {
|
|
372
442
|
indices.push_back(static_cast<uint32_t>(zone_idx));
|
|
373
443
|
// In case the zone exited after `EndRootRun` and was not yet added.
|
|
374
|
-
|
|
444
|
+
CountWorkersAndReset(zone_idx);
|
|
375
445
|
});
|
|
376
446
|
std::sort(indices.begin(), indices.end(), [&](uint32_t a, uint32_t b) {
|
|
377
447
|
return totals_[a].duration > totals_[b].duration;
|
|
378
448
|
});
|
|
449
|
+
printf(" %-40s: %10s x %15s / %5s (%5s %3s-%3s) = %9s\n", "Zone", "Calls",
|
|
450
|
+
"Cycles/Call", "Avg Count", "Count", "Min", "Max", "Wall Time(s)");
|
|
379
451
|
|
|
380
452
|
for (uint32_t zone_idx : indices) {
|
|
381
453
|
Accumulator& total = totals_[zone_idx]; // cleared after printing
|
|
@@ -391,29 +463,23 @@ class Results {
|
|
|
391
463
|
// Avoid division by zero.
|
|
392
464
|
const double concurrency_divisor = HWY_MAX(1.0, avg_concurrency);
|
|
393
465
|
printf("%s%-40s: %10.0f x %15.0f / %5.1f (%5zu %3zu-%3zu) = %9.6f\n",
|
|
394
|
-
total.zone.IsInclusive() ? "(I)" : " ",
|
|
466
|
+
total.zone.IsInclusive() ? "(I)" : " ", zones.Name(total.zone),
|
|
395
467
|
static_cast<double>(total.num_calls), per_call, avg_concurrency,
|
|
396
468
|
concurrency.Count(), concurrency.Min(), concurrency.Max(),
|
|
397
469
|
duration * inv_freq / concurrency_divisor);
|
|
398
470
|
|
|
399
471
|
total = Accumulator();
|
|
400
472
|
concurrency.Reset();
|
|
401
|
-
// `
|
|
473
|
+
// `workers_` was already reset by `CountWorkersAndReset`.
|
|
402
474
|
}
|
|
403
475
|
visited_zones_ = ZoneSet();
|
|
404
|
-
|
|
405
|
-
AddAnalysisTime(t0);
|
|
406
|
-
printf("Total analysis [s]: %f\n",
|
|
407
|
-
static_cast<double>(analyze_elapsed_) * inv_freq);
|
|
408
|
-
analyze_elapsed_ = 0;
|
|
409
476
|
}
|
|
410
477
|
|
|
411
478
|
private:
|
|
412
|
-
uint64_t analyze_elapsed_ = 0;
|
|
413
479
|
// Indicates which of the array entries are in use.
|
|
414
480
|
ZoneSet visited_zones_;
|
|
415
481
|
Accumulator totals_[kMaxZones];
|
|
416
|
-
|
|
482
|
+
WorkerSet workers_[kMaxZones];
|
|
417
483
|
ConcurrencyStats concurrency_[kMaxZones];
|
|
418
484
|
};
|
|
419
485
|
|
|
@@ -421,33 +487,33 @@ class Results {
|
|
|
421
487
|
// with frequency throttling disabled, this has a multimodal distribution,
|
|
422
488
|
// including 32, 34, 48, 52, 59, 62.
|
|
423
489
|
struct Overheads {
|
|
424
|
-
|
|
425
|
-
|
|
490
|
+
uint64_t self = 0;
|
|
491
|
+
uint64_t child = 0;
|
|
426
492
|
};
|
|
427
|
-
static_assert(sizeof(Overheads) ==
|
|
493
|
+
static_assert(sizeof(Overheads) == 16, "Wrong Overheads size");
|
|
428
494
|
|
|
429
495
|
class Accumulators {
|
|
430
496
|
// We generally want to group threads together because they are often
|
|
431
497
|
// accessed together during a zone, but also want to avoid threads sharing a
|
|
432
|
-
// cache line. Hence interleave 8 zones per
|
|
498
|
+
// cache line. Hence interleave 8 zones per worker.
|
|
433
499
|
static constexpr size_t kPerLine = HWY_ALIGNMENT / sizeof(Accumulator);
|
|
434
500
|
|
|
435
501
|
public:
|
|
436
|
-
Accumulator& Get(const size_t
|
|
437
|
-
HWY_DASSERT(
|
|
502
|
+
Accumulator& Get(const size_t global_idx, const size_t zone_idx) {
|
|
503
|
+
HWY_DASSERT(global_idx < kMaxWorkers);
|
|
438
504
|
HWY_DASSERT(zone_idx < kMaxZones);
|
|
439
505
|
const size_t line = zone_idx / kPerLine;
|
|
440
506
|
const size_t offset = zone_idx % kPerLine;
|
|
441
|
-
return zones_[(line *
|
|
507
|
+
return zones_[(line * kMaxWorkers + global_idx) * kPerLine + offset];
|
|
442
508
|
}
|
|
443
509
|
|
|
444
510
|
private:
|
|
445
|
-
Accumulator zones_[kMaxZones *
|
|
511
|
+
Accumulator zones_[kMaxZones * kMaxWorkers];
|
|
446
512
|
};
|
|
447
513
|
|
|
448
514
|
// Reacts to zone enter/exit events. Builds a stack of active zones and
|
|
449
515
|
// accumulates self/child duration for each.
|
|
450
|
-
class
|
|
516
|
+
class PerWorker {
|
|
451
517
|
public:
|
|
452
518
|
template <typename T>
|
|
453
519
|
static T ClampedSubtract(const T minuend, const T subtrahend) {
|
|
@@ -467,12 +533,11 @@ class PerThread {
|
|
|
467
533
|
t_enter_[depth] = t_enter;
|
|
468
534
|
child_total_[1 + depth] = 0;
|
|
469
535
|
depth_ = 1 + depth;
|
|
470
|
-
HWY_IF_CONSTEXPR(HWY_IS_DEBUG_BUILD) { any_ = 1; }
|
|
471
536
|
}
|
|
472
537
|
|
|
473
538
|
// Exiting the most recently entered zone (top of stack).
|
|
474
|
-
void Exit(const uint64_t t_exit, const size_t
|
|
475
|
-
Accumulators& accumulators) {
|
|
539
|
+
void Exit(const uint64_t t_exit, const size_t global_idx,
|
|
540
|
+
const ZoneHandle zone, Accumulators& accumulators) {
|
|
476
541
|
HWY_DASSERT(depth_ > 0);
|
|
477
542
|
const size_t depth = depth_ - 1;
|
|
478
543
|
const size_t zone_idx = zone.ZoneIdx();
|
|
@@ -484,8 +549,8 @@ class PerThread {
|
|
|
484
549
|
|
|
485
550
|
const uint64_t self_duration = ClampedSubtract(
|
|
486
551
|
duration, overheads_.self + overheads_.child + child_total);
|
|
487
|
-
accumulators.Get(
|
|
488
|
-
// For faster
|
|
552
|
+
accumulators.Get(global_idx, zone_idx).Add(zone, self_duration);
|
|
553
|
+
// For faster TakeAccumulator() - not all zones are encountered.
|
|
489
554
|
visited_zones_.Set(zone_idx);
|
|
490
555
|
|
|
491
556
|
// Adding this nested time to the parent's `child_total` will
|
|
@@ -495,11 +560,10 @@ class PerThread {
|
|
|
495
560
|
depth_ = depth;
|
|
496
561
|
}
|
|
497
562
|
|
|
498
|
-
bool HadAnyZones() const { return HWY_IS_DEBUG_BUILD ? (any_ != 0) : false; }
|
|
499
|
-
|
|
500
563
|
// Returns the duration of one enter/exit pair and resets all state. Called
|
|
501
564
|
// via `DetectSelfOverhead`.
|
|
502
|
-
uint64_t GetFirstDurationAndReset(size_t
|
|
565
|
+
uint64_t GetFirstDurationAndReset(size_t global_idx,
|
|
566
|
+
Accumulators& accumulators) {
|
|
503
567
|
HWY_DASSERT(depth_ == 0);
|
|
504
568
|
|
|
505
569
|
HWY_DASSERT(visited_zones_.Count() == 1);
|
|
@@ -508,32 +572,28 @@ class PerThread {
|
|
|
508
572
|
HWY_DASSERT(visited_zones_.Get(zone_idx));
|
|
509
573
|
visited_zones_.Clear(zone_idx);
|
|
510
574
|
|
|
511
|
-
Accumulator& zone = accumulators.Get(
|
|
575
|
+
Accumulator& zone = accumulators.Get(global_idx, zone_idx);
|
|
512
576
|
const uint64_t duration = zone.duration;
|
|
513
577
|
zone = Accumulator();
|
|
514
578
|
return duration;
|
|
515
579
|
}
|
|
516
580
|
|
|
517
581
|
// Adds all data to `results` and resets it here. Called from the main thread.
|
|
518
|
-
void MoveTo(const size_t
|
|
582
|
+
void MoveTo(const size_t global_idx, Accumulators& accumulators,
|
|
519
583
|
Results& results) {
|
|
520
|
-
const uint64_t t0 = timer::Start();
|
|
521
|
-
|
|
522
584
|
visited_zones_.Foreach([&](size_t zone_idx) {
|
|
523
|
-
results.
|
|
585
|
+
results.TakeAccumulator(global_idx, zone_idx,
|
|
586
|
+
accumulators.Get(global_idx, zone_idx));
|
|
524
587
|
});
|
|
525
588
|
// OK to reset even if we have active zones, because we set `visited_zones_`
|
|
526
589
|
// when exiting the zone.
|
|
527
590
|
visited_zones_ = ZoneSet();
|
|
528
|
-
|
|
529
|
-
results.AddAnalysisTime(t0);
|
|
530
591
|
}
|
|
531
592
|
|
|
532
593
|
private:
|
|
533
594
|
// 40 bytes:
|
|
534
|
-
ZoneSet visited_zones_; // Which `zones_` have been active on this
|
|
595
|
+
ZoneSet visited_zones_; // Which `zones_` have been active on this worker.
|
|
535
596
|
uint64_t depth_ = 0; // Current nesting level for active zones.
|
|
536
|
-
uint64_t any_ = 0;
|
|
537
597
|
Overheads overheads_;
|
|
538
598
|
|
|
539
599
|
uint64_t t_enter_[kMaxDepth];
|
|
@@ -541,9 +601,8 @@ class PerThread {
|
|
|
541
601
|
// Shifting by one avoids bounds-checks for depth_ = 0 (root zone).
|
|
542
602
|
uint64_t child_total_[1 + kMaxDepth] = {0};
|
|
543
603
|
};
|
|
544
|
-
|
|
545
604
|
// Enables shift rather than multiplication.
|
|
546
|
-
static_assert(sizeof(
|
|
605
|
+
static_assert(sizeof(PerWorker) == 256, "Wrong size");
|
|
547
606
|
|
|
548
607
|
} // namespace profiler
|
|
549
608
|
|
|
@@ -551,37 +610,59 @@ class Profiler {
|
|
|
551
610
|
public:
|
|
552
611
|
static HWY_DLLEXPORT Profiler& Get();
|
|
553
612
|
|
|
554
|
-
//
|
|
555
|
-
//
|
|
556
|
-
//
|
|
557
|
-
// `
|
|
558
|
-
//
|
|
559
|
-
|
|
560
|
-
static
|
|
613
|
+
// Returns `global_idx` from thread-local storage (0 for the main thread).
|
|
614
|
+
// Used by `PROFILER_ZONE/PROFILER_FUNC`. It is faster to instead pass the
|
|
615
|
+
// global_idx from `ThreadPool::Run` (if constructed with non-default
|
|
616
|
+
// `PoolWorkerMapping`) to `PROFILER_ZONE2/PROFILER_ZONE3`.
|
|
617
|
+
// DEPRECATED: use `GlobalIdx` instead.
|
|
618
|
+
static size_t Thread() { return s_global_idx; }
|
|
619
|
+
static size_t GlobalIdx() { return s_global_idx; }
|
|
620
|
+
// Must be called from all worker threads, and once also on the main thread,
|
|
621
|
+
// before any use of `PROFILER_ZONE/PROFILER_FUNC`.
|
|
622
|
+
static void SetGlobalIdx(size_t global_idx) { s_global_idx = global_idx; }
|
|
623
|
+
|
|
624
|
+
void ReserveWorker(size_t global_idx) {
|
|
625
|
+
HWY_ASSERT(!workers_reserved_.Get(global_idx));
|
|
626
|
+
workers_reserved_.Set(global_idx);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
void FreeWorker(size_t global_idx) {
|
|
630
|
+
HWY_ASSERT(workers_reserved_.Get(global_idx));
|
|
631
|
+
workers_reserved_.Clear(global_idx);
|
|
632
|
+
}
|
|
561
633
|
|
|
562
|
-
//
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
// the main-first worker numbering used by `ThreadPool`.
|
|
567
|
-
static size_t Thread() { return s_thread; }
|
|
634
|
+
// Called by `Zone` from any thread.
|
|
635
|
+
void Enter(uint64_t t_enter, size_t global_idx) {
|
|
636
|
+
GetWorker(global_idx).Enter(t_enter);
|
|
637
|
+
}
|
|
568
638
|
|
|
569
|
-
//
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
void SetMaxThreads(size_t max_threads) {
|
|
573
|
-
HWY_ASSERT(max_threads <= profiler::kMaxThreads);
|
|
574
|
-
max_threads_ = max_threads;
|
|
639
|
+
// Called by `~Zone` from any thread.
|
|
640
|
+
void Exit(uint64_t t_exit, size_t global_idx, profiler::ZoneHandle zone) {
|
|
641
|
+
GetWorker(global_idx).Exit(t_exit, global_idx, zone, accumulators_);
|
|
575
642
|
}
|
|
576
643
|
|
|
577
|
-
|
|
644
|
+
uint64_t GetFirstDurationAndReset(size_t global_idx) {
|
|
645
|
+
return GetWorker(global_idx)
|
|
646
|
+
.GetFirstDurationAndReset(global_idx, accumulators_);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
const char* Name(profiler::ZoneHandle zone) const {
|
|
650
|
+
return zones_.Name(zone);
|
|
651
|
+
}
|
|
578
652
|
|
|
579
653
|
// Copies `name` into the string table and returns its unique `zone`. Uses
|
|
580
654
|
// linear search, which is fine because this is called during static init.
|
|
581
655
|
// Called via static initializer and the result is passed to the `Zone` ctor.
|
|
582
656
|
profiler::ZoneHandle AddZone(const char* name,
|
|
583
657
|
ProfilerFlags flags = ProfilerFlags::kDefault) {
|
|
584
|
-
return
|
|
658
|
+
return zones_.AddZone(name, flags);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
void AddFunc(void* owner, ProfilerFunc func) {
|
|
662
|
+
funcs_.Add(reinterpret_cast<intptr_t>(owner), func);
|
|
663
|
+
}
|
|
664
|
+
void RemoveFunc(void* owner) {
|
|
665
|
+
funcs_.Remove(reinterpret_cast<intptr_t>(owner));
|
|
585
666
|
}
|
|
586
667
|
|
|
587
668
|
// For reporting average concurrency. Called by `ThreadPool::Run` on the main
|
|
@@ -602,9 +683,9 @@ class Profiler {
|
|
|
602
683
|
// broadcasts to "all cores", but there is no universal guarantee.
|
|
603
684
|
//
|
|
604
685
|
// Under the assumption that all concurrency is via our `ThreadPool`, we can
|
|
605
|
-
// record all `
|
|
686
|
+
// record all `global_idx` for each outermost (root) `ThreadPool::Run`. This
|
|
606
687
|
// collapses all nested pools into one 'invocation'. We then compute per-zone
|
|
607
|
-
// concurrency as the number of unique `
|
|
688
|
+
// concurrency as the number of unique `global_idx` seen per invocation.
|
|
608
689
|
bool IsRootRun() {
|
|
609
690
|
// We are not the root if a Run was already active.
|
|
610
691
|
return !run_active_.test_and_set(std::memory_order_acquire);
|
|
@@ -616,7 +697,7 @@ class Profiler {
|
|
|
616
697
|
// when `PrintResults` is called.
|
|
617
698
|
void EndRootRun() {
|
|
618
699
|
UpdateResults();
|
|
619
|
-
results_.
|
|
700
|
+
results_.CountWorkersAndReset();
|
|
620
701
|
|
|
621
702
|
run_active_.clear(std::memory_order_release);
|
|
622
703
|
}
|
|
@@ -625,58 +706,59 @@ class Profiler {
|
|
|
625
706
|
// zones. Resets all state, can be called again after more zones.
|
|
626
707
|
void PrintResults() {
|
|
627
708
|
UpdateResults();
|
|
628
|
-
// `
|
|
709
|
+
// `CountWorkersAndReset` is fused into `Print`, so do not call it here.
|
|
629
710
|
|
|
630
|
-
results_.
|
|
631
|
-
}
|
|
711
|
+
results_.PrintAndReset(zones_);
|
|
632
712
|
|
|
633
|
-
|
|
634
|
-
profiler::PerThread& GetThread(size_t thread) {
|
|
635
|
-
HWY_DASSERT(thread < profiler::kMaxThreads);
|
|
636
|
-
return threads_[thread];
|
|
713
|
+
funcs_.CallAll();
|
|
637
714
|
}
|
|
638
|
-
|
|
715
|
+
|
|
716
|
+
// TODO: remove when no longer called.
|
|
717
|
+
void SetMaxThreads(size_t) {}
|
|
639
718
|
|
|
640
719
|
private:
|
|
641
720
|
// Sets main thread index, computes self-overhead, and checks timer support.
|
|
642
721
|
Profiler();
|
|
643
722
|
|
|
644
|
-
|
|
723
|
+
profiler::PerWorker& GetWorker(size_t global_idx) {
|
|
724
|
+
HWY_DASSERT(workers_reserved_.Get(global_idx));
|
|
725
|
+
return workers_[global_idx];
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// Moves accumulators into Results. Called from the main thread.
|
|
645
729
|
void UpdateResults() {
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
}
|
|
730
|
+
// Ensure we see all writes from before the workers' release fence.
|
|
731
|
+
std::atomic_thread_fence(std::memory_order_acquire);
|
|
649
732
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
++thread) {
|
|
654
|
-
HWY_ASSERT(!threads_[thread].HadAnyZones());
|
|
655
|
-
}
|
|
656
|
-
}
|
|
733
|
+
workers_reserved_.Foreach([&](size_t global_idx) {
|
|
734
|
+
workers_[global_idx].MoveTo(global_idx, accumulators_, results_);
|
|
735
|
+
});
|
|
657
736
|
}
|
|
658
737
|
|
|
659
|
-
static thread_local size_t
|
|
660
|
-
|
|
661
|
-
|
|
738
|
+
static thread_local size_t s_global_idx;
|
|
739
|
+
|
|
740
|
+
// These are atomic because `ThreadFunc` reserves its slot(s) and even
|
|
741
|
+
// `ThreadPool::ThreadPool` may be called concurrently. Both have bit `i` set
|
|
742
|
+
// between calls to `Reserve*(i)` and `Free*(i)`. They are consulted in
|
|
743
|
+
// `UpdateResults` and to validate arguments in debug builds, and only updated
|
|
744
|
+
// in the pool/thread init/shutdown.
|
|
745
|
+
profiler::AtomicWorkerSet workers_reserved_;
|
|
662
746
|
|
|
663
747
|
std::atomic_flag run_active_ = ATOMIC_FLAG_INIT;
|
|
664
748
|
|
|
665
|
-
|
|
749
|
+
profiler::Funcs funcs_;
|
|
750
|
+
|
|
751
|
+
// To avoid locking, each worker has its own working set. We could access this
|
|
666
752
|
// through `thread_local` pointers, but that is slow to read on x86. Because
|
|
667
|
-
// our `ThreadPool` anyway passes a `
|
|
753
|
+
// our `ThreadPool` anyway passes a `global_idx` argument, we can instead pass
|
|
668
754
|
// that through the `PROFILER_ZONE2/PROFILER_ZONE3` macros.
|
|
669
|
-
profiler::
|
|
755
|
+
profiler::PerWorker workers_[profiler::kMaxWorkers];
|
|
670
756
|
|
|
671
757
|
profiler::Accumulators accumulators_;
|
|
672
758
|
|
|
673
|
-
// Updated by the main thread after the root `ThreadPool::Run` and during
|
|
674
|
-
// `PrintResults`.
|
|
675
|
-
profiler::ConcurrencyStats concurrency_[profiler::kMaxZones];
|
|
676
|
-
|
|
677
|
-
profiler::Names names_;
|
|
678
|
-
|
|
679
759
|
profiler::Results results_;
|
|
760
|
+
|
|
761
|
+
profiler::Zones zones_;
|
|
680
762
|
};
|
|
681
763
|
|
|
682
764
|
namespace profiler {
|
|
@@ -684,35 +766,33 @@ namespace profiler {
|
|
|
684
766
|
// RAII for zone entry/exit.
|
|
685
767
|
class Zone {
|
|
686
768
|
public:
|
|
687
|
-
// Thread-compatible; must not
|
|
688
|
-
//
|
|
689
|
-
// - passed from `ThreadPool`
|
|
690
|
-
//
|
|
691
|
-
//
|
|
692
|
-
// -
|
|
693
|
-
|
|
694
|
-
Zone(Profiler& profiler, size_t thread, ZoneHandle zone)
|
|
769
|
+
// Thread-compatible; must not call concurrently with the same `global_idx`,
|
|
770
|
+
// which is either:
|
|
771
|
+
// - passed from `ThreadPool::Run` (if it was constructed with non-default
|
|
772
|
+
// `PoolWorkerMapping`) to `PROFILER_ZONE2/PROFILER_ZONE3`;
|
|
773
|
+
// - obtained from `Profiler::GlobalIdx()`; or
|
|
774
|
+
// - 0 if running on the main thread.
|
|
775
|
+
Zone(Profiler& profiler, size_t global_idx, ZoneHandle zone)
|
|
695
776
|
: profiler_(profiler) {
|
|
696
777
|
HWY_FENCE;
|
|
697
778
|
const uint64_t t_enter = timer::Start();
|
|
698
779
|
HWY_FENCE;
|
|
699
|
-
|
|
780
|
+
global_idx_ = static_cast<uint32_t>(global_idx);
|
|
700
781
|
zone_ = zone;
|
|
701
|
-
profiler.
|
|
782
|
+
profiler.Enter(t_enter, global_idx);
|
|
702
783
|
HWY_FENCE;
|
|
703
784
|
}
|
|
704
785
|
|
|
705
786
|
~Zone() {
|
|
706
787
|
HWY_FENCE;
|
|
707
788
|
const uint64_t t_exit = timer::Stop();
|
|
708
|
-
profiler_.
|
|
709
|
-
profiler_.Accumulators());
|
|
789
|
+
profiler_.Exit(t_exit, static_cast<size_t>(global_idx_), zone_);
|
|
710
790
|
HWY_FENCE;
|
|
711
791
|
}
|
|
712
792
|
|
|
713
793
|
private:
|
|
714
794
|
Profiler& profiler_;
|
|
715
|
-
uint32_t
|
|
795
|
+
uint32_t global_idx_;
|
|
716
796
|
ZoneHandle zone_;
|
|
717
797
|
};
|
|
718
798
|
|
|
@@ -726,9 +806,15 @@ struct ZoneHandle {};
|
|
|
726
806
|
struct Profiler {
|
|
727
807
|
static HWY_DLLEXPORT Profiler& Get();
|
|
728
808
|
|
|
729
|
-
|
|
809
|
+
// DEPRECATED: use `GlobalIdx` instead.
|
|
730
810
|
static size_t Thread() { return 0; }
|
|
731
|
-
|
|
811
|
+
static size_t GlobalIdx() { return 0; }
|
|
812
|
+
static void SetGlobalIdx(size_t) {}
|
|
813
|
+
void ReserveWorker(size_t) {}
|
|
814
|
+
void FreeWorker(size_t) {}
|
|
815
|
+
void Enter(uint64_t, size_t) {}
|
|
816
|
+
void Exit(uint64_t, size_t, profiler::ZoneHandle) {}
|
|
817
|
+
uint64_t GetFirstDurationAndReset(size_t) { return 0; }
|
|
732
818
|
|
|
733
819
|
const char* Name(profiler::ZoneHandle) const { return nullptr; }
|
|
734
820
|
profiler::ZoneHandle AddZone(const char*,
|
|
@@ -736,10 +822,15 @@ struct Profiler {
|
|
|
736
822
|
return profiler::ZoneHandle();
|
|
737
823
|
}
|
|
738
824
|
|
|
825
|
+
void AddFunc(void*, ProfilerFunc) {}
|
|
826
|
+
void RemoveFunc(void*) {}
|
|
827
|
+
|
|
739
828
|
bool IsRootRun() { return false; }
|
|
740
829
|
void EndRootRun() {}
|
|
741
|
-
|
|
742
830
|
void PrintResults() {}
|
|
831
|
+
|
|
832
|
+
// TODO: remove when no longer called.
|
|
833
|
+
void SetMaxThreads(size_t) {}
|
|
743
834
|
};
|
|
744
835
|
|
|
745
836
|
namespace profiler {
|
|
@@ -754,26 +845,26 @@ struct Zone {
|
|
|
754
845
|
|
|
755
846
|
// Creates a `Zone` lvalue with a line-dependent name, which records the elapsed
|
|
756
847
|
// time from here until the end of the current scope. `p` is from
|
|
757
|
-
// `Profiler::Get()` or a cached reference. `
|
|
848
|
+
// `Profiler::Get()` or a cached reference. `global_idx < kMaxWorkers`. `zone`
|
|
758
849
|
// is the return value of `AddZone`. Separating its static init from the `Zone`
|
|
759
850
|
// may be more efficient than `PROFILER_ZONE2`.
|
|
760
|
-
#define PROFILER_ZONE3(p,
|
|
761
|
-
HWY_FENCE;
|
|
762
|
-
const hwy::profiler::Zone HWY_CONCAT(Z, __LINE__)(p,
|
|
851
|
+
#define PROFILER_ZONE3(p, global_idx, zone) \
|
|
852
|
+
HWY_FENCE; \
|
|
853
|
+
const hwy::profiler::Zone HWY_CONCAT(Z, __LINE__)(p, global_idx, zone); \
|
|
763
854
|
HWY_FENCE
|
|
764
855
|
|
|
765
856
|
// For compatibility with old callers that do not pass `p` nor `flags`.
|
|
766
|
-
// Also calls AddZone. Usage: `PROFILER_ZONE2(
|
|
767
|
-
#define PROFILER_ZONE2(
|
|
857
|
+
// Also calls AddZone. Usage: `PROFILER_ZONE2(global_idx, "MyZone");`
|
|
858
|
+
#define PROFILER_ZONE2(global_idx, name) \
|
|
768
859
|
static const hwy::profiler::ZoneHandle HWY_CONCAT(zone, __LINE__) = \
|
|
769
860
|
hwy::Profiler::Get().AddZone(name); \
|
|
770
|
-
PROFILER_ZONE3(hwy::Profiler::Get(),
|
|
771
|
-
#define PROFILER_FUNC2(
|
|
861
|
+
PROFILER_ZONE3(hwy::Profiler::Get(), global_idx, HWY_CONCAT(zone, __LINE__))
|
|
862
|
+
#define PROFILER_FUNC2(global_idx) PROFILER_ZONE2(global_idx, __func__)
|
|
772
863
|
|
|
773
|
-
// OBSOLETE: it is more efficient to pass `
|
|
864
|
+
// OBSOLETE: it is more efficient to pass `global_idx` from `ThreadPool` to
|
|
774
865
|
// `PROFILER_ZONE2/PROFILER_ZONE3`. Here we get it from thread_local storage.
|
|
775
|
-
#define PROFILER_ZONE(name) PROFILER_ZONE2(hwy::Profiler::
|
|
776
|
-
#define PROFILER_FUNC PROFILER_FUNC2(hwy::Profiler::
|
|
866
|
+
#define PROFILER_ZONE(name) PROFILER_ZONE2(hwy::Profiler::GlobalIdx(), name)
|
|
867
|
+
#define PROFILER_FUNC PROFILER_FUNC2(hwy::Profiler::GlobalIdx())
|
|
777
868
|
|
|
778
869
|
// DEPRECATED: Use `hwy::Profiler::Get()` directly instead.
|
|
779
870
|
#define PROFILER_ADD_ZONE(name) hwy::Profiler::Get().AddZone(name)
|