@@ -674,10 +674,11 @@ new_dict_with_shared_keys(PyDictKeysObject *keys)
674
674
}
675
675
676
676
677
- static PyObject *
678
- clone_combined_dict (PyDictObject * orig )
677
+ static PyDictKeysObject *
678
+ clone_combined_dict_keys (PyDictObject * orig )
679
679
{
680
- assert (PyDict_CheckExact (orig ));
680
+ assert (PyDict_Check (orig ));
681
+ assert (Py_TYPE (orig )-> tp_iter == (getiterfunc )dict_iter );
681
682
assert (orig -> ma_values == NULL );
682
683
assert (orig -> ma_keys -> dk_refcnt == 1 );
683
684
@@ -704,28 +705,14 @@ clone_combined_dict(PyDictObject *orig)
704
705
}
705
706
}
706
707
707
- PyDictObject * new = (PyDictObject * )new_dict (keys , NULL );
708
- if (new == NULL ) {
709
- /* In case of an error, `new_dict()` takes care of
710
- cleaning up `keys`. */
711
- return NULL ;
712
- }
713
- new -> ma_used = orig -> ma_used ;
714
- ASSERT_CONSISTENT (new );
715
- if (_PyObject_GC_IS_TRACKED (orig )) {
716
- /* Maintain tracking. */
717
- _PyObject_GC_TRACK (new );
718
- }
719
-
720
708
/* Since we copied the keys table we now have an extra reference
721
709
in the system. Manually call increment _Py_RefTotal to signal that
722
710
we have it now; calling dictkeys_incref would be an error as
723
711
keys->dk_refcnt is already set to 1 (after memcpy). */
724
712
#ifdef Py_REF_DEBUG
725
713
_Py_RefTotal ++ ;
726
714
#endif
727
-
728
- return (PyObject * )new ;
715
+ return keys ;
729
716
}
730
717
731
718
PyObject *
@@ -2527,12 +2514,45 @@ dict_merge(PyObject *a, PyObject *b, int override)
2527
2514
if (other == mp || other -> ma_used == 0 )
2528
2515
/* a.update(a) or a.update({}); nothing to do */
2529
2516
return 0 ;
2530
- if (mp -> ma_used == 0 )
2517
+ if (mp -> ma_used == 0 ) {
2531
2518
/* Since the target dict is empty, PyDict_GetItem()
2532
2519
* always returns NULL. Setting override to 1
2533
2520
* skips the unnecessary test.
2534
2521
*/
2535
2522
override = 1 ;
2523
+ PyDictKeysObject * okeys = other -> ma_keys ;
2524
+
2525
+ // If other is clean, combined, and just allocated, just clone it.
2526
+ if (other -> ma_values == NULL &&
2527
+ other -> ma_used == okeys -> dk_nentries &&
2528
+ (okeys -> dk_size == PyDict_MINSIZE ||
2529
+ USABLE_FRACTION (okeys -> dk_size /2 ) < other -> ma_used )) {
2530
+ PyDictKeysObject * keys = clone_combined_dict_keys (other );
2531
+ if (keys == NULL ) {
2532
+ return -1 ;
2533
+ }
2534
+
2535
+ dictkeys_decref (mp -> ma_keys );
2536
+ mp -> ma_keys = keys ;
2537
+ if (mp -> ma_values != NULL ) {
2538
+ if (mp -> ma_values != empty_values ) {
2539
+ free_values (mp -> ma_values );
2540
+ }
2541
+ mp -> ma_values = NULL ;
2542
+ }
2543
+
2544
+ mp -> ma_used = other -> ma_used ;
2545
+ mp -> ma_version_tag = DICT_NEXT_VERSION ();
2546
+ ASSERT_CONSISTENT (mp );
2547
+
2548
+ if (_PyObject_GC_IS_TRACKED (other ) && !_PyObject_GC_IS_TRACKED (mp )) {
2549
+ /* Maintain tracking. */
2550
+ _PyObject_GC_TRACK (mp );
2551
+ }
2552
+
2553
+ return 0 ;
2554
+ }
2555
+ }
2536
2556
/* Do one big resize at the start, rather than
2537
2557
* incrementally resizing as we insert new items. Expect
2538
2558
* that there will be no (or few) overlapping keys.
@@ -2718,12 +2738,13 @@ PyDict_Copy(PyObject *o)
2718
2738
return (PyObject * )split_copy ;
2719
2739
}
2720
2740
2721
- if (PyDict_CheckExact (mp ) && mp -> ma_values == NULL &&
2741
+ if (Py_TYPE (mp )-> tp_iter == (getiterfunc )dict_iter &&
2742
+ mp -> ma_values == NULL &&
2722
2743
(mp -> ma_used >= (mp -> ma_keys -> dk_nentries * 2 ) / 3 ))
2723
2744
{
2724
2745
/* Use fast-copy if:
2725
2746
2726
- (1) 'mp' is an instance of a subclassed dict ; and
2747
+ (1) type(mp) doesn't override tp_iter ; and
2727
2748
2728
2749
(2) 'mp' is not a split-dict; and
2729
2750
@@ -2735,13 +2756,31 @@ PyDict_Copy(PyObject *o)
2735
2756
operations and copied after that. In cases like this, we defer to
2736
2757
PyDict_Merge, which produces a compacted copy.
2737
2758
*/
2738
- return clone_combined_dict (mp );
2759
+ PyDictKeysObject * keys = clone_combined_dict_keys (mp );
2760
+ if (keys == NULL ) {
2761
+ return NULL ;
2762
+ }
2763
+ PyDictObject * new = (PyDictObject * )new_dict (keys , NULL );
2764
+ if (new == NULL ) {
2765
+ /* In case of an error, `new_dict()` takes care of
2766
+ cleaning up `keys`. */
2767
+ return NULL ;
2768
+ }
2769
+
2770
+ new -> ma_used = mp -> ma_used ;
2771
+ ASSERT_CONSISTENT (new );
2772
+ if (_PyObject_GC_IS_TRACKED (mp )) {
2773
+ /* Maintain tracking. */
2774
+ _PyObject_GC_TRACK (new );
2775
+ }
2776
+
2777
+ return (PyObject * )new ;
2739
2778
}
2740
2779
2741
2780
copy = PyDict_New ();
2742
2781
if (copy == NULL )
2743
2782
return NULL ;
2744
- if (PyDict_Merge (copy , o , 1 ) == 0 )
2783
+ if (dict_merge (copy , o , 1 ) == 0 )
2745
2784
return copy ;
2746
2785
Py_DECREF (copy );
2747
2786
return NULL ;
@@ -3359,16 +3398,15 @@ dict_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
3359
3398
d = (PyDictObject * )self ;
3360
3399
3361
3400
/* The object has been implicitly tracked by tp_alloc */
3362
- if (type == & PyDict_Type )
3401
+ if (type == & PyDict_Type ) {
3363
3402
_PyObject_GC_UNTRACK (d );
3403
+ }
3364
3404
3365
3405
d -> ma_used = 0 ;
3366
3406
d -> ma_version_tag = DICT_NEXT_VERSION ();
3367
- d -> ma_keys = new_keys_object (PyDict_MINSIZE );
3368
- if (d -> ma_keys == NULL ) {
3369
- Py_DECREF (self );
3370
- return NULL ;
3371
- }
3407
+ dictkeys_incref (Py_EMPTY_KEYS );
3408
+ d -> ma_keys = Py_EMPTY_KEYS ;
3409
+ d -> ma_values = empty_values ;
3372
3410
ASSERT_CONSISTENT (d );
3373
3411
return self ;
3374
3412
}
0 commit comments