Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance: use :persistent_term instead of :ets #96

Open
hissssst opened this issue Apr 27, 2020 · 5 comments
Open

Performance: use :persistent_term instead of :ets #96

hissssst opened this issue Apr 27, 2020 · 5 comments
Labels

Comments

@hissssst
Copy link

This would tune the performance, because in current implementation :ets is created without read_concurrency which makes all encryptions and decryptions synchronous.

We can:

  • use read_concurrency: true
  • use :persistent_term instead of :ets

I can implement this issue if you argee with this suggestion

@danielberkompas
Copy link
Owner

This sounds like it could be a good idea. I was not aware of :persistent_term previously. The only downside of using it is that it would require OTP 21.2 or later. (Released Dec 10, 2018)

This might require a version bump to 2.0.0, in order to drop support for earlier versions of OTP.

@danielberkompas
Copy link
Owner

Either way, I'd love to see a PR! (Benchmarks comparing it to :ets would be nice to have, as well)

@oliver-kriska
Copy link

I took @hissssst 's PR #97 and updated main branch, upgraded it to fully support Elixir 1.13.1 and Erlang 24.2. I did some really simple benchmarks with Benchee:

defmodule MyVault do
  use Cloak.Vault, otp_app: :cloak
  def load do
  key = :crypto.strong_rand_bytes(32)

  {:ok, pid} =
    MyVault.start_link(
      ciphers: [
        default: {Cloak.Ciphers.AES.GCM, tag: "AES.GCM.V1", key: key}
      ],
      json_library: Jason
    )
    pid
  end
end
MyVault.load()
{:ok, ciphertext} = MyVault.encrypt("aqGwdxdSRg5XIpOvuDJv6YSDOyLwJfdjwkwgA8sr")
{:ok, "aqGwdxdSRg5XIpOvuDJv6YSDOyLwJfdjwkwgA8sr"} = MyVault.decrypt(ciphertext)
Benchee.run(
  %{
    "encrypt" => fn -> MyVault.encrypt("aqGwdxdSRg5XIpOvuDJv6YSDOyLwJfdjwkwgA8sr") end,
    "decrypt" => fn -> MyVault.decrypt(ciphertext) end,
    "encrypt_and_decrypt" => fn -> MyVault.encrypt!("aqGwdxdSRg5XIpOvuDJv6YSDOyLwJfdjwkwgA8sr")|> MyVault.decrypt!() end,
  },
  time: 100,
  parallel: 1,
  memory_time: 2
)

:ets

Operating System: macOS
CPU Information: Apple M1 Pro
Number of Available Cores: 10
Available memory: 32 GB
Elixir 1.13.1
Erlang 24.2

Benchmark suite executing with the following configuration:
warmup: 2 s
time: 10 s
memory time: 2 s
parallel: 1
inputs: none specified
Estimated total run time: 42 s

Benchmarking decrypt...
Benchmarking encrypt...
Benchmarking encrypt_and_decrypt...

Name                          ips        average  deviation         median         99th %
decrypt                  427.14 K        2.34 μs  ±1266.90%           2 μs           3 μs
encrypt                  312.90 K        3.20 μs  ±1470.89%           3 μs           6 μs
encrypt_and_decrypt      186.35 K        5.37 μs   ±567.93%           5 μs          11 μs

Comparison:
decrypt                  427.14 K
encrypt                  312.90 K - 1.37x slower +0.85 μs
encrypt_and_decrypt      186.35 K - 2.29x slower +3.03 μs

Memory usage statistics:

Name                   Memory usage
decrypt                     3.40 KB
encrypt                     1.96 KB - 0.58x memory usage -1.43750 KB
encrypt_and_decrypt         6.20 KB - 1.82x memory usage +2.80 KB

**All measurements for memory usage were the same**

:persistent_term

Operating System: macOS
CPU Information: Apple M1 Pro
Number of Available Cores: 10
Available memory: 32 GB
Elixir 1.13.1
Erlang 24.2

Benchmark suite executing with the following configuration:
warmup: 2 s
time: 1.67 min
memory time: 2 s
parallel: 1
inputs: none specified
Estimated total run time: 5.20 min

Benchmarking decrypt...
Benchmarking encrypt...
Benchmarking encrypt_and_decrypt...

