fixed encoding table

This reduces global lock acquiring for reading.
https://bugs.ruby-lang.org/issues/18949
This commit is contained in:
Koichi Sasada 2022-12-15 17:53:55 +09:00
parent 15b60bb1a4
commit ae19ac5b5b
Notes: git 2022-12-16 01:04:55 +00:00
4 changed files with 61 additions and 115 deletions

View File

@ -202,6 +202,8 @@ Note: We're only listing outstanding class updates.
try to dynamically guess the endian based on a byte order mark. try to dynamically guess the endian based on a byte order mark.
Use `Encoding::UTF_16BE`/`UTF_16LE` and `Encoding::UTF_32BE`/`UTF_32LE` instead. Use `Encoding::UTF_16BE`/`UTF_16LE` and `Encoding::UTF_32BE`/`UTF_32LE` instead.
This change speeds up getting the encoding of a String. [[Feature #18949]] This change speeds up getting the encoding of a String. [[Feature #18949]]
* Limit maximum encoding set size by 256.
If exceeding maximum size, `EncodingError` will be raised. [[Feature #18949]]
* Enumerator * Enumerator

View File

@ -1474,7 +1474,7 @@ assert_equal "#{N}#{N}", %Q{
} }
# enc_table # enc_table
assert_equal "#{N/10}", %Q{ assert_equal "100", %Q{
Ractor.new do Ractor.new do
loop do loop do
Encoding.find("test-enc-#{rand(5_000)}").inspect Encoding.find("test-enc-#{rand(5_000)}").inspect
@ -1483,7 +1483,7 @@ assert_equal "#{N/10}", %Q{
end end
src = Encoding.find("UTF-8") src = Encoding.find("UTF-8")
#{N/10}.times{|i| 100.times{|i|
src.replicate("test-enc-\#{i}") src.replicate("test-enc-\#{i}")
} }
} }

View File

@ -56,9 +56,8 @@ int rb_encdb_alias(const char *alias, const char *orig);
static ID id_encoding; static ID id_encoding;
VALUE rb_cEncoding; VALUE rb_cEncoding;
#define DEFAULT_ENCODING_LIST_CAPA 128 #define ENCODING_LIST_CAPA 256
static VALUE rb_default_encoding_list; static VALUE rb_encoding_list;
static VALUE rb_additional_encoding_list;
struct rb_encoding_entry { struct rb_encoding_entry {
const char *name; const char *name;
@ -67,9 +66,8 @@ struct rb_encoding_entry {
}; };
static struct enc_table { static struct enc_table {
struct rb_encoding_entry *list; struct rb_encoding_entry list[ENCODING_LIST_CAPA];
int count; int count;
int size;
st_table *names; st_table *names;
} global_enc_table; } global_enc_table;
@ -128,47 +126,25 @@ enc_new(rb_encoding *encoding)
static void static void
enc_list_update(int index, rb_raw_encoding *encoding) enc_list_update(int index, rb_raw_encoding *encoding)
{ {
if (index < DEFAULT_ENCODING_LIST_CAPA) { RUBY_ASSERT(index < ENCODING_LIST_CAPA);
VALUE list = rb_default_encoding_list;
VALUE list = rb_encoding_list;
if (list && NIL_P(rb_ary_entry(list, index))) { if (list && NIL_P(rb_ary_entry(list, index))) {
/* initialize encoding data */ /* initialize encoding data */
rb_ary_store(list, index, enc_new(encoding)); rb_ary_store(list, index, enc_new(encoding));
} }
}
else {
RB_VM_LOCK_ENTER();
{
VALUE list = rb_additional_encoding_list;
if (list && NIL_P(rb_ary_entry(list, index))) {
/* initialize encoding data */
rb_ary_store(list, index - DEFAULT_ENCODING_LIST_CAPA, enc_new(encoding));
}
}
RB_VM_LOCK_LEAVE();
}
} }
static VALUE static VALUE
enc_list_lookup(int idx) enc_list_lookup(int idx)
{ {
VALUE list, enc; VALUE list, enc = Qnil;
if (idx < DEFAULT_ENCODING_LIST_CAPA) { if (idx < ENCODING_LIST_CAPA) {
if (!(list = rb_default_encoding_list)) { list = rb_encoding_list;
rb_bug("rb_enc_from_encoding_index(%d): no rb_default_encoding_list", idx); RUBY_ASSERT(list);
}
enc = rb_ary_entry(list, idx); enc = rb_ary_entry(list, idx);
} }
else {
RB_VM_LOCK_ENTER();
{
if (!(list = rb_additional_encoding_list)) {
rb_bug("rb_enc_from_encoding_index(%d): no rb_additional_encoding_list", idx);
}
enc = rb_ary_entry(list, idx - DEFAULT_ENCODING_LIST_CAPA);
}
RB_VM_LOCK_LEAVE();
}
if (NIL_P(enc)) { if (NIL_P(enc)) {
rb_bug("rb_enc_from_encoding_index(%d): not created yet", idx); rb_bug("rb_enc_from_encoding_index(%d): not created yet", idx);
@ -345,16 +321,10 @@ rb_find_encoding(VALUE enc)
static int static int
enc_table_expand(struct enc_table *enc_table, int newsize) enc_table_expand(struct enc_table *enc_table, int newsize)
{ {
struct rb_encoding_entry *ent; if (newsize > ENCODING_LIST_CAPA) {
int count = newsize; rb_raise(rb_eEncodingError, "too many encoding (> %d)", ENCODING_LIST_CAPA);
}
if (enc_table->size >= newsize) return newsize; return newsize;
newsize = (newsize + 7) / 8 * 8;
ent = REALLOC_N(enc_table->list, struct rb_encoding_entry, newsize);
memset(ent + enc_table->size, 0, sizeof(*ent)*(newsize - enc_table->size));
enc_table->list = ent;
enc_table->size = newsize;
return count;
} }
static int static int
@ -413,17 +383,7 @@ enc_from_index(struct enc_table *enc_table, int index)
rb_encoding * rb_encoding *
rb_enc_from_index(int index) rb_enc_from_index(int index)
{ {
rb_encoding *enc; return enc_from_index(&global_enc_table, index);
switch (index) {
case ENCINDEX_ASCII_8BIT: return global_enc_ascii;
case ENCINDEX_UTF_8: return global_enc_utf_8;
case ENCINDEX_US_ASCII: return global_enc_us_ascii;
default:
GLOBAL_ENC_TABLE_EVAL(enc_table,
enc = enc_from_index(enc_table, index));
return enc;
}
} }
int int
@ -484,11 +444,14 @@ rb_encdb_declare(const char *name)
} }
static void static void
enc_check_duplication(struct enc_table *enc_table, const char *name) enc_check_addable(struct enc_table *enc_table, const char *name)
{ {
if (enc_registered(enc_table, name) >= 0) { if (enc_registered(enc_table, name) >= 0) {
rb_raise(rb_eArgError, "encoding %s is already registered", name); rb_raise(rb_eArgError, "encoding %s is already registered", name);
} }
else if (!valid_encoding_name_p(name)) {
rb_raise(rb_eArgError, "invalid encoding name: %s", name);
}
} }
static rb_encoding* static rb_encoding*
@ -524,11 +487,7 @@ rb_enc_set_base(const char *name, const char *orig)
int int
rb_enc_set_dummy(int index) rb_enc_set_dummy(int index)
{ {
rb_encoding *enc; rb_encoding *enc = global_enc_table.list[index].enc;
GLOBAL_ENC_TABLE_EVAL(enc_table,
enc = enc_table->list[index].enc);
ENC_SET_DUMMY((rb_raw_encoding *)enc); ENC_SET_DUMMY((rb_raw_encoding *)enc);
return index; return index;
} }
@ -538,7 +497,7 @@ enc_replicate(struct enc_table *enc_table, const char *name, rb_encoding *encodi
{ {
int idx; int idx;
enc_check_duplication(enc_table, name); enc_check_addable(enc_table, name);
idx = enc_register(enc_table, name, encoding); idx = enc_register(enc_table, name, encoding);
if (idx < 0) rb_raise(rb_eArgError, "invalid encoding name: %s", name); if (idx < 0) rb_raise(rb_eArgError, "invalid encoding name: %s", name);
set_base_encoding(enc_table, idx, encoding); set_base_encoding(enc_table, idx, encoding);
@ -727,7 +686,7 @@ rb_enc_alias(const char *alias, const char *orig)
GLOBAL_ENC_TABLE_ENTER(enc_table); GLOBAL_ENC_TABLE_ENTER(enc_table);
{ {
enc_check_duplication(enc_table, alias); enc_check_addable(enc_table, alias);
if ((idx = rb_enc_find_index(orig)) < 0) { if ((idx = rb_enc_find_index(orig)) < 0) {
r = -1; r = -1;
} }
@ -764,7 +723,7 @@ rb_enc_init(struct enc_table *enc_table)
{ {
enc_table_expand(enc_table, ENCODING_COUNT + 1); enc_table_expand(enc_table, ENCODING_COUNT + 1);
if (!enc_table->names) { if (!enc_table->names) {
enc_table->names = st_init_strcasetable(); enc_table->names = st_init_strcasetable_with_size(ENCODING_LIST_CAPA);
} }
#define OnigEncodingASCII_8BIT OnigEncodingASCII #define OnigEncodingASCII_8BIT OnigEncodingASCII
#define ENC_REGISTER(enc) enc_register_at(enc_table, ENCINDEX_##enc, rb_enc_name(&OnigEncoding##enc), &OnigEncoding##enc) #define ENC_REGISTER(enc) enc_register_at(enc_table, ENCINDEX_##enc, rb_enc_name(&OnigEncoding##enc), &OnigEncoding##enc)
@ -877,11 +836,9 @@ rb_enc_autoload(rb_encoding *enc)
int int
rb_enc_find_index(const char *name) rb_enc_find_index(const char *name)
{ {
int i; int i = enc_registered(&global_enc_table, name);
rb_encoding *enc; rb_encoding *enc;
GLOBAL_ENC_TABLE_EVAL(enc_table, i = enc_registered(enc_table, name));
if (i < 0) { if (i < 0) {
i = load_encoding(name); i = load_encoding(name);
} }
@ -1368,10 +1325,7 @@ enc_names(VALUE self)
args[0] = (VALUE)rb_to_encoding_index(self); args[0] = (VALUE)rb_to_encoding_index(self);
args[1] = rb_ary_new2(0); args[1] = rb_ary_new2(0);
st_foreach(global_enc_table.names, enc_names_i, (st_data_t)args);
GLOBAL_ENC_TABLE_EVAL(enc_table,
st_foreach(enc_table->names, enc_names_i, (st_data_t)args));
return args[1]; return args[1];
} }
@ -1397,14 +1351,7 @@ static VALUE
enc_list(VALUE klass) enc_list(VALUE klass)
{ {
VALUE ary = rb_ary_new2(0); VALUE ary = rb_ary_new2(0);
rb_ary_replace(ary, rb_encoding_list);
RB_VM_LOCK_ENTER();
{
rb_ary_replace(ary, rb_default_encoding_list);
rb_ary_concat(ary, rb_additional_encoding_list);
}
RB_VM_LOCK_LEAVE();
return ary; return ary;
} }
@ -1553,15 +1500,17 @@ rb_locale_encindex(void)
if (idx < 0) idx = ENCINDEX_UTF_8; if (idx < 0) idx = ENCINDEX_UTF_8;
GLOBAL_ENC_TABLE_ENTER(enc_table); if (enc_registered(&global_enc_table, "locale") < 0) {
if (enc_registered(enc_table, "locale") < 0) {
# if defined _WIN32 # if defined _WIN32
void Init_w32_codepage(void); void Init_w32_codepage(void);
Init_w32_codepage(); Init_w32_codepage();
# endif # endif
GLOBAL_ENC_TABLE_ENTER(enc_table);
{
enc_alias_internal(enc_table, "locale", idx); enc_alias_internal(enc_table, "locale", idx);
} }
GLOBAL_ENC_TABLE_LEAVE(); GLOBAL_ENC_TABLE_LEAVE();
}
return idx; return idx;
} }
@ -1575,13 +1524,8 @@ rb_locale_encoding(void)
int int
rb_filesystem_encindex(void) rb_filesystem_encindex(void)
{ {
int idx; int idx = enc_registered(&global_enc_table, "filesystem");
if (idx < 0) idx = ENCINDEX_ASCII_8BIT;
GLOBAL_ENC_TABLE_EVAL(enc_table,
idx = enc_registered(enc_table, "filesystem"));
if (idx < 0)
idx = ENCINDEX_ASCII_8BIT;
return idx; return idx;
} }
@ -1872,15 +1816,8 @@ rb_enc_name_list_i(st_data_t name, st_data_t idx, st_data_t arg)
static VALUE static VALUE
rb_enc_name_list(VALUE klass) rb_enc_name_list(VALUE klass)
{ {
VALUE ary; VALUE ary = rb_ary_new2(global_enc_table.names->num_entries);
st_foreach(global_enc_table.names, rb_enc_name_list_i, (st_data_t)ary);
GLOBAL_ENC_TABLE_ENTER(enc_table);
{
ary = rb_ary_new2(enc_table->names->num_entries);
st_foreach(enc_table->names, rb_enc_name_list_i, (st_data_t)ary);
}
GLOBAL_ENC_TABLE_LEAVE();
return ary; return ary;
} }
@ -1926,8 +1863,7 @@ rb_enc_aliases(VALUE klass)
aliases[0] = rb_hash_new(); aliases[0] = rb_hash_new();
aliases[1] = rb_ary_new(); aliases[1] = rb_ary_new();
GLOBAL_ENC_TABLE_EVAL(enc_table, st_foreach(global_enc_table.names, rb_enc_aliases_enc_i, (st_data_t)aliases);
st_foreach(enc_table->names, rb_enc_aliases_enc_i, (st_data_t)aliases));
return aliases[0]; return aliases[0];
} }
@ -1996,13 +1932,7 @@ Init_Encoding(void)
struct enc_table *enc_table = &global_enc_table; struct enc_table *enc_table = &global_enc_table;
if (DEFAULT_ENCODING_LIST_CAPA < enc_table->count) rb_bug("DEFAULT_ENCODING_LIST_CAPA is too small"); list = rb_encoding_list = rb_ary_new2(ENCODING_LIST_CAPA);
list = rb_additional_encoding_list = rb_ary_new();
RBASIC_CLEAR_CLASS(list);
rb_gc_register_mark_object(list);
list = rb_default_encoding_list = rb_ary_new2(DEFAULT_ENCODING_LIST_CAPA);
RBASIC_CLEAR_CLASS(list); RBASIC_CLEAR_CLASS(list);
rb_gc_register_mark_object(list); rb_gc_register_mark_object(list);
@ -2024,5 +1954,5 @@ Init_encodings(void)
void void
rb_enc_foreach_name(int (*func)(st_data_t name, st_data_t idx, st_data_t arg), st_data_t arg) rb_enc_foreach_name(int (*func)(st_data_t name, st_data_t idx, st_data_t arg), st_data_t arg)
{ {
GLOBAL_ENC_TABLE_EVAL(enc_table, st_foreach(enc_table->names, func, arg)); st_foreach(global_enc_table.names, func, arg);
} }

View File

@ -69,7 +69,7 @@ class TestEncoding < Test::Unit::TestCase
def test_extra_encoding def test_extra_encoding
assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
begin; begin;
200.times {|i| 100.times {|i|
EnvUtil.suppress_warning { Encoding::UTF_8.replicate("dummy#{i}") } EnvUtil.suppress_warning { Encoding::UTF_8.replicate("dummy#{i}") }
} }
e = Encoding.list.last e = Encoding.list.last
@ -160,4 +160,18 @@ class TestEncoding < Test::Unit::TestCase
end end
end; end;
end end
def test_exceed_encoding_table_size
assert_separately(%w[--disable=gems], "#{<<~"begin;"}\n#{<<~'end;'}")
begin;
begin
enc = Encoding::UTF_8
1_000.times{|i| EnvUtil.suppress_warning{ enc.replicate("R_#{i}") } } # now 256 entries
rescue EncodingError => e
assert_match(/too many encoding/, e.message)
else
assert false
end
end;
end
end end