@@ -578,10 +578,11 @@ static_assert(BUFFER_HI < BUFFER_SIZE &&
578578 #define prefetch (ptr )
579579#endif
580580
581- // a contigous sequence of PyObject pointers, can contain NULLs
581+ // A strided sequence of PyObject pointers, which can contain NULLs.
582582typedef struct {
583583 PyObject * * start ;
584- PyObject * * end ;
584+ Py_ssize_t size ;
585+ Py_ssize_t stride ;
585586} gc_span_t ;
586587
587588typedef struct {
@@ -660,9 +661,10 @@ gc_mark_stack_push(_PyObjectStack *ms, PyObject *op)
660661}
661662
662663static int
663- gc_mark_span_push (gc_span_stack_t * ss , PyObject * * start , PyObject * * end )
664+ gc_mark_span_push (gc_span_stack_t * ss , PyObject * * start , Py_ssize_t size ,
665+ Py_ssize_t stride )
664666{
665- if (start == end ) {
667+ if (size == 0 ) {
666668 return 0 ;
667669 }
668670 if (ss -> size >= ss -> capacity ) {
@@ -678,9 +680,10 @@ gc_mark_span_push(gc_span_stack_t *ss, PyObject **start, PyObject **end)
678680 }
679681 ss -> stack = new_stack ;
680682 }
681- assert (end > start );
683+ assert (size > 0 );
682684 ss -> stack [ss -> size ].start = start ;
683- ss -> stack [ss -> size ].end = end ;
685+ ss -> stack [ss -> size ].size = size ;
686+ ss -> stack [ss -> size ].stride = stride ;
684687 ss -> size ++ ;
685688 return 0 ;
686689}
@@ -748,20 +751,21 @@ gc_mark_enqueue(PyObject *op, gc_mark_args_t *args)
748751 }
749752}
750753
751- // Called when we have a contigous sequence of PyObject pointers, either
752- // a tuple or list object . This will add the items to the buffer if there
754+ // Called for a strided sequence of PyObject pointers, including list and
755+ // tuple items and dictionary values . This will add the items to the buffer if there
753756// is space for them all otherwise push a new "span" on the span stack. Using
754757// spans has the advantage of not creating a deep _PyObjectStack stack when
755758// dealing with long sequences. Those sequences will be processed in smaller
756759// chunks by the gc_prime_from_spans() function.
757760static int
758- gc_mark_enqueue_span (PyObject * * item , Py_ssize_t size , gc_mark_args_t * args )
761+ gc_mark_enqueue_span_stride (PyObject * * item , Py_ssize_t size ,
762+ Py_ssize_t stride , gc_mark_args_t * args )
759763{
760764 Py_ssize_t used = gc_mark_buffer_len (args );
761765 Py_ssize_t free = BUFFER_SIZE - used ;
762766 if (free >= size ) {
763767 for (Py_ssize_t i = 0 ; i < size ; i ++ ) {
764- PyObject * op = item [ i ] ;
768+ PyObject * op = * ( PyObject * * )(( char * ) item + i * stride ) ;
765769 if (op == NULL ) {
766770 continue ;
767771 }
@@ -770,14 +774,39 @@ gc_mark_enqueue_span(PyObject **item, Py_ssize_t size, gc_mark_args_t *args)
770774 }
771775 else {
772776 assert (size > 0 );
773- PyObject * * end = & item [size ];
774- if (gc_mark_span_push (& args -> spans , item , end ) < 0 ) {
777+ if (gc_mark_span_push (& args -> spans , item , size , stride ) < 0 ) {
775778 return -1 ;
776779 }
777780 }
778781 return 0 ;
779782}
780783
784+ static int
785+ gc_mark_enqueue_span (PyObject * * item , Py_ssize_t size , gc_mark_args_t * args )
786+ {
787+ return gc_mark_enqueue_span_stride (item , size , sizeof (PyObject * ), args );
788+ }
789+
790+ static int
791+ gc_mark_traverse_dict (PyObject * self , gc_mark_args_t * args )
792+ {
793+ PyDictObject * dict = (PyDictObject * )self ;
794+ PyDictKeysObject * keys = dict -> ma_keys ;
795+ if (keys -> dk_nentries == 0 ) {
796+ return 0 ;
797+ }
798+ if (!DK_IS_UNICODE (keys )) {
799+ return _PyDict_Traverse (self , gc_mark_enqueue_buffer_visitproc , args );
800+ }
801+ if (_PyDict_HasSplitTable (dict )) {
802+ return gc_mark_enqueue_span (dict -> ma_values -> values ,
803+ keys -> dk_nentries , args );
804+ }
805+ PyDictUnicodeEntry * entries = DK_UNICODE_ENTRIES (keys );
806+ return gc_mark_enqueue_span_stride (& entries [0 ].me_value , keys -> dk_nentries ,
807+ sizeof (PyDictUnicodeEntry ), args );
808+ }
809+
781810static bool
782811gc_clear_alive_bits (const mi_heap_t * heap , const mi_heap_area_t * area ,
783812 void * block , size_t block_size , void * args )
@@ -1250,44 +1279,49 @@ gc_prime_from_spans(gc_mark_args_t *args)
12501279 assert (space <= BUFFER_HI );
12511280 gc_span_t entry = args -> spans .stack [-- args -> spans .size ];
12521281 // spans on the stack should always have one or more elements
1253- assert (entry .start < entry . end );
1282+ assert (entry .size > 0 );
12541283 do {
12551284 PyObject * op = * entry .start ;
1256- entry .start ++ ;
1285+ entry .size -- ;
1286+ if (entry .size > 0 ) {
1287+ entry .start = (PyObject * * )((char * )entry .start + entry .stride );
1288+ }
12571289 if (op != NULL ) {
12581290 gc_mark_buffer_push (op , args );
12591291 space -- ;
12601292 if (space == 0 ) {
12611293 // buffer is as full as we want and not done with span
1262- gc_mark_span_push (& args -> spans , entry .start , entry .end );
1294+ gc_mark_span_push (& args -> spans , entry .start , entry .size , entry . stride );
12631295 return ;
12641296 }
12651297 }
1266- } while (entry .start < entry . end );
1298+ } while (entry .size > 0 );
12671299}
12681300
12691301static void
12701302gc_prime_buffer (gc_mark_args_t * args )
12711303{
1272- if (args -> spans .size > 0 ) {
1304+ // Dictionary spans can contain only deleted entries.
1305+ while (args -> spans .size > 0 ) {
12731306 gc_prime_from_spans (args );
1307+ if (!gc_mark_buffer_is_empty (args )) {
1308+ return ;
1309+ }
12741310 }
1275- else {
1276- // When priming, don't fill the buffer too full since that would
1277- // likely cause the stack to be used shortly after when it
1278- // fills. We want to use the buffer as much as possible and so
1279- // we only fill to BUFFER_HI, not BUFFER_SIZE.
1280- Py_ssize_t space = BUFFER_HI - gc_mark_buffer_len (args );
1281- assert (space > 0 );
1282- do {
1283- PyObject * op = _PyObjectStack_Pop (& args -> stack );
1284- if (op == NULL ) {
1285- return ;
1286- }
1287- gc_mark_buffer_push (op , args );
1288- space -- ;
1289- } while (space > 0 );
1290- }
1311+ // When priming, don't fill the buffer too full since that would
1312+ // likely cause the stack to be used shortly after when it
1313+ // fills. We want to use the buffer as much as possible and so
1314+ // we only fill to BUFFER_HI, not BUFFER_SIZE.
1315+ Py_ssize_t space = BUFFER_HI - gc_mark_buffer_len (args );
1316+ assert (space > 0 );
1317+ do {
1318+ PyObject * op = _PyObjectStack_Pop (& args -> stack );
1319+ if (op == NULL ) {
1320+ return ;
1321+ }
1322+ gc_mark_buffer_push (op , args );
1323+ space -- ;
1324+ } while (space > 0 );
12911325}
12921326
12931327static int
@@ -1331,6 +1365,11 @@ gc_propagate_alive_prefetch(gc_mark_args_t *args)
13311365 return -1 ;
13321366 }
13331367 }
1368+ else if (traverse == _PyDict_Traverse ) {
1369+ if (gc_mark_traverse_dict (op , args ) < 0 ) {
1370+ return -1 ;
1371+ }
1372+ }
13341373 else if (traverse (op , gc_mark_enqueue_buffer_visitproc , args ) < 0 ) {
13351374 return -1 ;
13361375 }
0 commit comments