Name                          ips        average  deviation         median         99th %
decrypt                  396.50 K        2.52 μs  ±2661.82%        1.90 μs        3.90 μs
encrypt                  375.22 K        2.67 μs  ±1236.40%        2.90 μs        3.90 μs
encrypt_and_decrypt      197.04 K        5.08 μs   ±906.77%        4.90 μs        6.90 μs

Comparison:
decrypt                  396.50 K
encrypt                  375.22 K - 1.06x slower +0.143 μs
encrypt_and_decrypt      197.04 K - 2.01x slower +2.55 μs

Memory usage statistics:

Name                   Memory usage
decrypt                     3.06 KB
encrypt                     1.65 KB - 0.54x memory usage -1.41406 KB
encrypt_and_decrypt         5.80 KB - 1.90x memory usage +2.74 KB

**All measurements for memory usage were the same**

Merged results

Name                          ips        average    median         99th %
ets_decrypt              427.14 K        2.34 μs      2 μs           3 μs
pt_decrypt               396.50 K        2.52 μs   1.90 μs        3.90 μs

ets_encrypt              312.90 K        3.20 μs      3 μs           6 μs
pt_encrypt               375.22 K        2.67 μs   2.90 μs        3.90 μs

ets_encrypt_and_decrypt  186.35 K        5.37 μs      5 μs          11 μs
pt_encrypt_and_decrypt   197.04 K        5.08 μs   4.90 μs        6.90 μs


Comparison:
ets_decrypt              427.14 K
pt_decrypt               396.50 K

ets_encrypt              312.90 K
pt_encrypt               375.22 K

ets_encrypt_and_decrypt  186.35 K
pt_encrypt_and_decrypt   197.04 K


Memory usage statistics:

Name                   Memory usage
ets_decrypt                 3.40 KB
pt_decrypt                  3.06 KB

ets_encrypt                 1.96 KB
pt_encrypt                  1.65 KB

ets_encrypt_and_decrypt     6.20 KB
pt_encrypt_and_decrypt      5.80 KB

All my changes are here, better view of all changes here.

I think it's not ready for PR. I can remove that "import Config" changes and it should help with support for older versions of Elixir. Tests, credo and dialyzer checks passed well with Elixir 1.11.4, 1.12.3, 1.13.1 and Erlang 23.3, 24.2. See screenshots:
CleanShot 2022-01-06 at 20 44 23@2x
CleanShot 2022-01-06 at 21 11 07@2x

@hissssst
Copy link
Author

hissssst commented Jan 8, 2022

@oliver-kriska , thank you for this benchmark! However, I think that the performance difference between :persistent_term and :ets (without read_concurrency) implementations is visible only in parallel access. And to be clear about performance, we'd better test the configuration access part without encryption/decryption

@hissssst
Copy link
Author

hissssst commented Jan 8, 2022

For example, with the same benchmark with parallel: 4 on 8 core intel i7 shows

For ets without read_concurrency

Name                          ips        average  deviation         median         99th %
decrypt                  380.45 K        2.63 μs   ±668.24%        2.25 μs        5.18 μs
encrypt                  301.25 K        3.32 μs   ±591.75%        2.82 μs        6.04 μs
encrypt_and_decrypt      152.61 K        6.55 μs   ±253.95%        5.30 μs       12.57 μs

And for ets with read_concurrency

Name                          ips        average  deviation         median         99th %
decrypt                  455.37 K        2.20 μs   ±763.51%        1.76 μs        3.89 μs
encrypt                  381.48 K        2.62 μs   ±499.24%        2.15 μs        5.18 μs
encrypt_and_decrypt      174.41 K        5.73 μs   ±236.27%        4.34 μs       11.74 μs

What is a 20% increase in performance


Configuration:

Operating System: Linux
CPU Information: 11th Gen Intel(R) Core(TM) i7-1185G7 @ 3.00GHz
Number of Available Cores: 8
Available memory: 15.35 GB
Elixir 1.13.1
Erlang 24.1.7

Benchmark suite executing with the following configuration:
warmup: 5 s
time: 5 s
memory time: 0 ns
parallel: 4

By the way, for 1 in parallel (without read_concurrency) i7 beats m1 hehe

decrypt                  623.78 K        1.60 μs   ±704.08%        1.37 μs        2.87 μs
encrypt                  546.60 K        1.83 μs   ±437.18%        1.70 μs        2.96 μs
encrypt_and_decrypt      309.08 K        3.24 μs   ±450.31%        2.86 μs        4.99 μs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants