# Multi-threaded Array building

**URL:** <https://discourse.julialang.org/t/multi-threaded-array-building/132463>\
**Category:** Performance\
**Tags:** multithreading\
**Created:** [September 17, 2025, 12:23pm UTC](https://discourse.julialang.org/t/multi-threaded-array-building/132463 "2025-09-17T12:23:14Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![FerreolS](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ferreols/32/216854_2.png) [@FerreolS](https://discourse.julialang.org/u/FerreolS)\
**Post date:** [September 17, 2025, 12:23pm UTC](https://discourse.julialang.org/t/multi-threaded-array-building/132463/1 "2025-09-17T12:23:14Z")

</div>

Dear all,  
I have a code of this kind to build an array:

```julia
N= 2000
A = zeros(Float64,N,N)
B # some long (>>10_000) vector of custom struct
for idx in arrayofindex #> lots (>>10_000) of elements
      i,j,v = processing(B[idx])
     A[i,j] += v
end

```

It is quite long as `processing()` take some times and I want to multithreaded it ( `processing()` is thread-safe).  
I have read [https://discourse.julialang.org/t/editing-an-array-with-multiple-threads/25364](https://discourse.julialang.org/t/editing-an-array-with-multiple-threads/25364) and [https://discourse.julialang.org/t/thread-safe-array-building/3275/2](https://discourse.julialang.org/t/thread-safe-array-building/3275/2) but these answers (that are using of `A[threadid()]`) seems outdated.

What is now (with julia 1.10 or 1.11) the best way to multithread that kind of array building function?

---

<div class="post-metadata">

**Author:** ![FerreolS](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ferreols/32/216854_2.png) [@FerreolS](https://discourse.julialang.org/u/FerreolS)\
**Post date:** [September 17, 2025, 12:46pm UTC](https://discourse.julialang.org/t/multi-threaded-array-building/132463/2 "2025-09-17T12:46:46Z")

</div>

I just saw that a very similar question [Pool of locks?](https://discourse.julialang.org/t/pool-of-locks/132457) appears when I was writing this. I’ll see if the solution given there answer my problem

---

<div class="post-metadata">

**Author:** ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)\
**Post date:** [September 17, 2025, 12:53pm UTC](https://discourse.julialang.org/t/multi-threaded-array-building/132463/3 "2025-09-17T12:53:12Z")

</div>

You want to do data conversion? from B to A?  
if this is a deterministic procedure, why don’t you just do it once and then save `A`?  
If this data conversion procedure the main workload in your overall procedure?

I think `Threads.@threads` can do what you want.

---

<div class="post-metadata">

**Author:** ![FerreolS](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ferreols/32/216854_2.png) [@FerreolS](https://discourse.julialang.org/u/FerreolS)\
**Post date:** [September 17, 2025, 1:00pm UTC](https://discourse.julialang.org/t/multi-threaded-array-building/132463/4 "2025-09-17T13:00:47Z")

</div>

No it is not a conversion and `(i,j)` can appear multiple time.  
I could have use `@threads` to build an array of `(i,j,v)` for all idx and then performing the reduction but in my actual problem I can’t really store all `(i,j,v)` (it is a lot of memory).

---

<div class="post-metadata">

**Author:** ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)\
**Post date:** [September 17, 2025, 1:08pm UTC](https://discourse.julialang.org/t/multi-threaded-array-building/132463/5 "2025-09-17T13:08:31Z")

</div>

> [@FerreolS](#):
>
> `(i,j)` can appear multiple time.

What about

```julia
A = [Threads.Atomic{Float64}(0) for _ = 1:N, _ = 1:N]
Threads.@threads for k = eachindex(B)
    local (i, j, v) = processing(B[k])
    Threads.atomic_add!(A[i, j], v)
end

```

Finally recover `A = map(x -> x[], A)`.

---

<div class="post-metadata">

**Author:** ![gdalle](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/gdalle/32/27854_2.png) [@gdalle](https://discourse.julialang.org/u/gdalle)\
**Post date:** [September 17, 2025, 1:58pm UTC](https://discourse.julialang.org/t/multi-threaded-array-building/132463/6 "2025-09-17T13:58:01Z")

</div>

Is your array sparse?

---

<div class="post-metadata">

**Author:** ![FerreolS](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ferreols/32/216854_2.png) [@FerreolS](https://discourse.julialang.org/u/FerreolS)\
**Post date:** [September 17, 2025, 2:25pm UTC](https://discourse.julialang.org/t/multi-threaded-array-building/132463/7 "2025-09-17T14:25:16Z")

</div>

No `A` is basically an image with many patterns of parameters stored in `B`

---

<div class="post-metadata">

**Author:** ![FerreolS](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ferreols/32/216854_2.png) [@FerreolS](https://discourse.julialang.org/u/FerreolS)\
**Post date:** [September 17, 2025, 2:27pm UTC](https://discourse.julialang.org/t/multi-threaded-array-building/132463/8 "2025-09-17T14:27:48Z")

</div>

Thanks, I’ll try this and the ones given in [Pool of locks?](https://discourse.julialang.org/t/pool-of-locks/132457) .  
I will report a small benchmark as soon I have the time to perform it

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [September 17, 2025, 3:15pm UTC](https://discourse.julialang.org/t/multi-threaded-array-building/132463/9 "2025-09-17T15:15:00Z")

</div>

> [@FerreolS](#):
>
> What is now (with julia 1.10 or 1.11) the best way to multithread that kind of array building function?

You could

1. Use `task_local_storage` to have a per-task array (rather than per-thread). e.g. [No more threadid indexing? [thread-local storage] - #12 by stevengj](https://discourse.julialang.org/t/no-more-threadid-indexing-thread-local-storage/131535/12)
2. Use ChunkSplitters.jl (ala [Why has simple threading using `threadid` become so complex in v1.12...? - #28 by foobar\_lv2](https://discourse.julialang.org/t/why-has-simple-threading-using-threadid-become-so-complex-in-v1-12/129135/28)) to create a per-chunk array. (This has the downside of imposing essentially a static parallelization schedule, which might be bad if `processing(B[idx])` could take a very different amount of time depending on the argument.)
3. Use some higher-level primitives like in OhMyThreads.jl or Transducers.jl, e.g. expressing this as a parallel reduction operation.

---

<div class="post-metadata">

**Author:** ![simsurace](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/simsurace/32/30216_2.png) [@simsurace](https://discourse.julialang.org/u/simsurace)\
**Post date:** [September 17, 2025, 3:19pm UTC](https://discourse.julialang.org/t/multi-threaded-array-building/132463/10 "2025-09-17T15:19:29Z")

</div>

Why not use atomics? E.g.

```julia
N = 2000
buffers = [Threads.Atomic{Float64}() for _ in 1:N, _ in 1:N]
B # some long (>>10_000) vector of custom struct
OhMyThreads.@tasks for idx in arrayofindex #> lots (>>10_000) of elements
      i,j,v = processing(B[idx])
     Threads.atomic_add!(buffers[i, j], v)
end
A = getindex.(buffers)

```

---

<div class="post-metadata">

**Author:** ![foobar\_lv2](https://avatars.discourse-cdn.com/v4/letter/f/ee59a6/32.png) [@foobar\_lv2](https://discourse.julialang.org/u/foobar_lv2)\
**Post date:** [September 18, 2025, 8:37am UTC](https://discourse.julialang.org/t/multi-threaded-array-building/132463/11 "2025-09-18T08:37:42Z")

</div>

> [@simsurace](#):
>
> Why not use atomics?

The appropriate way of using atomics is

```julia-auto
atomic_view = unsafe_wrap(AtomicMemory{Float64}, pointer(A), N*N; own=false)
...
@atomic atomic_view[i + (j-1)*N] += v

```

However, you won’t be happy with the performance.

For your specific value of `N`, just give each thread its private copy of `A` (it’s a mere 32mb) and then add all the `A` copies together single-threaded.

Or adjust your sample code to be more representative of your real problem: Give us ballpark estimates for the actual sizes / numbers you care about.

---

<div class="post-metadata">

**Author:** ![simsurace](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/simsurace/32/30216_2.png) [@simsurace](https://discourse.julialang.org/u/simsurace)\
**Post date:** [September 18, 2025, 9:03am UTC](https://discourse.julialang.org/t/multi-threaded-array-building/132463/12 "2025-09-18T09:03:38Z")

</div>

Could you please elaborate on what is inappropriate about my suggestion using `Threads.Atomic`?

---

<div class="post-metadata">

**Author:** ![foobar\_lv2](https://avatars.discourse-cdn.com/v4/letter/f/ee59a6/32.png) [@foobar\_lv2](https://discourse.julialang.org/u/foobar_lv2)\
**Post date:** [September 18, 2025, 9:15am UTC](https://discourse.julialang.org/t/multi-threaded-array-building/132463/13 "2025-09-18T09:15:19Z")

</div>

You are building a large array of object references, each one pointing to a separately allocated box containing a single Float64, with atomic access.

I think it is somewhat unfortunate that julia has chosen the somewhat insane C++ as a model for atomics, instead of the reasonable C; i.e. that atomicity is a property of the object / field / memory location, instead of a property of the access. Heck, even java supports C-style atomics with VarHandle. (the syntax / ergonomics of VarHandle are somewhat atrocious, but at least the language provides the necessary tools)

But that ship has sailed unfortunately. Maybe we’ll get a VarHandle analogue in the future.

---

<div class="post-metadata">

**Author:** ![sgaure](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sgaure/32/14779_2.png) [@sgaure](https://discourse.julialang.org/u/sgaure)\
**Post date:** [September 18, 2025, 9:15am UTC](https://discourse.julialang.org/t/multi-threaded-array-building/132463/14 "2025-09-18T09:15:22Z")

</div>

> [@simsurace](#):
>
> Could you please elaborate on what is inappropriate about my suggestion using `Threads.Atomic`?

`Atomic` is a mutable struct, so each of them is allocated on the heap. Which means that your `buffers` `Vector` is a vector of pointers. This can cause inefficient use of the cache. Whether this is a performance problem depends on the use.

---

<div class="post-metadata">

**Author:** ![sgaure](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sgaure/32/14779_2.png) [@sgaure](https://discourse.julialang.org/u/sgaure)\
**Post date:** [September 18, 2025, 9:19am UTC](https://discourse.julialang.org/t/multi-threaded-array-building/132463/15 "2025-09-18T09:19:03Z")

</div>

In the upcoming v1.12 version, the `@atomic` macro can be used on array elements:

> <https://github.com/JuliaLang/julia/pull/54707>
>
> Following the discussion in #54642
> 
> Implemented:
> \- \[x\] \`modifyindex\_atomic!\`,… \`swapindex\_atomic!\`, \`replaceindex\_atomic!\` for \`GenericMemory\`
> \- \[x\] \`getindex\_atomic\`, \`setindex\_atomic!\`, \`setindexonce\_atomic!\` for \`GenericMemory\` 
> \- \[x\] add support for references in \`@atomic\` macros
> \- \[x\] add support for vararg indices in \`@atomic\` macros 
> \- \[x\] tests
> \- \[x\] update docstrings with example usage
> \- ~\[\] update Atomics section of the manual (?)~
> \- \[x\] news
> 
> @oscardssmith @vtjnash 
> 
> \# New \`@atomic\` transformations implemented here:
> \`\`\`julia
> julia\> @macroexpand (@atomic a\[i1,i2\])
> :(Base.getindex\_atomic(a, :sequentially\_consistent, i1, i2))
> 
> julia\> @macroexpand (@atomic order a\[i1,i2\])
> :(Base.getindex\_atomic(a, order, i1, i2))
> 
> julia\> @macroexpand (@atomic a\[i1,i2\] = 2.0)
> :(Base.setindex\_atomic!(a, :sequentially\_consistent, 2.0, i1, i2))
> 
> julia\> @macroexpand (@atomic order a\[i1,i2\] = 2.0)
> :(Base.setindex\_atomic!(a, order, 2.0, i1, i2))
> 
> julia\> @macroexpand (@atomicswap a\[i1,i2\] = 2.0)
> :(Base.swapindex\_atomic!(a, :sequentially\_consistent, 2.0, i1, i2))
> 
> julia\> @macroexpand (@atomicswap order a\[i1,i2\] = 2.0)
> :(Base.swapindex\_atomic!(a, order, 2.0, i1, i2))
> 
> julia\> @macroexpand (@atomic a\[i1,i2\] += 2.0)
> :((Base.modifyindex\_atomic!(a, :sequentially\_consistent, +, 2.0, i1, i2))\[2\])
> 
> julia\> @macroexpand (@atomic order a\[i1,i2\] += 2.0)
> :((Base.modifyindex\_atomic!(a, order, +, 2.0, i1, i2))\[2\])
> 
> julia\> @macroexpand (@atomiconce a\[i1,i2\] = 2.0)
> :(Base.setindexonce\_atomic!(a, :sequentially\_consistent, :sequentially\_consistent, 2.0, i1, i2))
> 
> julia\> @macroexpand (@atomiconce o1 o2 a\[i1,i2\] = 2.0)
> :(Base.setindexonce\_atomic!(a, o1, o2, 2.0, i1, i2))
> 
> julia\> @macroexpand (@atomicreplace a\[i1,i2\] (2.0=\>3.0))
> :(Base.replaceindex\_atomic!(a, :sequentially\_consistent, :sequentially\_consistent, 2.0, 3.0, i1, i2))
> 
> julia\> @macroexpand (@atomicreplace o1 o2 a\[i1,i2\] (2.0=\>3.0))
> :(Base.replaceindex\_atomic!(a, o1, o2, 2.0, 3.0, i1, i2))
> 
> \`\`\`

---

<div class="post-metadata">

**Author:** ![simsurace](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/simsurace/32/30216_2.png) [@simsurace](https://discourse.julialang.org/u/simsurace)\
**Post date:** [September 18, 2025, 9:27am UTC](https://discourse.julialang.org/t/multi-threaded-array-building/132463/16 "2025-09-18T09:27:05Z")

</div>

Sure. In the OP the assumption was that the computations take quite a long time, so I wouldn’t expect this to be a bottleneck. In a very hot loop I wouldn’t have suggested this pattern. Thanks for the clarification.

---

<div class="post-metadata">

**Author:** ![foobar\_lv2](https://avatars.discourse-cdn.com/v4/letter/f/ee59a6/32.png) [@foobar\_lv2](https://discourse.julialang.org/u/foobar_lv2)\
**Post date:** [September 18, 2025, 9:31am UTC](https://discourse.julialang.org/t/multi-threaded-array-building/132463/17 "2025-09-18T09:31:34Z")

</div>

> In the upcoming v1.12 version, the `@atomic` macro can be used on array elements:

Unfortunately not on current nightly:

```julia-auto
julia> v=[1]; m=v.ref.mem; @atomic m[1]
ERROR: ConcurrencyViolationError("memoryrefget: non-atomic memory cannot be accessed atomically")

```

In my view, this is plain bullshit. Atomic ops should be supported if alignment + size + architecture support them. And :notatomic should be a valid memory memory order that simply ignores the locks.

So sure, the ConcurrencyViolationError is sensible for `v=[(1,2,3)]; m=v.ref.mem; @atomic m[1]` because my machine doesn’t have an instruction to atomically load 24 bytes with 8 byte alignment; and there exists no lock that we could use as a fallback since I didn’t allocate space for it by making `m` an `AtomicMemory`.

---

<div class="post-metadata">

**Author:** ![Mason](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mason/32/2423_2.png) [@Mason](https://discourse.julialang.org/u/Mason)\
**Post date:** [September 18, 2025, 9:53am UTC](https://discourse.julialang.org/t/multi-threaded-array-building/132463/18 "2025-09-18T09:53:05Z")

</div>

> ```julia-auto
> N= 2000
> A = zeros(Float64,N,N)
> B # some long (>>10_000) vector of custom struct
> for idx in arrayofindex #> lots (>>10_000) of elements
> i,j,v = processing(B[idx])
> A[i,j] += v
> end
> 
> ```

It depends a lot on the details, but assuming I have enough RAM to store multiple copies of `A`, my intuition would be to parallelize this like so:

```julia-auto
using ChunkSplitters
function f(N, B, array_of_index; ntasks=Threads.nthreads())
    As = zeros(Float64, N, N, ntasks) # add extra dimension corresponding to which task is operating
    @sync for (k, chunk_of_indices) in enumerate(chunks(array_of_index; n=ntasks))
        Threads.@spawn begin
            local A = @view As[:, :, k]
            for idx in chunk_of_indices
                i,j,v = processing(B[idx])
                A[i,j] += v
            end
        end
    end
    # Now combine the different dimensions, you may want to also parallelize this step, but probably not
    dropdims(sum(As; dims=3); dims=3)
end

```

This has the advantage that each task gets its own buffer that it can work on in a lock-free manner, so there’s no per-index overhead in getting and setting indices. However, it also has the disadvantage that it allocates a lot more memory which may introduce its own bottlenecks.

I find this style much easier to reason about and use than atomics or locks though, and is typically preferable to creating `N^2` independant locks, or `N^2` independant heap allocated `AtomicInt`s.

---

<div class="post-metadata">

**Author:** ![FerreolS](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ferreols/32/216854_2.png) [@FerreolS](https://discourse.julialang.org/u/FerreolS)\
**Post date:** [September 20, 2025, 8:32am UTC](https://discourse.julialang.org/t/multi-threaded-array-building/132463/19 "2025-09-20T08:32:47Z")

</div>

I have made a small toy model for benchmarking with several ways to implement this multithreading:

```julia
using Primes
using Atomix
using OhMyThreads
using ChunkSplitters

function unbalanced_processing1(μx, μy, σ, i)
    isprime(i) && sleep(0.05)
    sleep(0.001)
    u = round(Int, μx)
    v = round(Int, μy)
    y = σ / sqrt((u - μx)^2 + ((v - μy)^2))
    return (u, v, y)
end

function balanced_processing1(μx, μy, σ, i)
    sleep(0.001)
    u = round(Int, μx)
    v = round(Int, μy)
    y = σ / sqrt((u - μx)^2 + ((v - μy)^2))
    return (u, v, y)
end

function simple_loop(f, N, B, arrayofindex)
    A = zeros(Float64, N, N)
    @inbounds for idx in arrayofindex
        u, v, y = f(B[idx]..., idx)
        A[u, v] += y
    end
    return A
end

function atomic_loop(f, N, B, arrayofindex)
    A = zeros(Float64, N, N)
    atomic_view = unsafe_wrap(AtomicMemory{Float64}, pointer(A), N * N; own = false)
    Threads.@threads :dynamic for idx in arrayofindex
        u, v, y = f(B[idx]..., idx)
        Atomix.@atomic atomic_view[u + (v - 1) * N] += y
    end
    return A
end

function atomic_add_loop(f, N, B, arrayofindex)
    buffers = [Threads.Atomic{Float64}() for _ in 1:N, _ in 1:N]
    Threads.@threads for idx in arrayofindex
        u, v, y = f(B[idx]..., idx)
        Threads.atomic_add!(buffers[u, v], y)
    end
    return getindex.(buffers)
end

function chunked_loop(f, N, B, arrayofindex)
    nchunks = Threads.nthreads()
    buffer = [zeros(Float64, N, N) for _ in 1:nchunks]
    Threads.@threads :dynamic for (ichunk, inds) in enumerate(index_chunks(arrayofindex; n = nchunks))
        @inbounds for idx in inds
            u, v, y = f(B[idx]..., idx)
            buffer[ichunk][u, v] += y
        end
    end
    return sum(buffer)
end

function chunked_loop2(f, N, B, arrayofindex)
    nchunks = Threads.nthreads()
    As = zeros(Float64, N, N, nchunks) # add extra dimension corresponding to which task is operating
    @sync for (k, chunk_of_indices) in enumerate(chunks(arrayofindex; n = nchunks))
        Threads.@spawn begin
            @inbounds for idx in chunk_of_indices
                u, v, y = f(B[idx]..., idx)
                As[u, v, k] += y
            end
        end
    end
    # Now combine the different dimensions, you may want to also parallelize this step, but probably not
    return dropdims(sum(As; dims = 3); dims = 3)
end

function channel_loop(f, N, B, arrayofindex)
    nbuffers = Threads.nthreads()
    chnl = Channel{Matrix{Float64}}(nbuffers)
    foreach(1:nbuffers) do _
        put!(chnl, zeros(Float64, N, N))
    end
    tmap(arrayofindex) do idx
        u, v, y = f(B[idx]..., idx)
        As = take!(chnl)
        As[u, v] += y
        put!(chnl, As)
    end
    return mapreduce(.+, 1:nbuffers) do _
        return take!(chnl)
    end
end

```

Here is the output of `@btime`

```julia
K = 100
N = 2048
M = 1_000_000

B = [(rand() * (N - 20) + 10, rand() * (N - 20) + 10, randn()) for i in 1:M]
arrayofindex = [rand(1:M) for i in 1:K]

@btime simple_loop(balanced_processing1, N, B, arrayofindex);

@btime simple_loop(unbalanced_processing1, N, B, arrayofindex);

...

```

for `K=100`

| Method | Unbalanced Time | Unbalanced Allocs | Unbalanced Mem | Balanced Time | Balanced Allocs | Balanced Mem |
| --- | --- | --- | --- | --- | --- | --- |
| simple\_loop | 584.909 ms | 2,252 | 32.05 MiB | 232.154 ms | 2,224 | 32.05 MiB |
| channel\_loop | 393.912 ms | 698 | 992.03 MiB | 307.413 ms | 668 | 992.03 MiB |
| atomic\_loop | 120.286 ms | 520 | 32.02 MiB | 23.987 ms | 491 | 32.02 MiB |
| atomic\_add\_loop | 166.522 ms | 4,194,825 | 128.02 MiB | 57.694 ms | 4,194,796 | 128.02 MiB |
| chunked\_loop | 564.911 ms | 683 | 992.03 MiB | 304.707 ms | 584 | 992.02 MiB |
| chunked\_loop2 | 456.940 ms | 545 | 544.02 MiB | 355.152 ms | 515 | 544.02 MiB |

for `K=10_000`

| Method | Unbalanced Time | Unbalanced Allocs | Unbalanced Mem | Balanced Time | Balanced Allocs | Balanced Mem |
| --- | --- | --- | --- | --- | --- | --- |
| simple\_loop | 63.656 s | 228,734 | 37.37 MiB | 23.596 s | 220,559 | 37.00 MiB |
| channel\_loop | 5.904 s | 43,810 | 993.72 MiB | 3.482 s | 40,656 | 993.63 MiB |
| atomic\_loop | 5.807 s | 43,616 | 33.33 MiB | 2.922 s | 40,460 | 33.23 MiB |
| atomic\_add\_loop | 6.213 s | 4,237,914 | 129.33 MiB | 2.822 s | 4,234,757 | 129.24 MiB |
| chunked\_loop | 8.773 s | 45,510 | 993.39 MiB | 3.266 s | 40,550 | 993.24 MiB |
| chunked\_loop2 | 6.749 s | 43,640 | 545.33 MiB | 3.533 s | 40,492 | 545.24 MiB |

It seems that `@atomic` as proposed by @foobar_lv2 is a good solution and I’m “happy with the performance”

---

<div class="post-metadata">

**Author:** ![FerreolS](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ferreols/32/216854_2.png) [@FerreolS](https://discourse.julialang.org/u/FerreolS)\
**Post date:** [September 20, 2025, 8:58am UTC](https://discourse.julialang.org/t/multi-threaded-array-building/132463/20 "2025-09-20T08:58:15Z")

</div>

I did not find an easy way to use `Transducers` or `task_local_storage`

[Next page](https://discourse.julialang.org/t/multi-threaded-array-building/132463.md?page=2)
