218 : f_domain( domain ) , f_options( options ) {
236 const unsigned p = f_options.threads;
239 std::vector< std::unique_ptr< Worker > > w( p );
240 for(
unsigned i = 0 ; i < p ; ++i )
241 w[ i ] = std::make_unique< Worker >( *
this , g );
244 const State s0 = f_domain.initial_state();
245 const std::size_t b = f_domain.nblock( s0 );
246 w[ 0 ]->insert( g.block( b ) , s0 ,
Cost( 0 ) ,
247 Cost( f_domain.heuristic( s0 ) ) ,
nullptr ,
no_op ,
248 f_domain.hash( s0 ) );
252 run_workers( f_ex , p , [ & ](
unsigned i ) { w[ i ]->run(); } );
256 for(
auto & x : w ) {
261 st.
stored += x->f_pool.size();
263 st.
stored_by.push_back( x->f_pool.size() );
265 if( g.limit_hit.load() )
270 sol.cost = g.goal->
g;
281 using Lock =
typename Executor::lock_type;
289 std::vector< std::size_t > interferes;
290 std::vector< std::size_t > scope;
292 unsigned sigma_h = 0;
295 bool in_free =
false;
305 explicit Graph(
const D & d )
306 : f_d( d ) , v_b( new std::atomic< NBlock * >[ d.num_nblocks() ] ) {
307 for( std::size_t i = 0 ; i < d.num_nblocks() ; ++i )
308 v_b[ i ].store(
nullptr , std::memory_order_relaxed );
312 NBlock * find( std::size_t b )
const {
313 return( v_b[ b ].load( std::memory_order_acquire ) );
317 NBlock & block( std::size_t b ) {
318 if( NBlock * x = find( b ) )
320 std::lock_guard< std::mutex > guard( f_build );
321 if( NBlock * x = v_b[ b ].load( std::memory_order_relaxed ) )
323 auto n = std::make_unique< NBlock >();
324 std::vector< std::size_t > succ , iset;
325 f_d.for_each_nblock_successor( b , [ & ]( std::size_t s ) {
329 for( std::size_t s : succ ) {
331 f_d.for_each_nblock_successor( s , [ & ]( std::size_t t ) {
335 std::sort( iset.begin() , iset.end() );
336 iset.erase( std::unique( iset.begin() , iset.end() ) , iset.end() );
337 iset.erase( std::remove( iset.begin() , iset.end() , b ) , iset.end() );
338 n->interferes = std::move( iset );
339 n->scope = std::move( succ );
340 n->scope.push_back( b );
341 v_b[ b ].store( n.get() , std::memory_order_release );
343 v_own.push_back( std::move( n ) );
349 Cost best_f( NBlock & n ) {
350 while( ! n.open.empty() && n.open.top().stale() )
356 bool is_free( NBlock & n ) {
357 return( ! n.inuse && ( n.sigma == 0 ) && ( n.sigma_h == 0 ) &&
358 ( best_f( n ) < incumbent.load() ) );
362 void make_free( std::size_t b ) {
363 NBlock & n = block( b );
364 if( n.in_free || ! is_free( n ) )
366 n.free_key = best_f( n );
367 n.best.store( n.free_key , std::memory_order_relaxed );
368 free.insert( { n.free_key , b } , true );
374 void make_busy( std::size_t b ) {
375 NBlock & n = block( b );
377 free.remove( { n.free_key , b } );
383 Cost best_free(
void )
const {
385 : ( *free.begin() ).first.first );
389 std::pair< Cost , std::size_t > best_scope(
const NBlock & n ) {
392 for( std::size_t i : n.interferes )
393 if(
const NBlock * x = find( i ) ) {
394 const Cost f = x->best.load( std::memory_order_relaxed );
402 void set_hot( std::size_t b ) {
403 NBlock & n = block( b );
404 if( n.hot || ( n.sigma == 0 ) )
406 for( std::size_t i : n.interferes )
407 if(
const NBlock * x = find( i ) ; x && x->hot &&
408 ( x->best.load( std::memory_order_relaxed ) <
409 n.best.load( std::memory_order_relaxed ) ) )
412 for( std::size_t i : n.interferes ) {
413 NBlock & m = block( i );
423 void set_cold( std::size_t b ) {
424 NBlock & n = block( b );
426 for( std::size_t i : n.interferes ) {
427 NBlock & m = block( i );
429 if( ( m.sigma == 0 ) && ( m.sigma_h == 0 ) ) {
438 void release( std::size_t b ) {
439 NBlock & n = block( b );
442 for( std::size_t i : n.interferes ) {
443 NBlock & m = block( i );
444 if( --m.sigma == 0 ) {
445 if( ( m.sigma_h == 0 ) && m.hot )
454 void acquire( std::size_t b ) {
456 NBlock & n = block( b );
459 for( std::size_t i : n.interferes ) {
460 NBlock & m = block( i );
468 void prune_free(
void ) {
469 const Cost c = incumbent.load();
470 for(
auto it = free.lowerBound( { c , 0 } ) ; it != free.end() ; ) {
471 block( ( *it ).first.second ).in_free =
false;
472 it = free.erase( it );
477 std::unique_ptr< std::atomic< NBlock * >[] > v_b;
478 std::vector< std::unique_ptr< NBlock > > v_own;
481 std::condition_variable_any wake;
482 ADS::BTreeMap< std::pair< Cost , std::size_t > ,
bool > free;
486 std::atomic< bool > limit_hit{
false };
487 std::mutex goal_mutex;
488 NodeT * goal =
nullptr;
494 struct alignas( cache_line ) Worker {
496 Worker(
const SafePBNF & a , Graph & g ) : f_a( a ) , f_g( g ) {}
500 std::size_t b = none;
501 bool just_hot =
false;
503 b = next_nblock( b , just_hot );
507 NBlock & n = f_g.block( b );
509 while( ! should_switch( b , n , exp , just_hot ) ) {
510 const OpenEntry< NodeT > e = n.open.pop();
514 if( m->g + m->h >= f_g.incumbent.load() ) {
515 while( ! n.open.empty() )
523 std::memory_order_relaxed );
524 if( f_a.f_options.node_limit &&
525 ( f_pool.size() >= f_a.f_options.node_limit ) ) {
526 std::lock_guard< Lock > guard( f_g.lock );
527 f_g.limit_hit.store(
true );
529 f_g.wake.notify_all();
537 bool should_switch( std::size_t b , NBlock & n ,
unsigned & exp ,
539 while( ! n.open.empty() && n.open.top().stale() )
543 if( exp < f_a.f_options.min_expansions )
546 const Cost mine = n.open.top_f();
547 std::lock_guard< Lock > guard( f_g.lock );
550 const auto [ fs , bs ] = f_g.best_scope( n );
551 const Cost ff = f_g.best_free();
552 if( ( ff < mine ) || ( fs < mine ) ) {
553 if( ( bs != none ) && ( fs < ff ) ) {
559 for( std::size_t i : n.interferes )
560 if(
const NBlock * x = f_g.find( i ) ; x && x->hot )
567 std::size_t next_nblock( std::size_t b ,
bool just_hot ) {
568 std::unique_lock< Lock > guard( f_g.lock , std::defer_lock );
569 NBlock * n = b == none ? nullptr : & f_g.block( b );
570 if( ! n || n->open.empty() || just_hot )
573 if( ! guard.try_lock() )
576 const Cost mine = f_g.best_f( *n );
577 if( ! f_g.done && ( mine < f_g.incumbent.load() ) &&
578 ( mine <= f_g.best_scope( *n ).first ) &&
579 ( mine <= f_g.best_free() ) && ! just_hot )
581 n->best.store( mine , std::memory_order_relaxed );
588 if( ! f_g.free.empty() )
590 if( f_g.inuse == 0 ) {
592 f_g.wake.notify_all();
595 f_g.wake.wait( guard );
598 const std::size_t m = ( *f_g.free.begin() ).first.second;
604 void expand( NBlock & ,
NodeT * m ) {
610 if( f_a.f_domain.is_goal( m->state ) ) {
611 std::lock_guard< std::mutex > guard( f_g.goal_mutex );
612 if( m->g < f_g.incumbent.load() ) {
613 f_g.incumbent.store( m->g );
625 const Cost g = m->g + c;
626 if( g + ht >= f_g.incumbent.load( std::memory_order_relaxed ) )
628 NBlock & cb = f_g.block( f_a.f_domain.nblock( t ) );
629 const std::uint64_t hash = f_a.f_domain.hash( t );
630 cb.table.prefetch( hash );
631 v_kids.push_back( Kid{ t , g , ht , hash , & cb , ot } );
633 for(
const Kid & k : v_kids )
634 if( insert( *k.cb , k.s , k.g , k.h , m , k.op , k.hash ) )
635 k.cb->best.store( std::min( k.cb->best.load( std::memory_order_relaxed ) ,
636 k.g + k.h ) , std::memory_order_relaxed );
641 bool insert( NBlock & cb ,
const State & t ,
Cost g ,
Cost h ,
642 NodeT * parent , std::uint8_t op , std::uint64_t hash ) {
643 auto [ x , fresh ] = cb.table.find_or_insert( t , hash , [ & ] {
644 NodeT * y = f_pool.allocate();
645 *y =
NodeT{ t , g , h , parent , false , false , op };
662 static constexpr std::size_t none = std::size_t( -1 );
674 std::vector< Kid > v_kids;
686 Executor * f_ex =
nullptr;