Skip to content

Commit

Permalink
Speeding up C implementation
Browse files Browse the repository at this point in the history
Switch from Sarwate's algorithm to byte-order free Slicing-by-8.
The specialized so files for the built-in CRCs by digest-crc contain only the initialization functions of the library and the table data.
Each table data file is generated by `extconf.rb`.

User-defined CRC classes are also accelerated since a generic `Digest::CRC#update` is provided.
However, the `Digest::CRC::POLYNOMIAL` constants must be overridden.

Also, at build time, the environment variable "RUBY_DIGEST_CRC_ENABLE_SLICING_BY_16=1" can be used to enable "Slicing-by-16" for a further slight speedup.
  • Loading branch information
dearblue committed Jul 13, 2023
1 parent 91ee75f commit 62e6d2b
Show file tree
Hide file tree
Showing 135 changed files with 1,017 additions and 2,528 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pkg
/ext/digest/crc*/Makefile
/ext/digest/crc*/mkmf.log
/ext/digest/crc*/extconf.h
/ext/digest/crc*/*_table.h
/ext/digest/crc*/*.o
/ext/digest/crc*/*.so
/spec/integration/docker/digest-crc.gem
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ module Digest

WIDTH = 32

POLYNOMIAL = 0x04c11db7

REFLECT_INPUT = true

INIT_CRC = 0xffffffff
Expand All @@ -130,6 +132,21 @@ module Digest
end
```

### Notes on using with Ractor

* The `Digest::CRC#update` method in Ruby requires `YourCRC::TABLE` to be
in the "frozen" state. This is because if `YourCRC::TABLE` is mutable,
it can only be referenced by the main Ractor.
* The `Digest::CRC#update` method by C must first be called in the main
Ractor. This is because the internal tables must be initialized from the
main Ractor.

```ruby
# To digest empty characters in the main Ractor
# for initialization of the internal tables.
YourCRC.digest("")
```

## Benchmarks

### Ruby 2.7.4 (pure Ruby)
Expand Down
1 change: 1 addition & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ end
require 'rake/clean'
CLEAN.include('ext/digest/crc*/extconf.h')
CLEAN.include('ext/digest/crc*/Makefile')
CLEAN.include('ext/digest/crc*/*_table.h')
CLEAN.include('ext/digest/crc*/*.o')
CLEAN.include('ext/digest/crc*/*.so')

Expand Down
2 changes: 2 additions & 0 deletions ext/digest/Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ CRCS = Dir['crc*']
DLEXT = MakeMakefile::CONFIG['DLEXT']

CRCS.each do |crc|
next unless File.file?(File.join(crc, "extconf.rb"))

crc_ext = "#{crc}_ext"

file "#{crc}/Makefile" => "#{crc}/extconf.rb" do
Expand Down
70 changes: 70 additions & 0 deletions ext/digest/crc.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#ifndef RUBY_DIGEST_CRC_H
#define RUBY_DIGEST_CRC_H 1

#include <ruby.h>
#include <stddef.h>
#include <stdint.h>

enum crc_input_direction {
crc_normal_input = 0,
crc_reflect_input = 1
};

enum crc_allocation_type {
crc_alloc_static = 0,
crc_alloc_heap = 1
};

struct crc_model
{
int bitwidth : 8;
enum crc_input_direction reflect_input : 1;
enum crc_allocation_type table_heap : 1;
uint64_t polynomial;
const void *table;
};

/*
* This structure is materialized in each shared object file that defines
* a CRC. Therefore, instead of checking the structure address, the
* `wrap_struct_name` field is simply checked. This is to avoid link errors
* at build time.
*/
static const rb_data_type_t crc_model_const_type = {
"digest-crc:model-const",
{
NULL,
NULL,
NULL,
NULL
}
};

static inline void ruby_digest_crc_model_implant(VALUE crc_class, const struct crc_model *model)
{
ID id_model = rb_intern("digest-crc-model");
VALUE obj = rb_data_typed_object_wrap(rb_cObject, (void *)(uintptr_t)model, &crc_model_const_type);
rb_obj_freeze(obj);
rb_ivar_set(crc_class, id_model, obj);
}

#if !defined(HAVE_RB_EXT_RACTOR_SAFE)
static inline void ruby_digest_crc_ensure_ractor_main(const char *mesg)
{
(void)mesg;
}
#else
#include <ruby/ractor.h>

static inline void ruby_digest_crc_ensure_ractor_main(const char *mesg)
{
if (rb_funcallv(rb_cRactor, rb_intern("main"), 0, NULL) !=
rb_funcallv(rb_cRactor, rb_intern("current"), 0, NULL)) {
VALUE ractor = rb_const_get(rb_cObject, rb_intern("Ractor"));
VALUE ractor_error = rb_const_get(ractor, rb_intern("Error"));
rb_raise(ractor_error, "%s", mesg);
}
}
#endif

#endif // RUBY_DIGEST_CRC_H
Loading

0 comments on commit 62e6d2b

Please sign in to comment.