Skip to content

Add optimized SHA1 and SHA256 using ARMv8 Crypto Intrinsics. - #153

Open
samyron wants to merge 1 commit into
ruby:masterfrom
samyron:sm/arm-crypto-extension-impl
Open

samyron wants to merge 1 commit into
ruby:masterfrom
samyron:sm/arm-crypto-extension-impl

Conversation

@samyron

@samyron samyron commented Sep 17, 2026

Copy link
Copy Markdown

Add optimized SHA1 and SHA256 using ARMv8 Crypto Intrinsics. These instructions will be detected at runtime on MacOS, Linux and Android using the appropriate operating system specific mechanism.

Note: These implementations came from https://github.com/noloader/SHA-Intrinsics. This PR only includes the ARMv8 implementations as I do not have an x86 CPU with the correct instruction set support for testing those implementations.

These should recover some of the speed after removing OpenSSL support mentioned in issue #35 on ARMv8.

This commit adds the following extconf.rb flags:

  1. --without-common-digest - Disable CommonCrypto detection to use one of the bundled implementations.
  2. --without-arm-crypto - Disable ARMv8 Crypto extension detection to force the portable C fallback implementation.

This commit also includes NIST test vectors for SHA1 and SHA256 to verify the compiled implementation works correctly.

These were executed on an M1 Macbook Air.

=== SHA1 / short (16 bytes) -- backend: Portable C ===
ruby 3.4.8 (2025-12-17 revision 995b59f666) +PRISM [arm64-darwin24]
Warming up --------------------------------------
     SHA1 Portable C   200.019k i/100ms
Calculating -------------------------------------
     SHA1 Portable C      1.986M (± 1.3%) i/s  (503.47 ns/i) -      6.001M in   3.021090s

Comparison:
  SHA1 ARM Crypto:  2214084.8 i/s
  SHA1 Portable C:  1986226.8 i/s - 1.11x  slower
SHA1 CommonCrypto:  1350872.5 i/s - 1.64x  slower

=== SHA1 / medium (1024 bytes) -- backend: Portable C ===
ruby 3.4.8 (2025-12-17 revision 995b59f666) +PRISM [arm64-darwin24]
Warming up --------------------------------------
     SHA1 Portable C    55.810k i/100ms
Calculating -------------------------------------
     SHA1 Portable C    550.800k (± 4.2%) i/s    (1.82 μs/i) -      1.674M in   3.039760s

Comparison:
  SHA1 ARM Crypto:  1056399.8 i/s
SHA1 CommonCrypto:   832120.4 i/s - 1.27x  slower
  SHA1 Portable C:   550800.1 i/s - 1.92x  slower

=== SHA1 / large (1048576 bytes) -- backend: Portable C ===
ruby 3.4.8 (2025-12-17 revision 995b59f666) +PRISM [arm64-darwin24]
Warming up --------------------------------------
     SHA1 Portable C    77.000 i/100ms
Calculating -------------------------------------
     SHA1 Portable C    776.174 (± 0.5%) i/s    (1.29 ms/i) -      2.387k in   3.075341s

Comparison:
SHA1 CommonCrypto:     2307.0 i/s
  SHA1 ARM Crypto:     2058.3 i/s - 1.12x  slower
  SHA1 Portable C:      776.2 i/s - 2.97x  slower

=== SHA256 / short (16 bytes) -- backend: Portable C ===
ruby 3.4.8 (2025-12-17 revision 995b59f666) +PRISM [arm64-darwin24]
Warming up --------------------------------------
   SHA256 Portable C   187.472k i/100ms
Calculating -------------------------------------
   SHA256 Portable C      1.879M (± 0.3%) i/s  (532.16 ns/i) -      5.812M in   3.092709s

Comparison:
  SHA256 ARM Crypto:  2821313.8 i/s
SHA256 CommonCrypto:  2366863.2 i/s - 1.19x  slower
  SHA256 Portable C:  1879139.6 i/s - 1.50x  slower

=== SHA256 / medium (1024 bytes) -- backend: Portable C ===
ruby 3.4.8 (2025-12-17 revision 995b59f666) +PRISM [arm64-darwin24]
Warming up --------------------------------------
   SHA256 Portable C    25.901k i/100ms
Calculating -------------------------------------
   SHA256 Portable C    258.693k (± 0.3%) i/s    (3.87 μs/i) -    777.030k in   3.003673s

