react-native-windows 0.81.35 → 0.81.37
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/Microsoft.ReactNative/Fabric/Composition/TooltipService.cpp +48 -2
- package/Microsoft.ReactNative/Fabric/WindowsImageManager.cpp +40 -13
- package/Microsoft.ReactNative/TurboModulesProvider.cpp +30 -13
- package/PropertySheets/Generated/PackageVersion.g.props +3 -3
- package/Scripts/creaternwlib.cmd +2 -2
- package/external/fast-float/.syncignore +0 -1
- package/external/fast-float/README.md +545 -0
- package/external/fmt/.syncignore +0 -2
- package/external/fmt/README.md +484 -0
- package/external/fmt/doc/ChangeLog-old.md +3290 -0
- package/external/fmt/doc/api.md +650 -0
- package/external/fmt/doc/fmt.css +61 -0
- package/external/fmt/doc/fmt.js +4 -0
- package/external/fmt/doc/get-started.md +222 -0
- package/external/fmt/doc/index.md +151 -0
- package/external/fmt/doc/perf.svg +1 -0
- package/external/fmt/doc/python-license.txt +290 -0
- package/external/fmt/doc/syntax.md +887 -0
- package/external/folly/.syncignore +2 -3
- package/external/folly/README.md +263 -0
- package/external/folly/folly/docs/AtomicHashMap.md +134 -0
- package/external/folly/folly/docs/Benchmark.md +310 -0
- package/external/folly/folly/docs/Conv.md +291 -0
- package/external/folly/folly/docs/Dynamic.md +220 -0
- package/external/folly/folly/docs/DynamicConverter.md +71 -0
- package/external/folly/folly/docs/Executors.md +63 -0
- package/external/folly/folly/docs/FBString.md +46 -0
- package/external/folly/folly/docs/FBVector.md +200 -0
- package/external/folly/folly/docs/Fbvector--graphical_solutions.png +0 -0
- package/external/folly/folly/docs/Format.md +177 -0
- package/external/folly/folly/docs/Function.md +92 -0
- package/external/folly/folly/docs/Futures.md +263 -0
- package/external/folly/folly/docs/GroupVarint.md +46 -0
- package/external/folly/folly/docs/Histogram.md +104 -0
- package/external/folly/folly/docs/MapUtils.md +80 -0
- package/external/folly/folly/docs/Overview.md +263 -0
- package/external/folly/folly/docs/PackedSyncPtr.md +77 -0
- package/external/folly/folly/docs/Poly.md +500 -0
- package/external/folly/folly/docs/ProducerConsumerQueue.md +82 -0
- package/external/folly/folly/docs/Rcu.md +218 -0
- package/external/folly/folly/docs/SmallLocks.md +24 -0
- package/external/folly/folly/docs/Synchronized.md +793 -0
- package/external/folly/folly/docs/ThreadCachedInt.md +98 -0
- package/external/folly/folly/docs/ThreadLocal.md +105 -0
- package/external/folly/folly/docs/Traits.md +161 -0
- package/external/folly/folly/docs/small_vector.md +64 -0
- package/external/folly/folly/docs/style.css +23 -0
- package/external/folly/static/logo.svg +1 -0
- package/package.json +2 -2
- package/templates/cpp-lib/example/metro.config.js +31 -6
- package/Scripts/Warm-RnwFeedCache.ps1 +0 -413
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
`folly/dynamic.h`
|
|
2
|
+
-----------------
|
|
3
|
+
|
|
4
|
+
`folly/dynamic.h` provides a runtime dynamically typed value for
|
|
5
|
+
C++, similar to the way languages with runtime type systems work
|
|
6
|
+
(e.g. Python). It can hold types from a predetermined set of types
|
|
7
|
+
(ints, bools, arrays of other dynamics, etc), similar to something like
|
|
8
|
+
`std::variant`, but the syntax is intended to be a little more like
|
|
9
|
+
using the native type directly.
|
|
10
|
+
|
|
11
|
+
### Overview
|
|
12
|
+
***
|
|
13
|
+
|
|
14
|
+
Here are some code samples to get started (assumes a `using
|
|
15
|
+
folly::dynamic;` was used):
|
|
16
|
+
|
|
17
|
+
``` Cpp
|
|
18
|
+
dynamic twelve = 12; // creates a dynamic that holds an integer
|
|
19
|
+
dynamic str = "string"; // yep, this one is an fbstring
|
|
20
|
+
|
|
21
|
+
// A few other types.
|
|
22
|
+
dynamic nul = nullptr;
|
|
23
|
+
dynamic boolean = false;
|
|
24
|
+
|
|
25
|
+
// Arrays can be initialized with dynamic::array.
|
|
26
|
+
dynamic array = dynamic::array("array ", "of ", 4, " elements");
|
|
27
|
+
assert(array.size() == 4);
|
|
28
|
+
dynamic emptyArray = dynamic::array;
|
|
29
|
+
assert(emptyArray.empty());
|
|
30
|
+
|
|
31
|
+
// Maps from dynamics to dynamics are called objects. The
|
|
32
|
+
// dynamic::object constant is how you make an empty map from dynamics
|
|
33
|
+
// to dynamics.
|
|
34
|
+
dynamic map = dynamic::object;
|
|
35
|
+
map["something"] = 12;
|
|
36
|
+
map["another_something"] = map["something"] * 2;
|
|
37
|
+
|
|
38
|
+
// Dynamic objects may be initialized this way
|
|
39
|
+
dynamic map2 = dynamic::object("something", 12)("another_something", 24);
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Runtime Type Checking and Conversions
|
|
43
|
+
***
|
|
44
|
+
|
|
45
|
+
Any operation on a dynamic requires checking at runtime that the
|
|
46
|
+
type is compatible with the operation. If it isn't, you'll get a
|
|
47
|
+
`folly::TypeError`. Other exceptions can also be thrown if
|
|
48
|
+
you try to do something impossible (e.g. if you put a very large
|
|
49
|
+
64-bit integer in and try to read it out as a double).
|
|
50
|
+
|
|
51
|
+
More examples should hopefully clarify this:
|
|
52
|
+
|
|
53
|
+
``` Cpp
|
|
54
|
+
dynamic dint = 42;
|
|
55
|
+
|
|
56
|
+
dynamic str = "foo";
|
|
57
|
+
dynamic anotherStr = str + "something"; // fine
|
|
58
|
+
dynamic thisThrows = str + dint; // TypeError is raised
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Explicit type conversions can be requested for some of the basic types:
|
|
62
|
+
|
|
63
|
+
``` Cpp
|
|
64
|
+
dynamic dint = 12345678;
|
|
65
|
+
dynamic doub = dint.asDouble(); // doub will hold 12345678.0
|
|
66
|
+
dynamic str = dint.asString(); // str == "12345678"
|
|
67
|
+
|
|
68
|
+
dynamic hugeInt = std::numeric_limits<int64_t>::max();
|
|
69
|
+
dynamic hugeDoub = hugeInt.asDouble(); // throws a folly/Conv.h error,
|
|
70
|
+
// since it can't fit in a double
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
For more complicated conversions, see [DynamicConverter](DynamicConverter.md).
|
|
74
|
+
|
|
75
|
+
### Comparison Operators and Hashing
|
|
76
|
+
#### Equality Operators
|
|
77
|
+
Equality operators (`==`, `!=`) are supported for all types.
|
|
78
|
+
|
|
79
|
+
For dynamics of the same type, the underlying equality operator shall apply.
|
|
80
|
+
|
|
81
|
+
For comparisons between different numeric types (double and int64), numeric
|
|
82
|
+
equality will apply - thus `2.0 == 2`.
|
|
83
|
+
|
|
84
|
+
Values of any other different types will be deemed to not be equal.
|
|
85
|
+
|
|
86
|
+
#### Ordering operators
|
|
87
|
+
Ordering operators (`<`, `<=`, `>`, `>=`) are supported for all types, except
|
|
88
|
+
`dynamic::object` which will throw if it is involved in an ordering operator.
|
|
89
|
+
|
|
90
|
+
For dynamics of the same type, the underlying ordering operator shall apply.
|
|
91
|
+
|
|
92
|
+
For comparisons between different numeric types (double and int64), numeric
|
|
93
|
+
ordering will apply - thus `1.5 < 2`.
|
|
94
|
+
|
|
95
|
+
Ordering of values between other different types will maintain total ordering
|
|
96
|
+
properties and be consistent within a given binary run, and thus safe for use in
|
|
97
|
+
e.g. `std::set`. The actual ordering is undefined and could change across
|
|
98
|
+
versions, thus a dependency should not be taken outside of the total ordering
|
|
99
|
+
property within a given binary.
|
|
100
|
+
|
|
101
|
+
#### Hashing
|
|
102
|
+
Hashing is supported by all types, and the hashes of two values will match if
|
|
103
|
+
they are equal per `dynamic::operator==`.
|
|
104
|
+
|
|
105
|
+
As a result, numerical types have the same numerical hashing regardless of int64
|
|
106
|
+
vs double - so e.g. `std::hash<dynamic>()(2)` will give the same value as
|
|
107
|
+
`std::hash<dynamic>()(2.0)`.
|
|
108
|
+
|
|
109
|
+
### Iteration and Lookup
|
|
110
|
+
***
|
|
111
|
+
|
|
112
|
+
You can iterate over dynamic arrays as you would over any C++ sequence container.
|
|
113
|
+
|
|
114
|
+
``` Cpp
|
|
115
|
+
dynamic array = dynamic::array(2, 3, "foo");
|
|
116
|
+
|
|
117
|
+
for (auto& val : array) {
|
|
118
|
+
doSomethingWith(val);
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
You can iterate over dynamic maps by calling `items()`, `keys()`,
|
|
123
|
+
`values()`, which behave similarly to the homonymous methods of Python
|
|
124
|
+
dictionaries.
|
|
125
|
+
|
|
126
|
+
``` Cpp
|
|
127
|
+
dynamic obj = dynamic::object(2, 3)("hello", "world")("x", 4);
|
|
128
|
+
|
|
129
|
+
for (auto& pair : obj.items()) {
|
|
130
|
+
// Key is pair.first, value is pair.second
|
|
131
|
+
processKey(pair.first);
|
|
132
|
+
processValue(pair.second);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
for (auto& key : obj.keys()) {
|
|
136
|
+
processKey(key);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
for (auto& value : obj.values()) {
|
|
140
|
+
processValue(value);
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
You can find an element by key in a dynamic map using the `find()` method,
|
|
145
|
+
which returns an iterator compatible with `items()`:
|
|
146
|
+
|
|
147
|
+
``` Cpp
|
|
148
|
+
dynamic obj = dynamic::object(2, 3)("hello", "world")("x", 4);
|
|
149
|
+
|
|
150
|
+
auto pos = obj.find("hello");
|
|
151
|
+
// pos->first is "hello"
|
|
152
|
+
// pos->second is "world"
|
|
153
|
+
|
|
154
|
+
auto pos = obj.find("no_such_key");
|
|
155
|
+
// pos == obj.items().end()
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
### Use for JSON
|
|
160
|
+
***
|
|
161
|
+
|
|
162
|
+
The original motivation for implementing this type was to try to
|
|
163
|
+
make dealing with json documents in C++ almost as easy as it is
|
|
164
|
+
in languages with dynamic type systems (php or javascript, etc).
|
|
165
|
+
The reader can judge whether we're anywhere near that goal, but
|
|
166
|
+
here's what it looks like:
|
|
167
|
+
|
|
168
|
+
``` Cpp
|
|
169
|
+
// Parsing JSON strings and using them.
|
|
170
|
+
std::string jsonDocument = R"({"key":12,"key2":[false, null, true, "yay"]})";
|
|
171
|
+
dynamic parsed = folly::parseJson(jsonDocument);
|
|
172
|
+
assert(parsed["key"] == 12);
|
|
173
|
+
assert(parsed["key2"][0] == false);
|
|
174
|
+
assert(parsed["key2"][1] == nullptr);
|
|
175
|
+
|
|
176
|
+
// Building the same document programmatically.
|
|
177
|
+
dynamic sonOfAJ = dynamic::object
|
|
178
|
+
("key", 12)
|
|
179
|
+
("key2", dynamic::array(false, nullptr, true, "yay"));
|
|
180
|
+
|
|
181
|
+
// Printing. (See also folly::toPrettyJson)
|
|
182
|
+
auto str = folly::toJson(sonOfAJ);
|
|
183
|
+
assert(jsonDocument.compare(str) == 0);
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Performance
|
|
187
|
+
***
|
|
188
|
+
|
|
189
|
+
Dynamic typing is more expensive than static typing, even when
|
|
190
|
+
you do it in C++. ;)
|
|
191
|
+
|
|
192
|
+
However, some effort has been made to keep `folly::dynamic` and
|
|
193
|
+
the json (de)serialization at least reasonably performant for
|
|
194
|
+
common cases. The heap is only used for arrays and objects, and
|
|
195
|
+
move construction is fully supported. String formatting
|
|
196
|
+
internally also uses the highly performant `folly::to<>` (see
|
|
197
|
+
`folly/Conv.h`).
|
|
198
|
+
|
|
199
|
+
A trade off to keep in mind though, is that
|
|
200
|
+
`sizeof(folly::dynamic)` is 64 bytes. You probably don't want to
|
|
201
|
+
use it if you need to allocate large numbers of them (prefer
|
|
202
|
+
static types, etc).
|
|
203
|
+
|
|
204
|
+
### Some Design Rationale
|
|
205
|
+
***
|
|
206
|
+
|
|
207
|
+
**Q. Why doesn't a dynamic string support begin(), end(), and operator[]?**
|
|
208
|
+
|
|
209
|
+
The value_type of a dynamic iterator is `dynamic`, and `operator[]`
|
|
210
|
+
(or the `at()` function) has to return a reference to a dynamic. If
|
|
211
|
+
we wanted this to work for strings, this would mean we'd have to
|
|
212
|
+
support dynamics with a character type, and moreover that the internal
|
|
213
|
+
representation of strings would be such that we can hand out
|
|
214
|
+
references to dynamic as accessors on individual characters. There
|
|
215
|
+
are a lot of potential efficiency drawbacks with this, and it seems
|
|
216
|
+
like a feature that is not needed too often in practice.
|
|
217
|
+
|
|
218
|
+
**Q. Isn't this just a poor imitation of the C# language feature?**
|
|
219
|
+
|
|
220
|
+
Pretty much.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
`folly/DynamicConverter.h`
|
|
2
|
+
--------------------------
|
|
3
|
+
|
|
4
|
+
When dynamic objects contain data of a known type, it is sometimes
|
|
5
|
+
useful to have its well-typed representation. A broad set of
|
|
6
|
+
type-conversions are contained in `DynamicConverter.h`, and
|
|
7
|
+
facilitate the transformation of dynamic objects into their well-typed
|
|
8
|
+
format.
|
|
9
|
+
|
|
10
|
+
### Usage
|
|
11
|
+
***
|
|
12
|
+
|
|
13
|
+
Simply pass a dynamic into a templated convertTo:
|
|
14
|
+
|
|
15
|
+
```cpp
|
|
16
|
+
dynamic d = { { 1, 2, 3 }, { 4, 5 } }; // a vector of vector of int
|
|
17
|
+
auto vvi = convertTo<fbvector<fbvector<int>>>(d);
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Supported Types
|
|
21
|
+
***
|
|
22
|
+
|
|
23
|
+
convertTo naturally supports conversions to
|
|
24
|
+
|
|
25
|
+
1. arithmetic types (such as int64_t, unsigned short, bool, and double)
|
|
26
|
+
2. fbstring, std::string
|
|
27
|
+
3. containers and map-containers
|
|
28
|
+
|
|
29
|
+
NOTE:
|
|
30
|
+
|
|
31
|
+
convertTo<Type> will assume that Type is a container if
|
|
32
|
+
* it has a Type::value_type, and
|
|
33
|
+
* it has a Type::iterator, and
|
|
34
|
+
* it has a constructor that accepts two InputIterators
|
|
35
|
+
|
|
36
|
+
Additionally, convertTo<Type> will assume that Type is a map if
|
|
37
|
+
* it has a Type::key_type, and
|
|
38
|
+
* it has a Type::mapped_type, and
|
|
39
|
+
* value_type is a pair of const key_type and mapped_type
|
|
40
|
+
|
|
41
|
+
If Type meets the container criteria, then it will be constructed
|
|
42
|
+
by calling its InputIterator constructor.
|
|
43
|
+
|
|
44
|
+
### Customization
|
|
45
|
+
***
|
|
46
|
+
|
|
47
|
+
If you want to use convertTo to convert dynamics into your own custom
|
|
48
|
+
class, then all you have to do is provide a template specialization
|
|
49
|
+
of DynamicConverter with the static method convert. Make sure you put it
|
|
50
|
+
in namespace folly.
|
|
51
|
+
|
|
52
|
+
Example:
|
|
53
|
+
|
|
54
|
+
``` Cpp
|
|
55
|
+
struct Token {
|
|
56
|
+
int kind_;
|
|
57
|
+
fbstring lexeme_;
|
|
58
|
+
|
|
59
|
+
explicit Token(int kind, const fbstring& lexeme)
|
|
60
|
+
: kind_(kind), lexeme_(lexeme) {}
|
|
61
|
+
};
|
|
62
|
+
namespace folly {
|
|
63
|
+
template <> struct DynamicConverter<Token> {
|
|
64
|
+
static Token convert(const dynamic& d) {
|
|
65
|
+
int k = convertTo<int>(d["KIND"]);
|
|
66
|
+
fbstring lex = convertTo<fbstring>(d["LEXEME"]);
|
|
67
|
+
return Token(k, lex);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
```
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<section class="dex_document"><h1>Thread pools & Executors</h1><p class="dex_introduction">Run your concurrent code in a performant way</p><h2 id="all-about-thread-pools">All about thread pools <a href="#all-about-thread-pools" class="headerLink">#</a></h2>
|
|
2
|
+
|
|
3
|
+
<h3 id="how-do-i-use-the-thread">How do I use the thread pools? <a href="#how-do-i-use-the-thread" class="headerLink">#</a></h3>
|
|
4
|
+
|
|
5
|
+
<p>Wangle provides two concrete thread pools (IOThreadPoolExecutor, CPUThreadPoolExecutor) as well as building them in as part of a complete async framework. Generally you might want to grab the global executor, and use it with a future, like this:</p>
|
|
6
|
+
|
|
7
|
+
<div class="remarkup-code-block" data-code-lang="php"><pre class="remarkup-code"><span class="no">auto</span> <span class="no">f</span> <span class="o">=</span> <span class="nf" data-symbol-name="someFutureFunction">someFutureFunction</span><span class="o">().</span><span class="nf" data-symbol-name="via">via</span><span class="o">(</span><span class="nf" data-symbol-name="getCPUExecutor">getCPUExecutor</span><span class="o">()).</span><span class="nf" data-symbol-name="then">then</span><span class="o">(...)</span></pre></div>
|
|
8
|
+
|
|
9
|
+
<p>Or maybe you need to construct a thrift/memcache client, and need an event base:</p>
|
|
10
|
+
|
|
11
|
+
<div class="remarkup-code-block" data-code-lang="php"><pre class="remarkup-code"><span class="no">auto</span> <span class="no">f</span> <span class="o">=</span> <span class="nf" data-symbol-name="getClient">getClient</span><span class="o">(</span><span class="nf" data-symbol-name="getIOExecutor">getIOExecutor</span><span class="o">()-></span><span class="na" data-symbol-name="getEventBase">getEventBase</span><span class="o">())-></span><span class="na" data-symbol-name="callSomeFunction">callSomeFunction</span><span class="o">(</span><span class="no">args</span><span class="o">...)</span>
|
|
12
|
+
<span class="o">.</span><span class="nf" data-symbol-name="via">via</span><span class="o">(</span><span class="nf" data-symbol-name="getCPUExecutor">getCPUExecutor</span><span class="o">())</span>
|
|
13
|
+
<span class="o">.</span><span class="nf" data-symbol-name="then">then</span><span class="o">([](</span><span class="no">Result</span> <span class="no">r</span><span class="o">){</span> <span class="o">....</span> <span class="k">do</span> <span class="no">something</span> <span class="no">with</span> <span class="no">result</span><span class="o">});</span></pre></div>
|
|
14
|
+
|
|
15
|
+
<h3 id="vs-c-11-s-std-launch">vs. C++11's std::launch <a href="#vs-c-11-s-std-launch" class="headerLink">#</a></h3>
|
|
16
|
+
|
|
17
|
+
<p>The current C++11 std::launch only has two modes: async or deferred. In a production system, neither is what you want: async will launch a new thread for every launch without limit, while deferred will defer the work until it is needed lazily, but then do the work <strong>in the current thread synchronously</strong> when it is needed.</p>
|
|
18
|
+
|
|
19
|
+
<p>Wangle's thread pools always launch work as soon as possible, have limits to the maximum number of tasks / threads allowed, so we will never use more threads than absolutely needed. See implementation details below about each type of executor.</p>
|
|
20
|
+
|
|
21
|
+
<h3 id="why-do-we-need-yet-anoth">Why do we need yet another set of thread pools? <a href="#why-do-we-need-yet-anoth" class="headerLink">#</a></h3>
|
|
22
|
+
|
|
23
|
+
<p>Unfortunately none of the existing thread pools had every feature needed - things based on pipes are too slow. Several older ones didn't support std::function.</p>
|
|
24
|
+
|
|
25
|
+
<h3 id="why-do-we-need-several-d">Why do we need several different types of thread pools? <a href="#why-do-we-need-several-d" class="headerLink">#</a></h3>
|
|
26
|
+
|
|
27
|
+
<p>If you want epoll support, you need an fd - event_fd is the latest notification hotness. Unfortunately, an active fd triggers all the epoll loops it is in, leading to thundering herd - so if you want a fair queue (one queue total vs. one queue per worker thread), you need to use some kind of semaphore. Unfortunately semaphores can't be put in epoll loops, so they are incompatible with IO. Fortunately, you usually want to separate the IO and CPU bound work anyway to give stronger tail latency guarantees on IO.</p>
|
|
28
|
+
|
|
29
|
+
<h3 id="iothreadpoolexecutor">IOThreadPoolExecutor <a href="#iothreadpoolexecutor" class="headerLink">#</a></h3>
|
|
30
|
+
|
|
31
|
+
<ul>
|
|
32
|
+
<li>Uses event_fd for notification, and waking an epoll loop.</li>
|
|
33
|
+
<li>There is one queue (NotificationQueue specifically) per thread/epoll.</li>
|
|
34
|
+
<li>If the thread is already running and not waiting on epoll, we don't make any additional syscalls to wake up the loop, just put the new task in the queue.</li>
|
|
35
|
+
<li>If any thread has been waiting for more than a few seconds, its stack is madvised away. Currently however tasks are scheduled round robin on the queues, so unless there is <strong>no</strong> work going on, this isn't very effective.</li>
|
|
36
|
+
<li>::getEventBase() will return an EventBase you can schedule IO work on directly, chosen round-robin.</li>
|
|
37
|
+
<li>Since there is one queue per thread, there is hardly any contention on the queues - so a simple spinlock around an std::deque is used for the tasks. There is no max queue size.</li>
|
|
38
|
+
<li>By default, there is one thread per core - it usually doesn't make sense to have more IO threads than this, assuming they don't block.</li>
|
|
39
|
+
</ul>
|
|
40
|
+
|
|
41
|
+
<h3 id="cputhreadpoolexecutor">CPUThreadPoolExecutor <a href="#cputhreadpoolexecutor" class="headerLink">#</a></h3>
|
|
42
|
+
|
|
43
|
+
<ul>
|
|
44
|
+
<li>A single queue backed by folly/LifoSem and folly/MPMC queue. Since there is only a single queue, contention can be quite high, since all the worker threads and all the producer threads hit the same queue. MPMC queue excels in this situation. MPMC queue dictates a max queue size.</li>
|
|
45
|
+
<li>LifoSem wakes up threads in Lifo order - i.e. there are only few threads as necessary running, and we always try to reuse the same few threads for better cache locality.</li>
|
|
46
|
+
<li>Inactive threads have their stack madvised away. This works quite well in combination with Lifosem - it almost doesn't matter if more threads than are necessary are specified at startup.</li>
|
|
47
|
+
<li>stop() will finish all outstanding tasks at exit</li>
|
|
48
|
+
<li>Supports priorities - priorities are implemented as multiple queues - each worker thread checks the highest priority queue first. Threads themselves don't have priorities set, so a series of long running low priority tasks could still hog all the threads. (at last check pthreads thread priorities didn't work very well)</li>
|
|
49
|
+
</ul>
|
|
50
|
+
|
|
51
|
+
<h3 id="threadpoolexecutor">ThreadPoolExecutor <a href="#threadpoolexecutor" class="headerLink">#</a></h3>
|
|
52
|
+
|
|
53
|
+
<p>Base class that contains the thread startup/shutdown/stats logic, since this is pretty disjoint from how tasks are actually run</p>
|
|
54
|
+
|
|
55
|
+
<h3 id="observers">Observers <a href="#observers" class="headerLink">#</a></h3>
|
|
56
|
+
|
|
57
|
+
<p>An observer interface is provided to listen for thread start/stop events. This is useful to create objects that should be one-per-thread, but also have them work correctly if threads are added/removed from the thread pool.</p>
|
|
58
|
+
|
|
59
|
+
<h3 id="stats">Stats <a href="#stats" class="headerLink">#</a></h3>
|
|
60
|
+
|
|
61
|
+
<p>PoolStats are provided to get task count, running time, waiting time, etc.</p>
|
|
62
|
+
</section>
|
|
63
|
+
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
`folly/FBString.h`
|
|
2
|
+
------------------
|
|
3
|
+
|
|
4
|
+
`fbstring` is a drop-in replacement for `std::string`. The main
|
|
5
|
+
benefit of `fbstring` is significantly increased performance on
|
|
6
|
+
virtually all important primitives. This is achieved by using a
|
|
7
|
+
three-tiered storage strategy and by cooperating with the memory
|
|
8
|
+
allocator. In particular, `fbstring` is designed to detect use of
|
|
9
|
+
jemalloc and cooperate with it to achieve significant improvements in
|
|
10
|
+
speed and memory usage.
|
|
11
|
+
|
|
12
|
+
`fbstring` supports 32- and 64-bit and little- and big-endian
|
|
13
|
+
architectures.
|
|
14
|
+
|
|
15
|
+
### Storage strategies
|
|
16
|
+
***
|
|
17
|
+
|
|
18
|
+
* Small strings (<= 23 chars) are stored in-situ without memory
|
|
19
|
+
allocation.
|
|
20
|
+
|
|
21
|
+
* Medium strings (24 - 255 chars) are stored in malloc-allocated
|
|
22
|
+
memory and copied eagerly.
|
|
23
|
+
|
|
24
|
+
* Large strings (> 255 chars) are stored in malloc-allocated memory and
|
|
25
|
+
copied lazily.
|
|
26
|
+
|
|
27
|
+
### Implementation highlights
|
|
28
|
+
***
|
|
29
|
+
|
|
30
|
+
* 100% compatible with `std::string`.
|
|
31
|
+
|
|
32
|
+
* Thread-safe reference counted copy-on-write for "large"
|
|
33
|
+
strings (> 255 chars).
|
|
34
|
+
|
|
35
|
+
* Uses `malloc` instead of allocators.
|
|
36
|
+
|
|
37
|
+
* Jemalloc-friendly. `fbstring` automatically detects if application
|
|
38
|
+
uses jemalloc and if so, significantly improves allocation
|
|
39
|
+
strategy by using non-standard jemalloc extensions.
|
|
40
|
+
|
|
41
|
+
* `find()` is implemented using simplified Boyer-Moore
|
|
42
|
+
algorithm. Casual tests indicate a 30x speed improvement over
|
|
43
|
+
`string::find()` for successful searches and a 1.5x speed
|
|
44
|
+
improvement for failed searches.
|
|
45
|
+
|
|
46
|
+
* Offers conversions to and from `std::string`.
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
`folly/FBVector.h`
|
|
2
|
+
------------------
|
|
3
|
+
|
|
4
|
+
Simply replacing `std::vector` with `folly::fbvector` (after
|
|
5
|
+
having included the `folly/FBVector.h` header file) will
|
|
6
|
+
improve the performance of your C++ code using vectors with
|
|
7
|
+
common coding patterns. The improvements are always non-negative,
|
|
8
|
+
almost always measurable, frequently significant, sometimes
|
|
9
|
+
dramatic, and occasionally spectacular.
|
|
10
|
+
|
|
11
|
+
### Sample
|
|
12
|
+
***
|
|
13
|
+
```cpp
|
|
14
|
+
folly::fbvector<int> numbers({0, 1, 2, 3});
|
|
15
|
+
numbers.reserve(10);
|
|
16
|
+
for (int i = 4; i < 10; i++) {
|
|
17
|
+
numbers.push_back(i * 2);
|
|
18
|
+
}
|
|
19
|
+
assert(numbers[6] == 12);
|
|
20
|
+
```
|
|
21
|
+
### Motivation
|
|
22
|
+
***
|
|
23
|
+
|
|
24
|
+
`std::vector` is the stalwart abstraction many use for
|
|
25
|
+
dynamically-allocated arrays in C++. It is also the best known
|
|
26
|
+
and most used of all containers. It may therefore seem a
|
|
27
|
+
surprise that `std::vector` leaves important - and sometimes
|
|
28
|
+
vital - efficiency opportunities on the table. This document
|
|
29
|
+
explains how our own drop-in abstraction `fbvector` improves key
|
|
30
|
+
performance aspects of `std::vector`. Refer to
|
|
31
|
+
folly/test/FBVectorTest.cpp for a few benchmarks.
|
|
32
|
+
|
|
33
|
+
### Memory Handling
|
|
34
|
+
***
|
|
35
|
+
|
|
36
|
+
It is well known that `std::vector` grows exponentially (at a
|
|
37
|
+
constant factor) in order to avoid quadratic growth performance.
|
|
38
|
+
The trick is choosing a good factor. Any factor greater than 1
|
|
39
|
+
ensures O(1) amortized append complexity towards infinity. But a
|
|
40
|
+
factor that's too small (say, 1.1) causes frequent vector reallocation, and
|
|
41
|
+
one that's too large (say, 3 or 4) forces the vector to consume much more
|
|
42
|
+
memory than needed.
|
|
43
|
+
|
|
44
|
+
The initial HP implementation by Stepanov used a
|
|
45
|
+
growth factor of 2; i.e., whenever you'd `push_back` into a vector
|
|
46
|
+
without there being room, it would double the current capacity. This
|
|
47
|
+
was not a good choice: it can be mathematically proven that a growth factor of
|
|
48
|
+
2 is rigorously the <i>worst</i> possible because it never allows the vector
|
|
49
|
+
to reuse any of its previously-allocated memory. Despite other compilers
|
|
50
|
+
reducing the growth factor to 1.5, gcc has staunchly maintained its factor of
|
|
51
|
+
2. This makes `std::vector` cache- unfriendly and memory manager unfriendly.
|
|
52
|
+
|
|
53
|
+
To see why that's the case, consider a large vector of capacity C.
|
|
54
|
+
When there's a request to grow the vector, the vector
|
|
55
|
+
(assuming no in-place resizing, see the appropriate section in
|
|
56
|
+
this document) will allocate a chunk of memory next to its current chunk,
|
|
57
|
+
copy its existing data to the new chunk, and then deallocate the old chunk.
|
|
58
|
+
So now we have a chunk of size C followed by a chunk of size k * C. Continuing
|
|
59
|
+
this process we'll then have a chunk of size k * k * C to the right and so on.
|
|
60
|
+
That leads to a series of the form (using ^^ for power):
|
|
61
|
+
|
|
62
|
+
C, C*k, C*k^^2, C*k^^3, ...
|
|
63
|
+
|
|
64
|
+
If we choose k = 2 we know that every element in the series will
|
|
65
|
+
be strictly larger than the sum of all previous ones because of
|
|
66
|
+
the remarkable equality:
|
|
67
|
+
|
|
68
|
+
1 + 2^^1 + 2^^2 + 2^^3... + 2^^n = 2^^(n+1) - 1
|
|
69
|
+
|
|
70
|
+
This means that any new chunk requested will be larger
|
|
71
|
+
than all previously used chunks combined, so the vector must
|
|
72
|
+
crawl forward in memory; it can't move back to its deallocated chunks.
|
|
73
|
+
But any number smaller than 2 guarantees that you'll at some point be
|
|
74
|
+
able to reuse the previous chunks. For instance, choosing 1.5 as the factor
|
|
75
|
+
allows memory reuse after 4 reallocations; 1.45 allows memory reuse after 3
|
|
76
|
+
reallocations; and 1.3 allows reuse after only 2 reallocations.
|
|
77
|
+
|
|
78
|
+
Of course, the above makes a number of simplifying assumptions
|
|
79
|
+
about how the memory allocator works, but definitely you don't
|
|
80
|
+
want to choose the theoretically absolute worst growth factor.
|
|
81
|
+
`fbvector` uses a growth factor of 1.5. That does not impede good
|
|
82
|
+
performance at small sizes because of the way `fbvector`
|
|
83
|
+
cooperates with jemalloc (below).
|
|
84
|
+
|
|
85
|
+
### The jemalloc Connection
|
|
86
|
+
***
|
|
87
|
+
|
|
88
|
+
Virtually all modern allocators allocate memory in fixed-size
|
|
89
|
+
quanta that are chosen to minimize management overhead while at
|
|
90
|
+
the same time offering good coverage at low slack. For example, an
|
|
91
|
+
allocator may choose blocks of doubling size (32, 64, 128,
|
|
92
|
+
<t_co>, ...) up to 4096, and then blocks of size multiples of a
|
|
93
|
+
page up until 1MB, and then 512KB increments and so on.
|
|
94
|
+
|
|
95
|
+
As discussed above, `std::vector` also needs to (re)allocate in
|
|
96
|
+
quanta. The next quantum is usually defined in terms of the
|
|
97
|
+
current size times the infamous growth constant. Because of this
|
|
98
|
+
setup, `std::vector` has some slack memory at the end much like
|
|
99
|
+
an allocated block has some slack memory at the end.
|
|
100
|
+
|
|
101
|
+
It doesn't take a rocket surgeon to figure out that an allocator-
|
|
102
|
+
aware `std::vector` would be a marriage made in heaven: the
|
|
103
|
+
vector could directly request blocks of "perfect" size from the
|
|
104
|
+
allocator so there would be virtually no slack in the allocator.
|
|
105
|
+
Also, the entire growth strategy could be adjusted to work
|
|
106
|
+
perfectly with allocator's own block growth strategy. That's
|
|
107
|
+
exactly what `fbvector` does - it automatically detects the use
|
|
108
|
+
of jemalloc and adjusts its reallocation strategy accordingly.
|
|
109
|
+
|
|
110
|
+
But wait, there's more. Many memory allocators do not support in-
|
|
111
|
+
place reallocation, although most of them could. This comes from
|
|
112
|
+
the now notorious design of `realloc()` to opaquely perform
|
|
113
|
+
either in-place reallocation or an allocate-memcpy-deallocate
|
|
114
|
+
cycle. Such lack of control subsequently forced all clib-based
|
|
115
|
+
allocator designs to avoid in-place reallocation, and that
|
|
116
|
+
includes C++'s `new` and `std::allocator`. This is a major loss of
|
|
117
|
+
efficiency because an in-place reallocation, being very cheap,
|
|
118
|
+
may mean a much less aggressive growth strategy. In turn that
|
|
119
|
+
means less slack memory and faster reallocations.
|
|
120
|
+
|
|
121
|
+
### Object Relocation
|
|
122
|
+
***
|
|
123
|
+
|
|
124
|
+
One particularly sensitive topic about handling C++ values is
|
|
125
|
+
that they are all conservatively considered <i>non-
|
|
126
|
+
relocatable</i>. In contrast, a relocatable value would preserve
|
|
127
|
+
its invariant even if its bits were moved arbitrarily in memory.
|
|
128
|
+
For example, an `int32_t` is relocatable because moving its 4 bytes
|
|
129
|
+
would preserve its actual value, so the address of that value
|
|
130
|
+
does not "matter" to its integrity.
|
|
131
|
+
|
|
132
|
+
C++'s assumption of non-relocatable values hurts everybody for
|
|
133
|
+
the benefit of a few questionable designs. The issue is that
|
|
134
|
+
moving a C++ object "by the book" entails (a) creating a new copy
|
|
135
|
+
from the existing value; (b) destroying the old value. This is
|
|
136
|
+
quite vexing and violates common sense; consider this
|
|
137
|
+
hypothetical conversation between Captain Picard and an
|
|
138
|
+
incredulous alien:
|
|
139
|
+
|
|
140
|
+
Incredulous Alien: "So, this teleporter, how does it work?"<br>
|
|
141
|
+
Picard: "It beams people and arbitrary matter from one place to
|
|
142
|
+
another."<br> Incredulous Alien: "Hmmm... is it safe?"<br>
|
|
143
|
+
Picard: "Yes, but earlier models were a hassle. They'd clone the
|
|
144
|
+
person to another location. Then the teleporting chief would have
|
|
145
|
+
to shoot the original. Ask O'Brien, he was an intern during those
|
|
146
|
+
times. A bloody mess, that's what it was."
|
|
147
|
+
|
|
148
|
+
Only a tiny minority of objects are genuinely non-relocatable:
|
|
149
|
+
|
|
150
|
+
* Objects that use internal pointers, e.g.:
|
|
151
|
+
```cpp
|
|
152
|
+
class Ew {
|
|
153
|
+
char buffer[1024];
|
|
154
|
+
char * pointerInsideBuffer;
|
|
155
|
+
public:
|
|
156
|
+
Ew() : pointerInsideBuffer(buffer) {}
|
|
157
|
+
...
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
* Objects that need to update "observers" that store pointers to them.
|
|
161
|
+
|
|
162
|
+
The first class of designs can always be redone at small or no
|
|
163
|
+
cost in efficiency. The second class of objects should not be
|
|
164
|
+
values in the first place - they should be allocated with `new`
|
|
165
|
+
and manipulated using (smart) pointers. It is highly unusual for
|
|
166
|
+
a value to have observers that alias pointers to it.
|
|
167
|
+
|
|
168
|
+
Relocatable objects are of high interest to `std::vector` because
|
|
169
|
+
such knowledge makes insertion into the vector and vector
|
|
170
|
+
reallocation considerably faster: instead of going to Picard's
|
|
171
|
+
copy-destroy cycle, relocatable objects can be moved around
|
|
172
|
+
simply by using `memcpy` or `memmove`. This optimization can
|
|
173
|
+
yield arbitrarily high wins in efficiency; for example, it
|
|
174
|
+
transforms `vector< vector<double> >` or `vector< hash_map<int,
|
|
175
|
+
string> >` from risky liabilities into highly workable
|
|
176
|
+
compositions.
|
|
177
|
+
|
|
178
|
+
In order to allow fast relocation without risk, `fbvector` uses a
|
|
179
|
+
trait `folly::IsRelocatable` defined in `"folly/Traits.h"`. By default,
|
|
180
|
+
`folly::IsRelocatable::value` conservatively yields false. If
|
|
181
|
+
you know that your type `Widget` is in fact relocatable, go right
|
|
182
|
+
after `Widget`'s definition and write this:
|
|
183
|
+
```cpp
|
|
184
|
+
// at global namespace level
|
|
185
|
+
namespace folly {
|
|
186
|
+
struct IsRelocatable<Widget> : boost::true_type {};
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
If you don't do this, `fbvector<Widget>` will fail to compile
|
|
190
|
+
with a `static_assert`.
|
|
191
|
+
|
|
192
|
+
### Miscellaneous
|
|
193
|
+
***
|
|
194
|
+
|
|
195
|
+
`fbvector` uses a careful implementation all around to make
|
|
196
|
+
sure it doesn't lose efficiency through the cracks. Some future
|
|
197
|
+
directions may be in improving raw memory copying (`memcpy` is
|
|
198
|
+
not an intrinsic in gcc and does not work terribly well for
|
|
199
|
+
large chunks) and in furthering the collaboration with
|
|
200
|
+
jemalloc. Have fun!
|
|
Binary file
|