17db96d56Sopenharmony_ciNOTES ON DICTIONARIES
27db96d56Sopenharmony_ci================================
37db96d56Sopenharmony_ci
47db96d56Sopenharmony_ciPrincipal Use Cases for Dictionaries
57db96d56Sopenharmony_ci------------------------------------
67db96d56Sopenharmony_ci
77db96d56Sopenharmony_ciPassing keyword arguments
87db96d56Sopenharmony_ci    Typically, one read and one write for 1 to 3 elements.
97db96d56Sopenharmony_ci    Occurs frequently in normal python code.
107db96d56Sopenharmony_ci
117db96d56Sopenharmony_ciClass method lookup
127db96d56Sopenharmony_ci    Dictionaries vary in size with 8 to 16 elements being common.
137db96d56Sopenharmony_ci    Usually written once with many lookups.
147db96d56Sopenharmony_ci    When base classes are used, there are many failed lookups
157db96d56Sopenharmony_ci        followed by a lookup in a base class.
167db96d56Sopenharmony_ci
177db96d56Sopenharmony_ciInstance attribute lookup and Global variables
187db96d56Sopenharmony_ci    Dictionaries vary in size.  4 to 10 elements are common.
197db96d56Sopenharmony_ci    Both reads and writes are common.
207db96d56Sopenharmony_ci
217db96d56Sopenharmony_ciBuiltins
227db96d56Sopenharmony_ci    Frequent reads.  Almost never written.
237db96d56Sopenharmony_ci    About 150 interned strings (as of Py3.3).
247db96d56Sopenharmony_ci    A few keys are accessed much more frequently than others.
257db96d56Sopenharmony_ci
267db96d56Sopenharmony_ciUniquification
277db96d56Sopenharmony_ci    Dictionaries of any size.  Bulk of work is in creation.
287db96d56Sopenharmony_ci    Repeated writes to a smaller set of keys.
297db96d56Sopenharmony_ci    Single read of each key.
307db96d56Sopenharmony_ci    Some use cases have two consecutive accesses to the same key.
317db96d56Sopenharmony_ci
327db96d56Sopenharmony_ci    * Removing duplicates from a sequence.
337db96d56Sopenharmony_ci        dict.fromkeys(seqn).keys()
347db96d56Sopenharmony_ci
357db96d56Sopenharmony_ci    * Counting elements in a sequence.
367db96d56Sopenharmony_ci        for e in seqn:
377db96d56Sopenharmony_ci          d[e] = d.get(e,0) + 1
387db96d56Sopenharmony_ci
397db96d56Sopenharmony_ci    * Accumulating references in a dictionary of lists:
407db96d56Sopenharmony_ci
417db96d56Sopenharmony_ci        for pagenumber, page in enumerate(pages):
427db96d56Sopenharmony_ci          for word in page:
437db96d56Sopenharmony_ci            d.setdefault(word, []).append(pagenumber)
447db96d56Sopenharmony_ci
457db96d56Sopenharmony_ci    Note, the second example is a use case characterized by a get and set
467db96d56Sopenharmony_ci    to the same key.  There are similar use cases with a __contains__
477db96d56Sopenharmony_ci    followed by a get, set, or del to the same key.  Part of the
487db96d56Sopenharmony_ci    justification for d.setdefault is combining the two lookups into one.
497db96d56Sopenharmony_ci
507db96d56Sopenharmony_ciMembership Testing
517db96d56Sopenharmony_ci    Dictionaries of any size.  Created once and then rarely changes.
527db96d56Sopenharmony_ci    Single write to each key.
537db96d56Sopenharmony_ci    Many calls to __contains__() or has_key().
547db96d56Sopenharmony_ci    Similar access patterns occur with replacement dictionaries
557db96d56Sopenharmony_ci        such as with the % formatting operator.
567db96d56Sopenharmony_ci
577db96d56Sopenharmony_ciDynamic Mappings
587db96d56Sopenharmony_ci    Characterized by deletions interspersed with adds and replacements.
597db96d56Sopenharmony_ci    Performance benefits greatly from the re-use of dummy entries.
607db96d56Sopenharmony_ci
617db96d56Sopenharmony_ciData Layout
627db96d56Sopenharmony_ci-----------
637db96d56Sopenharmony_ci
647db96d56Sopenharmony_ciDictionaries are composed of 3 components:
657db96d56Sopenharmony_ciThe dictobject struct itself
667db96d56Sopenharmony_ciA dict-keys object (keys & hashes)
677db96d56Sopenharmony_ciA values array
687db96d56Sopenharmony_ci
697db96d56Sopenharmony_ci
707db96d56Sopenharmony_ciTunable Dictionary Parameters
717db96d56Sopenharmony_ci-----------------------------
727db96d56Sopenharmony_ci
737db96d56Sopenharmony_ciSee comments for PyDict_MINSIZE, USABLE_FRACTION and GROWTH_RATE in
747db96d56Sopenharmony_cidictobject.c
757db96d56Sopenharmony_ci
767db96d56Sopenharmony_ciTune-ups should be measured across a broad range of applications and
777db96d56Sopenharmony_ciuse cases.  A change to any parameter will help in some situations and
787db96d56Sopenharmony_cihurt in others.  The key is to find settings that help the most common
797db96d56Sopenharmony_cicases and do the least damage to the less common cases.  Results will
807db96d56Sopenharmony_civary dramatically depending on the exact number of keys, whether the
817db96d56Sopenharmony_cikeys are all strings, whether reads or writes dominate, the exact
827db96d56Sopenharmony_cihash values of the keys (some sets of values have fewer collisions than
837db96d56Sopenharmony_ciothers).  Any one test or benchmark is likely to prove misleading.
847db96d56Sopenharmony_ci
857db96d56Sopenharmony_ciWhile making a dictionary more sparse reduces collisions, it impairs
867db96d56Sopenharmony_ciiteration and key listing.  Those methods loop over every potential
877db96d56Sopenharmony_cientry.  Doubling the size of dictionary results in twice as many
887db96d56Sopenharmony_cinon-overlapping memory accesses for keys(), items(), values(),
897db96d56Sopenharmony_ci__iter__(), iterkeys(), iteritems(), itervalues(), and update().
907db96d56Sopenharmony_ciAlso, every dictionary iterates at least twice, once for the memset()
917db96d56Sopenharmony_ciwhen it is created and once by dealloc().
927db96d56Sopenharmony_ci
937db96d56Sopenharmony_ciDictionary operations involving only a single key can be O(1) unless
947db96d56Sopenharmony_ciresizing is possible.  By checking for a resize only when the
957db96d56Sopenharmony_cidictionary can grow (and may *require* resizing), other operations
967db96d56Sopenharmony_ciremain O(1), and the odds of resize thrashing or memory fragmentation
977db96d56Sopenharmony_ciare reduced. In particular, an algorithm that empties a dictionary
987db96d56Sopenharmony_ciby repeatedly invoking .pop will see no resizing, which might
997db96d56Sopenharmony_cinot be necessary at all because the dictionary is eventually
1007db96d56Sopenharmony_cidiscarded entirely.
1017db96d56Sopenharmony_ci
1027db96d56Sopenharmony_ciThe key differences between this implementation and earlier versions are:
1037db96d56Sopenharmony_ci    1. The table can be split into two parts, the keys and the values.
1047db96d56Sopenharmony_ci
1057db96d56Sopenharmony_ci    2. There is an additional key-value combination: (key, NULL).
1067db96d56Sopenharmony_ci       Unlike (<dummy>, NULL) which represents a deleted value, (key, NULL)
1077db96d56Sopenharmony_ci       represented a yet to be inserted value. This combination can only occur
1087db96d56Sopenharmony_ci       when the table is split.
1097db96d56Sopenharmony_ci
1107db96d56Sopenharmony_ci    3. No small table embedded in the dict,
1117db96d56Sopenharmony_ci       as this would make sharing of key-tables impossible.
1127db96d56Sopenharmony_ci
1137db96d56Sopenharmony_ci
1147db96d56Sopenharmony_ciThese changes have the following consequences.
1157db96d56Sopenharmony_ci   1. General dictionaries are slightly larger.
1167db96d56Sopenharmony_ci
1177db96d56Sopenharmony_ci   2. All object dictionaries of a single class can share a single key-table,
1187db96d56Sopenharmony_ci      saving about 60% memory for such cases.
1197db96d56Sopenharmony_ci
1207db96d56Sopenharmony_ciResults of Cache Locality Experiments
1217db96d56Sopenharmony_ci--------------------------------------
1227db96d56Sopenharmony_ci
1237db96d56Sopenharmony_ciExperiments on an earlier design of dictionary, in which all tables were
1247db96d56Sopenharmony_cicombined, showed the following:
1257db96d56Sopenharmony_ci
1267db96d56Sopenharmony_ci  When an entry is retrieved from memory, several adjacent entries are also
1277db96d56Sopenharmony_ci  retrieved into a cache line.  Since accessing items in cache is *much*
1287db96d56Sopenharmony_ci  cheaper than a cache miss, an enticing idea is to probe the adjacent
1297db96d56Sopenharmony_ci  entries as a first step in collision resolution.  Unfortunately, the
1307db96d56Sopenharmony_ci  introduction of any regularity into collision searches results in more
1317db96d56Sopenharmony_ci  collisions than the current random chaining approach.
1327db96d56Sopenharmony_ci
1337db96d56Sopenharmony_ci  Exploiting cache locality at the expense of additional collisions fails
1347db96d56Sopenharmony_ci  to payoff when the entries are already loaded in cache (the expense
1357db96d56Sopenharmony_ci  is paid with no compensating benefit).  This occurs in small dictionaries
1367db96d56Sopenharmony_ci  where the whole dictionary fits into a pair of cache lines.  It also
1377db96d56Sopenharmony_ci  occurs frequently in large dictionaries which have a common access pattern
1387db96d56Sopenharmony_ci  where some keys are accessed much more frequently than others.  The
1397db96d56Sopenharmony_ci  more popular entries *and* their collision chains tend to remain in cache.
1407db96d56Sopenharmony_ci
1417db96d56Sopenharmony_ci  To exploit cache locality, change the collision resolution section
1427db96d56Sopenharmony_ci  in lookdict() and lookdict_string().  Set i^=1 at the top of the
1437db96d56Sopenharmony_ci  loop and move the  i = (i << 2) + i + perturb + 1 to an unrolled
1447db96d56Sopenharmony_ci  version of the loop.
1457db96d56Sopenharmony_ci
1467db96d56Sopenharmony_ciFor split tables, the above will apply to the keys, but the value will
1477db96d56Sopenharmony_cialways be in a different cache line from the key.
1487db96d56Sopenharmony_ci
1497db96d56Sopenharmony_ci
150