Rolling maxima

julia> x = rand(1000000);

julia> K = 9000;

julia> result2 = @btime movingmax($x, $K);
  21.238 ms (4 allocations: 7.58 MiB)

julia> result3 = @btime rolling_max($x, $K);
  24.017 ms (5 allocations: 7.64 MiB)

Yes they are close even for bigger N and K, I think the trick my algorithm is doing is no looking backward to the vector in each iteration, cause my Deque is keeping more information in a tuple. We have also a bit different memory access pattern (last/first in Deque). I have no idea why I did it this way. It is a rewrite from my old code in C#, but I recall I spent a lot of time on tuning this thing.

UPDATE: This package implements similar algorithm using custom ring buffers, it’s 2x faster than our approach.

1 Like

Alright. With some help from @Jakub_Wronowski I fixed a bug in the code. I also switched to using a CircularDeque (this gives a big improvement in speed - more than 2x at least for some parameters). By doing that I could get close to the speed of the best libraries that are using a custom ring buffer. I will be satisfied with that for now! :slight_smile:

Below is the current code:

function rolling_optima(vect::Any, win_size::Integer; debug=false)

    roll_max = zeros(Float64, length(vect))
    roll_min = zeros(Float64, length(vect))
    opt_type = zeros(Int8, length(vect))
    opt_index = zeros(Int64, length(vect))
    d_max = CircularDeque{Int64}(win_size)
    d_min = CircularDeque{Int64}(win_size)

    win_forward = win_size
    win_backward = win_forward*2
    opt_ind = 0
    #comp = 10
    pushfirst!(d_max,1)
    pushfirst!(d_min,1)
    @inbounds for i in 2:length(vect)
        if vect[i] > vect[i-1]
            @inbounds for k in 1:length(d_max)
                if vect[i] >= vect[first(d_max)]
                    popfirst!(d_max)
                    else break end
            end
                    pushfirst!(d_max,i)

        else
            @inbounds for k in 1:length(d_min)
                if vect[i] <= vect[first(d_min)]
                    popfirst!(d_min)
                    else break end
            end
                    pushfirst!(d_min,i)

        end
        j = i + 1 - win_forward
        if j >= 1
            roll_max[i] = vect[last(d_max)]
            roll_min[i] = vect[last(d_min)]
            if j == last(d_max)
                if roll_max[i] == roll_max[j]
                    if j-win_backward > 0
                        failed = false
                        for v in @view vect[j-win_backward:j-1]
                           if roll_max[j] < v
                               failed = true
                               break end
                        end
                        if !failed
                            opt_ind +=1
                            opt_index[opt_ind] = j
                            opt_type[opt_ind] = 1
                        end
                    end
                end
                pop!(d_max)
            elseif j == last(d_min)
                if roll_min[i] == roll_min[j]
                    if j-win_backward > 0
                        failed = false
                        for v in @view vect[j-win_backward:j-1]
                           if roll_min[j] > v
                               failed = true
                               break end
                        end
                        if !failed
                            opt_ind +=1
                            opt_index[opt_ind] = j
                            opt_type[opt_ind] = -1
                        end
                    end
                end
                pop!(d_min)
            end
        end
    end
    opt_index = opt_index[1:opt_ind]
    opt_type = opt_type[1:opt_ind]
    if debug
        return opt_index, opt_type, roll_max, roll_min
    else
        return opt_index, opt_type
    end
end
3 Likes

Hi everyone!

I attach the latest version of my code below. For many cases it almost matches the movmaxmin() from MaxMinFilters.jl which was the fastest implementation I could find. My code is surely not as good but adds the functionality of finding optima with the use of rolling maximum/minimum.

function rolling_optima(vect::Any, win_size::Integer; debug=false)

    roll_max = Vector{Float64}(undef, length(vect))
    roll_min = Vector{Float64}(undef, length(vect))
    opt_type = Vector{Int8}(undef, length(vect))
    opt_index = Vector{Int64}(undef, length(vect))
    d_max = CircularDeque{Int64}(win_size)
    d_min = CircularDeque{Int64}(win_size)

    win_forward = win_size
    win_backward = win_forward*2
    opt_ind = 0
    pushfirst!(d_max,1)
    pushfirst!(d_min,1)
    @inbounds for i in 2:length(vect)
        if vect[i] > vect[i-1]
            @inbounds for k in 1:length(d_max)
                if vect[i] >= vect[first(d_max)]
                    popfirst!(d_max)
                else break end
            end
                    pushfirst!(d_max,i)
        else
            @inbounds for k in 1:length(d_min)
                if vect[i] <= vect[first(d_min)]
                    popfirst!(d_min)
                else break end
            end
                    pushfirst!(d_min,i)
        end
        j = i + 1 - win_forward
        if j >= 1
            roll_max[i] = vect[last(d_max)]
            roll_min[i] = vect[last(d_min)]
            if j == last(d_max)
                if roll_max[i] == roll_max[j]
                    if j-win_backward > 0
                        failed = false
                      #  @inbounds  for v in @view vect[j-win_backward:j-1]
                        check_back =  @view vect[j-1:-1:j-win_backward]
                        @inbounds  for k in 1:(win_backward)
                           if roll_max[j] < check_back[k]
                               failed = true
                               break end
                        end
                        if !failed
                            opt_ind +=1
                            opt_index[opt_ind] = j
                            opt_type[opt_ind] = 1
                        end
                    end
                end
                pop!(d_max)
            elseif j == last(d_min)
                if roll_min[i] == roll_min[j]
                    if j-win_backward > 0
                        failed = false
                     # @inbounds  for v in @view vect[j-win_backward:j-1]
                        check_back =  @view vect[j-1:-1:j-win_backward]
                            @inbounds  for k in 1:(win_backward)
                           if roll_min[j] > check_back[k]
                               failed = true
                               break end
                        end
                        if !failed
                            opt_ind +=1
                            opt_index[opt_ind] = j
                            opt_type[opt_ind] = -1
                        end
                    end
                end
                pop!(d_min)
            end
        else
            roll_max[i] = 0
            roll_min[i] = 0
        end
    end
    opt_index = opt_index[1:opt_ind]
    opt_type = opt_type[1:opt_ind]
    if debug
        return opt_index, opt_type, roll_max, roll_min
    else
        return opt_index, opt_type
    end
end
3 Likes

Nice! Are you sure you pointed correct package? Aren’t it in MaxMinFilters.jl?

Sorry, my fault!