Elixir - 以二进制替换位置

时间:2018-02-27 20:01:48

标签: elixir

问题

我正在尝试在Elixir中创建一个基本图像(位图)写入器,但我坚持了一点。

我尝试创建一个将像素设置为二进制的函数。我使用模式匹配,但我的功能显然太慢(超过10分钟将所有像素设置为1024 * 768的图片)。

目前,我的二进制文件大小等于宽度*高度。就像你在下面的代码中看到的那样,我的函数将x和y作为参数,并且必须在这个位置修改一个int。

当前代码

# Function
def replace_by_test(output, width, x, y) do
    out_offset = y * width + x
    <<
        o_before :: binary-size(out_offset),
        _ :: binary-size(4),
        o_after :: binary
    >> = output

    << o_before :: binary, "TEST" :: binary, o_after :: binary >>
end

# Test on a 1024 * 768 resolution image
out_size = 1024 * 768 * 8
output = << 0 :: size(out_size) >>
for x <- 0..(1024*768-1), do: replace_by_test(output, 1024, 0, 0)

目标

让这段代码更快。如果可能,请在不到10秒的时间内运行。

1 个答案:

答案 0 :(得分:5)

构造一个新的二进制文件会导致Erlang必须在每次迭代时复制整个二进制文件。复制786432个字节786432次必然会很慢(需要分配的内存为618GB,然后很快就会自由发送!)。您需要为此使用不同的数据结构。

一种选择是在构造二进制文件时使用Map,然后在完成修改后将其转换为二进制文件:

# Create a blank image.
map = for x <- 1..1024, y <- 1..768, into: %{}, do: {{x, y}, 0}

# Set each pixel once to x * y
map =
  0..1023
  |> Enum.reduce(map, fn x, map ->
    0..767 |> Enum.reduce(map, fn y, map ->
      Map.put(map, {x, y}, 0)
    end)
  end)

# Get back a binary
binary = for x <- 0..1023, y <- 0..767, into: <<>>, do: <<Map.get(map, {x, y})>>

IO.inspect binary
IO.inspect byte_size(binary)

输出:

<<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...>>
786432

整个脚本大约需要2.5秒才能在我的机器上执行。

另一种选择是使用Erlang's array module。它可能比地图更快。在阅读该模块的文档后,应该很容易实现。

修改:此处转换为array的代码。它在我的机器上几乎只运行1秒,比地图快2.5倍。

# Create a blank image.
array = :array.new(size: 1024 * 768, default: 0)

# Set each pixel once to x * y
array =
  0..1023
  |> Enum.reduce(array, fn x, array ->
    0..767 |> Enum.reduce(array, fn y, array ->
      :array.set(y * 1024 + x, 123, array)
    end)
  end)

# Get back a binary
binary = :array.foldl(fn _, x, acc -> <<acc::binary, x>> end, <<>>, array)