# Slicing array on julia 4000ms vs c++ 400ms

**URL:** <https://discourse.julialang.org/t/slicing-array-on-julia-4000ms-vs-c-400ms/122389>\
**Category:** Performance\
**Tags:** vector\
**Created:** [November 7, 2024, 6:06pm UTC](https://discourse.julialang.org/t/slicing-array-on-julia-4000ms-vs-c-400ms/122389 "2024-11-07T18:06:29Z")\
**Posts on this page:** 5\
**Page:** 2

<div class="post-metadata">

**Author:** ![Palli](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/palli/32/3380_2.png) [@Palli](https://discourse.julialang.org/u/Palli)\
**Post date:** [November 9, 2024, 5:36pm UTC](https://discourse.julialang.org/t/slicing-array-on-julia-4000ms-vs-c-400ms/122389/21 "2024-11-09T17:36:56Z")

</div>

You can do something like:

```julia
julia> my_rates_buffer = zeros(1001)

julia> function custom_test!(rates, data::Vector{Float64}, i::Int64)
           if(i < 1001)
               return nothing
           end
           copyto!(rates, @view data[i-1000:i])
           return nothing
       end
custom_test! (generic function with 1 method)

julia> @time custom_test!(my_rates_buffer, my_test, 5000) # Only first use, no problem at all.
  0.076472 seconds (112.11 k allocations: 5.599 MiB, 99.96% compilation time)

julia> @time custom_test!(my_rates_buffer, my_test, 5000)
  0.000009 seconds

```

Now there’s no allocation, but still a copy, despite that `@view`. You need it otherwise you get _two_ allocations (I believe 1 more since 1.11). copyto! returns something, and without that `return nothing` after it would be returned. If comment it out I get it, and still no allocation. I think it’s then returning the view (i.e. without that it will allocate, not more though). I mainly include it in to not return either nothing or the view, but that type stability seems to cost you nothing (probably unless you use that result?).

> [@Palli](#):
>
> Copying per se shouldn’t be slower than in C++, but you accumulate garbage. That might be the problem, and [Bumper.jl](https://juliahub.com/ui/Packages/General/Bumper) of help? C++ will destruct/free memory early.

Before in your code, you did `malloc` implicitly, as in C++ (`new` in C++ needs not actually call `malloc` according to the C++ standard, though in practice it’s always implemented that way; your “new” is implicit, also, comes from invoking `std::vector`).

That malloc isn’t any slower in Julia (maybe even faster…). Not in Julia you’re allocating a lot (and copying to that memory buffer that’s never in the same place since the previous wasn’t yet released), i.e.:

```julia
julia> @time for i in 1:10000 Libc.malloc(200) end
  0.000889 seconds

julia> @time for i in 1:10000 b = Libc.malloc(200); Libc.free(b) end
  0.000201 seconds

```

Julia could do effectively the latter, like in C++. And add your code in-between. But at least freeing (the most recent allocation) makes malloc (in Libc) faster.

With Bumper.jl or something you can in effect do the latter, except it’s even faster “malloc” and “free”, since not really done at all. Something in Julia that does implicit free is coming, i.e. allocating on the stack when possible, for such no cost, when it can apply.

---

<div class="post-metadata">

**Author:** ![tradingtsm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tradingtsm/32/213252_2.png) [@tradingtsm](https://discourse.julialang.org/u/tradingtsm)\
**Post date:** [November 9, 2024, 5:56pm UTC](https://discourse.julialang.org/t/slicing-array-on-julia-4000ms-vs-c-400ms/122389/22 "2024-11-09T17:56:17Z")

</div>

Hi everyone,

I will post an update on what I found that works close to c++ speed of 400ms, correct me if you think I’m wrong at any code, also I have put some questions commented inside code,  
remember that this is only about copying a slice of a vector, not using references or views :

the inital julia code (4000ms) that ran on a loop was this

```julia
rates = data[i-1000:i] //maybe this is slow because we have to allocate in the loop a new vector each time and we have to copy access data

```

the cpp code was this //400ms

```julia
std::vector rates = std::vector<double>(data.begin() + (i - 1000), data.begin() + i); //this was also allocating a new vector each time in the loop, why do you think in cpp the same code runs fast ?
we can also use std::copy

```

Let’s see the 3 options that measure around 300ms, I will just show the important piece of code not the full code

Code 1  
this allocates using the vector operation without creating a new array each time inside the loop, also I can change any value of rates without modifying d.c values, because it’s preallocated.

```julia
rates::Vector{Float64} = Vector{Float64}(undef, 1001)
rates .= @view d.c[i-1000:i]

```

Code 2  
both of these functions, copy in place to rates vector, also I use @views for the data being copied (d.c), so we avoid a second copy of d.c I guess, the difference between using @view and not is from 4000ms to 300ms. Also we can change rates values without affecting d.c values.

```julia
copyto!(rates, @view d.c[i-1000:i])
copy!(rates, @view d.c[i-1000:i])

```

Code 3  
this one is similar to the code we used before : rates .= @view d.c[i-1000:i].

```julia
@inbounds begin
    for j in 1:1001
        rates[j] = data[i - 1001 + j]
    end
end

```

That’s it, post your opinions before we can close this post with a solution, and a big thank you everyone for giving tips and corrections.

---

<div class="post-metadata">

**Author:** ![tradingtsm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tradingtsm/32/213252_2.png) [@tradingtsm](https://discourse.julialang.org/u/tradingtsm)\
**Post date:** [November 9, 2024, 6:22pm UTC](https://discourse.julialang.org/t/slicing-array-on-julia-4000ms-vs-c-400ms/122389/23 "2024-11-09T18:22:59Z")

</div>

That’s really interesting Palli, I’m checking this out, but I can’t make it work using loops on my code.  
this first code shows an improvement of time in the second call

```julia
@time custom_test!(d.data, rates, 5000)
@time custom_test!(d.data, rates, 5000)

```

but calling custom test two times in loop doesn’t show an improvement on speed

```julia
@elapsed begin
        for i in 1:length(d.data)
            custom_test!(d.data, rates, i)
        end
end
@elapsed begin
        for i in 1:length(d.data)
            custom_test!(d.data, rates, i)
        end
end

```

if I do this in your code, then I see the improvement on speed as you show on your code, I will paste the example

```julia
my_rates_buffer = zeros(1001)
my_test = fill(1.0, 10001)

function custom_test!(rates, data::Vector{Float64}, i::Int64, firstcall)
           if(firstcall)
            return nothing
           end
           if(i < 1001)
               return nothing
           end
           copyto!(rates, @view data[i-1000:i])
           return nothing
       end

@time begin 
    for i in 1:10000
        custom_test!(my_rates_buffer, my_test, i, true)
    end
end

@time begin 
    for i in 1:10000
        custom_test!(my_rates_buffer, my_test, i, false)
    end
end

```

I don’t know why in my code is not working as expected, I will continue trying.

---

<div class="post-metadata">

**Author:** ![Palli](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/palli/32/3380_2.png) [@Palli](https://discourse.julialang.org/u/Palli)\
**Post date:** [November 9, 2024, 6:34pm UTC](https://discourse.julialang.org/t/slicing-array-on-julia-4000ms-vs-c-400ms/122389/24 "2024-11-09T18:34:48Z")

</div>

Since I don’t have the full code, i.e. am only testing one iteration, I get lower:

```julia
julia> @btime custom_test!(my_rates_buffer, my_test, 5000) # wrong way for get the $ and $
  199.713 ns (0 allocations: 0 bytes)

julia> @btime custom_test!($my_rates_buffer, $my_test, 5000)
  178.102 ns (0 allocations: 0 bytes)

and even lower:
julia> @btime copyto!($my_rates_buffer, @view $my_test[1001-1000:1001]);
  121.402 ns (0 allocations: 0 bytes)

julia> @code_native @inbounds copyto!(my_rates_buffer, @view my_test[1001-1000:1001]);

```

Note the `@inbounds` there is helpful, for time, still strangely I seemingly see some assembly related to it. I think though mostly gone.

> [@tradingtsm](#):
>
> but I can’t make it work using loops on my code.

You mean:

> [@tradingtsm](#):
>
> Code 3  
> this one is similar to the code we used before : rates .= @view d.c[i-1000:i].
> 
> ```julia
> @inbounds begin
> for j in 1:1001
> rates[j] = data[i - 1001 + j]
> end
> end
> 
> ```

It’s seemingly plausible, but while loops are ok in general I think not needed here. Copyto! should be optimized, maybe even more? My only concern was if it does in-bound checks, it does (good by default), and if you could fully do away with them if you wanted.

I don’t see any reason to use:

> [@tradingtsm](#):
>
> `copy!(rates, @view d.c[i-1000:i])`

I think/thought only for when from within same array:

```julia
help?> copy!
..

  │ Warning
  │
  │ Behavior can be unexpected when any mutated argument shares memory with any other argument.

  See also copyto!.

  │ Julia 1.1
  │
  │ This method requires at least Julia 1.1. In Julia 1.0 this method is available from the Future standard library as Future.copy!.

```

The warning can’t applies when dst [destination array] is separate.

I do not ever recall hearing before of this deprecated Future stdlib,

```julia
julia> using Future

help?> Future
search: Future outer return

  The Future module implements future behavior of already existing functions, which will replace the current version in a future release of Julia.

```

```julia
julia> @edit copy!(my_rates_buffer, @view my_test[1001-1000:1001]) # it does end with copy_to! so both see ok

copyto! also does some checks, then:

help?> Base.copyto_unaliased!

```

I must be doing something wrong since calling `Base.copyto_unaliased!` directly is ~~4.5x~~ 20% slower…:

```julia
julia> @time s_prime = Base.unalias(my_rates_buffer, @view my_test[1001-1000:1001])
  0.000012 seconds (2 allocations: 96 bytes)

julia> @btime Base.copyto_unaliased!(IndexStyle(my_rates_buffer), my_rates_buffer, IndexStyle(s_prime), s_prime)
  548.102 ns (0 allocations: 0 bytes)

julia> @btime Base.copyto_unaliased!(IndexStyle($my_rates_buffer), my_rates_buffer, IndexStyle($s_prime), $s_prime)
  145.298 ns (1 allocation: 48 bytes)

```

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [November 10, 2024, 2:17am UTC](https://discourse.julialang.org/t/slicing-array-on-julia-4000ms-vs-c-400ms/122389/25 "2024-11-10T02:17:04Z")

</div>

> [@tradingtsm](#):
>
> I have corrected that in both languages, but there is not a big difference

Correcting the off-by-1 errors wasn’t expected to make a difference, shaving off one element or iteration out of thousands is negligible. Changing returns could possibly make more of a difference, but without the full benchmarking calls and compiled code analysis, we can’t tell you what ended up happening; we don’t even know when you instantiate `custom_struct`. As mentioned, the compilers can make very strange decisions (the large difference in semantically allocating a buffer and copying values to it does suggest C++ is eliminating some code), so it’s worth making a benchmark specifically for copying vectors without the extra composite type and indices checking, and verifying it occurs at runtime. I’ve never gotten far enough into C++ to benchmark and analyze compiled code so I won’t be of help there.

> [@tradingtsm](#):
>
> I mean that cpp is taking data argument as a reference (std::vector& data) to make then the copy, but in julia I think this was not the case but I’ve tried inplace copy

Julia doesn’t have pass-by-copy or pass-by-reference semantics on the language level, not even pointers. However, mutable types like `Vector` do have to be allocated in a central location and shared by references (variables, elements, fields), so it’s communicated as a memory address, the same way passing by reference compiles to. If you’re preallocating `rates` because you’re worried about large buffer copying when passing arguments, then the worry is unwarranted and you’re actually changing the benchmark by allocating before the `custom_test` loop, not per iteration (then again, it’s possible the C++ compiler did such an optimization and that’s why the preallocated Julia performance is so close, we need compiled code analysis to know).

> [@tradingtsm](#):
>
> ```julia
> my_rates_buffer = zeros(1001)
> my_test = fill(1.0, 10001)
> 
> ```

Unannotated global variables have uninferrable types (or rather they must be inferred as `::Any`) and cannot be optimized well; whether this significantly affects the runtime depends on how long `custom_test!` takes to run compared to the overhead of its dynamically dispatched call per iteration. However, it’s recommended to put benchmarking code in a function and pass preallocated buffers as arguments; in fact BenchmarkTools’ macros and `$`-interpolation support does this for you.

An aside, I confirmed in v1.11.1 that unconditionally returning `nothing` after allocating a vector is NOT compiled to removing the vector allocation. It could happen if a constant call is inlined e.g. into a benchmark loop, so it does appear that a minor degree of side effect analysis happens sometimes to confirm the vector is unnecessary. It’s a fair design for the compiler to not eliminate obviously dead code like this because dead code elimination is more useful for optimizing constants and inlined calls; your code is neater and the compiler saves unnecessary work if you remove the obviously dead code manually.

[Previous page](https://discourse.julialang.org/t/slicing-array-on-julia-4000ms-vs-c-400ms/122389.md?page=1)