Comparison:
SHA256 CommonCrypto:  1217067.5 i/s
  SHA256 ARM Crypto:  1187297.8 i/s - 1.03x  slower
  SHA256 Portable C:   258693.3 i/s - 4.70x  slower

=== SHA256 / large (1048576 bytes) -- backend: Portable C ===
ruby 3.4.8 (2025-12-17 revision 995b59f666) +PRISM [arm64-darwin24]
Warming up --------------------------------------
   SHA256 Portable C    29.000 i/100ms
Calculating -------------------------------------
   SHA256 Portable C    288.299 (± 4.9%) i/s    (3.47 ms/i) -    870.000 in   3.017705s

Comparison:
SHA256 CommonCrypto:     2330.5 i/s
  SHA256 ARM Crypto:     1889.2 i/s - 1.23x  slower
  SHA256 Portable C:      288.3 i/s - 8.08x  slower

Additional benchmarks running Debian on Virtualbox on the same M1 Macbook Air

=== SHA1 / short (16 bytes) -- backend: Portable C ===
ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +PRISM [aarch64-linux]
Warming up --------------------------------------
     SHA1 Portable C   158.975k i/100ms
Calculating -------------------------------------
     SHA1 Portable C      1.634M (± 1.6%) i/s  (612.06 ns/i) -      4.928M in   3.016349s

Comparison:
SHA1 ARM Crypto:  1663326.0 i/s
SHA1 Portable C:  1633837.8 i/s - same-ish: difference falls within error

=== SHA1 / medium (1024 bytes) -- backend: Portable C ===
ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +PRISM [aarch64-linux]
Warming up --------------------------------------
     SHA1 Portable C    46.630k i/100ms
Calculating -------------------------------------
     SHA1 Portable C    467.343k (± 0.4%) i/s    (2.14 μs/i) -      1.446M in   3.093081s

Comparison:
SHA1 ARM Crypto:   916649.5 i/s
SHA1 Portable C:   467343.1 i/s - 1.96x  slower

=== SHA1 / large (1048576 bytes) -- backend: Portable C ===
ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +PRISM [aarch64-linux]
Warming up --------------------------------------
     SHA1 Portable C    65.000 i/100ms
Calculating -------------------------------------
     SHA1 Portable C    656.318 (± 0.5%) i/s    (1.52 ms/i) -      2.015k in   3.070158s

Comparison:
SHA1 ARM Crypto:     2066.3 i/s
SHA1 Portable C:      656.3 i/s - 3.15x  slower

=== SHA256 / short (16 bytes) -- backend: Portable C ===
ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +PRISM [aarch64-linux]
Warming up --------------------------------------
   SHA256 Portable C   165.590k i/100ms
Calculating -------------------------------------
   SHA256 Portable C      1.655M (± 0.5%) i/s  (604.36 ns/i) -      4.968M in   3.002281s

Comparison:
SHA256 ARM Crypto:  2118511.1 i/s
SHA256 Portable C:  1654641.7 i/s - 1.28x  slower

=== SHA256 / medium (1024 bytes) -- backend: Portable C ===
ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +PRISM [aarch64-linux]
Warming up --------------------------------------
   SHA256 Portable C    22.921k i/100ms
Calculating -------------------------------------
   SHA256 Portable C    230.231k (± 0.4%) i/s    (4.34 μs/i) -    710.551k in   3.086248s

Comparison:
SHA256 ARM Crypto:  1057810.6 i/s
SHA256 Portable C:   230231.4 i/s - 4.59x  slower

=== SHA256 / large (1048576 bytes) -- backend: Portable C ===
ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +PRISM [aarch64-linux]
Warming up --------------------------------------
   SHA256 Portable C    26.000 i/100ms
Calculating -------------------------------------
   SHA256 Portable C    260.243 (± 0.4%) i/s    (3.84 ms/i) -    806.000 in   3.097108s

Comparison:
SHA256 ARM Crypto:     1848.7 i/s
SHA256 Portable C:      260.2 i/s - 7.10x  slower

Additional benchmarks including OpenSSL

ruby 3.4.8 (2025-12-17 revision 995b59f666) +PRISM [arm64-darwin24]
Warming up --------------------------------------
   SHA256 Portable C    25.861k i/100ms
      SHA256 OpenSSL    76.391k i/100ms
Calculating -------------------------------------
   SHA256 Portable C    254.553k (± 2.3%) i/s    (3.93 μs/i) -    775.830k in   3.047817s
      SHA256 OpenSSL    750.597k (± 2.2%) i/s    (1.33 μs/i) -      2.292M in   3.053211s

