Skip to content

Commit bb9c4d0

Browse files
author
4kangjc
committed
scheduler: Support moved tast captures / arguments
1 parent e5ffc99 commit bb9c4d0

File tree

5 files changed

+369
-21
lines changed

5 files changed

+369
-21
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,7 @@ if(MARL_BUILD_TESTS)
340340
${MARL_SRC_DIR}/marl_test.cpp
341341
${MARL_SRC_DIR}/marl_test.h
342342
${MARL_SRC_DIR}/memory_test.cpp
343+
${MARL_SRC_DIR}/move_only_function_test.cpp
343344
${MARL_SRC_DIR}/osfiber_test.cpp
344345
${MARL_SRC_DIR}/parallelize_test.cpp
345346
${MARL_SRC_DIR}/pool_test.cpp

include/marl/move_only_function.h

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
// Copyright 2023 The Marl Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef marl_move_only_function_h
16+
#define marl_move_only_function_h
17+
18+
#include "memory.h"
19+
20+
#include <cstddef>
21+
#include <functional>
22+
#include <new>
23+
#include <type_traits>
24+
#include <utility>
25+
26+
namespace marl {
27+
28+
#if __cplusplus > 201402L
29+
30+
using std::invoke;
31+
32+
#else
33+
// std::invoke for C++11 and C++14
34+
template <typename Functor, typename... Args>
35+
typename std::enable_if<
36+
std::is_member_pointer<typename std::decay<Functor>::type>::value,
37+
typename std::result_of<Functor && (Args && ...)>::type>::type
38+
invoke(Functor&& f, Args&&... args) {
39+
return std::mem_fn(f)(std::forward<Args>(args)...);
40+
}
41+
42+
template <typename Functor, typename... Args>
43+
typename std::enable_if<
44+
!std::is_member_pointer<typename std::decay<Functor>::type>::value,
45+
typename std::result_of<Functor && (Args && ...)>::type>::type
46+
invoke(Functor&& f, Args&&... args) {
47+
return std::forward<Functor>(f)(std::forward<Args>(args)...);
48+
}
49+
50+
#endif // __cplusplus > 201402L
51+
52+
template <class R>
53+
class move_only_function;
54+
55+
#if __cplusplus > 201402L && (defined(__GNUC__) || defined(__clang__))
56+
template <class R, class... Args, bool kNoexcept>
57+
class move_only_function<R(Args...) noexcept(kNoexcept)> {
58+
#else
59+
template <class R, class... Args>
60+
class move_only_function<R(Args...)> {
61+
static const bool kNoexcept = false; // private
62+
#endif
63+
64+
public:
65+
constexpr move_only_function() = default;
66+
move_only_function(std::nullptr_t) {}
67+
68+
template <class F, class = typename std::enable_if<!std::is_same<typename std::decay<F>::type, move_only_function>::value>::type>
69+
move_only_function(F&& function) {
70+
if (sizeof(typename std::decay<F>::type) <= kMaximumOptimizableSize) {
71+
ops_ = EraseCopySmall(&object_, std::forward<F>(function));
72+
} else {
73+
ops_ = EraseCopyLarge(&object_, std::forward<F>(function));
74+
}
75+
}
76+
77+
move_only_function(move_only_function&& function) noexcept {
78+
ops_ = function.ops_;
79+
function.ops_ = nullptr;
80+
if (ops_) {
81+
ops_->manager(&object_, &function.object_);
82+
}
83+
}
84+
~move_only_function() {
85+
if (ops_) {
86+
ops_->manager(&object_, nullptr);
87+
}
88+
}
89+
90+
template <class F, class = typename std::enable_if<!std::is_same<typename std::decay<F>::type, move_only_function>::value>::type>
91+
move_only_function& operator=(F&& function) {
92+
this->~move_only_function();
93+
new (this) move_only_function(std::forward<F>(function));
94+
return *this;
95+
}
96+
97+
move_only_function& operator=(move_only_function&& function) noexcept {
98+
if (&function != this) {
99+
this->~move_only_function();
100+
new (this) move_only_function(std::move(function));
101+
}
102+
return *this;
103+
}
104+
105+
move_only_function& operator=(std::nullptr_t) {
106+
ops_->manager(&object_, nullptr);
107+
ops_ = nullptr;
108+
return *this;
109+
}
110+
111+
// The behavior is undefined if `*this == nullptr` holds.
112+
R operator()(Args... args) const noexcept(kNoexcept) {
113+
return ops_->invoker(&object_, std::forward<Args>(args)...);
114+
}
115+
116+
constexpr explicit operator bool() const { return ops_; }
117+
118+
private:
119+
static constexpr std::size_t kMaximumOptimizableSize = 3 * sizeof(void*);
120+
121+
struct TypeOps {
122+
using Invoker = R (*)(void* object, Args&&... args);
123+
using Manager = void (*)(void* dest, void* src);
124+
125+
Invoker invoker;
126+
Manager manager;
127+
};
128+
129+
template <class F>
130+
static R Invoke(F&& f, Args&&... args) {
131+
return invoke(std::forward<F>(f), std::forward<Args>(args)...);
132+
}
133+
134+
template <class T>
135+
const TypeOps* EraseCopySmall(void* buffer, T&& obejct) {
136+
using Decayed = typename std::decay<T>::type;
137+
138+
static const TypeOps ops = {
139+
// Invoker
140+
[](void* object, Args&&... args) -> R {
141+
return Invoke(*static_cast<Decayed*>(object),
142+
std::forward<Args>(args)...);
143+
},
144+
// Manager
145+
[](void* dest, void* src) {
146+
if (src) {
147+
new (dest) Decayed(std::move(*static_cast<Decayed*>(src)));
148+
static_cast<Decayed*>(src)->~Decayed();
149+
} else {
150+
static_cast<Decayed*>(dest)->~Decayed();
151+
}
152+
}};
153+
154+
new (buffer) Decayed(std::forward<T>(obejct));
155+
return &ops;
156+
}
157+
158+
template <class T>
159+
const TypeOps* EraseCopyLarge(void* buffer, T&& object) {
160+
using Decayed = typename std::decay<T>::type;
161+
using Stored = Decayed*;
162+
163+
static const TypeOps ops = {
164+
/* invoker */
165+
[](void* object, Args&&... args) -> R {
166+
return Invoke(**static_cast<Stored*>(object),
167+
std::forward<Args>(args)...);
168+
},
169+
/* Manager */
170+
[](void* dest, void* src) {
171+
if (src) {
172+
new (dest) Stored(*static_cast<Stored*>(src));
173+
} else {
174+
delete *static_cast<Stored*>(dest);
175+
}
176+
},
177+
};
178+
new (buffer) Stored(new Decayed(std::forward<T>(object)));
179+
return &ops;
180+
}
181+
182+
mutable marl::aligned_storage<kMaximumOptimizableSize, 1>::type object_;
183+
const TypeOps* ops_ = nullptr;
184+
};
185+
186+
} // namespace marl
187+
188+
#endif

include/marl/task.h

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,14 @@
1616
#define marl_task_h
1717

1818
#include "export.h"
19-
20-
#include <functional>
19+
#include "move_only_function.h"
2120

2221
namespace marl {
2322

2423
// Task is a unit of work for the scheduler.
2524
class Task {
2625
public:
27-
using Function = std::function<void()>;
26+
using Function = move_only_function<void()>;
2827

2928
enum class Flags {
3029
None = 0,
@@ -37,14 +36,9 @@ class Task {
3736
};
3837

3938
MARL_NO_EXPORT inline Task();
40-
MARL_NO_EXPORT inline Task(const Task&);
4139
MARL_NO_EXPORT inline Task(Task&&);
42-
MARL_NO_EXPORT inline Task(const Function& function,
43-
Flags flags = Flags::None);
4440
MARL_NO_EXPORT inline Task(Function&& function, Flags flags = Flags::None);
45-
MARL_NO_EXPORT inline Task& operator=(const Task&);
4641
MARL_NO_EXPORT inline Task& operator=(Task&&);
47-
MARL_NO_EXPORT inline Task& operator=(const Function&);
4842
MARL_NO_EXPORT inline Task& operator=(Function&&);
4943

5044
// operator bool() returns true if the Task has a valid function.
@@ -62,28 +56,15 @@ class Task {
6256
};
6357

6458
Task::Task() {}
65-
Task::Task(const Task& o) : function(o.function), flags(o.flags) {}
6659
Task::Task(Task&& o) : function(std::move(o.function)), flags(o.flags) {}
67-
Task::Task(const Function& function_, Flags flags_ /* = Flags::None */)
68-
: function(function_), flags(flags_) {}
6960
Task::Task(Function&& function_, Flags flags_ /* = Flags::None */)
7061
: function(std::move(function_)), flags(flags_) {}
71-
Task& Task::operator=(const Task& o) {
72-
function = o.function;
73-
flags = o.flags;
74-
return *this;
75-
}
7662
Task& Task::operator=(Task&& o) {
7763
function = std::move(o.function);
7864
flags = o.flags;
7965
return *this;
8066
}
8167

82-
Task& Task::operator=(const Function& f) {
83-
function = f;
84-
flags = Flags::None;
85-
return *this;
86-
}
8768
Task& Task::operator=(Function&& f) {
8869
function = std::move(f);
8970
flags = Flags::None;

0 commit comments

Comments
 (0)