Skip to content

Commit 788f46f

Browse files
committed
Reorder task structure and add padding
To ensure that the task metrics do not cross a cache line boundary. Also rename `cpu_time_ns` to `running_time_ns`.
1 parent 6871b73 commit 788f46f

File tree

6 files changed

+78
-46
lines changed

6 files changed

+78
-46
lines changed

base/boot.jl

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -175,15 +175,33 @@
175175
#end
176176

177177
#mutable struct Task
178-
# parent::Task
178+
# next::Any
179+
# queue::Any
179180
# storage::Any
180-
# state::Symbol
181181
# donenotify::Any
182182
# result::Any
183-
# exception::Any
184-
# backtrace::Any
185183
# scope::Any
186184
# code::Any
185+
# @atomic _state::UInt8
186+
# sticky::UInt8
187+
# priority::UInt16
188+
# @atomic _isexception::UInt8
189+
# pad00::UInt8
190+
# pad01::UInt8
191+
# pad02::UInt8
192+
# rngState0::UInt64
193+
# rngState1::UInt64
194+
# rngState2::UInt64
195+
# rngState3::UInt64
196+
# rngState4::UInt64
197+
# const metrics_enabled::Bool
198+
# pad10::UInt8
199+
# pad11::UInt8
200+
# pad12::UInt8
201+
# @atomic first_enqueued_at::UInt64
202+
# @atomic last_started_running_at::UInt64
203+
# @atomic running_time_ns::UInt64
204+
# @atomic finished_at::UInt64
187205
#end
188206

189207
export

base/experimental.jl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,7 @@ disable_new_worlds() = ccall(:jl_disable_new_worlds, Cvoid, ())
510510
511511
Enable or disable the collection of per-task metrics.
512512
A `Task` created when `Base.Experimental.task_metrics(true)` is in effect will have
513-
[`Base.Experimental.task_cpu_time_ns`](@ref) and [`Base.Experimental.task_wall_time_ns`](@ref)
513+
[`Base.Experimental.task_running_time_ns`](@ref) and [`Base.Experimental.task_wall_time_ns`](@ref)
514514
timing information available.
515515
516516
!!! note
@@ -526,7 +526,7 @@ function task_metrics(b::Bool)
526526
end
527527

528528
"""
529-
Base.Experimental.task_cpu_time_ns(t::Task) -> Union{UInt64, Nothing}
529+
Base.Experimental.task_running_time_ns(t::Task) -> Union{UInt64, Nothing}
530530
531531
Return the total nanoseconds that the task `t` has spent running.
532532
This metric is only updated when `t` yields or completes unless `t` is the current task, in
@@ -543,14 +543,14 @@ See [`Base.Experimental.task_metrics`](@ref).
543543
!!! compat "Julia 1.12"
544544
This method was added in Julia 1.12.
545545
"""
546-
function task_cpu_time_ns(t::Task=current_task())
546+
function task_running_time_ns(t::Task=current_task())
547547
t.metrics_enabled || return nothing
548548
if t == current_task()
549549
# These metrics fields can't update while we're running.
550550
# But since we're running we need to include the time since we last started running!
551-
return t.cpu_time_ns + (time_ns() - t.last_started_running_at)
551+
return t.running_time_ns + (time_ns() - t.last_started_running_at)
552552
else
553-
return t.cpu_time_ns
553+
return t.running_time_ns
554554
end
555555
end
556556

@@ -560,7 +560,7 @@ end
560560
Return the total nanoseconds that the task `t` was runnable.
561561
This is the time since the task first entered the run queue until the time at which it
562562
completed, or until the current time if the task has not yet completed.
563-
See also [`Base.Experimental.task_cpu_time_ns`](@ref).
563+
See also [`Base.Experimental.task_running_time_ns`](@ref).
564564
565565
Returns `nothing` if task timings are not enabled.
566566
See [`Base.Experimental.task_metrics`](@ref).

base/task.jl

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1070,15 +1070,15 @@ immediately yields to `t` before calling the scheduler.
10701070
Throws a `ConcurrencyViolationError` if `t` is the currently running task.
10711071
"""
10721072
function yield(t::Task, @nospecialize(x=nothing))
1073-
current = current_task()
1073+
ct = current_task()
10741074
# [task] user_time -yield-> wait_time
1075-
record_cpu_time!(current)
1076-
t === current && throw(ConcurrencyViolationError("Cannot yield to currently running task!"))
1075+
record_running_time!(ct)
1076+
t === ct && throw(ConcurrencyViolationError("Cannot yield to currently running task!"))
10771077
(t._state === task_state_runnable && t.queue === nothing) || throw(ConcurrencyViolationError("yield: Task not runnable"))
10781078
# [task] created -scheduled-> wait_time
10791079
maybe_record_enqueued!(t)
10801080
t.result = x
1081-
enq_work(current)
1081+
enq_work(ct)
10821082
set_next_task(t)
10831083
return try_yieldto(ensure_rescheduled)
10841084
end
@@ -1092,8 +1092,9 @@ call to `yieldto`. This is a low-level call that only switches tasks, not consid
10921092
or scheduling in any way. Its use is discouraged.
10931093
"""
10941094
function yieldto(t::Task, @nospecialize(x=nothing))
1095+
ct = current_task()
10951096
# [task] user_time -yield-> wait_time
1096-
record_cpu_time!(current_task())
1097+
record_running_time!(ct)
10971098
# TODO: these are legacy behaviors; these should perhaps be a scheduler
10981099
# state error instead.
10991100
if t._state === task_state_done
@@ -1133,8 +1134,9 @@ end
11331134