Comparison:
SHA256 CommonCrypto:  1209718.4 i/s
  SHA256 ARM Crypto:  1169577.7 i/s - same-ish: difference falls within error
     SHA256 OpenSSL:   750596.7 i/s - 1.61x  slower
  SHA256 Portable C:   254552.7 i/s - 4.75x  slower


=== SHA256 / large (1048576 bytes) -- backend: Portable C ===
ruby 3.4.8 (2025-12-17 revision 995b59f666) +PRISM [arm64-darwin24]
Warming up --------------------------------------
   SHA256 Portable C    29.000 i/100ms
      SHA256 OpenSSL   233.000 i/100ms
Calculating -------------------------------------
   SHA256 Portable C    290.571 (± 0.7%) i/s    (3.44 ms/i) -    899.000 in   3.093913s
      SHA256 OpenSSL      2.329k (± 0.8%) i/s  (429.36 μs/i) -      6.990k in   3.001219s

Comparison:
SHA256 CommonCrypto:     2332.4 i/s
     SHA256 OpenSSL:     2329.1 i/s - same-ish: difference falls within error
  SHA256 ARM Crypto:     1872.5 i/s - 1.25x  slower
  SHA256 Portable C:      290.6 i/s - 8.03x  slower

…structions will be detected at runtime on MacOS, Linux and Android using the appropriate operating system specific mechanism.

This commit adds the following extconf.rb flags:
1. `--without-common-digest` - Disable CommonCrypto detection to use one of the bundled implementations.
2. `--without-arm-crypto` - Disable ARMv8 Crypto extension detection to force the portable C fallback implementation.

This commit also includes NIST test vectors for SHA1 and SHA256 to verify the compiled implementation works correctly.

These were executed on an M1 Macbook Air.

```
=== SHA1 / short (16 bytes) -- backend: Portable C ===
ruby 3.4.8 (2025-12-17 revision 995b59f666) +PRISM [arm64-darwin24]
Warming up --------------------------------------
     SHA1 Portable C   200.019k i/100ms
Calculating -------------------------------------
     SHA1 Portable C      1.986M (± 1.3%) i/s  (503.47 ns/i) -      6.001M in   3.021090s

Comparison:
  SHA1 ARM Crypto:  2214084.8 i/s
  SHA1 Portable C:  1986226.8 i/s - 1.11x  slower
SHA1 CommonCrypto:  1350872.5 i/s - 1.64x  slower

=== SHA1 / medium (1024 bytes) -- backend: Portable C ===
ruby 3.4.8 (2025-12-17 revision 995b59f666) +PRISM [arm64-darwin24]
Warming up --------------------------------------
     SHA1 Portable C    55.810k i/100ms
Calculating -------------------------------------
     SHA1 Portable C    550.800k (± 4.2%) i/s    (1.82 μs/i) -      1.674M in   3.039760s

Comparison:
  SHA1 ARM Crypto:  1056399.8 i/s
SHA1 CommonCrypto:   832120.4 i/s - 1.27x  slower
  SHA1 Portable C:   550800.1 i/s - 1.92x  slower

=== SHA1 / large (1048576 bytes) -- backend: Portable C ===
ruby 3.4.8 (2025-12-17 revision 995b59f666) +PRISM [arm64-darwin24]
Warming up --------------------------------------
     SHA1 Portable C    77.000 i/100ms
Calculating -------------------------------------
     SHA1 Portable C    776.174 (± 0.5%) i/s    (1.29 ms/i) -      2.387k in   3.075341s

Comparison:
SHA1 CommonCrypto:     2307.0 i/s
  SHA1 ARM Crypto:     2058.3 i/s - 1.12x  slower
  SHA1 Portable C:      776.2 i/s - 2.97x  slower

=== SHA256 / short (16 bytes) -- backend: Portable C ===
ruby 3.4.8 (2025-12-17 revision 995b59f666) +PRISM [arm64-darwin24]
Warming up --------------------------------------
   SHA256 Portable C   187.472k i/100ms
Calculating -------------------------------------
   SHA256 Portable C      1.879M (± 0.3%) i/s  (532.16 ns/i) -      5.812M in   3.092709s

Comparison:
  SHA256 ARM Crypto:  2821313.8 i/s
SHA256 CommonCrypto:  2366863.2 i/s - 1.19x  slower
  SHA256 Portable C:  1879139.6 i/s - 1.50x  slower

=== SHA256 / medium (1024 bytes) -- backend: Portable C ===
ruby 3.4.8 (2025-12-17 revision 995b59f666) +PRISM [arm64-darwin24]
Warming up --------------------------------------
   SHA256 Portable C    25.901k i/100ms
Calculating -------------------------------------
   SHA256 Portable C    258.693k (± 0.3%) i/s    (3.87 μs/i) -    777.030k in   3.003673s

Comparison:
SHA256 CommonCrypto:  1217067.5 i/s
  SHA256 ARM Crypto:  1187297.8 i/s - 1.03x  slower
  SHA256 Portable C:   258693.3 i/s - 4.70x  slower

=== SHA256 / large (1048576 bytes) -- backend: Portable C ===
ruby 3.4.8 (2025-12-17 revision 995b59f666) +PRISM [arm64-darwin24]
Warming up --------------------------------------
   SHA256 Portable C    29.000 i/100ms
Calculating -------------------------------------
   SHA256 Portable C    288.299 (± 4.9%) i/s    (3.47 ms/i) -    870.000 in   3.017705s

Comparison:
SHA256 CommonCrypto:     2330.5 i/s
  SHA256 ARM Crypto:     1889.2 i/s - 1.23x  slower
  SHA256 Portable C:      288.3 i/s - 8.08x  slower
```

