# cerlc
## A Native Erlang Library of CRC Algorithms
[![Erlang CI](https://github.com/mdsebald/cerlc/workflows/Erlang%20CI/badge.svg)](https://github.com/mdsebald/cerlc/actions)
### Goals
- Implemented in native Erlang. No NIFs or other depenencies required.
- One algorithm per module.
### Current Algorithms and Associated Modules
- CRC-8 crc8.erl
- CRC-16/AUG-CCITT crc16_aug_ccitt.erl
- CRC-16/MODBUS crc16_modbus.erl
- CRC-16/USB crc16_usb.erl
- CRC-32 crc32.erl
- CRC-32/C crc32_c.erl
### Build
```
$ rebar3 compile
```
### Test
```
$ rebar3 eunit
```
### Use
#### In an Erlang application
```erlang
% Add to list of dependencies in rebar.config
{deps, [
{cerlc, "0.1.0"}
]}.
% Code
CrcValue = crc16_aug_ccitt:crc(Data),
```
#### In an Elixir application
```elixir
# Add to list of dependencies in mix.exs
def deps do
[
{:cerlc, "~> 0.1.0"}
]
end
# Code
crc_value = :crc16_aug_ccitt.crc(data)
```
### A Word on Performance
There are too many variables to definitively declare one implementation method is always faster than another in all environments. However, in benchmarking various CRC implementations, I have found, in general, the performance rank of various CRC implementation methods, from fastest to slowest is as follows:
1. Erlang Built In Function (BIF) (Only CRC-32 algorithm available)
2. Erlang/Elixir with Natively Implemented Functions in 'C' (NIF's)
3. Native Erlang/Elixir with Native Compilation enabled (Also known as: HiPE)
1. In some cases, HiPE is faster than a NIF implementation
4. Native Erlang/Elixir compiled with OTP-24, which introduced the Just In Time compiler (JIT)
1. HiPE is not available in OTP-24 and later
5. Native Erlang/Elixir without HiPE or JIT compilation
#### Benchmarking Various CRC implementations
For comparison purposes only. Your environment may produce different results.
[Crc Benchmark Source](https://github.com/mdsebald/CrcBenchmarks)
##### Benchmark CRC routines:
1. Erlang BIF crc32()
2. [Erlang implementation for CRC32-C using NIF's](https://hex.pm/packages/crc32cer)
3. [Elixir configurable CRC-32 implemented using NIF's](https://hex.pm/packages/crc)
4. Native Erlang CRC-32 (cerlc library)
5. Native Erlang CRC-32/C (cerlc library)
##### PC Benchmark Environment:
OS: WSL2, Ubuntu 18.04, on 64 bit Windows 10 Processor: Intel(R) Core(TM) i5-6300U CPU @ 2.40GHz, 2501 Mhz, 2 Core(s), 4 Logical Processor(s) 8 GB Ram
###### Benchmark run 3 times, using a 100 bytes of random data each time
1. OTP-23 with HiPE enabled
2. OTP-24 uses JIT compiler
3. OTP-23 without HiPE enabled
###### OTP-23 with HiPE
```
iex(2)> CrcBenchmarks.run_benchmark_crc32(100)
*** &:erlang.crc32/1 ***
1.2 sec 8M iterations 0.16 μs/op
*** &:crc32cer.nif/1 ***
1.2 sec 8M iterations 0.16 μs/op
*** &CRC.crc_32/1 ***
1.5 sec 1M iterations 1.51 μs/op
*** &:crc32.crc/1 ***
1.9 sec 2M iterations 0.93 μs/op
*** &:crc32_c.crc/1 ***
1.0 sec 1M iterations 1.0 μs/op
```
###### OTP-24 with JIT compilation
```
iex(6)> CrcBenchmarks.run_benchmark_crc32(100)
*** &:erlang.crc32/1 ***
1.3 sec 8M iterations 0.16 μs/op
*** &:crc32cer.nif/1 ***
1.3 sec 8M iterations 0.17 μs/op
*** &CRC.crc_32/1 ***
1.5 sec 1M iterations 1.47 μs/op
*** &:crc32.crc/1 ***
1.8 sec 1M iterations 1.77 μs/op
*** &:crc32_c.crc/1 ***
1.8 sec 1M iterations 1.76 μs/op
```
###### OTP-23 without HiPE
```
iex(2)> CrcBenchmarks.run_benchmark_crc32(100)
*** &:erlang.crc32/1 ***
1.3 sec 8M iterations 0.16 μs/op
*** &:crc32cer.nif/1 ***
1.2 sec 8M iterations 0.15 μs/op
*** &CRC.crc_32/1 ***
1.3 sec 1M iterations 1.28 μs/op
*** &:crc32.crc/1 ***
1.0 sec 262K iterations 4.12 μs/op
*** &:crc32_c.crc/1 ***
1.0 sec 262K iterations 3.92 μs/op
```
As expected, the BIF and NIF implementations' performance remain constant in all of the runs, as they are not affected by HiPE or JIT.
##### [Nerves](https://www.nerves-project.org/) RPI 2 Benchmark Environment:
For another perspective on performance, I added the CRC benchmark code to a base Nerves firmware project and ran it on a Raspberry Pi 2 (900MHz quad-core ARM Cortex-A7 CPU 1GB RAM). There is only one performance run, as the "native" (HiPE) compilation flag does not appear to have any effect, and the JIT for OTP-24 is currently only works for x86 processors. Also, the crc32cer Erlang with NIF implementation was not included because it would not compile.
###### OTP-23 without HiPE
```
iex(2)> CrcBenchmarks.run_benchmark_crc32(100)
*** &:erlang.crc32/1 ***
1.4 sec 1M iterations 1.36 μs/op
*** &CRC.crc_32/1 ***
1.4 sec 131K iterations 10.97 μs/op
*** &:crc32.crc/1 ***
1.4 sec 8K iterations 178.72 μs/op
*** &:crc32_c.crc/1 ***
1.4 sec 8K iterations 177.97 μs/op
```
##### 8, 16, & 32 bit Native Erlang CRCs (OTP-23 without HiPE)
Finally I compared the relative performance of various bit width native Erlang CRC implementations on the PC and RPI2.
###### On the PC
```
iex(2)> CrcBenchmarks.run_benchmark_erlang(100)
*** &:crc8.crc/1 ***
1.1 sec 1M iterations 1.14 μs/op
*** &:crc16_aug_ccitt.crc/1 ***
1.9 sec 1M iterations 1.87 μs/op
*** &:crc16_modbus.crc/1 ***
1.9 sec 1M iterations 1.83 μs/op
*** &:crc32.crc/1 ***
1.8 sec 1M iterations 1.81 μs/op
*** &:crc32_c.crc/1 ***
1.9 sec 1M iterations 1.83 μs/op
```
###### On RPI 2 with Nerves
```
iex(3)> CrcBenchmarks.run_benchmark_erlang(100)
*** &:crc8.crc/1 ***
1.4 sec 65K iterations 21.66 μs/op
*** &:crc16_aug_ccitt.crc/1 ***
1.3 sec 32K iterations 41.22 μs/op
*** &:crc16_modbus.crc/1 ***
1.2 sec 32K iterations 39.05 μs/op
*** &:crc32.crc/1 ***
1.4 sec 8K iterations 177.54 μs/op
*** &:crc32_c.crc/1 ***
1.4 sec 8K iterations 177.34 μs/op
```
The PC execution time is pretty much constant over the various CRC bit widths, while the RPI2 appears to exhibit a 32bit 4X 16bit 2X 8bit relationship.
### Sources of Truth
[Javascript CRC Calculator](http://www.sunshine2k.de/coding/javascript/crc/crc_js.html)
[crccalc](https://crccalc.com/)
### See any bugs? Don't see the CRC algorithm you need?
Open an issue and I'll fix it or add it.
### Thanks!