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