mirror of
https://gitlab.rlp.net/mobitar/julia_course.git
synced 2024-11-16 13:28:10 +00:00
Day 4 done
This commit is contained in:
parent
7ad274779b
commit
54ce7b46a2
1 changed files with 126 additions and 4 deletions
130
Day_4/Day_4.jl
130
Day_4/Day_4.jl
|
@ -8,6 +8,8 @@ using InteractiveUtils
|
||||||
using BenchmarkTools
|
using BenchmarkTools
|
||||||
|
|
||||||
# ╔═╡ 8b1dfee2-bd8b-4d23-b9f8-9406002e0eaa
|
# ╔═╡ 8b1dfee2-bd8b-4d23-b9f8-9406002e0eaa
|
||||||
|
# Builtin package with useful functions for dealing with randomness
|
||||||
|
# Imported for the function `shuffle`
|
||||||
using Random
|
using Random
|
||||||
|
|
||||||
# ╔═╡ 1fb7d9af-333e-44f2-b693-09ff97937d4c
|
# ╔═╡ 1fb7d9af-333e-44f2-b693-09ff97937d4c
|
||||||
|
@ -144,9 +146,13 @@ If you want syntax highlighting in your REPL, add the package `OhMyREPL` to your
|
||||||
# ╔═╡ 0e53e4ee-16a7-47ef-9992-77cbfd1ed258
|
# ╔═╡ 0e53e4ee-16a7-47ef-9992-77cbfd1ed258
|
||||||
md"""
|
md"""
|
||||||
# Benchmarking
|
# Benchmarking
|
||||||
|
Benchmarking is a tool to evaluate the performance of code.
|
||||||
|
|
||||||
|
Although you can just measure the time a function call takes, it is not a good idea to use only one measurement. Therefore, the package `BenchmarkTools.jl` runs a function multiple times and evaluates it statistically.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# ╔═╡ d86e4a2f-e737-49a4-bc16-a149e81785bd
|
# ╔═╡ d86e4a2f-e737-49a4-bc16-a149e81785bd
|
||||||
|
# Calculating factorials
|
||||||
function normal_for_loop(N)
|
function normal_for_loop(N)
|
||||||
factorials = zeros(BigInt, N)
|
factorials = zeros(BigInt, N)
|
||||||
|
|
||||||
|
@ -161,9 +167,11 @@ end
|
||||||
N = 5000
|
N = 5000
|
||||||
|
|
||||||
# ╔═╡ 55d1d616-b6ee-40dd-a0ed-6274a98b1e73
|
# ╔═╡ 55d1d616-b6ee-40dd-a0ed-6274a98b1e73
|
||||||
|
# This macro shows a lot of information
|
||||||
@benchmark normal_for_loop(N)
|
@benchmark normal_for_loop(N)
|
||||||
|
|
||||||
# ╔═╡ 00c27927-9c72-417a-862f-9b66318d9751
|
# ╔═╡ 00c27927-9c72-417a-862f-9b66318d9751
|
||||||
|
# This macro only shows the most important values
|
||||||
@btime normal_for_loop(N)
|
@btime normal_for_loop(N)
|
||||||
|
|
||||||
# ╔═╡ d21771de-272e-4d57-8c76-c75be709ad0a
|
# ╔═╡ d21771de-272e-4d57-8c76-c75be709ad0a
|
||||||
|
@ -199,6 +207,7 @@ md"""
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# ╔═╡ cacc94b4-21e0-410e-acaa-80e37b447f94
|
# ╔═╡ cacc94b4-21e0-410e-acaa-80e37b447f94
|
||||||
|
# Using multiple threads for calculating factorials
|
||||||
function multithreaded_for_loop(N)
|
function multithreaded_for_loop(N)
|
||||||
factorials = zeros(BigInt, N)
|
factorials = zeros(BigInt, N)
|
||||||
|
|
||||||
|
@ -210,9 +219,15 @@ function multithreaded_for_loop(N)
|
||||||
end
|
end
|
||||||
|
|
||||||
# ╔═╡ a3029480-bcdc-44f3-b504-8bd3bf3aa14d
|
# ╔═╡ a3029480-bcdc-44f3-b504-8bd3bf3aa14d
|
||||||
|
# Magic 🪄
|
||||||
@btime multithreaded_for_loop(N)
|
@btime multithreaded_for_loop(N)
|
||||||
|
|
||||||
|
# ╔═╡ 3fcdf8ff-224c-4616-acd6-d8062f3a7af0
|
||||||
|
# Demonstration of shuffeling
|
||||||
|
shuffle(1:10)
|
||||||
|
|
||||||
# ╔═╡ f141dbb4-bdc5-4f16-8d97-fc0a3d5981f2
|
# ╔═╡ f141dbb4-bdc5-4f16-8d97-fc0a3d5981f2
|
||||||
|
# Shuffle to change the order of calculating factorials
|
||||||
function shuffle_multithreaded_for_loop(N)
|
function shuffle_multithreaded_for_loop(N)
|
||||||
factorials = zeros(BigInt, N)
|
factorials = zeros(BigInt, N)
|
||||||
|
|
||||||
|
@ -224,11 +239,38 @@ function shuffle_multithreaded_for_loop(N)
|
||||||
end
|
end
|
||||||
|
|
||||||
# ╔═╡ e9dcda88-1eef-4c0a-99b2-12eaec56186b
|
# ╔═╡ e9dcda88-1eef-4c0a-99b2-12eaec56186b
|
||||||
|
# It did boost the performance even further 🤯
|
||||||
@btime shuffle_multithreaded_for_loop(N)
|
@btime shuffle_multithreaded_for_loop(N)
|
||||||
|
|
||||||
|
# ╔═╡ 768203a6-345d-4fa8-89a3-91e227579a38
|
||||||
|
md"""
|
||||||
|
The macro `@threads` splits the elements to iterate over evenly and gives every available thread a portion of these elements.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
You have 2 threads and a loop over 1:10.
|
||||||
|
|
||||||
|
Then, one thread will get the values 1:5.
|
||||||
|
|
||||||
|
The other thread will get the values 6:10.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
For our function above, calculating the factorial of the first elements does not take that much time as calculating the factorial of the last elements.
|
||||||
|
|
||||||
|
Therefore, the threads taking the first elements finish their job and wait for the thread that did get the portion with the biggest numbers.
|
||||||
|
|
||||||
|
With `shuffle`, we spread the hard job with big numbers over all threads. Therefore, the work is more evenly distributed and threads will finish (almost) together.
|
||||||
|
|
||||||
|
If you don't want the static work allocation of the macro `@threads` and do want to have more control about threads and what they do and when (dynamic thread allocation), take a look at the macro `Threads.@spawn`.
|
||||||
|
"""
|
||||||
|
|
||||||
# ╔═╡ 009bb2e8-f03e-40f7-a66b-166dc6a1962d
|
# ╔═╡ 009bb2e8-f03e-40f7-a66b-166dc6a1962d
|
||||||
md"""
|
md"""
|
||||||
## Thread safety
|
## Thread safety
|
||||||
|
Using threads is easy in Julia, but you have to pay attention when using threads!
|
||||||
|
|
||||||
|
If multiple threads try to modify a variable or an element in a container at the same time, weird dangerous things happen! 😵💫
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# ╔═╡ dd5e5073-be29-47e7-91c5-9e47c35f905c
|
# ╔═╡ dd5e5073-be29-47e7-91c5-9e47c35f905c
|
||||||
|
@ -248,7 +290,13 @@ end
|
||||||
# The output is random 🤯
|
# The output is random 🤯
|
||||||
thread_unsafe()
|
thread_unsafe()
|
||||||
|
|
||||||
|
# ╔═╡ 7520326f-5576-42c7-aefd-29bc7d2c6b56
|
||||||
|
md"""
|
||||||
|
Lets see how to avoid such situations using another example.
|
||||||
|
"""
|
||||||
|
|
||||||
# ╔═╡ ec08a80c-8886-4312-9481-5c89951681e1
|
# ╔═╡ ec08a80c-8886-4312-9481-5c89951681e1
|
||||||
|
# Calculating the sum of sums
|
||||||
function thread_unsafe_sum(N)
|
function thread_unsafe_sum(N)
|
||||||
sum_of_sums = 0
|
sum_of_sums = 0
|
||||||
|
|
||||||
|
@ -263,8 +311,16 @@ end
|
||||||
N2 = 1000000
|
N2 = 1000000
|
||||||
|
|
||||||
# ╔═╡ fbd2423b-aaea-47a7-a3cf-537860e11a93
|
# ╔═╡ fbd2423b-aaea-47a7-a3cf-537860e11a93
|
||||||
|
# Also random!
|
||||||
thread_unsafe_sum(N2)
|
thread_unsafe_sum(N2)
|
||||||
|
|
||||||
|
# ╔═╡ 73c86a45-d7f7-4d65-a588-1f5ff3adcf6f
|
||||||
|
md"""
|
||||||
|
The problem can be solved by using a vector with `N` elements. After calculating a sum, the thread that did calculate it places the result on a unique place in the vector that is reserved for this specific result.
|
||||||
|
|
||||||
|
At the end, the sum of sums is calculated by summing over the vector of sums.
|
||||||
|
"""
|
||||||
|
|
||||||
# ╔═╡ e65ad214-33ba-4d08-81f0-5f98022a9f78
|
# ╔═╡ e65ad214-33ba-4d08-81f0-5f98022a9f78
|
||||||
function thread_safe_sum(N)
|
function thread_safe_sum(N)
|
||||||
sums = zeros(Int64, N)
|
sums = zeros(Int64, N)
|
||||||
|
@ -277,12 +333,15 @@ function thread_safe_sum(N)
|
||||||
end
|
end
|
||||||
|
|
||||||
# ╔═╡ 8ad3daa6-d221-4ff7-9bc2-8e8a66bdd8c7
|
# ╔═╡ 8ad3daa6-d221-4ff7-9bc2-8e8a66bdd8c7
|
||||||
|
# Stable!
|
||||||
@btime thread_safe_sum(N2)
|
@btime thread_safe_sum(N2)
|
||||||
|
|
||||||
# ╔═╡ 95dffc7f-3393-487e-8521-c96291cdc7bf
|
# ╔═╡ 95dffc7f-3393-487e-8521-c96291cdc7bf
|
||||||
|
# Verify that we did not exceed the limit!
|
||||||
typemax(Int64)
|
typemax(Int64)
|
||||||
|
|
||||||
# ╔═╡ ebd3a9d9-7a12-4001-9b53-913f664fb1c8
|
# ╔═╡ ebd3a9d9-7a12-4001-9b53-913f664fb1c8
|
||||||
|
# Lets try shuffeling again
|
||||||
function shuffle_safe_thread_sum(N)
|
function shuffle_safe_thread_sum(N)
|
||||||
sums = zeros(Int64, N)
|
sums = zeros(Int64, N)
|
||||||
|
|
||||||
|
@ -300,18 +359,55 @@ end
|
||||||
# Always benchmark! This is the only method to make sure that an "optimization" indeed an optimization is
|
# Always benchmark! This is the only method to make sure that an "optimization" indeed an optimization is
|
||||||
@btime shuffle_safe_thread_sum(N2)
|
@btime shuffle_safe_thread_sum(N2)
|
||||||
|
|
||||||
|
# ╔═╡ 24ad64f9-b0a4-48ac-b6dc-06a5d1c7b072
|
||||||
|
function shuffeling_cost(N)
|
||||||
|
shuffle(1:N)
|
||||||
|
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
# ╔═╡ fe0b18c0-cbf0-421d-b6a0-987321a0b09d
|
||||||
|
# The shuffeling itself is too expensive compared to the calculation in the loop
|
||||||
|
@btime shuffeling_cost(N2)
|
||||||
|
|
||||||
# ╔═╡ 09f71a9e-6798-492f-98df-45087d0c4c8b
|
# ╔═╡ 09f71a9e-6798-492f-98df-45087d0c4c8b
|
||||||
md"""
|
md"""
|
||||||
# Performance
|
# Performance optimization
|
||||||
Julia has a focus on high performance. But if you don't pay attention, you might produce code that either can not be optimized by the compiler or results in a lot of allocations. In both cases, your code will be slow.
|
Julia has a focus on high performance. But if you don't pay attention, you might produce code that either can not be optimized by the compiler or results in a lot of allocations. In both cases, your code will be slow.
|
||||||
|
|
||||||
In this section, tools and tips for performance optimization in Julia are presented.
|
In this section, some tools and tips for performance optimization in Julia are presented.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# ╔═╡ 491e077c-fbf0-4ae7-b54b-9f9c68f8f1b0
|
||||||
|
md"""
|
||||||
|
## Tips
|
||||||
|
Most important performance tips:
|
||||||
|
- Don't use global variables! 🌐
|
||||||
|
- Don't use containers with abstract type! 🆎
|
||||||
|
- Don't write long functions! 🦒
|
||||||
|
- Don't change the type of a variable! 🥸
|
||||||
|
- Preallocate when possible! 🕰️
|
||||||
|
- Reuse containers when possible! 🔄
|
||||||
|
- Use views instead of copies when possible! 🧐
|
||||||
|
- Benchmark, benchmark, benchmark! Also pay attention to allocations. ⌚️
|
||||||
|
|
||||||
|
These are some of the tips in the official documentation of Julia.
|
||||||
|
|
||||||
|
If you are writing performance critical code in Julia, make sure you have a **very good relationship** with all performance tips in the documentation:
|
||||||
|
|
||||||
|
https://docs.julialang.org/en/v1/manual/performance-tips/
|
||||||
|
"""
|
||||||
|
|
||||||
|
# ╔═╡ 32981b03-edb9-417f-b5e0-c652e3ac715c
|
||||||
|
md"""
|
||||||
|
## Demo
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# ╔═╡ 6509dddd-ff17-49db-8e5e-fcea1ef0026c
|
# ╔═╡ 6509dddd-ff17-49db-8e5e-fcea1ef0026c
|
||||||
N3 = 1000000
|
N3 = 1000000
|
||||||
|
|
||||||
# ╔═╡ 2a24aebc-0654-4d00-bdab-627a8e1a75f2
|
# ╔═╡ 2a24aebc-0654-4d00-bdab-627a8e1a75f2
|
||||||
|
# Use a global array of type Any
|
||||||
begin
|
begin
|
||||||
sin_vals = []
|
sin_vals = []
|
||||||
|
|
||||||
|
@ -327,6 +423,7 @@ begin
|
||||||
end
|
end
|
||||||
|
|
||||||
# ╔═╡ 56058ab1-4ea2-479d-88f9-5da6ac8c39c2
|
# ╔═╡ 56058ab1-4ea2-479d-88f9-5da6ac8c39c2
|
||||||
|
# Not using an array of type Any
|
||||||
begin
|
begin
|
||||||
typed_sin_vals = Float64[]
|
typed_sin_vals = Float64[]
|
||||||
|
|
||||||
|
@ -342,6 +439,7 @@ begin
|
||||||
end
|
end
|
||||||
|
|
||||||
# ╔═╡ ef164e7c-668a-4312-83f1-687ca7d4c8f9
|
# ╔═╡ ef164e7c-668a-4312-83f1-687ca7d4c8f9
|
||||||
|
# Preallocation
|
||||||
begin
|
begin
|
||||||
preallocated_sin_vals = zeros(Float64, N3)
|
preallocated_sin_vals = zeros(Float64, N3)
|
||||||
|
|
||||||
|
@ -357,6 +455,8 @@ begin
|
||||||
end
|
end
|
||||||
|
|
||||||
# ╔═╡ ebc621b5-3aa3-4cf7-bcdf-e4c5fbb79f50
|
# ╔═╡ ebc621b5-3aa3-4cf7-bcdf-e4c5fbb79f50
|
||||||
|
# The difference of not using global variables
|
||||||
|
# Never use global variables!
|
||||||
begin
|
begin
|
||||||
passed_preallocated_sin_vals = zeros(Float64, N3)
|
passed_preallocated_sin_vals = zeros(Float64, N3)
|
||||||
|
|
||||||
|
@ -387,6 +487,18 @@ begin
|
||||||
@btime local_access(N3, passed_sin_vals)
|
@btime local_access(N3, passed_sin_vals)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# ╔═╡ 43d2cbda-a21b-46ae-8433-7a9ef30c536b
|
||||||
|
md"""
|
||||||
|
## `StaticArrays`
|
||||||
|
If you are dealing with small arrays with less than 100 elements, then take a look at the package [`StaticArrays.jl`](https://github.com/JuliaArrays/StaticArrays.jl). Especially if you are dealing with 2d or 3d coordinates, using `StaticArrays` will make a big performance difference.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# ╔═╡ f0b634a5-19a9-4c61-932f-7ae357e13be2
|
||||||
|
md"""
|
||||||
|
## Profiling
|
||||||
|
Of course, you can profile your code in Julia. Check the package [ProfileView](https://github.com/timholy/ProfileView.jl) for example.
|
||||||
|
"""
|
||||||
|
|
||||||
# ╔═╡ 00000000-0000-0000-0000-000000000001
|
# ╔═╡ 00000000-0000-0000-0000-000000000001
|
||||||
PLUTO_PROJECT_TOML_CONTENTS = """
|
PLUTO_PROJECT_TOML_CONTENTS = """
|
||||||
[deps]
|
[deps]
|
||||||
|
@ -620,7 +732,7 @@ uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"
|
||||||
# ╟─7f45c502-0909-42df-b93d-384f743df6a9
|
# ╟─7f45c502-0909-42df-b93d-384f743df6a9
|
||||||
# ╟─f23ad33d-af1d-40c2-9efc-17ef8c4d1fb8
|
# ╟─f23ad33d-af1d-40c2-9efc-17ef8c4d1fb8
|
||||||
# ╟─6340aec8-6f77-4a30-8815-ce76ddecd6e8
|
# ╟─6340aec8-6f77-4a30-8815-ce76ddecd6e8
|
||||||
# ╠═0e53e4ee-16a7-47ef-9992-77cbfd1ed258
|
# ╟─0e53e4ee-16a7-47ef-9992-77cbfd1ed258
|
||||||
# ╠═7a9ccfbc-bd2e-41d0-be5d-dea04b90d397
|
# ╠═7a9ccfbc-bd2e-41d0-be5d-dea04b90d397
|
||||||
# ╠═d86e4a2f-e737-49a4-bc16-a149e81785bd
|
# ╠═d86e4a2f-e737-49a4-bc16-a149e81785bd
|
||||||
# ╠═bc777c73-9573-41c3-8ab5-843335539f96
|
# ╠═bc777c73-9573-41c3-8ab5-843335539f96
|
||||||
|
@ -632,26 +744,36 @@ uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"
|
||||||
# ╠═cacc94b4-21e0-410e-acaa-80e37b447f94
|
# ╠═cacc94b4-21e0-410e-acaa-80e37b447f94
|
||||||
# ╠═a3029480-bcdc-44f3-b504-8bd3bf3aa14d
|
# ╠═a3029480-bcdc-44f3-b504-8bd3bf3aa14d
|
||||||
# ╠═8b1dfee2-bd8b-4d23-b9f8-9406002e0eaa
|
# ╠═8b1dfee2-bd8b-4d23-b9f8-9406002e0eaa
|
||||||
|
# ╠═3fcdf8ff-224c-4616-acd6-d8062f3a7af0
|
||||||
# ╠═f141dbb4-bdc5-4f16-8d97-fc0a3d5981f2
|
# ╠═f141dbb4-bdc5-4f16-8d97-fc0a3d5981f2
|
||||||
# ╠═e9dcda88-1eef-4c0a-99b2-12eaec56186b
|
# ╠═e9dcda88-1eef-4c0a-99b2-12eaec56186b
|
||||||
# ╠═009bb2e8-f03e-40f7-a66b-166dc6a1962d
|
# ╟─768203a6-345d-4fa8-89a3-91e227579a38
|
||||||
|
# ╟─009bb2e8-f03e-40f7-a66b-166dc6a1962d
|
||||||
# ╠═dd5e5073-be29-47e7-91c5-9e47c35f905c
|
# ╠═dd5e5073-be29-47e7-91c5-9e47c35f905c
|
||||||
# ╠═4554cbf0-36f5-45c6-a966-ad18b1592a60
|
# ╠═4554cbf0-36f5-45c6-a966-ad18b1592a60
|
||||||
|
# ╟─7520326f-5576-42c7-aefd-29bc7d2c6b56
|
||||||
# ╠═ec08a80c-8886-4312-9481-5c89951681e1
|
# ╠═ec08a80c-8886-4312-9481-5c89951681e1
|
||||||
# ╠═3b5d9f7c-1fc9-4e85-8c07-8b5709895a10
|
# ╠═3b5d9f7c-1fc9-4e85-8c07-8b5709895a10
|
||||||
# ╠═fbd2423b-aaea-47a7-a3cf-537860e11a93
|
# ╠═fbd2423b-aaea-47a7-a3cf-537860e11a93
|
||||||
|
# ╟─73c86a45-d7f7-4d65-a588-1f5ff3adcf6f
|
||||||
# ╠═e65ad214-33ba-4d08-81f0-5f98022a9f78
|
# ╠═e65ad214-33ba-4d08-81f0-5f98022a9f78
|
||||||
# ╠═8ad3daa6-d221-4ff7-9bc2-8e8a66bdd8c7
|
# ╠═8ad3daa6-d221-4ff7-9bc2-8e8a66bdd8c7
|
||||||
# ╠═95dffc7f-3393-487e-8521-c96291cdc7bf
|
# ╠═95dffc7f-3393-487e-8521-c96291cdc7bf
|
||||||
# ╠═ebd3a9d9-7a12-4001-9b53-913f664fb1c8
|
# ╠═ebd3a9d9-7a12-4001-9b53-913f664fb1c8
|
||||||
# ╠═ddd2409e-de34-4eb9-a0b7-e10cc6c0ce9f
|
# ╠═ddd2409e-de34-4eb9-a0b7-e10cc6c0ce9f
|
||||||
|
# ╠═24ad64f9-b0a4-48ac-b6dc-06a5d1c7b072
|
||||||
|
# ╠═fe0b18c0-cbf0-421d-b6a0-987321a0b09d
|
||||||
# ╟─09f71a9e-6798-492f-98df-45087d0c4c8b
|
# ╟─09f71a9e-6798-492f-98df-45087d0c4c8b
|
||||||
|
# ╟─491e077c-fbf0-4ae7-b54b-9f9c68f8f1b0
|
||||||
|
# ╟─32981b03-edb9-417f-b5e0-c652e3ac715c
|
||||||
# ╠═6509dddd-ff17-49db-8e5e-fcea1ef0026c
|
# ╠═6509dddd-ff17-49db-8e5e-fcea1ef0026c
|
||||||
# ╠═2a24aebc-0654-4d00-bdab-627a8e1a75f2
|
# ╠═2a24aebc-0654-4d00-bdab-627a8e1a75f2
|
||||||
# ╠═56058ab1-4ea2-479d-88f9-5da6ac8c39c2
|
# ╠═56058ab1-4ea2-479d-88f9-5da6ac8c39c2
|
||||||
# ╠═ef164e7c-668a-4312-83f1-687ca7d4c8f9
|
# ╠═ef164e7c-668a-4312-83f1-687ca7d4c8f9
|
||||||
# ╠═ebc621b5-3aa3-4cf7-bcdf-e4c5fbb79f50
|
# ╠═ebc621b5-3aa3-4cf7-bcdf-e4c5fbb79f50
|
||||||
# ╠═afcc15de-81e0-484f-80cf-3d805517c6e8
|
# ╠═afcc15de-81e0-484f-80cf-3d805517c6e8
|
||||||
|
# ╟─43d2cbda-a21b-46ae-8433-7a9ef30c536b
|
||||||
|
# ╟─f0b634a5-19a9-4c61-932f-7ae357e13be2
|
||||||
# ╟─1fb7d9af-333e-44f2-b693-09ff97937d4c
|
# ╟─1fb7d9af-333e-44f2-b693-09ff97937d4c
|
||||||
# ╟─00000000-0000-0000-0000-000000000001
|
# ╟─00000000-0000-0000-0000-000000000001
|
||||||
# ╟─00000000-0000-0000-0000-000000000002
|
# ╟─00000000-0000-0000-0000-000000000002
|
||||||
|
|
Loading…
Reference in a new issue