@@ -4331,12 +4331,16 @@ mono_class_is_variant_compatible_slow (MonoClass *klass, MonoClass *oklass)
4331
4331
}
4332
4332
return TRUE;
4333
4333
}
4334
- /*Check if @candidate implements the interface @target*/
4334
+
4335
4335
static gboolean
4336
- mono_class_implement_interface_slow (MonoClass * target , MonoClass * candidate )
4336
+ mono_class_implement_interface_slow_cached (MonoClass * target , MonoClass * candidate , dn_simdhash_ptrpair_ptr_t * cache );
4337
+
4338
+ static gboolean
4339
+ mono_class_implement_interface_slow_uncached (MonoClass * target , MonoClass * candidate , dn_simdhash_ptrpair_ptr_t * cache )
4337
4340
{
4338
4341
ERROR_DECL (error );
4339
4342
int i ;
4343
+
4340
4344
gboolean is_variant = mono_class_has_variant_generic_params (target );
4341
4345
4342
4346
if (is_variant && MONO_CLASS_IS_INTERFACE_INTERNAL (candidate )) {
@@ -4365,7 +4369,7 @@ mono_class_implement_interface_slow (MonoClass *target, MonoClass *candidate)
4365
4369
return TRUE;
4366
4370
if (is_variant && mono_class_is_variant_compatible_slow (target , iface_class ))
4367
4371
return TRUE;
4368
- if (mono_class_implement_interface_slow (target , iface_class ))
4372
+ if (mono_class_implement_interface_slow_cached (target , iface_class , cache ))
4369
4373
return TRUE;
4370
4374
}
4371
4375
}
@@ -4390,7 +4394,7 @@ mono_class_implement_interface_slow (MonoClass *target, MonoClass *candidate)
4390
4394
if (is_variant && mono_class_is_variant_compatible_slow (target , candidate_interfaces [i ]))
4391
4395
return TRUE;
4392
4396
4393
- if (mono_class_implement_interface_slow (target , candidate_interfaces [i ]))
4397
+ if (mono_class_implement_interface_slow_cached (target , candidate_interfaces [i ], cache ))
4394
4398
return TRUE;
4395
4399
}
4396
4400
}
@@ -4400,6 +4404,107 @@ mono_class_implement_interface_slow (MonoClass *target, MonoClass *candidate)
4400
4404
return FALSE;
4401
4405
}
4402
4406
4407
+ // #define LOG_INTERFACE_CACHE_HITS 1
4408
+
4409
+ #if LOG_INTERFACE_CACHE_HITS
4410
+ static gint64 implement_interface_hits = 0 , implement_interface_misses = 0 ;
4411
+
4412
+ static void
4413
+ log_hit_rate (dn_simdhash_ptrpair_ptr_t * cache )
4414
+ {
4415
+ gint64 total_calls = implement_interface_hits + implement_interface_misses ;
4416
+ if ((total_calls % 500 ) != 0 )
4417
+ return ;
4418
+ double hit_rate = implement_interface_hits * 100.0 / total_calls ;
4419
+ g_printf ("implement_interface cache hit rate: %f (%lld total calls). Overflow count: %u\n" , hit_rate , total_calls , dn_simdhash_overflow_count (cache ));
4420
+ }
4421
+ #endif
4422
+
4423
+ static gboolean
4424
+ mono_class_implement_interface_slow_cached (MonoClass * target , MonoClass * candidate , dn_simdhash_ptrpair_ptr_t * cache )
4425
+ {
4426
+ gpointer cached_result = NULL ;
4427
+ dn_ptrpair_t key = { target , candidate };
4428
+ gboolean result = 0 , cache_hit = 0 ;
4429
+
4430
+ // Skip the caching logic for exact matches
4431
+ if (candidate == target )
4432
+ return TRUE;
4433
+
4434
+ cache_hit = dn_simdhash_ptrpair_ptr_try_get_value (cache , key , & cached_result );
4435
+ if (cache_hit ) {
4436
+ // Testing shows a cache hit rate of 60% on S.R.Tests and S.T.J.Tests,
4437
+ // and 40-50% for small app startup. Near-zero overflow count.
4438
+ #if LOG_INTERFACE_CACHE_HITS
4439
+ implement_interface_hits ++ ;
4440
+ log_hit_rate (cache );
4441
+ #endif
4442
+ result = (cached_result != NULL );
4443
+ #ifndef ENABLE_CHECKED_BUILD
4444
+ return result ;
4445
+ #endif
4446
+ }
4447
+
4448
+ gboolean uncached_result = mono_class_implement_interface_slow_uncached (target , candidate , cache );
4449
+
4450
+ if (!cache_hit ) {
4451
+ #if LOG_INTERFACE_CACHE_HITS
4452
+ implement_interface_misses ++ ;
4453
+ log_hit_rate (cache );
4454
+ #endif
4455
+ dn_simdhash_ptrpair_ptr_try_add (cache , key , uncached_result ? GUINT_TO_POINTER (1 ) : NULL );
4456
+ }
4457
+
4458
+ #ifdef ENABLE_CHECKED_BUILD
4459
+ if (cache_hit ) {
4460
+ if (result != uncached_result )
4461
+ g_print (
4462
+ "Cache mismatch for %s.%s and %s.%s: cached=%d, uncached=%d\n" ,
4463
+ m_class_get_name_space (target ), m_class_get_name (target ),
4464
+ m_class_get_name_space (candidate ), m_class_get_name (candidate ),
4465
+ result , uncached_result
4466
+ );
4467
+ g_assert (result == uncached_result );
4468
+ }
4469
+ #endif
4470
+ return uncached_result ;
4471
+ }
4472
+
4473
+ static dn_simdhash_ptrpair_ptr_t * implement_interface_scratch_cache = NULL ;
4474
+
4475
+ /*Check if @candidate implements the interface @target*/
4476
+ static gboolean
4477
+ mono_class_implement_interface_slow (MonoClass * target , MonoClass * candidate )
4478
+ {
4479
+ gpointer cas_result ;
4480
+ gboolean result ;
4481
+ dn_simdhash_ptrpair_ptr_t * cache = (dn_simdhash_ptrpair_ptr_t * )mono_atomic_xchg_ptr ((volatile gpointer * )& implement_interface_scratch_cache , NULL );
4482
+ if (!cache )
4483
+ // Roughly 64KB of memory usage and big enough to have fast lookups
4484
+ // Smaller is viable but makes the hit rate worse
4485
+ cache = dn_simdhash_ptrpair_ptr_new (2048 , NULL );
4486
+ else if (dn_simdhash_count (cache ) >= 2250 ) {
4487
+ // FIXME: 2250 is arbitrary (roughly 256 11-item buckets w/load factor)
4488
+ // One step down reduces hit rate by approximately 2-4%
4489
+ // HACK: Only clear the scratch cache once it gets too big.
4490
+ // The pattern is that (especially during startup), we have lots
4491
+ // of mono_class_implement_interface_slow calls back to back that
4492
+ // perform similar checks, so keeping the cache data around between
4493
+ // sequential calls will potentially optimize them a lot.
4494
+ dn_simdhash_clear (cache );
4495
+ }
4496
+
4497
+ result = mono_class_implement_interface_slow_cached (target , candidate , cache );
4498
+
4499
+ // Under most circumstances we won't have multiple threads competing to run implement_interface_slow,
4500
+ // so it's not worth making this thread-local and potentially keeping a cache instance around per-thread.
4501
+ cas_result = mono_atomic_cas_ptr ((volatile gpointer * )& implement_interface_scratch_cache , cache , NULL );
4502
+ if (cas_result != NULL )
4503
+ dn_simdhash_free (cache );
4504
+
4505
+ return result ;
4506
+ }
4507
+
4403
4508
/*
4404
4509
* Check if @oklass can be assigned to @klass.
4405
4510
* This function does the same as mono_class_is_assignable_from_internal but is safe to be used from mono_class_init_internal context.
@@ -4416,8 +4521,9 @@ mono_class_is_assignable_from_slow (MonoClass *target, MonoClass *candidate)
4416
4521
return TRUE;
4417
4522
4418
4523
/*If target is not an interface there is no need to check them.*/
4419
- if (MONO_CLASS_IS_INTERFACE_INTERNAL (target ))
4524
+ if (MONO_CLASS_IS_INTERFACE_INTERNAL (target )) {
4420
4525
return mono_class_implement_interface_slow (target , candidate );
4526
+ }
4421
4527
4422
4528
if (m_class_is_delegate (target ) && mono_class_has_variant_generic_params (target ))
4423
4529
return mono_class_is_variant_compatible (target , candidate , FALSE);
0 commit comments