11341135
# yield to a task, throwing an exception in it
11351136
function throwto(t::Task, @nospecialize exc)
1137+
ct = current_task()
11361138
# [task] user_time -yield-> wait_time
1137-
record_cpu_time!(current_task())
1139+
record_running_time!(ct)
11381140
# [task] created -scheduled-unfairly-> wait_time
11391141
maybe_record_enqueued!(t)
11401142
t.result = exc
@@ -1189,8 +1191,9 @@ checktaskempty = Partr.multiq_check_empty
11891191
end
11901192

11911193
function wait()
1194+
ct = current_task()
11921195
# [task] user_time -yield-or-done-> wait_time
1193-
record_cpu_time!(current_task())
1196+
record_running_time!(ct)
11941197
GC.safepoint()
11951198
W = workqueue_for(Threads.threadid())
11961199
poptask(W)
@@ -1206,10 +1209,10 @@ else
12061209
pause() = ccall(:pause, Cvoid, ())
12071210
end
12081211

1209-
# update the `cpu_time_ns` field of `t` to include the time since it last started running.
1210-
function record_cpu_time!(t::Task)
1212+
# update the `running_time_ns` field of `t` to include the time since it last started running.
1213+
function record_running_time!(t::Task)
12111214
if t.metrics_enabled && !istaskdone(t)
1212-
@atomic :monotonic t.cpu_time_ns += time_ns() - t.last_started_running_at
1215+
@atomic :monotonic t.running_time_ns += time_ns() - t.last_started_running_at
12131216
end
12141217
return t
12151218
end

src/jltypes.c

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3746,46 +3746,58 @@ void jl_init_types(void) JL_GC_DISABLED
37463746
NULL,
37473747
jl_any_type,
37483748
jl_emptysvec,
3749-
jl_perm_symsvec(21,
3749+
jl_perm_symsvec(27,
37503750
"next",
37513751
"queue",
37523752
"storage",
37533753
"donenotify",
37543754
"result",
37553755
"scope",
37563756
"code",
3757+
"_state",
3758+
"sticky",
3759+
"priority",
3760+
"_isexception",
3761+
"pad00",
3762+
"pad01",
3763+
"pad02",
37573764
"rngState0",
37583765
"rngState1",
37593766
"rngState2",
37603767
"rngState3",
37613768
"rngState4",
3762-
"_state",
3763-
"sticky",
3764-
"_isexception",
3765-
"priority",
37663769
"metrics_enabled",
3770+
"pad10",
3771+
"pad11",
3772+
"pad12",
37673773
"first_enqueued_at",
37683774
"last_started_running_at",
3769-
"cpu_time_ns",
3775+
"running_time_ns",
37703776
"finished_at"),
3771-
jl_svec(21,
3777+
jl_svec(27,
37723778
jl_any_type,
37733779
jl_any_type,
37743780
jl_any_type,
37753781
jl_any_type,
37763782
jl_any_type,
37773783
jl_any_type,
37783784
jl_any_type,
3785+
jl_uint8_type,
3786+
jl_bool_type,
3787+
jl_uint16_type,
3788+
jl_bool_type,
3789+
jl_uint8_type,
3790+
jl_uint8_type,
3791+
jl_uint8_type,
37793792
jl_uint64_type,
37803793
jl_uint64_type,
37813794
jl_uint64_type,
37823795
jl_uint64_type,
37833796
jl_uint64_type,
3784-
jl_uint8_type,
3785-
jl_bool_type,
3786-
jl_bool_type,
3787-
jl_uint16_type,
37883797
jl_bool_type,
3798+
jl_uint8_type,
3799+
jl_uint8_type,
3800+
jl_uint8_type,
37893801
jl_uint64_type,
37903802
jl_uint64_type,
37913803
jl_uint64_type,
@@ -3795,10 +3807,10 @@ void jl_init_types(void) JL_GC_DISABLED
37953807
XX(task);
37963808
jl_value_t *listt = jl_new_struct(jl_uniontype_type, jl_task_type, jl_nothing_type);
37973809
jl_svecset(jl_task_type->types, 0, listt);
3798-
// Set field 17 (metrics_enabled) as const
3799-
// Set fields 13 (_state) and 18-21 (metric counters) as atomic
3800-
const static uint32_t task_constfields[1] = { 0b000010000000000000000 };
3801-
const static uint32_t task_atomicfields[1] = { 0b111100001000000000000 };
3810+
// Set field 20 (metrics_enabled) as const
3811+
// Set fields 8 (_state) and 24-27 (metric counters) as atomic
3812+
const static uint32_t task_constfields[1] = { 0b00000000000010000000000000000000 };
3813+
const static uint32_t task_atomicfields[1] = { 0b00000111100000000000000010000000 };
38023814
jl_task_type->name->constfields = task_constfields;
38033815
jl_task_type->name->atomicfields = task_atomicfields;
38043816

src/julia.h

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2276,24 +2276,23 @@ typedef struct _jl_task_t {
22762276
jl_value_t *result;
22772277
jl_value_t *scope;
22782278
jl_function_t *start;
2279-
// 4 byte padding on 32-bit systems
2280-
// uint32_t padding0;
2281-
uint64_t rngState[JL_RNG_SIZE];
22822279
_Atomic(uint8_t) _state;
22832280
uint8_t sticky; // record whether this Task can be migrated to a new thread
2284-
_Atomic(uint8_t) _isexception; // set if `result` is an exception to throw or that we exited with
2285-
// 1 byte padding
2286-
// uint8_t padding1;
2287-
// multiqueue priority
22882281
uint16_t priority;
2282+
_Atomic(uint8_t) _isexception; // set if `result` is an exception to throw or that we exited with
2283+
uint8_t pad0[3];
2284+
// === 64 bytes (cache line)
2285+
uint64_t rngState[JL_RNG_SIZE];
22892286
// flag indicating whether or not to record timing metrics for this task
22902287
uint8_t metrics_enabled;
2288+
uint8_t pad1[3];
22912289
// timestamp this task first entered the run queue
22922290
_Atomic(uint64_t) first_enqueued_at;
22932291
// timestamp this task was most recently scheduled to run
22942292
_Atomic(uint64_t) last_started_running_at;
22952293
// time this task has spent running; updated when it yields or finishes.
2296-
_Atomic(uint64_t) cpu_time_ns;
2294+
_Atomic(uint64_t) running_time_ns;
2295+
// === 64 bytes (cache line)
22972296
// timestamp this task finished (i.e. entered state DONE or FAILED).
22982297
_Atomic(uint64_t) finished_at;
22992298

src/task.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ void JL_NORETURN jl_finish_task(jl_task_t *ct)
318318
assert(jl_atomic_load_relaxed(&ct->first_enqueued_at) != 0);
319319
uint64_t now = jl_hrtime();
320320
jl_atomic_store_relaxed(&ct->finished_at, now);
321-
jl_atomic_fetch_add_relaxed(&ct->cpu_time_ns, now - jl_atomic_load_relaxed(&ct->last_started_running_at));
321+
jl_atomic_fetch_add_relaxed(&ct->running_time_ns, now - jl_atomic_load_relaxed(&ct->last_started_running_at));
322322
}
323323
if (jl_atomic_load_relaxed(&ct->_isexception))
324324
jl_atomic_store_release(&ct->_state, JL_TASK_STATE_FAILED);
@@ -1156,7 +1156,7 @@ JL_DLLEXPORT jl_task_t *jl_new_task(jl_function_t *start, jl_value_t *completion
11561156
t->metrics_enabled = jl_atomic_load_relaxed(&jl_task_metrics_enabled) != 0;
11571157
jl_atomic_store_relaxed(&t->first_enqueued_at, 0);
11581158
jl_atomic_store_relaxed(&t->last_started_running_at, 0);
1159-
jl_atomic_store_relaxed(&t->cpu_time_ns, 0);
1159+
jl_atomic_store_relaxed(&t->running_time_ns, 0);
11601160
jl_atomic_store_relaxed(&t->finished_at, 0);
11611161
jl_timing_task_init(t);
11621162

@@ -1614,7 +1614,7 @@ jl_task_t *jl_init_root_task(jl_ptls_t ptls, void *stack_lo, void *stack_hi)
16141614
ct->ptls = ptls;
16151615
ct->world_age = 1; // OK to run Julia code on this task
16161616
ct->reentrant_timing = 0;
1617-
jl_atomic_store_relaxed(&ct->cpu_time_ns, 0);
1617+
jl_atomic_store_relaxed(&ct->running_time_ns, 0);
16181618
jl_atomic_store_relaxed(&ct->finished_at, 0);
16191619
ct->metrics_enabled = jl_atomic_load_relaxed(&jl_task_metrics_enabled) != 0;
16201620
if (ct->metrics_enabled) {

0 commit comments

Comments
 (0)