Additional benchmarks running Debian on Virtualbox on the same M1 Macbook Air

```
=== SHA1 / short (16 bytes) -- backend: Portable C ===
ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +PRISM [aarch64-linux]
Warming up --------------------------------------
     SHA1 Portable C   158.975k i/100ms
Calculating -------------------------------------
     SHA1 Portable C      1.634M (± 1.6%) i/s  (612.06 ns/i) -      4.928M in   3.016349s

Comparison:
SHA1 ARM Crypto:  1663326.0 i/s
SHA1 Portable C:  1633837.8 i/s - same-ish: difference falls within error

=== SHA1 / medium (1024 bytes) -- backend: Portable C ===
ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +PRISM [aarch64-linux]
Warming up --------------------------------------
     SHA1 Portable C    46.630k i/100ms
Calculating -------------------------------------
     SHA1 Portable C    467.343k (± 0.4%) i/s    (2.14 μs/i) -      1.446M in   3.093081s

Comparison:
SHA1 ARM Crypto:   916649.5 i/s
SHA1 Portable C:   467343.1 i/s - 1.96x  slower

=== SHA1 / large (1048576 bytes) -- backend: Portable C ===
ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +PRISM [aarch64-linux]
Warming up --------------------------------------
     SHA1 Portable C    65.000 i/100ms
Calculating -------------------------------------
     SHA1 Portable C    656.318 (± 0.5%) i/s    (1.52 ms/i) -      2.015k in   3.070158s

Comparison:
SHA1 ARM Crypto:     2066.3 i/s
SHA1 Portable C:      656.3 i/s - 3.15x  slower

=== SHA256 / short (16 bytes) -- backend: Portable C ===
ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +PRISM [aarch64-linux]
Warming up --------------------------------------
   SHA256 Portable C   165.590k i/100ms
Calculating -------------------------------------
   SHA256 Portable C      1.655M (± 0.5%) i/s  (604.36 ns/i) -      4.968M in   3.002281s

Comparison:
SHA256 ARM Crypto:  2118511.1 i/s
SHA256 Portable C:  1654641.7 i/s - 1.28x  slower

=== SHA256 / medium (1024 bytes) -- backend: Portable C ===
ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +PRISM [aarch64-linux]
Warming up --------------------------------------
   SHA256 Portable C    22.921k i/100ms
Calculating -------------------------------------
   SHA256 Portable C    230.231k (± 0.4%) i/s    (4.34 μs/i) -    710.551k in   3.086248s

Comparison:
SHA256 ARM Crypto:  1057810.6 i/s
SHA256 Portable C:   230231.4 i/s - 4.59x  slower

=== SHA256 / large (1048576 bytes) -- backend: Portable C ===
ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +PRISM [aarch64-linux]
Warming up --------------------------------------
   SHA256 Portable C    26.000 i/100ms
Calculating -------------------------------------
   SHA256 Portable C    260.243 (± 0.4%) i/s    (3.84 ms/i) -    806.000 in   3.097108s

Comparison:
SHA256 ARM Crypto:     1848.7 i/s
SHA256 Portable C:      260.2 i/s - 7.10x  slower
```
@samyron
samyron requested a review from knu as a code owner September 17, 2026 02:18

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant