NHI1 10.0
NHI1 -
theKernel -
theLink -
theConfig -
theSq3Lite -
theCompiler -
theBrain -
theGuard
Nhi1Label - Nhi1BuildExtension - Nhi1Config - Nhi1Exec - Nhi1Switch - Nhi1Tags - Nhi1Docs |
Ruby is, together with TCL, Python, Perl and PHP, a SECOND generation language, followed by Java and C#, which I call THIRD generation languages.
The SECOND generation languages all have at least one design bug that leads to increasingly tragic consequences over the years.
The performance of Ruby in the executed code is good overall, although the biggest weakness by far is the slow startup, which in turn, together with the lack of parallelism in the execution, has consequences for a server application.
The performance is measured with the performance-test-suite from theLink.
setup
--spawn
and --fork
as startup.--send-X
--send-XXX
speed is fast, close to C and C++, faster than Python.--send-nothing
is 5% slower than Python, this is cost of the Ruby specific overhead for long-jump protection on error.--parent
In my specific case, the Garbage-Collection (GC) had a problem with a programming error in the constructor of a callback or the unintentional deletion of a local variable.
Every Ruby C-API function basically has the problem that there is NO error return, but in the event of an error a long jump is carried out to a remote address. To avoid this, a C->RUBY callback is not called directly, but via an API function called rb_protect
, which is de facto a try..catch
.
In the event of an error, the error message is catched and passed to the Programming-Language-Micro-Kernel (PLMK), which then takes over further processing or forwarding, so far so good.
Because there seems to be a problem with the selection of the objects, which affects the runtime, for example, there has been an upgrade in the current 3.3.5 release of Ruby.
### GC / Memory management * Major performance improvements over Ruby 3.2 * Young objects referenced by old objects are no longer immediately promoted to the old generation. This significantly reduces the frequency of major GC collections. [[Feature #19678]] * A new `REMEMBERED_WB_UNPROTECTED_OBJECTS_LIMIT_RATIO` tuning variable was introduced to control the number of unprotected objects cause a major GC collection to trigger. The default is set to `0.01` (1%). This significantly reduces the frequency of major GC collection. [[Feature #19571]] * Write Barriers were implemented for many core types that were missing them, notably `Time`, `Enumerator`, `MatchData`, `Method`, `File::Stat`, `BigDecimal` and several others. This significantly reduces minor GC collection time and major GC collection frequency. * Most core classes are now using Variable Width Allocation, notably `Hash`, `Time`, `Thread::Backtrace`, `Thread::Backtrace::Location`, `File::Stat`, `Method`. This makes these classes faster to allocate and free, use less memory and reduce heap fragmentation. * `defined?(@ivar)` is optimized with Object Shapes.
But now comes the bombshell: Ruby explicitly does not allow a new object to be created during the Garbage-Collection (GC), which then immediately ends in a:
This restriction in itself raises doubts, but it gets even better. This restriction applies not only to any code, but also to the ruby-kernel itself, so that in principle every ruby API call could end with a CORE.
In Programming-Language-Micro-Kernel (PLMK), when an object is deleted, the destructor is called and this destructor does cleanup, which then de facto has consequences.
One of the consequences is that in RPC mode a delete-message is sent from the server to the client because it is quite common that when one object is deleted, all other dependent objects are also deleted.
Sending such a delete message is a task and this task is code that runs within the Garbage-Collection (GC) → funny.
NHI1_HOME/example/rb/LibLcConfigRpcServer.rb:939: [BUG] object allocation during garbage collection phase ruby 3.3.5 (2024-09-03 revision ef084cc8f4) [x86_64-linux-gnu] -- Control frame information ----------------------------------------------- c:0006 p:0003 s:0029 e:000027 METHOD NHI1_HOME/example/rb/LibLcConfigRpcServer.rb:939 [FINISH] c:0005 p:---- s:0023 e:000022 CFUNC :WriteString c:0004 p:0011 s:0019 e:000018 METHOD NHI1_HOME/example/rb/LibLcConfigRpcServer.rb:313 [FINISH] c:0003 p:---- s:0013 e:000012 CFUNC :ProcessEvent c:0002 p:0038 s:0008 E:000340 EVAL NHI1_HOME/example/rb/LcConfigServer.rb:72 [FINISH] c:0001 p:0000 s:0003 E:0012a0 DUMMY [FINISH] -- Ruby level backtrace information ---------------------------------------- NHI1_HOME/example/rb/LcConfigServer.rb:72:in `<main>' NHI1_HOME/example/rb/LcConfigServer.rb:72:in `ProcessEvent' NHI1_HOME/example/rb/LibLcConfigRpcServer.rb:313:in `LcConfigWriteString' NHI1_HOME/example/rb/LibLcConfigRpcServer.rb:313:in `WriteString' <<< here RUBY decides to run the GC NHI1_HOME/example/rb/LibLcConfigRpcServer.rb:939:in `ObjectDeleteCall' <<< this is the function to sync the client -- Threading information --------------------------------------------------- Total ractor count: 1 Ruby thread count for this ractor: 1 -- C level backtrace information ------------------------------------------- NHI1_EXT/x86_64-suse-linux-gnu/debug2/lib64/libruby.so.3.3(rb_print_backtrace+0x14) [0x7f32cd75471b] vm_dump.c:820 NHI1_EXT/x86_64-suse-linux-gnu/debug2/lib64/libruby.so.3.3(rb_vm_bugreport) vm_dump.c:1151 NHI1_EXT/x86_64-suse-linux-gnu/debug2/lib64/libruby.so.3.3(bug_report_end+0x0) [0x7f32cd54aa3a] error.c:1042 NHI1_EXT/x86_64-suse-linux-gnu/debug2/lib64/libruby.so.3.3(rb_bug_without_die) error.c:1042 NHI1_EXT/x86_64-suse-linux-gnu/debug2/lib64/libruby.so.3.3(die+0x0) [0x7f32cd491a01] error.c:1050 NHI1_EXT/x86_64-suse-linux-gnu/debug2/lib64/libruby.so.3.3(rb_bug) error.c:1052 // ^^^ this is the BUG: rb_bug("object allocation during garbage collection phase"); NHI1_EXT/x86_64-suse-linux-gnu/debug2/lib64/libruby.so.3.3(gc_event_hook_body+0x0) [0x7f32cd492626] gc.c:2867 NHI1_EXT/x86_64-suse-linux-gnu/debug2/lib64/libruby.so.3.3(newobj_slowpath) gc.c:2880 NHI1_EXT/x86_64-suse-linux-gnu/debug2/lib64/libruby.so.3.3(newobj_slowpath_wb_protected) gc.c:2895 NHI1_EXT/x86_64-suse-linux-gnu/debug2/lib64/libruby.so.3.3(newobj_of0+0x6c) [0x7f32cd578904] gc.c:2937 NHI1_EXT/x86_64-suse-linux-gnu/debug2/lib64/libruby.so.3.3(newobj_of) gc.c:2947 NHI1_EXT/x86_64-suse-linux-gnu/debug2/lib64/libruby.so.3.3(rb_wb_protected_newobj_of) gc.c:2962 NHI1_EXT/x86_64-suse-linux-gnu/debug2/lib64/libruby.so.3.3(ec_str_alloc_embed+0x15) [0x7f32cd6bbfc5] string.c:1695 NHI1_EXT/x86_64-suse-linux-gnu/debug2/lib64/libruby.so.3.3(ec_str_duplicate) string.c:1760 // ^^^ ruby-kernel try to create a NEW object "ec_str_duplicate" NHI1_EXT/x86_64-suse-linux-gnu/debug2/lib64/libruby.so.3.3(rb_ec_str_resurrect) string.c:1812 NHI1_EXT/x86_64-suse-linux-gnu/debug2/lib64/libruby.so.3.3(vm_exec_core+0xbca) [0x7f32cd738e3a] insns.def:378 NHI1_EXT/x86_64-suse-linux-gnu/debug2/lib64/libruby.so.3.3(vm_exec_loop+0xa) [0x7f32cd73e2a9] vm.c:2513 NHI1_EXT/x86_64-suse-linux-gnu/debug2/lib64/libruby.so.3.3(rb_vm_exec) vm.c:2489 NHI1_EXT/x86_64-suse-linux-gnu/debug2/lib64/libruby.so.3.3(rb_vm_call_kw+0x137) [0x7f32cd7474c7] vm_eval.c:110 NHI1_EXT/x86_64-suse-linux-gnu/debug2/lib64/libruby.so.3.3(rb_method_call_with_block_kw+0x7e) [0x7f32cd636d8e] proc.c:2459 NHI1_BUILD/x86_64-suse-linux-gnu/debug2/theKernel/rb/.libs/librbmkkernel.so.22(rb_mkkernel_sCallMethodWithOne+0x1f) [0x7f32b1c6f76d] NHI1_HOME/theKernel/rb/MkCall_rb.c:41 NHI1_EXT/x86_64-suse-linux-gnu/debug2/lib64/libruby.so.3.3(rb_protect+0xe7) [0x7f32cd554637] eval.c:983 NHI1_BUILD/x86_64-suse-linux-gnu/debug2/theKernel/rb/.libs/librbmkkernel.so.22(rb_mkkernel_sRescue+0x19) [0x7f32b1c6f791] NHI1_HOME/theKernel/rb/MkCall_rb.c:29 NHI1_BUILD/x86_64-suse-linux-gnu/debug2/theKernel/rb/.libs/librbmkkernel.so.22(rb_mkkernel_ObjectDeleteCall+0xac) [0x7f32b1c6fc18] NHI1_HOME/theKernel/rb/MkCall_rb.c:192 // ^^^ this is my 'ObjectDeleteCall' to sync the server with the client NHI1_BUILD/x86_64-suse-linux-gnu/debug2/theKernel/c/.libs/libmkkernel.so.22(MkObjectDeleteCall_RT+0x60) [0x7f32b1c0d24f] NHI1_HOME/theKernel/c/MkObjectS_mk.c:1220 NHI1_BUILD/x86_64-suse-linux-gnu/debug2/theKernel/c/.libs/libmkkernel.so.22(MkRefDecrWithoutSelf_RT+0x83) [0x7f32b1c0ee11] NHI1_HOME/theKernel/c/MkObjectS_mk.c:270 NHI1_BUILD/x86_64-suse-linux-gnu/debug2/theKernel/rb/.libs/librbmkkernel.so.22(rb_mkkernel_AtomDeleteSoft+0x1c) [0x7f32b1c74254] NHI1_HOME/theKernel/rb/MkObjectC_rb.c:236 NHI1_EXT/x86_64-suse-linux-gnu/debug2/lib64/libruby.so.3.3(RTYPEDDATA_TYPE+0x0) [0x7f32cd56ce53] gc.c:3500 NHI1_EXT/x86_64-suse-linux-gnu/debug2/lib64/libruby.so.3.3(rb_data_free) gc.c:3501 NHI1_EXT/x86_64-suse-linux-gnu/debug2/lib64/libruby.so.3.3(obj_free) gc.c:3659 NHI1_EXT/x86_64-suse-linux-gnu/debug2/lib64/libruby.so.3.3(gc_sweep_plane+0x45) [0x7f32cd56d9d5] gc.c:5681 NHI1_EXT/x86_64-suse-linux-gnu/debug2/lib64/libruby.so.3.3(gc_sweep_page) gc.c:5759 NHI1_EXT/x86_64-suse-linux-gnu/debug2/lib64/libruby.so.3.3(gc_sweep_step) gc.c:6048 ... // ^^^ the garbage-collector does his "job"
Ruby has a general problem with Garbage-Collection (GC).
It seems to me that the Garbage-Collection (GC) approach is too ambitious, so that, for example, the C programmer has little influence on protecting his data. I have found two methods to be useful for limiting the problem, but that doesn't say whether the problem just continues to exist "hidden".
VALUE
with:rb_gc_register_address(ADDRESS)
.VALUE
from [1] with:rb_gc_unregister_address(ADDRESS)
.ADDRESS
is important here because it has to be a Global-Memory (HEAP) address.Example: protect a VALUE
stored in a C struct
If a struct
is created use:
If a struct
is destroyed use:
VALUE
:Protecting a Global-Memory (HEAP) accessible VALUE
helped a lot but it was not enough to provide complete protection under ruby-3.3.5
.
Additionally, each new TypedData_Wrap_Struct
VALUE had to be protected with rb_gc_register_mark_object(VALUE)
.
Example: protect a new VALUE
from TypedData_Wrap_Struct
used in Ruby - Programming-Language-Micro-Kernel (PLMK)