JuliaImages / QRCoders.jl

Creating QR Codes within Julia
https://juliaimages.org/QRCoders.jl/
MIT License
67 stars 11 forks source link

Weird behavior of sort function #20

Closed RexWzh closed 1 year ago

RexWzh commented 1 year ago

The new function qrcode is slower than the original one, as the following codes suggests.

import QRCoders.qrcode as newqrcode
import QRCode.qrcode as originqrcode

# Benchmark
@btime newqrcode("HELLO WORLD" ^ 30; compact=true); 
# 47.176 ms (1669117 allocations: 79.98 MiB)
@btime originqrcode("HELLO WORLD" ^ 30; compact=true); 
# 38.998 ms (1567245 allocations: 75.29 MiB)

I split the function qrcode into two part to figure out why.

First part:

using QRCoders: getmode, getversion, encodemessage, emptymatrix, makemasks, placedata!, penalty
@btime begin
    # initial values
    mode, version, eclevel = nothing, 0, Medium()
    bestmode = getmode(message)
    mode = !isnothing(mode) && bestmode ⊆ mode ? mode : bestmode

    minversion = getversion(message, mode, eclevel)
    # in case that the specified version is too small
    version = max(minversion, version)

    # encode message
    data = encodemessage(message, mode, eclevel, version)
    matrix = emptymatrix(version)
    masks = makemasks(matrix)
    placedata!(matrix, data)

    # Pick the best mask
    candidates = [(i - 1, xor.(matrix, m)) for (i, m) in enumerate(masks)]
end;
# similar code for QRCode: line 288 to line 326 in QRCode.jl

Second part:

mask, matrix = @btime first(sort!(candidates, by = penalty ∘ last))
Test result of part 1 version result
QRCoders.jl 559.462 μs (3144 allocations: 557.91 KiB)
QRCode.jl 676.829 μs (5491 allocations: 875.22 KiB)
Test result of part 2 version result
QRCoders.jl 15.422 ms (728832 allocations: 34.75 MiB)
QRCode.jl 38.992 ms (1561712 allocations: 74.46 MiB)

However, if I combine the two parts, that is

@btime begin
    mode, version, eclevel = nothing, 0, Medium()
    # ... 
    candidates = [(i - 1, xor.(matrix, m)) for (i, m) in enumerate(masks)]
    mask, matrix = first(sort!(candidates, by = penalty ∘ last))
end
the result will be version result
QRCoders.jl 42.179 ms (1669104 allocations: 79.98 MiB)
QRCode.jl 38.375 ms (1567236 allocations: 75.32 MiB)

This seems really weird to me!


Glad to discuss with you if you are available ;) @johnnychen94

RexWzh commented 1 year ago

Also, it can be fixed by replacing the last two lines by

candidates = [xor.(matrix, mat) for mat in masks]
scores = penalty.(candidates)
mask = first(sort(1:8, by = i -> scores[i]))
matrix = candidates[mask]

And the test result will be much better: 9.184 ms (419671 allocations: 20.40 MiB)

However, I still don't know why it happened.