cloak
cloak copied to clipboard
Performance: use :persistent_term instead of :ets
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_terminstead of:ets
I can implement this issue if you argee with this suggestion
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.
Either way, I'd love to see a PR! (Benchmarks comparing it to :ets would be nice to have, as well)
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:

@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
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