From fcf3939780972d587b18afc26c4abd2da2c0b7ec Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Wed, 3 Dec 2025 14:50:35 +0100 Subject: Speedup TypedData_Get_Struct MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While profiling `Monitor#synchronize` and `Mutex#synchronize` I noticed a fairly significant amount of time spent in `rb_check_typeddata`. By implementing a fast path that assumes the object is valid and that can be inlined, it does make a significant difference: Before: ``` Mutex 13.548M (± 3.6%) i/s (73.81 ns/i) - 68.566M in 5.067444 Monitor 10.497M (± 6.5%) i/s (95.27 ns/i) - 52.529M in 5.032698s ``` After: ``` Mutex 20.887M (± 0.3%) i/s (47.88 ns/i) - 106.021M in 5.075989s Monitor 16.245M (±13.3%) i/s (61.56 ns/i) - 80.705M in 5.099680s ``` ```ruby require 'bundler/inline' gemfile do gem "benchmark-ips" end mutex = Mutex.new require "monitor" monitor = Monitor.new Benchmark.ips do |x| x.report("Mutex") { mutex.synchronize { } } x.report("Monitor") { monitor.synchronize { } } end ``` --- include/ruby/internal/core/rtypeddata.h | 45 +++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/include/ruby/internal/core/rtypeddata.h b/include/ruby/internal/core/rtypeddata.h index 24e87e63f9..aaf8f7997c 100644 --- a/include/ruby/internal/core/rtypeddata.h +++ b/include/ruby/internal/core/rtypeddata.h @@ -507,19 +507,6 @@ RBIMPL_SYMBOL_EXPORT_END() sizeof(type)) #endif -/** - * Obtains a C struct from inside of a wrapper Ruby object. - * - * @param obj An instance of ::RTypedData. - * @param type Type name of the C struct. - * @param data_type The data type describing `type`. - * @param sval Variable name of obtained C struct. - * @exception rb_eTypeError `obj` is not a kind of `data_type`. - * @return Unwrapped C struct that `obj` holds. - */ -#define TypedData_Get_Struct(obj,type,data_type,sval) \ - ((sval) = RBIMPL_CAST((type *)rb_check_typeddata((obj), (data_type)))) - static inline bool RTYPEDDATA_EMBEDDED_P(VALUE obj) { @@ -614,6 +601,38 @@ RTYPEDDATA_TYPE(VALUE obj) return (const struct rb_data_type_struct *)(RTYPEDDATA(obj)->type & TYPED_DATA_PTR_MASK); } +RBIMPL_ATTR_PURE_UNLESS_DEBUG() +RBIMPL_ATTR_ARTIFICIAL() +/** + * @private + * + * This is an implementation detail of TypedData_Get_Struct(). Don't use it + * directly. + */ +static inline void * +rbimpl_check_typeddata(VALUE obj, const rb_data_type_t *type) +{ + if (RB_LIKELY(RB_TYPE_P(obj, T_DATA) && RTYPEDDATA_P(obj) && RTYPEDDATA_TYPE(obj) == type)) { + return RTYPEDDATA_GET_DATA(obj); + } + + return rb_check_typeddata(obj, type); +} + + +/** + * Obtains a C struct from inside of a wrapper Ruby object. + * + * @param obj An instance of ::RTypedData. + * @param type Type name of the C struct. + * @param data_type The data type describing `type`. + * @param sval Variable name of obtained C struct. + * @exception rb_eTypeError `obj` is not a kind of `data_type`. + * @return Unwrapped C struct that `obj` holds. + */ +#define TypedData_Get_Struct(obj,type,data_type,sval) \ + ((sval) = RBIMPL_CAST((type *)rbimpl_check_typeddata((obj), (data_type)))) + /** * While we don't stop you from using this function, it seems to be an * implementation detail of #TypedData_Make_Struct, which is preferred over -- cgit v1.2.3