1818
1919#define MSGPACK_EMBED_STACK_SIZE (1024)
2020#define MSGPACK_MAX_PREALLOC (10000)
21+ /* Direct-mapped cache of decoded, interned ASCII map keys. It is enabled only
22+ * after MSGPACK_KEY_CACHE_THRESHOLD keys, so small messages skip its setup. */
23+ #define MSGPACK_KEY_CACHE_SIZE (256) /* must be a power of two */
24+ #define MSGPACK_KEY_CACHE_MAX_LEN (64)
25+ #define MSGPACK_KEY_CACHE_THRESHOLD (32)
2126#include "unpack_define.h"
2227
2328typedef struct unpack_user {
@@ -34,8 +39,22 @@ typedef struct unpack_user {
3439 PyObject * utc ;
3540 const char * unicode_errors ;
3641 Py_ssize_t max_str_len , max_bin_len , max_array_len , max_map_len , max_ext_len ;
42+ unsigned int map_keys_seen ;
43+ bool key_cache_ready ;
44+ PyObject * key_cache [MSGPACK_KEY_CACHE_SIZE ]; /* valid only if key_cache_ready */
3745} unpack_user ;
3846
47+ static inline void unpack_key_cache_clear (unpack_user * u )
48+ {
49+ if (u -> key_cache_ready ) {
50+ for (unsigned int i = 0 ; i < MSGPACK_KEY_CACHE_SIZE ; i ++ ) {
51+ Py_CLEAR (u -> key_cache [i ]);
52+ }
53+ u -> key_cache_ready = false;
54+ }
55+ u -> map_keys_seen = 0 ;
56+ }
57+
3958typedef PyObject * msgpack_unpack_object ; //TODO: remove typedef and use PyObject* directly
4059struct unpack_context ;
4160typedef struct unpack_context unpack_context ;
@@ -203,6 +222,26 @@ static inline int unpack_callback_map(unpack_user* u, unsigned int n, msgpack_un
203222 return 0 ;
204223}
205224
225+ static inline PyObject * * unpack_key_cache_slot (unpack_user * u , const char * p , Py_ssize_t l )
226+ {
227+ uint32_t hash = 2166136261u ; /* FNV-1a */
228+ for (Py_ssize_t i = 0 ; i < l ; i ++ ) {
229+ hash = (hash ^ (unsigned char )p [i ]) * 16777619u ;
230+ }
231+ return & u -> key_cache [hash & (MSGPACK_KEY_CACHE_SIZE - 1 )];
232+ }
233+
234+ /* Only ASCII keys are stored, so a cached key's data equals its raw bytes. */
235+ static inline void unpack_key_cache_store (unpack_user * u , PyObject * key )
236+ {
237+ Py_ssize_t l = PyUnicode_GET_LENGTH (key );
238+ if (!u -> key_cache_ready || !PyUnicode_IS_ASCII (key ) || l > MSGPACK_KEY_CACHE_MAX_LEN )
239+ return ;
240+ PyObject * * slot = unpack_key_cache_slot (u , (const char * )PyUnicode_DATA (key ), l );
241+ Py_INCREF (key );
242+ Py_XSETREF (* slot , key );
243+ }
244+
206245/* Consumes k and v on both success and failure; borrows the container. */
207246static inline int unpack_callback_map_item (unpack_user * u , unsigned int current , msgpack_unpack_object * c , msgpack_unpack_object k , msgpack_unpack_object v )
208247{
@@ -212,8 +251,10 @@ static inline int unpack_callback_map_item(unpack_user* u, unsigned int current,
212251 Py_DECREF (v );
213252 return -1 ;
214253 }
215- if (PyUnicode_CheckExact (k )) {
254+ if (PyUnicode_CheckExact (k ) && ! PyUnicode_CHECK_INTERNED ( k ) ) {
216255 PyUnicode_InternInPlace (& k );
256+ /* Cache only keys of complete pairs, as interned strings may be immortal. */
257+ unpack_key_cache_store (u , k );
217258 }
218259 if (u -> has_pairs_hook ) {
219260 msgpack_unpack_object item = PyTuple_Pack (2 , k , v );
@@ -262,6 +303,31 @@ static inline int unpack_callback_raw(unpack_user* u, const char* b, const char*
262303 return 0 ;
263304}
264305
306+ /* Map keys repeat across objects of the same shape, so reuse the interned
307+ * string instead of decoding and interning the same bytes again. */
308+ static inline int unpack_callback_map_key_raw (unpack_user * u , const char * b , const char * p , unsigned int l , msgpack_unpack_object * o )
309+ {
310+ if (u -> raw || l > MSGPACK_KEY_CACHE_MAX_LEN || l > u -> max_str_len ) {
311+ return unpack_callback_raw (u , b , p , l , o );
312+ }
313+ if (!u -> key_cache_ready ) {
314+ if (++ u -> map_keys_seen < MSGPACK_KEY_CACHE_THRESHOLD ) {
315+ return unpack_callback_raw (u , b , p , l , o );
316+ }
317+ memset (u -> key_cache , 0 , sizeof (u -> key_cache ));
318+ u -> key_cache_ready = true;
319+ }
320+
321+ PyObject * cached = * unpack_key_cache_slot (u , p , l );
322+ if (cached && PyUnicode_GET_LENGTH (cached ) == (Py_ssize_t )l &&
323+ memcmp (PyUnicode_DATA (cached ), p , l ) == 0 ) {
324+ Py_INCREF (cached );
325+ * o = cached ;
326+ return 0 ;
327+ }
328+ return unpack_callback_raw (u , b , p , l , o );
329+ }
330+
265331static inline int unpack_callback_bin (unpack_user * u , const char * b , const char * p , unsigned int l , msgpack_unpack_object * o )
266332{
267333 if (l > u -> max_bin_len ) {
0 commit comments